动态代理

520 阅读6分钟

前言

最近在学习 Mybatis 的时候发现,里面就是用 JDK动态代理。MapperProxy 实现了 InvocationHandler 接口的 invoke 方法,最终所有的实际调用都会调用到这个方法的包装逻辑。

但是由于不了解,所以得专门来学习学习。

静态代理

先从简单的入手,静态代理:代码在运行前就已经生成了。在我看来,静态代理就是类似于组合调用。

比如说,下面的实现类接口,我们现在不想动这个接口,但是想额外的增加一些逻辑处理,那么我们就需要代理类。

public interface UserService {
    public void select();
}

public class UserServiceImpl implements UserService{
    @Override
    public void select() {
        System.out.println("查询数据");
    }
}

实现同一个接口,并将接口注入到代理类当中。

public class UserServiceProxy implements UserService {

    private UserService userService;

    public UserServiceProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void select() {
        before();
        userService.select();
        after();
    }

    //这里定义为私有方法,防止外部调用
    private void before(){
        System.out.println("做些权限认证啥的");
    }

    private void after(){
        System.out.println("记录些日志啥的");
    }

}

测试:把实际的业务类 UserServiceImpl 传入代理类,那么代理类就会做额外的一些逻辑处理。

public static void main(String[] args) {
    UserServiceImpl userService=new UserServiceImpl();
    UserServiceProxy userServiceProxy=new UserServiceProxy(userService);
    userServiceProxy.select();
}

动态代理

这里主要介绍常见的两种动态代理:JDK 动态代理和 CGLIB 动态代理。动态由于和反射机制联系紧密,需要先了解下放射相关的知识:juejin.cn/post/720144…

JDK 动态代理

介绍

JDK 动态代理是不需要依赖第三方的库,只要 JDK 环境就可以进行代理,需要满足以下要求:

  • 实现 InvocationHandler 接口,重写 invoke()
  • 使用 Proxy.newProxyInstance() 生成代理对象
  • 被代理的对象必须要实现接口

代码实战

public class LogHandler implements InvocationHandler {
    Object target; //被代理的对象,实际方法执行者

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

    /**
* 定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用
*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK的动态代理");
        Object result = method.invoke(target, args);
        return result;
    }

}

tip:这里需要使用到上面的 UserServiceImpl 类。

public class LogHandlerProxy {
    public static void main(String[] args) {

//设置变量可以保存动态代理类,默认名称以 $Proxy0 格式命名
// (在项目的根路径下会生成一个 com.sun.proxy 的包,类以 $Proxy0 这种命名方式 )
        System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// JDK 动态代理只能基于接口实现,为什么?因为生成的动态代理接口就已经继承了 Proxy 类。Java 只能单继承


        // 创建被代理的对象
        UserServiceImpl userService = new UserServiceImpl();
        // 获取对应的类加载器
        ClassLoader classLoader = userService.getClass().getClassLoader();
        // 获取所有接口的Class,这里只实现了一个接口 UserService
        Class[] interfaces = userService.getClass().getInterfaces();
        // 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
        InvocationHandler handler = new LogHandler(userService);
        /**
* 根据上面的信息创建代理对象,在这个过程中:
* a.JDK会通过根据传入的参数信息动态的在内存中创建和 .class 文件等同的字节码
* b.然后根据相应的字节码转换成对应的 class
* c.然后调用 newInstance() 创建代理实例
*
* ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的。
* Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
* InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
*
*/
        UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, handler);
        // 调用代理的方法 :执行 invoke 方法
        proxy.select();
    }

}

上面的代码中,对每行代码都已经做了详细的说明,这里不在赘述。

  1. 既然动态代理是运行时生成的,那么我们能不能把动态代理生成的类保存下来,分析分析?
  2. 动态代理涉及的关键类:InvocationHandler、Proxy(生成代理的方法,需要三个参数,我们“凑齐”这三个参数就能“实现代理”),以及他们的使用方法。

JDK动态生成的代理类

  1. 看类的定义就知道为什么 JDK 只能基于实现类实现了,因为它默认就继承了 Proxy 类,而 Java 没有多继承,只有多实现。
  2. 里面会重写 select() 方法
public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    private static Method m4;

    public $Proxy0(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 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 void select() throws  {
        try {
            super.h.invoke(this, m3, (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);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.practice.thinkinbasic.Proxy.StaticProxy.UserService").getMethod("select");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

CGLIB 动态代理

介绍

CGLib 必须依赖于 CGLib 的类库,需要满足以下要求:

  • 实现 MethodInterceptor 接口,重写 intercept()
  • 使用 Enhancer 对象的 create() 方法产生代理对象

代码实战

public class CGLibFactroy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    public Object myCGLibCreator(Class clazz) {

        // 为代理对象设置父类,即指定目标类
        enhancer.setSuperclass(clazz);
        /**
* 设置回调接口对象 注意,只所以在 setCallback()方法中可以写上 this,
* 是因为 MethodIntecepter 接口继承自 Callback,是其子接口
*/
        enhancer.setCallback(this);
        return enhancer.create();// create 用以生成 CGLib 代理对象
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLib 动态代理");
        return methodProxy.invokeSuper(o,objects);
        //        return method.invoke(o,objects);
    }

}

为了验证 CGLib 可以对类做动态代理,我们这里还定义了一个简单的类做测试。

public class UserDao {
    public void select(){
        System.out.println("方法查询");
    }

   final public void update(){
        System.out.println("方法更新");
    }
}


public class ApiTest {

    public static void main(String[] args) {
        // 设置动态代理生成代理类的位置
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\myself\gitlab_self\keepon-java\keepon\think-in-basic\src\main\java\com\practice\thinkinbasic\Proxy\DynamicProxy\CGLibProxy\");
//        UserDao userDao = new UserDao();
        CGLibFactroy cgLibFactroy = new CGLibFactroy();

        //基于类
//        UserDao userService1 = (UserDao) cgLibFactroy.myCGLibCreator(UserDao.class);
//        userService1.select();

        //基于接口
        UserService userService1 = (UserService) cgLibFactroy.myCGLibCreator(UserServiceImpl.class);
        userService1.select();
    }

}

CGLib 动态生成的代理类

下图是示例:会生成很多类。其中一个类是继承了被代理类,不管是代理接口还是代理类,都是基于继承,只不过接口是继承的实现类。

总结

静态代理 VS 动态代理

静态:在程序运行之前就已经存在代理类的字节码文件,代理类和真实逻辑类的关系在运行前就确定了。

动态:在程序运行期间由 JVM 根据反射等机制动态生成的,运行前不存在代理类的字节码文件。

JDK VS CGLIB

实现方式生成字节码方式代理调用方式
JDK 动态代理实现被代理对象接口(只能用于接口,从生成的代理类可以看出)JDK 直接写 Class 字节码通过放射机制
CGLIB 动态代理继承被代理对象使用 ASM 框架写Class 字节码,代理实现更复杂,比JDK 效率低通过 FastClass 机制直接调用方法,执行效率更高

参考:www.cnblogs.com/whirly/p/10…