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