Spring AOP中的动态代理

  • 0  前言
  • 1  动态代理
    • 1.1 JDK动态代理
    • 1.2 CGLIB动态代理
      • 1.2.1 CGLIB的代理用法
      • 1.2.2 CGLIB的过滤功能
  • 2  Spring AOP中的动态代理机制
    • 2.1 JdkDynamicAopProxy
    • 2.2 CglibAopProxy
  • 3 总结

0  前言

    前一个季度旅游TDC的Thames服务有几次宕机,根据组内原因认真查找发现是数据库事务造成的,后来把服务中的事务配置全部去掉,服务恢复正常。根据这次教训,虽然现在还是很难确定是哪一个方面的真正原因,但是激发了我学习Spring事务方面的兴趣。而Spring事务的实现是根据AOP来实现的,对于我这个小菜鸟,只能一步一步来了,决定先从Spring的AOP开始。

1  动态代理

    Spring AOP中使用了两种动态代理,一种是JDK的动态代理,一种CGLIB的动态代理。JDK的动态代理必须指定接口,这些接口都是已经被代理对象实现了的;而CGLIB代理则不需要指定接口。

1.1 JDK动态代理

JDK的动态代理网上有很多资料,这里只说我自己的理解。

JDK动态代理必须实现InvocationHandler接口,然后通过Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)获得动态代理对象。

示例:

public interface Service {
    public void help();
    public void hello(String str);
}
  
public interface Item {
    void test();
}
  
public class ServiceImpl implements Service, Item {
    @Override
    public void help() {
        System.out.println("I want to buy some book");
    }
 
    @Override
    public void hello(String str) {
        System.out.println("hello: " + str);
    }
 
 
    @Override
    public void test() {
        System.out.println("Test dynamic proxy.");
    }
}
  
public class DynamicProxy implements InvocationHandler {
    //这个就是我们要代理的真实对象
    private Object service;
 
    //构造方法,给我们要代理的真实对象赋初值
    public DynamicProxy(Object service) {
        this.service = service;
    }
 
    @Override
    public Object invoke(Object object, Method method, Object[] args)
            throws Throwable {
        //在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before buy some book");
 
        System.out.println("Method:" + method);
 
        //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        method.invoke(service, args);
 
        //在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after buy some book");
 
        return null;
    }
}
  
public class Client {
    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);
 
        //要代理的真实对象
        Service service = new ServiceImpl();
 
        //要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
        InvocationHandler handler = new DynamicProxy(service);
 
        //添加以下的几段代码, 就可以将代理生成的字节码保存起来
        try {
            Field field = System.class.getDeclaredField("props");
            field.setAccessible(true);
            Properties props = (Properties) field.get(null);
            props.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
 
            Package pkg = Client.class.getPackage();
            if (pkg != null) {
                String packagePath = pkg.getName().replace(".", File.pathSeparator);
                new File(packagePath).mkdirs();
            }
 
            Service serviceProxy = (Service) Proxy.newProxyInstance(service.getClass().getClassLoader(),
                    service.getClass().getInterfaces(), handler);
 
            System.out.println(serviceProxy.getClass().getName());
            serviceProxy.help();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
 
    }
}
  
//生成的字节码文件$Proxy01.class反编译后的代码:
public final class $Proxy0 extends Proxy implements Service, Item {
    private static Method m1;
    private static Method m5;
    private static Method m2;
    private static Method m3;
    private static Method m4;
    private static Method m0;
 
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
 
    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
 
    public final void test() throws  {
        try {
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
 
    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
 
    public final void help() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
 
    public final void hello(String var1) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
 
    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
 
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m5 = Class.forName("com.meituan.proxy.Item").getMethod("test", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.meituan.proxy.Service").getMethod("help", new Class[0]);
            m4 = Class.forName("com.meituan.proxy.Service").getMethod("hello", new Class[]{Class.forName("java.lang.String")});
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
View Code

所有方法都会被导向调用InvocationHandler接口的唯一的方法invoke,这个就是我们在被代理对象前后插入相关逻辑的地方。

1.2 CGLIB动态代理

1.2.1 CGLIB的代理用法

使用CGLib动态代理,被代理类不需要强制实现接口。

CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

示例:

public class Biz {
    public void add() {
        System.out.println("这是一个普通方法...加法。");
    }
 
    public void minus() {
        System.out.println("这是一个普通方法...减法。");
    }
}
  
public class BizInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("BizInterceptor, 调用开始 method: " + method +
                ", methodProxy: " + methodProxy);
        methodProxy.invokeSuper(o, objects);
        System.out.println("BizInterceptor, 调用结束");
        return null;
    }
}
  
public class BizCglibClient {
    private Object target;
 
    /**
     * 创建代理对象
     *
     * @param target
     * @return
     */
    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        /*Class<?> clazz[] = {Service.class};
        enhancer.setInterfaces(clazz);*/
        //回调方法
        Callback[] obj = {new BizInterceptor(), new BizInterceptor2()};
        enhancer.setCallbacks(obj);
        enhancer.setCallbackFilter(new BizClassFilter());
        //创建代理对象
        return enhancer.create();
    }
 
    public static void main(String[] args) {
        try {
            Field field = System.class.getDeclaredField("props");
            field.setAccessible(true);
            Properties props = (Properties) field.get(null);
            props.put(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/lanhongzhong/Workspace/LuceneTest");
 
            /*Package pkg = Client.class.getPackage();
            if (pkg != null) {
                String packagePath = pkg.getName().replace(".", File.pathSeparator);
                new File(packagePath).mkdirs();
            }*/
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
 
        BizCglibClient cglib = new BizCglibClient();
        Biz bizCglib = (Biz) cglib.getInstance(new Biz());
        bizCglib.add();
 
        bizCglib.minus();
    }
}
View Code

intercept方法中的参数:Object obj为由CGLib动态生成的代理类实例,Method method为被代理类中的方法引用,Object[] args为参数值列表,MethodProxy为生成的代理类对方法的代理引用。

1.2.2 CGLIB的过滤功能

CallbackFilter可以实现不同的方法使用不同的回调方法,用于分发不同的拦截器。

示例:

//结合1.2.1中的代码,添加如下代码
public class BizInterceptor2 implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("BizInterceptor2, 调用开始 method: " + method +
                ", methodProxy: " + methodProxy);
        methodProxy.invokeSuper(o, objects);
        System.out.println("BizInterceptor2, 调用结束");
        return null;
    }
}
  
public class BizClassFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if (method.getName().equals("minus")) {
            return 1;
        }
 
