代理模式

223 阅读7分钟

不积跬步,无以至千里。

代理,顾名思义就是找个人来帮我做事,放到程序里面就是,一个对象让另一个对象帮自己执行一部分逻辑。

现在有一个 UserService 接口,其中提供了一个查询及一个更新方法。

public interface UserService {
    /**
     * 用户查询
     */
    void query();

    /**
     * 用户更新
     */
    void update();
}

其实现类UserServiceImpl 如下

public class UserServiceImpl implements UserService {
    @Override
    public void query() {
        System.out.println("UserService 执行用户查询");
    }

    @Override
    public void update() {
        System.out.println("UserService 执行用户更新");
    }
}

现在考虑一个场景,需要在方法执行前后都打印当前时间,如何实现?由于是学习代理模式,直接跳过在业务代码前后加逻辑的方式,直入正题。

静态代理

现在有一个实现类 UserServiceStaticProxy,它也实现了 UserService 接口,所以他也实现了 queryupdate 方法。通过构造函数,将需要UserServiceStaticProxy帮助的对象注入进来,这样之后就能帮它做事啦。需求时在执行方法前后都打印当前时间,所以代理类中实现了 beforeafter方法,用于输出时间。在代理类的 queryupdate 方法中,调用了被代理对象的对应方法,并在其前后添加了输入时间的代码。这样,当代理类的对应方法被调用时就会执行额外的逻辑并调用被代理对象的原始方法,以此来实现需求。

public class UserServiceStaticProxy implements UserService {
    private UserService target;

    public UserServiceStaticProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void query() {
        before();
        target.query();
        after();
    }

    @Override
    public void update() {
        before();
        target.update();
        after();
    }

    private void before() {
        System.out.println(String.format("static proxy log start time [%s] ", new Date()));
    }

    private void after() {
        System.out.println(String.format("static proxy log end time [%s] ", new Date()));
    }
}

调用时传入需要被代理的对象,并调用代理类上的方法

 public static void staticProxy() {
        UserService userService = new UserServiceImpl();
        UserService proxyService = new UserServiceStaticProxy(userService);
        proxyService.query();
        proxyService.update();
    }
staticProxy.png

小结:

  • 静态代理需要自己写代理类-->代理类需要实现与目标对象相同的接口
  • 如果目标接口有很多方法的话,需要一一实现,比较麻烦

动态代理

JDK 动态代理

实现 InvocationHandler,这里面就是代理对象需要增强的逻辑。在下方的代码中,我们在调用被代理对象的前后,添加了时间输出的逻辑。

public class LogHandler implements InvocationHandler {
    private Object target;

    public LogHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    private void before() {
        System.out.println(String.format("jdk proxy log start time [%s] ", new Date()));
    }

    private void after() {
        System.out.println(String.format("jdk proxy log end time [%s] ", new Date()));
    }
}

创建代理对象需要通过java.lang.reflect.ProxynewProxyInstance方法

JDK-Proxy-Class.png

此方法接收三个参数:

  • 参数一:生成代理对象使用哪个类装载器【一般我们使用的是被代理类的装载器】
  • 参数二:生成哪个对象的代理对象,通过接口指定【指定要被代理类的接口】
  • 参数三:生成的代理对象的方法里干什么事【实现handler接口,我们想怎么实现就怎么实现】

在编写动态代理之前,要明确几个概念:

  • 代理对象拥有目标对象相同的方法【因为参数二指定了对象的接口,代理对象会实现接口的所有方法】
  • 用户调用代理对象的什么方法,都是在调用处理器的invoke方法。【被拦截】
  • 使用JDK动态代理必须要有接口【参数二需要接口】

下方代码段是一个简单的获取代理对象并调用的过程

public static void jdkProxy() {
        //1.获取被代理对象
        UserService userService = new UserServiceImpl();

        //2.获取代理对象使用的类加载器
        ClassLoader classLoader = userService.getClass().getClassLoader();

        //3.获取被代理的接口
        Class[] interfaces = userService.getClass().getInterfaces();

        //4.实现需要对被代理对象进行的增强,实现 InvocationHandler 接口
        InvocationHandler localHandler = new LogHandler(userService);

        //获取代理对象
        UserService proxyService = (UserService) Proxy.newProxyInstance(classLoader, interfaces, localHandler);

        //通过代理对象进行方法调用
        proxyService.query();
        proxyService.update();

        ProxyUtils.generateClassFile(userService.getClass(),"UserServiceProxy");
    }
