一、AOP
AOP 产生的背景
“存在即合理”,任何一种理论或技术的产生,必然有它的原因。了解它产生的背景、为了解决的问题有助于我们更好地把握AOP的概念。
软件开发一直在寻求一种高效开发、护展、维护的方式。从面向过程的开发实践中,前人将关注点抽象出来,对行为和属性进行聚合,形成了面向对象的开发思想,其在一定程度上影响了软件开发的过程。鉴于此,我们在开发的过程中会对软件开发进行抽象、分割成各个模块或对象。例如,我们会对API进行抽象成四个模块:Controller,Service,Gateway,Command.这很好地解决了业务级别的开发,但对于系统级别的开发我们很难聚焦。比如、对于每一个模块需要进行打日志、代码监控、异常处理。以打日志为例,我只能将日志代码嵌套在各个对象上,而无法关注日志本身,而这种现象又偏离了OOP思想。二、AOP的技术实现Proxy
AOP仅仅是一种思想,那为了让这种思想发光,必然脱离语言本身的技术支持,Java在实现该技术时就是采用的代理Proxy,那我们就去了解一下,如何通过代理实现面向切面
1.静态代理
就像我们去买二手房要经过中介一样,房主将房源委托给中介,中介将房源推荐给买方。中间的任何手续的承办都由中介来处理,不需要我们和房主直接打交道。无论对买方还是卖房都都省了很多事情,但同时也要付出代价,对于买房当然是中介费,对于代码的话就是性能。下面我们来介绍实现AOP的三种代理方式。
下面我就以买房的过程中需要打日志为例介绍三种代理方式 静态和动态是由代理产生的时间段来决定的。静态代理产生于代码编译阶段,即一旦代码运行就不可变了。下面我们来看一个例子public interface IPerson { public void doSomething(); }
public class Person implements IPerson { public void doSomething(){ System.out.println("I want wo sell this house"); } }
public class PersonProxy { private IPerson iPerson; private final static Logger logger = LoggerFactory.getLogger(PersonProxy.class); public PersonProxy(IPerson iPerson) { this.iPerson = iPerson; } public void doSomething() { logger.info("Before Proxy"); iPerson.doSomething(); logger.info("After Proxy"); } public static void main(String[] args) { PersonProxy personProxy = new PersonProxy(new Person()); personProxy.doSomething(); } }
通过代理类我们实现了将日志代码集成到了目标类,但从上面我们可以看出它具有很大的局限性:需要固定的类编写接口(或许还可以接受,毕竟有提倡面向接口编程),需要实现接口的每一个函数(不可接受),同样会造成代码的大量重复,将会使代码更加混乱。
2.动态代理
那能否通过实现一次代码即可将logger织入到所有函数中呢,答案当然是可以的,此时就要用到java中的反射机制
public class PersonProxy implements InvocationHandler{ private Object delegate; private final Logger logger = LoggerFactory.getLogger(this.getClass(); public Object bind(Object delegate) { this.delegate = delegate; return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { logger.info("Before Proxy"); result = method.invoke(delegate, args); logger.info("After Proxy"); } catch (Exception e) { throw e; } return result; } public static void main(String[] args) { PersonProxy personProxy = new PersonProxy(); IPerson iperson = (IPerson) personProxy.bind(new Person()); iperson.doSomething(); } }
它的好处理时可以为我们生成任何一个接口的代理类,并将需要增强的方法织入到任意目标函数。但它仍然具有一个局限性,就是只有实现了接口的类,才能为其实现代理。
3.CGLIB
CGLIB解决了动态代理的难题,它通过生成目标类子类的方式来实现来实现代理,而不是接口,规避了接口的局限性。
CGLIB是一个强大的高性能代码生成包(还没研究过),其在运行时期(非编译时期)生成被 代理对象的子类,并重写了被代理对象的所有方法,从而作为代理对象。public class PersonProxy implements MethodInterceptor { private Object delegate; private final Logger logger = LoggerFactory.getLogger(this.getClass()); public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { logger.info("Before Proxy"); Object result = methodProxy.invokeSuper(method, args); logger.info("After Proxy"); return result; } public static Person getProxyInstance() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Person.class); enhancer.setCallback(new PersonProxy()); return (Person) enhancer.create(); } }
当然CGLIB也具有局限性,对于无法生成子类的类(final类),肯定是没有办法生成代理子类的。
以上就是三种代理的实现方式,但千成别被迷惑了,在Spring AOP中这些东西已经被封装了,不需要我们自己实现。要不然得累死,但了解AOP的实现原理(即基于代理)还是很有必要的。
五、最佳实践
那什么时候来使用AOP呢?我就私自定义一下:当一个功能和对象本身没有必段关系,重复出现在多个模块时,就应该用AOP来解耦。
有人总结了一下使用情景(, 是否认同自己决定): Authentication 权限 Caching 缓存 Context passing 内容传递 Error handling 错误处理 Lazy loading 懒加载 Debugging 调试 logging, tracing, profiling and monitoring 记录跟踪 优化 校准 Performance optimization 性能优化 Persistence 持久化 Resource pooling 资源池 Synchronization 同步 Transactions 事务5.1 日志
看了很多资料都是用日志做AOP的例子,其实这个并不太好,因为AOP很难完全实现log的行为,但对于某一类型的日志处理还是有用的。
例如:对Command的异常进行统一处理,对Controller层的请求进行打日志 请参考日志:5.2 代码性能测试
利用PerformanceMonitorInterceptor来协助应用性能优化, spring自带的
当然还有这个JamonPerformanceMonitorInterceptor