        return 0;
    }
}
View Code

2  Spring AOP中的动态代理机制

    Spring AOP中的代理根据被代理对象是否实现了接口选择不同的生成代理对象的方式,即第1部分中的两个情况。

2.1 JdkDynamicAopProxy

    如果被代理对象实现了需要被代理的接口,则使用JDK的动态代理,在Spring AOP中对应的包装类为JdkDynamicAopProxy。

    JdkDynamicAopProxy类实现了InvocationHandler接口,将被代理对象和拦截器作为参数传入,然后生成代理对象。

    当代理对象被调用时,JdkDynamicAopProxy的invoke方法作为Proxy对象的回调函数而被触发,从而通过invoke的具体实现,来完成对目标对象方法调用的拦截或者说功能增强的工作。

    在invoke方法中,设置了包括获取目标对象、拦截器链,同时把这些对象作为输入,创建了ReflectiveMethodInvocation对象,通过这个ReflectiveMethodInvocation对象来完成对AOP功能实现的封装。在这个invoke方法中,包含了一个完整的拦截器链对目标对象的拦截过程,比如获得拦截器链并对其中的拦截器进行配置,逐个运行拦截器链里的拦截增强,直到最后对目标对象方法的运行。具体可以看源代码。

//我只是搬运工
  
  
/**
 * Implementation of {@code InvocationHandler.invoke}.
 * <p>Callers will see exactly the exception thrown by the target,
 * unless a hook method throws an exception.
 */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   MethodInvocation invocation;
   Object oldProxy = null;
   boolean setProxyContext = false;
 
   TargetSource targetSource = this.advised.targetSource;
   Class targetClass = null;
   Object target = null;
 
   try {
      ... ...
 
      // May be null. Get as late as possible to minimize the time we "own" the target,
      // in case it comes from a pool.
      target = targetSource.getTarget();
      if (target != null) {
         targetClass = target.getClass();
      }
 
      // Get the interception chain for this method.
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
 
      // Check whether we have any advice. If we don't, we can fallback on direct
      // reflective invocation of the target, and avoid creating a MethodInvocation.
      if (chain.isEmpty()) {
         // We can skip creating a MethodInvocation: just invoke the target directly
         // Note that the final invoker must be an InvokerInterceptor so we know it does
         // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
         retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
      }
      else {
         // We need to create a method invocation...
         invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
         // Proceed to the joinpoint through the interceptor chain.
         retVal = invocation.proceed();
      }
 
      ... ...
 
      return retVal;
   }
}
View Code

2.2 CglibAopProxy

    Cglib2AopProxy的intercept回调方法的实现和JdkDynamicAopProxy的回调实现是非常类似的,只是在Cglib2AopProxy中构造的是CglibMethodInvocation对象来完成拦截器链的调用,而在JdkDynamicAopProxy中是通过构造ReflectiveMethodInvocation对象来完成这个功能的。

3 总结

    Spring AOP的核心实现原理就是采用的动态代理,根据被代理对象是否实现了所要被代理的接口这个条件,动态代理会选择不同的实现方案。本文只是尽我所能简单的拿着各方资料来了一个汇总,是一个自己的学习总结。对于Spring AOP的设计架构是我下一步的学习目标。

 

参考文献

1、http://blog.csdn.net/mhmyqn/article/details/48474815

2、《Spring技术内幕:深入解析Spring架构与设计原理》    许文柯

posted on 2017-01-24 10:56  lanhz  阅读(15343)  评论(0编辑  收藏  举报