当前位置: 首页 > news >正文

Spring:动态代理

代理模式:动态代理与静态代理
In the Spring Framework, an AOP proxy will be a JDK dynamic proxy or a CGLIB proxy。
动态代理的两种方式:JDK动态代理与CGLIB代理
默认情况下,Spring AOP的实现对于接口来说就是使用的JDK的动态代理来实现的,而对于类的代理使用CGLIB来实现。


1.JDK动态代理
如何使用JDK动态代理。JDK提供了java.lang.reflect.Proxy类来实现动态代理的,可通过它的newProxyInstance来获得代理实现类。同时对于代理的接口的实际处理,是一个java.lang.reflect.InvocationHandler,它提供了一个invoke方法供实现者提供相应的代理逻辑的实现。可以对实际的实现进行一些特殊的处理,像Spring AOP中的各种advice。下面来看看如何使用。
//被代理的接口

  package com.mikan.proxy;    
    public interface HelloWorld {  
        void sayHello(String name);  
    }  

 

接口的实现类:

package com.mikan.proxy;  
    public class HelloWorldImpl implements HelloWorld {  
        @Override  
        public void sayHello(String name) {  
            System.out.println("Hello " + name);  
        }  
    }  

 

实现一个java.lang.reflect.InvocationHandler:

 package com.mikan.proxy;  
    import java.lang.reflect.InvocationHandler;  
    import java.lang.reflect.Method;  
    public class CustomInvocationHandler implements InvocationHandler {  
        private Object target;  
        public CustomInvocationHandler(Object target) {  
            this.target = target;  
        }  
      
        @Override  
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
            System.out.println("Before invocation");  
            Object retVal = method.invoke(target, args);  
            System.out.println("After invocation");  
            return retVal;  
        }  
    }  

 

使用代理:

package com.mikan.proxy;  
    import java.lang.reflect.Proxy;  
    public class ProxyTest {  
        public static void main(String[] args) throws Exception {  
         System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");  
         CustomInvocationHandler handler = new CustomInvocationHandler(new HelloWorldImpl());  
         HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(),new Class[]{HelloWorld.class}, handler);  
         proxy.sayHello("Mikan");  
        }  
    }  

 

 运行的输出结果:

   localhost:classes mikan$ java com/mikan/proxy/ProxyTest  
    Before invocation  
    Hello Mikan  
    After invocation 

 

从上面可以看出,JDK的动态代理使用起来非常简单,但是只知道如何使用是不够的,知其然,还需知其所以然。

所以要想搞清楚它的实现,那么得从源码入手。这里的源码是1.7.0_79。首先来看看它是如何生成代理类的:

 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException {
        if (h == null) {
            throw new NullPointerException();
        }

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        // 这里是生成class的地方
        Class<?> cl = getProxyClass0(loader, intfs);
        // 使用我们实现的InvocationHandler作为参数调用构造方法来获得代理类的实例
        try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        return newInstance(cons, ih);
                    }
                });
            } else {
                return newInstance(cons, ih);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
    }
其中newInstance只是调用Constructor.newInstance来构造相应的代理类实例,这里重点是看getProxyClass0这个方法的实现:
    private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {  
        // 代理的接口数量不能超过65535(没有这种变态吧)  
        if (interfaces.length > 65535) {  
            throw new IllegalArgumentException("interface limit exceeded");  
        }  
        // JDK对代理进行了缓存,如果已经存在相应的代理类,则直接返回,否则才会通过ProxyClassFactory来创建代理  
        return proxyClassCache.get(loader, interfaces);  
    }  

 可以看到,动态生成的代理类有如下特性:
   1) 继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。
   2) 提供了一个使用InvocationHandler作为参数的构造方法。
   3) 生成静态代码块来初始化接口中方法的Method对象,以及Object类的equals、hashCode、toString方法。
   4) 重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。
   5) 代理类实现代理接口的sayHello方法中,只是简单的调用了InvocationHandler的invoke方法,我们可以在invoke方法中进行一些特殊操作,甚至不调用实现的方法,直接返回。


2.JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

   简单的实现举例:

这是一个需要被代理的类,也就是父类,通过字节码技术创建这个类的子类,实现动态代理。

    public class SayHello {  
     public void say(){  
      System.out.println("hello everyone");  
     }  
    }  

该类实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例。proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。

