LXX的网络日志
人因梦想而伟大
聊聊代理模式

介绍

代理模式(Proxy Pattern)为其他对象提供一种代理以控制对这个对象的访问。比如,在反问这个对象时,想要加一些操作但是又不想改动原来已有的对象。这个时候就访问代理对象,先通过代理对象,进行一些额外的操作之后再执行原来已有的对象。

举个,我们非常常见的例子,客户端请求服务端接口,想统计接口的执行时间。但是,又不想改变原有类,在里面添加代码。

这个时候,我们就可以使用代理模式来帮助我们完成这个需求。

首先看一个没有使用代理时,获取执行时间的示例:

public class UserController {
    /**
     * 依赖注入
     */
    private UserService userService;

    public UserVO login(UserVO userVO) {
        long startTime = System.currentTimeMillis();
        UserVO result = userService.login(userVO);
        System.out.println("responseTime:" + (System.currentTimeMillis() - startTime));
        return result;
    }
    public Boolean register(UserVO userVO) {
        long startTime = System.currentTimeMillis();
        boolean result = userService.register(userVO);
        System.out.println("responseTime:" + (System.currentTimeMillis() - startTime));
        return result;
    }
}

这样的方式去统计执行时间,与业务代码的耦合太高了。这样的代码不应该出现在这里。

静态代理

所以为了解耦,我们先使用静态代理模式来实现这个需求。

现在,我们需要将现有的代码进行修改:

public interface IUserController {
    Boolean register(UserVO vo);
    UserVO login(UserVO vo);
}
public class UserController implements IUserController {
    // 省略
    @Override
    public UserVO login(UserVO userVO) {
        // 业务代码
    }

    @Override
    public Boolean register(UserVO userVO) {
        // 业务代码
    }
}

// 代理类
public class ProxyUserController implements IUserController {
    private final UserController userController;

    public ProxyUserController(UserController userController) {
        this.userController = userController;
    }

    @Override
    public Boolean register(UserVO vo) {
        long startTime = System.currentTimeMillis();
        Boolean result = userController.register(vo);
        System.out.println("ResponseTime:" + (System.currentTimeMillis() - startTime));
        return result;
    }

    @Override
    public UserVO login(UserVO vo) {
        long startTime = System.currentTimeMillis();
        UserVO result = userController.login(vo);
        System.out.println("ResponseTime:" + (System.currentTimeMillis() - startTime));
        return result;
    }
}

// 使用
IUserController userController = new ProxyUserController(new UserController);
// 调用...

我们通过面向接口编程,将原始类对象替换成代理类对象。再将之前的的统计代码在实现一次即可。

不过,需要注意的是静态代理是基于接口来实现的,要是原先的类并没有定义接口,我们也没办法修改原先的类,去给它去定义一个相同的接口。而且,越来越多的类需要实现这个需求,那么也就要定义越来越多的接口,开发成本太大,而且重复性的代码也太多了。

动态代理

好在有动态代理(Dynamic Proxy),能帮我们解决这个问题,我们事先不必为每个原始类编写代理类,而是在运行时动态生成的。

少说多做,直接来试试吧:

public class ControllerProxy {
    public Object createProxy(Object proxy) {
        Class<?>[] interfaces = proxy.getClass().getInterfaces();
        DynamicProxyHandler handler = new DynamicProxyHandler(proxy);
        return Proxy.newProxyInstance(proxy.getClass().getClassLoader(), interfaces, handler);
    }

    private static class DynamicProxyHandler implements InvocationHandler {
        private final Object proxyObject;

        public DynamicProxyHandler(Object proxyObject) {
            this.proxyObject = proxyObject;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long startTime = System.currentTimeMillis();
            Object invoke = method.invoke(proxyObject, args);
            long endTime = System.currentTimeMillis();
            System.out.println("ResponseTime:" + (endTime - startTime));
            return invoke;
        }
    }
}

// 使用
ControllerProxy proxy = new ControllerProxy();
IUserController userController = (IUserController) proxy.createProxy(new UserController());
userController.login(new UserVO(1, "1111"));

不过使用JDK的动态代理,原始类必须要有有接口,否则就会报错。

像常用的后端框架Spring,它的AOP就是基于动态代理来实现的,但被代理类(原始类)有接口时使用的是JDK的动态代理,没有接口时就使用Cglib,通过动态生成被代理类的子类,去重写它的方法以及增强。