zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    spl_autoload_register()和__autoload()区别
    编程语言相关科目
    类继承和重写的区别
    心理扭曲和心理变态含义分别是什么?
    java中的静态方法
    《深入理解JavaScript》—— JSON
    《深入理解JavaScript》—— Date
    《深入理解JavaScript》—— 正则表达式
    《深入理解JavaScript》—— 数组
    《深入理解JavaScript》—— 对象与继承(第2层)
  • 原文地址:https://www.cnblogs.com/understander/p/5979889.html
Copyright © 2011-2022 走看看