Spring以IoC为基础,发展了另外一个底层组件,就是AOP。
AOP的含义是“面向切面的编程”,将业务无关的代码但是又和业务缠在一起的代码剥离出去。AOP是一个很复杂的概念,这里只是拿出冰山一角说明一下。
AOP的术语
1.连接点(Joinpoint):程序执行的某一个特定的位置:比如类开始初始化前,初始化后,某个方法调用的前后,方法抛出异常的时候,等等具有某一种边界性质的特定点都可以称之为“连接点”。
2.切点(Pointcut):感兴趣的点的集合,如果说连接点是数据库中存储的数据的话,那切点就是where子句查询出来的目标点了。
3.增强(Advice):有的地方按照字面的意思翻译成通知,这里使用增强更加形象,即对目标对象的功能进行增强。
4.目标对象(Target):即待增强的对象。
5.引介(Introduction):特殊的增强,可以为类添加方法和属性。
6.织入(Weaver):织入就是以某种技术,将增强和目标对象融合起来,构建新类的过程,总结起来有3种织入技术。
- 编译期织入:需要特殊的Java编译器
- 类装载期织入:需要特殊的类装载器
- 动态代理织入:在运行期为目标类添加增强
Spring采用第三种代理织入,所以不需要额外的编译器。
7.代理(Proxy);织入增强之后的目标类生成的新的类。
8.切面(Advisor):由增强和切点组合形成。
现在来看一个带有横切逻辑的实例:
public class ForumServiceImpl implements ForumService { public void removeTopic(int topicId) { PerformanceMonitor.begin("com.firethewhole.maventest07.proxy.ForumService.Impl.removeTopic"); System.out.println("模拟删除Topic记录:" + topicId); try { Thread.currentThread().sleep(20); } catch (InterruptedException e) { throw new RuntimeException(e); } PerformanceMonitor.end(); } public void removeForum(int forumId) { PerformanceMonitor.begin("com.firethewhole.maventest07.proxy.ForumService.Impl.removeForum"); System.out.println("模拟删除Forum记录:" + forumId); try { Thread.currentThread().sleep(20); } catch (InterruptedException e) { throw new RuntimeException(e); } PerformanceMonitor.end(); } }
public class PerformanceMonitor { private static ThreadLocalperformanceRecord = new ThreadLocal (); 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(); } }
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 + "毫秒。"); } }
ForumService forumService = new ForumServiceImpl(); forumService.removeForum(10); forumService.removeTopic(1012);
输出以下信息:
begin monitor...
模拟删除Forum记录:10
end monitor...
com.firethewhole.maventest07.proxy.ForumService.Impl.removeForum花费20毫秒。
begin monitor...
模拟删除Topic记录:1012
end monitor...
com.firethewhole.maventest07.proxy.ForumService.Impl.removeTopic花费20毫秒。
这里性能代码和业务代码都在一起,将来修改业务代码的时候,性能监控代码可能也需要同时修改,显的很不清晰。
使用JDK动态代理技术
首先将性能横切代码都删除掉
public class ForumServiceAopStyleImpl implements ForumService { public void removeTopic(int topicId) { System.out.println("模拟删除Topic记录:" + topicId); try { Thread.currentThread().sleep(20); } catch (InterruptedException e) { throw new RuntimeException(e); } } public void removeForum(int forumId) { System.out.println("模拟删除Forum记录:" + forumId); try { Thread.currentThread().sleep(20); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
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; } }
在InvocationHandler中创建代理类,在调用ForumServiceAopStyleImpl的方法的时候,可以只能增强逻辑,先执行性能监控代码。接下来看下如何使用
// 加入动态代理的方法 ForumService target = new ForumServiceAopStyleImpl(); 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...
com.firethewhole.maventest07.proxy.ForumServiceAopStyleImpl.removeForum花费47毫秒。
begin monitor...
模拟删除Topic记录:1012
end monitor...
com.firethewhole.maventest07.proxy.ForumServiceAopStyleImpl.removeTopic花费31毫秒。
使用CGLib创建代理
使用JDK自带的InvocationHandler有一个局限性,就是只能针对接口进行代理,如果想直接针对类进行代理的话,就只能使用CGLib了。首先需要加入maven依赖。
cglib cglib 2.2.2
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 obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { PerformanceMonitor.begin(obj.getClass().getName() + "." + method.getName()); Object result = proxy.invokeSuper(obj, args); PerformanceMonitor.end(); return result; } }
基于CGLib创建代理类。
// 使用Cglib代理 CglibProxy proxyCglib = new CglibProxy(); ForumServiceAopStyleImpl forumServiceImpl = (ForumServiceAopStyleImpl) proxyCglib.getProxy(ForumServiceAopStyleImpl.class); forumServiceImpl.removeForum(10); forumServiceImpl.removeTopic(1023);
输出结果:
begin monitor...
模拟删除Forum记录:10
end monitor...
com.firethewhole.maventest07.proxy.ForumServiceAopStyleImpl$$EnhancerByCGLIB$$58fbcf09.removeForum花费125毫秒。
begin monitor...
模拟删除Topic记录:1023
end monitor...
com.firethewhole.maventest07.proxy.ForumServiceAopStyleImpl$$EnhancerByCGLIB$$58fbcf09.removeTopic花费32毫秒。
可以看出CGLib创建出来的类的名字还是比较奇怪的,另外还有一点 CGLib在创建对象的时候没有Invocation快,不适合需要反复创建对象的场合,在IoC容器启动的时候创建较为合适。