public class CglibProxy implements MethodInterceptor{  
     private Enhancer enhancer = new Enhancer();  
     public Object getProxy(Class clazz){  
      //设置需要创建子类的类  
      enhancer.setSuperclass(clazz);  
      enhancer.setCallback(this);  
      //通过字节码技术动态创建子类实例  
      return enhancer.create();  
     }  
     //实现MethodInterceptor接口方法  
     public Object intercept(Object obj, Method method, Object[] args,  
       MethodProxy proxy) throws Throwable {  
      System.out.println("前置代理");  
      //通过代理类调用父类中的方法  
      Object result = proxy.invokeSuper(obj, args);  
      System.out.println("后置代理");  
      return result;  
     }  
    }  

 

具体实现类:

  public class DoCGLib {  
     public static void main(String[] args) {  
      CglibProxy proxy = new CglibProxy();  
      //通过生成子类的方式创建代理类  
      SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);  
      proxyImp.say();  
     }  
    }  

 

输出结果:

 前置代理  
    hello everyone  
    后置代理  

 

  

3.CGLIB与JDK动态代理之间的比较:
JDK代理需要实现某个接口,而CGLIB却没有此限制。
CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

————————————————————————————————————————————

备注:aop pointcut表达式(*)
execution(方法修饰符+返回值 完整类名+方法名(方法参数))
例如:
A、execution(public void *(..)):所有返回值是public void的方法都会被拦截到
B、execution(public void day6.com.beans.PersonService.*(..)):表示day6.com.beans.PersonService下所有返回值是public void的方法都会被拦截到
C、execution(public void day6.com.beans.PersonService.save(java.lang.String...)):表示day6.com.beans.PersonService类中的第一个形参类型是String的save方法会被拦截到
D、execution(public void save(..)):表示所有类中的save方法会被拦截到
E、execution(public void day6.com.service..*(..)):表示day6.com.service包下的类以及子包的类所有public void方法都会被拦截到
F、execution(public !void day6.com.service..*(..)):表示day6.com.service包下的类以及子包的类所有public 不是void返回类型的方法都会被拦截到

 

end

转载于:https://www.cnblogs.com/understander/p/5979889.html

相关文章:

  • 解决“添加远程依赖方式没有效果”的bug
  • z-index用法总结
  • redis-在乌班图下设置自动启动
  • Python成长笔记 - 基础篇 (十一)----RabbitMQ、Redis 、线程queue
  • 【SqlServer】empty table and delete table and create table
  • Java文件操作大全
  • 2016/10/29 action与form表单的结合使用
  • 中小企业如何搭建数据可视化平台
  • JavaScript 事件绑定及深入
  • 2016最新京东商城首页静态模板下载
  • python 日期和时间
  • Javascript的setTimeOut()和setInterval()的定时器用法
  • rsync实现同步
  • C++中static的作用和使用方法
  • 关于Html编码问题,例如字符:#183;
  • [译]CSS 居中(Center)方法大合集
  • C++11: atomic 头文件
  • css布局,左右固定中间自适应实现
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • input的行数自动增减
  • LintCode 31. partitionArray 数组划分
  • Linux中的硬链接与软链接
  • PHP的Ev教程三(Periodic watcher)
  • Promise初体验
  • React+TypeScript入门
  • Spring-boot 启动时碰到的错误
  • 阿里云Kubernetes容器服务上体验Knative
  • 提醒我喝水chrome插件开发指南
  • 为视图添加丝滑的水波纹
  • 学习Vue.js的五个小例子
  • 7行Python代码的人脸识别
  • 东超科技获得千万级Pre-A轮融资,投资方为中科创星 ...
  • 进程与线程(三)——进程/线程间通信
  • #我与Java虚拟机的故事#连载17:我的Java技术水平有了一个本质的提升
  • (3)(3.2) MAVLink2数据包签名(安全)
  • (9)STL算法之逆转旋转
  • (9)YOLO-Pose:使用对象关键点相似性损失增强多人姿态估计的增强版YOLO
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (分类)KNN算法- 参数调优
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (转)memcache、redis缓存
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载
  • .halo勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .net通用权限框架B/S (三)--MODEL层(2)
  • @column注解_MyBatis注解开发 -MyBatis(15)
  • @property @synthesize @dynamic 及相关属性作用探究
  • [Android] Android ActivityManager
  • [Android]How to use FFmpeg to decode Android f...
  • [BZOJ] 2044: 三维导弹拦截
  • [CSS]中子元素在父元素中居中
  • [DM复习]关联规则挖掘(下)
  • [LeetCode] Sort List
  • [LeetCode][面试算法]逻辑闭环的二分查找代码思路
  • [luogu P1527]矩阵乘法(矩形k小)
  • [Machine Learning][Part 8]神经网络的学习训练过程