invoke_jdk_proxy.png

本例中代理对象的生成,是利用JDKAPI,动态地在内存中构建代理对象(需要我们指定创建 代理对象/目标对象 实现的接口的类型),并且会默认实现接口的全部方法

下方的 UserServiceProxy 类就是我们生成的代理对象

  • UserServiceProxy 继承了 java.lang.reflect.Proxy类,并且实现了被代理的所有接口,以及equals、hashCode、toString 等方法
  • 由于 UserServiceProxy 继承了 java.lang.reflect.Proxy类,所以每个代理类都会关联一个 InvocationHandler方法调用处理器
  • 类和所有方法都被 public final 修饰,所以代理类只可被使用,不可以再被继承
  • 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以 m + 数字 的格式命名
  • 调用方法的时候通过 super.h.invoke(this, m1, (Object[])null); 调用,其中的 super.h.invoke 实际上是在创建代理的时候传递给 Proxy.newProxyInstanceLogHandler对象,它实现了 InvocationHandler接口,LogHandler的 invoke 方法接收到 method、args 等参数后,执行增强逻辑并通过反射让被代理的对象 target 执行对应方法
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import design.pattern.proxy.service.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class UserServiceProxy extends Proxy implements UserService {
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m0;
    private static Method m3;

    public UserServiceProxy(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void query() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void update() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m4 = Class.forName("design.pattern.proxy.service.UserService").getMethod("query");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("design.pattern.proxy.service.UserService").getMethod("update");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
CGLIB 动态代理

现有用户信息访问控制器 UserController,也需要加入输出时间的需求。

public class UserController {
    public String query() {
        System.out.println("UserController 执行用户查询");
        return "result";
    }

    public Boolean update() {
        System.out.println("UserController 执行用户更新");

        return false;
    }
}

由于 Controller 未实现接口,故不能使用 JDK 动态代理,所以此处采用 CGLIB 动态代理实现此需求,CGLIB 的处理模式如下:

  1. 查找目标类上的所有非final 的public类型的方法定义;
  2. 将这些方法的定义转换成字节码;
  3. 将组成的字节码转换成相应的代理的class对象;
  4. 实现 MethodInterceptor 接口,用来处理对代理类上所有方法的请求
public static void cglibProxy(){
        LogInterceptor logInterceptor = new LogInterceptor();
        Enhancer enhancer = new Enhancer();
        // 设置超类,cglib是通过继承来实现的
        enhancer.setSuperclass(UserController.class);
    
        //设置拦截器,里面是实现的增强逻辑
        enhancer.setCallback(logInterceptor);

        //创建代理类
        UserController userControllerProxy = (UserController) enhancer.create();
        userControllerProxy.query();
        userControllerProxy.update();
    }

拦截器实现

public class LogInterceptor implements MethodInterceptor {
   
    /**
     * @param object 表示要进行增强的对象
     * @param method 表示拦截的方法
     * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
     * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
     * @return 执行结果
     * @throws Throwable
     */
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(object, args);
        after();
        return null;
    }

    private void before() {
        System.out.println(String.format("cglib proxy log start time [%s] ", new Date()));
    }

    private void after() {
        System.out.println(String.format("cglib proxy log end time [%s] ", new Date()));
    }
}
小结

JDK 动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。

CGLIB 动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。

JDK 动态代理的优势:

  • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 CGLIB 更加可靠。
  • 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
  • 代码实现简单。

基于类似 CGLIB 框架的优势:

  • 无需实现接口,达到代理类无侵入
  • 只操作我们关心的类,而不必为其他相关类增加工作量。
  • 高性能

应用:

Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接⼝,那么Spring AOP会使

JDK Proxy,去创建代理对象,⽽对于没有实现接⼝的对象,就⽆法使⽤ JDK Proxy 去进⾏代

理了,这时候 Spring AOP会使⽤ CGLIB ,这时候Spring AOP会使⽤ CGLIB ⽣成⼀个被代理对象的

⼦类来作为代理,如下图所示:

spring-aop-process.png