动态AOP

  • 动态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
        
            cglib
            cglib
            3.2.4
        
  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)