JAVA之代理模式(二)——动态代理

926 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情

上一节,我们学习了Java代理相关知识静态代理,这一节我们继续学习动态代理。

一、JDK动态代理

重要的类:

java的动态代理,有两个重要的类,一个是InvocationHandler,一个是Proxy,以及InvocationHandler这个接口的invoke方法。

InvocationHandler是每一个动态代理类都必须要实现的接口,我们通过代理对象调用一个方法的时候,该方法就会被转发由InvocationHandler这个接口的invoke方法来调用。 Proxy类,动态的创建一个代理对象的类,它提供了许多方法,我们用的最多的是Proxy.newProxyInstance方法,该方法的作用就是得到一个动态的代理对象

JDK代理实现:

public class MyInvocationHandler implements InvocationHandler{

        private Object object;

        public MyInvocationHandler(Object object){
                this.object = object;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                // TODO Auto-generated method stub
                System.out.println("MyInvocationHandler invoke begin");
                System.out.println("proxy: "+ proxy.getClass().getName());
                System.out.println("method: "+ method.getName());
                for(Object o : args){
                        System.out.println("arg: "+ o);
                }
                //通过反射调用 被代理类的方法
                method.invoke(object, args);
                System.out.println("MyInvocationHandler invoke end");
                return null;
        }

        public static void main(String [] args){
                //创建需要被代理的类
                Student s = new Student();
                //这一句是生成代理类的class文件,前提是你需要在工程根目录下创建com/sun/proxy目录,不然会报找不到路径的io异常
                System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
                //获得加载被代理类的 类加载器
                ClassLoader loader = Thread.currentThread().getContextClassLoader();
                //指明被代理类实现的接口
                Class<?>[] interfaces = s.getClass().getInterfaces();
                // 创建被代理类的委托类,之后想要调用被代理类的方法时,都会委托给这个类的invoke(Object proxy, Method method, Object[] args)方法
                MyInvocationHandler h = new MyInvocationHandler(s);
                //生成代理类
                //注意newProxyInstance的三个参数所代表的含义
                Person proxy = (Person)Proxy.newProxyInstance(loader, interfaces, h);
                //通过代理类调用 被代理类的方法
                proxy.sayHello("yujie.wang", 20);
                proxy.sayGoodBye(true, 100);
                System.out.println("end");
        }

}

JDK动态代理特点:

  • Proxy对象不需要implements接口;
  • Proxy对象的生成利用JDK的Api,在JVM内存中动态的构建Proxy对象。需要使用java.lang.reflect.Proxy类的newProxyInstance方法
    static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler invocationHandler );
    
    方法,方法参数说明:
        ClassLoader loader:指定当前target对象使用类加载器,获取加载器的方法是固定的
        Class<?>[] interfaces:target对象实现的接口的类型,使用泛型方式确认类型
        InvocationHandler invocationHandler:事件处理,执行target对象的方法时,会触发事件处理器的方法,会把当前执行target对象的方法作为参数传入。

二、CGLIB动态代理

Cglib动态代理是基于Java的继承机制实现的。

Cglib生成的动态代理类ProxyObject继承于目标类 TargetSubject,并且持有一个类型为MethodInterceptor 接口的实例引用,这个引用需要我们自己实现,这一点和JDK动态代理的InvocationHandler接口是一样的。那么Cglib是如何实现的?

CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

假如target对象只是一个单独的对象,并没有实现任何接口,这时候就会用到Cglib代理(Code Generation Library),即通过构建一个子类对象,从而实现对target对象的代理,因此目标对象不能是final类(报错),且目标对象的方法不能是final或static(不执行代理功能)。

Cglib依赖的jar:

  <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.10</version>
  </dependency>

Cglib代码实现:

首先、目标对象类AdminCglibService.java

    package com.lance.proxy.demo.service;

    public class AdminCglibService {
        public void update() {
            System.out.println("修改管理系统数据");
        }

        public Object find() {
            System.out.println("查看管理系统数据");
            return new Object();
        }
    }
 

代理类AdminServiceCglibProxy.java

public class AdminServiceCglibProxy implements MethodInterceptor {

    private Object target;

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

    //给目标对象创建一个代理对象
    public Object getProxyInstance() {
        //工具类
        Enhancer en = new Enhancer();
        //设置父类
        en.setSuperclass(target.getClass());
        //设置回调函数
        en.setCallback(this);
        //创建子类代理对象
        return en.create();
    }

    public Object intercept(Object object, Method method, Object[] arg2, MethodProxy proxy) throws Throwable {

        System.out.println("判断用户是否有权限进行操作");
        Object obj = method.invoke(target);
        System.out.println("记录用户执行操作的用户信息、更改内容和时间等");
        return obj;
    }


}

Cglib代理测试类CglibProxyTest.java

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

        AdminCglibService target = new AdminCglibService();
        AdminServiceCglibProxy proxyFactory = new AdminServiceCglibProxy(target);
        AdminCglibService proxy = (AdminCglibService)proxyFactory.getProxyInstance();

        System.out.println("代理对象:" + proxy.getClass());

        Object obj = proxy.find();
        System.out.println("find 返回对象:" + obj.getClass());
        System.out.println("----------------------------------");
        proxy.update();
    }
}

三、总结

1、CgLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CgLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CgLib合适,反之,使用JDK方式要更为合适一些。同时,由于CgLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

2、Spring也可以通过<aop:config proxy-target-class="true">强制使用Cglib代理,使用Java字节码编辑类库ASM操作字节码来实现,直接以二进制形式动态地生成类或其他代理类。

3、SpringBoot 2.x 中会默认使用 Cglib来实现。如果需要修改 AOP 的实现,需要通过spring.aop.proxy-target-class这个配置项来修改。

#配置文件中通过spring.aop.proxy-target-class=false来配置使用JDK
spring.aop.proxy-target-class=false

下图是springboot配置类:

image.png