动态AOP

作者: 黄二的NPE | 来源:发表于2018-07-28 09:26 被阅读5次
    • 动态AOP

    上篇文章我们讲到,AOP分为静态AOP和动态AOP。静态AOP在代码编译之后,已经有代理类或者已经改变了目标对象代码,而动态AOP则是在代码运行过程中才生成的代理类。动态AOP可以用JDK Proxy代理和CGLib的方法来实现。

    • 动态AOP之JDK aop

    JDK AOP是基于接口来实现的,即动态生成的代理类是目标对象的接口的子类。下面有一个例子来说明,例子里面实现的是保存一只猫,在aop中输出“我是AOP,在方法执行之后...”。步骤如下:

    1. 写好接口和实现类(一定要接口)
    public interface CatService {
        void save();
    }
    
    public class CatServiceImpl implements CatService{
        @Override
        public void save() {
            System.out.println("保存了一只猫...");
        }
    }
    
    1. 实现InvocationHandler接口,写好AOP内容
    public class LogInvocationHandler implements InvocationHandler {
    
        private Object target;//目标对象
    
        public LogInvocationHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object invoke = method.invoke(target, args);
            //此处为织入的AOP内容,method.invoke表示方法的执行,我们可以在这个方法执行之前或者之后织入我们需要的内容
            System.out.println("我是AOP,在方法执行之后...");
            return invoke;
        }
    }
    
    1. 创建代理类并执行
        public static void main(String[] args) {
            //如果想在本地看到生成的代理类,可在此设置
            System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
            CatService catServiceImpl = new CatServiceImpl();
            LogInvocationHandler handler = new LogInvocationHandler(catServiceImpl);
            //生成代理类
            CatService catService = (CatService) Proxy
                    .newProxyInstance(CatServiceImpl.class.getClassLoader(), catServiceImpl.getClass().getInterfaces(), handler);
            catService.save();
        }
    

    此时代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑。

    public final class $Proxy0 extends Proxy implements CatService {
        ...
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
        public final void save() throws  {
            try {
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
        ...
    }
    

    如果我们在代码中设置了生成代理类保存到本地,我们可以在com/sun/proxy文件夹中找到我们生成的代理类如上。生成的代理类实现了CatService的接口,继承了Proxy类,在Proxy类中,InvocationHandler h又是它的一个成员变量,所以h.invoke()会调用到LogInvocationHandler的invoke,即执行了我们的aop方法。
    JDK动态代理只能针对实现了接口的类生成代理。

    • 动态AOP之CGlib(类代理)

    动态AOP还可以通过CGlib来实现,CGlib是通过生成目标对象的子类 + 方法拦截器来实现代理的,所以如果目标对象是final类的话,就不可以用这种方法了。那CGlib是怎么生成目标对象的子类的呢?它用到的是Enhancer的增强功能,我们可以通过Enhancer动态生成一个代码中没有的class对象的实例。先来看看例子:

    1. pom
            <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib</artifactId>
                <version>3.2.4</version>
            </dependency>
    
    1. Java类
    public class CatService {
        public void save(){
            System.out.println("保存了一只猫...");
        }
    }
    
    1. 实现MethodInterceptor
    public class MyProxy implements MethodInterceptor {
    
        private Enhancer enhancer = new Enhancer();
    
        public Object getProxy(Class clazz) {
            //利用enhancer动态生成class对象的实例
            //设置目标对象为父类
            enhancer.setSuperclass(clazz);
            //设置回调,在这里指本身,MethodInterceptor 实现了callback接口,如果回调会调用intercept方法
            enhancer.setCallback(this);
            //使用字节码技术动态创建子类的实例
            return enhancer.create();
        }
    
    
        @Override
        public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy)
                throws Throwable {
            Object result = methodProxy.invokeSuper(target, args);
            System.out.println("织入成功...");
            return result;
        }
    }
    
    1. 测试
        public static void main(String[] args) {
            //设置生成的代理类保存到本地
           System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "target/cglib");
            MyProxy proxy = new MyProxy();
            CatService catService = (CatService) proxy.getProxy(CatService.class);
            catService.save();
        }
    

    CgLib是指使用字节码技术动态创建子类的实例的一种方法,我们甚至可以找到它创建出来的子类,可以通过参数把生成类保存到本地(比如以上我放在了target/cglib文件夹中),以下就是创建的一个CatService的子类:

    public class CatService$$EnhancerByCGLIB$$bcaabe8 extends CatService implements Factory {
        private MethodInterceptor CGLIB$CALLBACK_0;
        public final void save() {
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if(this.CGLIB$CALLBACK_0 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }
    
            if(var10000 != null) {
                var10000.intercept(this, CGLIB$save$0$Method, CGLIB$emptyArgs, CGLIB$save$0$Proxy);
            } else {
                super.save();
            }
        }
    }
    

    可以看到代理类的成员变量 CGLIB $CALLBACK_0不为空的话,会调用CGLIB$CALLBACK_0的intercept方法,那么CGLIB$CALLBACK_0是什么呢?它是MethodInterceptor 的类型(一个方法拦截器),是我们通过Enhancer setCallback设置进来的MyProxy,我们所需的方法参数传过去即可。

    利用cglib创建的动态代理对象的性能,是 JDK 的 10 倍;但是创建动态代理对象所花费的时间上,却比 JDK 多花 8 倍的时间。所以,对于单例模式或者具有实例池的代理类,适合采用 CGLib 技术;反之,则适合采用 JDK 技术。

    相关文章

      网友评论

          本文标题:动态AOP

          本文链接:https://www.haomeiwen.com/subject/yygnmftx.html