在实际开发作业中,我们经常需要对实现功能做性能监视。
如上图所示,在eat和sleep方法中,我们重复了①和②的代码块,将我们的业务逻辑与性能监视代码合并在了一起,不能做有效的区分。那如何将这些业务逻辑独立出来,这便是AOP要解决的主要问题了。
AOP的实现由很多,这里我们采用Spring AOP,它不需要专门的编译方式和特殊的类装载器,它在运行期通过动态代理方式来实现想目标类织入上文说的性能监视代码。Spring AOP存在两种动态代理,分别为基于JDK的动态代理和基于Cglib的动态代理。
JDK动态代理
JDK的动态代理主要涉及java.lang.reflect包中的两个类:Proxy和InvocationHandler。
其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,即我们上文中说到的性能监视逻辑,并通过反射机制调用我们的目标类代码,动态的将业务逻辑代码和横切代码编织到一起。我们先将上图中①②处性能监视代码移除,接下来我们上代码
public class PerformanceHandler implements InvocationHandler {
private Object target;
public PerformanceHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName());
// 通过反射方法调用业务类的目标方法
Object obj = method.invoke(target, args);
PerformanceMonitor.end();
return obj;
}
}
下面通过Proxy结合PerformanceHandler创建IPersonService接口的代理实例
// 希望被代理的目标业务类
PersonService service = new PersonService();
// 将目标业务类与横切代码编织到一起
PerformanceHandler handler = new PerformanceHandler(service);
// JDK动态代理,创建代理实例,注意此处,只能为接口创建代理实例
IPersonService proxy = (IPersonService) Proxy.newProxyInstance(service.getClass().getClassLoader(),
service.getClass().getInterfaces(), handler);
proxy.eat();
proxy.sleep();
运行结果如下:
CGLib动态代理
在使用JDK代理的时候,会有一个限制,即它只能为接口创建代理实例,一旦我们没有通过接口定义业务方法,JDK动态代理也就没有用武之地了。这个时候CGLib登场了,CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中拦截所有父类的方法调用,织入横切逻辑。直接看代码
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
// 设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
// 通过字节码技术动态创建子类实例
return enhancer.create();
}
public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
PerformanceMonitor.begin(object.getClass().getName() + "." + method.getName());
Object result = proxy.invokeSuper(object, args);
PerformanceMonitor.end();
return result;
}
}
再通过CglibProxy为PersonService类创建代理对象,并调用方法测试
CglibProxy proxy = new CglibProxy();
PersonService service = (PersonService) proxy.getProxy(PersonService.class);
service.eat();
service.sleep();
测试结果如下
从上图可以看出在CGLib代理中,代理类的名称已经改变了,实际上这就是动态创建的子类,且不能对目标类中的final或private方法进行代理。
小结
以上是Spring AOP两种代理方式,但上面的两个实例暴露出了几个明显不符合实际应用的地方
1.我们对目标类的所有方法都进行了代理,实际应用中,我们有时候只需要对某些指定的方法代理
2.织入点不灵活,我们只能在目标业务方法的开始和结束点织入代码
3.在动态创建代理实例的时候,不同类需要分别编写出创建代码,无法通用。
以上三个问题在AOP中占着重要的地位。
另外有研究表明CGLib所创建动态代理对象的性能比JDK所创建的动态代理对象的性能高不少(大概10倍)。但CGLib在创建代理对象时所花费的时间却比JDK动态代理多(大概8倍)。对于singleton的代理对象或者具有实例池的代理,比较适合采用CGLib技术;反之则适合采用JDK技术。