动态代理详解


  • 在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。

一、JDK动态代理

  • DK动态代理技术:只能代理接口。

  • Java 动态代理实现相关类位于 java.lang.reflect 包,主要涉及两个类:

    • InvocationHandler 接口。它是代理实例的调用处理程序实现的接口,该接口中定义了如下方法:

      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
      
      • invoke() 方法上有三个参数:
        • 第一个参数 proxy 表示代理类。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用。
        • 第二个参数 method 表示需要代理的方法。
        • 第三个参数 args 表示代理方法的参数数组。
    • Proxy 类。该类即为动态代理类,该类最常用的方法为:

      public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 
      
      • 这行代码做了两件事:
        • 第一件事:在内存中生成了代理类的字节码。
        • 第二件事:创建代理对象。
      • newProxyInstance() 方法有三个参数:
        • 第一个参数 loader 表示代理类的类加载器。。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
        • 第二个参数 interfaces 表示代理类实现的接口列表(与真实主题类的接口列表一致)。通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
        • 第三个参数 h 表示所指派的调用处理程序类。这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。
  • 还是使用静态代理中的例子:一个接口和一个实现类。(参考上面的代码)

  • 要写一下java.lang.reflect.InvocationHandler接口的实现类,并且实现接口中的方法,代码如下:

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * 动态代理类
     **/
    
    public class TimerInvocationHandler implements InvocationHandler {
    
        private Object target; //需要代理的目标对象
    
        public TimerInvocationHandler() {
        }
    
        public TimerInvocationHandler(Object target) {
    
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 目标执行之前增强。
            long begin = System.currentTimeMillis();
            // 调用目标对象的目标方法
            Object retValue = method.invoke(target, args);
            // 目标执行之后增强。
            long end = System.currentTimeMillis();
            System.out.println("耗费时常" + (end - begin) + "毫秒");
            // 返回目标对象方法的返回值。
            return retValue;
        }
    }
    
  • 编写 Client 测试程序:

    import service.OrderService;
    import service.OrderServiceImpl;
    
    public class Client {
        public static void main(String[] args) {
        	 // 创建目标对象
            OrderService target = new OrderServiceImpl();
    		// 创建代理对象
            OrderService ret = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target));
    		 // 调用代理对象的代理方法
            ret.generate();
            ret.detail();
            ret.delete();
            //当你调用代理对象的代理方法的时候,注册在InvocationHandler接口中的invoke()方法会被调用。
        }
    }
    
  • 提供一个工具类:ProxyUtil,封装一个方法:

    package utils;
    
    import dynamic_proxy.TimerInvocationHandler;
    
    import java.lang.reflect.Proxy;
    
    public class ProxyUtil {
        private ProxyUtil(){}
    
        public static Object newProxyInstance(Object target) {
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target));
        }
    }
    
  • 这样客户端代码就不需要写那么繁琐了:

    import service.OrderService;
    import service.OrderServiceImpl;
    import utils.ProxyUtil;
    
    public class Client {
        public static void main(String[] args) {
        	 // 创建目标对象
            OrderService target = new OrderServiceImpl();
    		// 创建代理对象
            OrderService ret = (OrderService) ProxyUtil.newProxyInstance(target);
    		 // 调用代理对象的代理方法
            ret.generate();
            ret.detail();
            ret.delete();
            //当你调用代理对象的代理方法的时候,注册在InvocationHandler接口中的invoke()方法会被调用。
        }
    }
    
  • Spring AOP 实现中使用了动态代理模式,使得代码中不存在与具体要用到的接口或类相关的引用。


二、CGLIB动态代理

  • CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
  • CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。
  • 使用CGLIB,需要引入它的依赖:
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>
  • 我们准备一个没有实现接口的类,如下:
public class UserService {
    public String  login(String username, String password) {

        try {
            Thread.sleep(13654);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (username.equals("root") && password.equals("123456")) {
            return "success~";
        }
        return "username or password error!";
    }
}
  • 和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor编写MethodInterceptor接口实现类:
public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 前增强
        long begin = System.currentTimeMillis();
        // 调用目标
        Object retValue = methodProxy.invokeSuper(target, objects);
        // 后增强
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - begin) + "毫秒");
        // 一定要返回
        return retValue;
    }
}
  • MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:
    • 第一个参数:目标对象
    • 第二个参数:目标方法
    • 第三个参数:目标方法调用时的实参
    • 第四个参数:代理方法
  • 使用CGLIB在内存中为UserService类生成代理类,并创建对象:
public class Client {
    public static void main(String[] args) {
        // 创建字节码增强器
        Enhancer enhancer = new Enhancer();
        // 告诉cglib要继承哪个类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口
        enhancer.setCallback(new TimerMethodInterceptor());
        // 生成源码,编译class,加载到JVM,并创建代理对象
        UserService userServiceProxy = (UserService)enhancer.create();

        String ret = userServiceProxy.login("root", "123456");
        System.out.println(ret);
    }
}
  • 对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:
    在这里插入图片描述
  • –add-opens java.base/java.lang=ALL-UNNAMED
  • –add-opens java.base/sun.net.util=ALL-UNNAMED

三、Javassist动态代理技术


  • 12
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小宝945

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值