AOP是Aspect Oriented Programing,“面向方面编程”
Spring AOP底层使用动态代理
技术在运行期织入增强代码,生成代理类或者子类,这个类中已经融合了逻辑代码和增强代码,在AOP中我们实际上调用的就是这个类
使用了两种代理机制:
1.基于JDK的动态代理;
2.基于CGLib的动态代理。
当我们在代码中进行一些监测时,可以这么写代码;先定义一个监测接口ForumService
,接口实现类ForumServiceImpl
,监测实现类PerformanceMonitor
,监测信息类MethodPerformance
这是监测接口ForumService
package noaop;
/**
* 被监测类接口
*/
public interface ForumService {
void removeTopic(int topicId);
void removeForum(int forumId);
}
这是接口实现类ForumServiceImpl
,在方法中前后分别调用了PerformanceMonitor
监测类的begin和end方法进行监测执行时间
package noaop;
/**
* 被监测类
*/
public class ForumServiceImpl implements ForumService {
@Override
public void removeTopic(int topicId) {
PerformanceMonitor.begin("noaop.ForumServiceImpl.removeTopic");//执行前时间
System.out.println("模拟删除topic记录:" + topicId);
try {
Thread.currentThread().sleep(20);
} catch (Exception e) {
throw new RuntimeException(e);
}
PerformanceMonitor.end();//执行后时间
}
@Override
public void removeForum(int forumId) {
PerformanceMonitor.begin("noaop.ForumServiceImpl.removeForum");
System.out.println("模拟删除forum记录:" + forumId);
try {
Thread.currentThread().sleep(40);
} catch (Exception e) {
throw new RuntimeException(e);
}
PerformanceMonitor.end();
}
}
这是监测实现类PerformanceMonitor
,使用ThreadLocal
实现线程安全
package noaop;
/**
* 性能监测实现类
*/
public class PerformanceMonitor {
//保存与当前线程相关的性能监测信息
private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();
public static void begin(String method) {
System.out.println("begin monitor...");
MethodPerformance mp = new MethodPerformance(method);
performanceRecord.set(mp);
}
public static void end() {
System.out.println("end monitor...");
MethodPerformance mp = performanceRecord.get();
mp.printPerformance();
}
}
监测信息类MethodPerformance
package noaop;
/**
* 记录想性能监测信息类
*/
public class MethodPerformance {
private long begin;
private long end;
private String serviceMethod;
public MethodPerformance(String serviceMethod) {
this.serviceMethod = serviceMethod;
this.begin = System.currentTimeMillis();
}
public void printPerformance() {
end = System.currentTimeMillis();
long elapse = end - begin;
System.out.println(serviceMethod + "花费" + elapse + "毫秒");
}
}
执行以上代码
public class TestForumService {
public static void main(String[] args) {
ForumService forumService = new ForumServiceImpl();
forumService.removeForum(10);
forumService.removeTopic(1012);
}
}
结果为
begin monitor...
模拟删除forum记录:10
end monitor...
noaop.ForumServiceImpl.removeForum花费55毫秒
begin monitor...
模拟删除topic记录:1012
end monitor...
noaop.ForumServiceImpl.removeTopic花费21毫秒
Process finished with exit code 0
以上实现需要在每个监测的方法中都加入begin和end方法,对业务代码来说是没有用的,而且很繁琐
JDK动态代理主要使用java.lang.reflect包中的InvocationHandler
和Proxy
两个类
创建PerformanceHandler
实现InvocationHandler
接口,target
是监测目标类。proxy
是最终生成的代理实例,method
是被代理目标的某个方法,args
是被代理实例某个方法的参数。invoke
在调用PerformanceHandler
类方法时被调用,并向其传递Method对象和原始的参数。在实现接口的invoke方法中监测代码和业务代码融合在一起,利用反射执行业务代码
package aop;
import noaop.PerformanceMonitor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PerformanceHandler implements InvocationHandler {
private Object target;
public PerformanceHandler(Object target) {
this.target = target;
}
@Override
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;
}
}
调用上面的PerformanceHandler
类使监测代码和业务代码融合在一起。使用Proxy.newProxyInstance
创建一个代理类,第一个参数是一个类加载器,第二个参数是创建代理实例所需实现的一组接口,第三个参数是整合业务逻辑和横切逻辑的编织器对象
调用以上代理对象
package aop;
import noaop.ForumService;
import noaop.ForumServiceImpl;
import java.lang.reflect.Proxy;
public class TestForumService {
public static void main(String[] args) {
//希望被代理的目标类
ForumService target = new ForumServiceImpl();
//将业务类和横切带吗交织在一起
PerformanceHandler handler = new PerformanceHandler(target);
ForumService proxy = (ForumService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
proxy.removeForum(10);
proxy.removeTopic(1012);
}
}
结果为
begin monitor...
模拟删除forum记录:10
end monitor...
noaop.ForumServiceImpl.removeForum花费49毫秒
begin monitor...
模拟删除topic记录:1012
end monitor...
noaop.ForumServiceImpl.removeTopic花费22毫秒
Process finished with exit code 0
利用代理对象实现了监测
使用JDK创建代理有个限制,只能为接口创建实例。Proxy.newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)
第二个参数需要接口。如果非接口实现类,则使用CGLib代理。它采用底层字节码技术,生成目标类的子类,在子类中采用方法拦截的技术拦截父类所有方法的调用并织入横切逻辑。
使用前需要导入cglib和asm的jar包
创建CglibProxy
代理类
在getProxy
方法中创建子类,intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
拦截所有目标类方法的调用。由于以创建子类的方式生成代理对象,所以不能对目标类中的final和private方法进行代理
obj
表示目标类实例
method
目标类方法的反射对象
args
方法的动态入参
proxy
代理类实例
在intercept
方法中,增强代码和业务代码融合在一起,通过代理类调用父类方法
package cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import noaop.PerformanceMonitor;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);//设置需要创建子类的类
enhancer.setCallback(this);
return enhancer.create();//通过字节码技术动态创建子类实例
}
/**
* 拦截所有目标类方法的调用
* @param o 目标类实例
* @param method 目标类方法的反射对象
* @param objects 方法的动态入参
* @param methodProxy 代理类实例
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
PerformanceMonitor.begin(o.getClass().getName() + "." + method.getName());
Object result = methodProxy.invokeSuper(o, objects);//通过代理类调用父类方法
PerformanceMonitor.end();
return result;
}
}
运行代码
package cglib;
import noaop.ForumServiceImpl;
public class TestForumService {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
ForumServiceImpl forumService = (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class);
forumService.removeForum(101);
forumService.removeTopic(1023);
}
}
结果为:
begin monitor...
模拟删除forum记录:101
end monitor...
noaop.ForumServiceImpl$$EnhancerByCGLIB$$4d48892f.removeForum花费59毫秒
begin monitor...
模拟删除topic记录:1023
end monitor...
noaop.ForumServiceImpl$$EnhancerByCGLIB$$4d48892f.removeTopic花费22毫秒
Process finished with exit code 0
上述例子只是为了理解Spring AOP的动态代理原理,很多地方需要改进
在org.springframework.aop.framework
中定义了AopProxy
,两个实现类是JdkDynamicAopProxy
和CglibAopProxy
。通过ProxyFactory
的setInterfaces(Class... interfaces)
方法来指定接口,则会调用JdkDynamicAopProxy
代理,设置setOptimize(boolean optimize)
方法为true,则会调用CglibAopProxy
代理