软件重构的理念,如果多个类中出现相同的代码,可以考虑定义一个共同的抽象类,将这些相同的代码提取到抽象类中。
但如果是重复的横切代码(比如性能监控,方法开始时,执行一段代码,方法快结束时再执行一段代码)就很难采用上面的方式。
如图:
此时,AOP(aspect oriented programming)应运而生,通过横向抽取机制为这类无法通过纵向继承体系进行抽象的重复性代码提供了解决方案。将分散在各个业务逻辑代码中的相同代码,通过横向切割的方式抽取到一个独立的模块中,从而和业务保持一个较低的耦合性。当然抽取只是从代码简洁性、复用性的角度考虑,最终程序执行还是要按原来的串行顺序,一步一步执行。
AOP的专用术语
1. 连接点:程序执行的某个特定位置,如类初始化前、某个方法调用前
2. 切点:特定的连接点,如果将连接点比作数据库中的记录,那么切点相当于查询条件
3. 增加:织入到目标类连接点的一段代码
4. 引介:一种特殊的增强,要为类增加一些属性和方法
5. 织入:将代码添加到连接点上的过程
编译前织入,要求特殊的java编译器
类装载织入,要求特殊的类装载器
动态代理织入,在运行期间为目标类添加增强生成子类的方式
6. 代理:一个类一旦被增强后,就产生一个子类,融合了原类和增强逻辑的功能
spring AOP使用两种代理机制:一种基于JDK的动态代理;另一种是基于CGLib的动态代理
区别是JDK只提供接口的代理,而不支持类的代理
代码实例:
一、传统实现
业务逻辑类:
public class ForumServiceImpl implements ForumService {
public void removeTopic(int topicId) {
PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeTopic");
System.out.println("模拟删除Topic记录:"+topicId);
try {
Thread.currentThread().sleep(20);
} catch (Exception e) {
throw new RuntimeException(e);
}
PerformanceMonitor.end();
}
性能监控增强类:
public class PerformanceMonitor {
private static ThreadLocal<MethodPerformace> performaceRecord = new ThreadLocal<MethodPerformace>();
public static void begin(String method) {
System.out.println("begin monitor...");
MethodPerformace mp = performaceRecord.get();
if(mp == null){
mp = new MethodPerformace(method);
performaceRecord.set(mp);
}else{
mp.reset(method);
}
}
public static void end() {
System.out.println("end monitor...");
MethodPerformace mp = performaceRecord.get();
mp.printPerformace();
}
}
性能实体类:
public class MethodPerformace {
private long begin;
private long end;
private String serviceMethod;
public MethodPerformace(String serviceMethod){
reset(serviceMethod);
}
public void printPerformace(){
end = System.currentTimeMillis();
long elapse = end - begin;
System.out.println(serviceMethod+"花费"+elapse+"毫秒。");
}
public void reset(String serviceMethod){
this.serviceMethod = serviceMethod;
this.begin = System.currentTimeMillis();
}
}
总结:增强逻辑已侵入到业务逻辑代码中,扩展性和灵活性都不好。唯一优点就是通过ThreadLocal将非线程安全类改造为线程安全类。
二、JDK动态代理依赖两个类:Proxy和InvocationHandler,其中InvocationHandler是一个接口,用来实现该接口定义的横切逻辑,并通过反射机制调用目标类的代码,动态将增加逻辑和业务逻辑编织在一起。
代码实例:
增强器类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PerformaceHandler implements InvocationHandler {
private Object target;
public PerformaceHandler(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;
}
调用类:
//使用JDK动态代理
ForumService target = new ForumServiceImpl();
PerformaceHandler handler = new PerformaceHandler(target);
ForumService proxy = (ForumService) Proxy.newProxyInstance(target
.getClass().getClassLoader(),
target.getClass().getInterfaces(), handler);
proxy.removeForum(10);
proxy.removeTopic(1012);
主要是将业务代码和横切代码编织到一起生成代理子类。
参考例子:http://www.iteye.com/topic/683613
三、CGLib动态代理。可以解决没有定义接口的业务方法类。采用底层的字节码技术,为一个类创建子类,并在子类中采用方法拦截技术拦截所有父类方法的调用,并织入增强逻辑。
例子:
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
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动态代理
CglibProxy proxy = new CglibProxy();
ForumService forumService = (ForumService)proxy.getProxy(ForumServiceImpl.class);
forumService.removeForum(10);
forumService.removeTopic(1023);
参考例子:
http://blog.csdn.net/cb_121/article/details/2653196
不足:
1)目标类的所有方法都添加了性能监控横切逻辑,一刀切并不是我们想要的
2)采用硬编码的方式指定了横切逻辑的织入点
3)我们手动编写代理子类的创建过程,如果为不同的类创建代理时,同样需要再造轮子,无法做到通用性
spring 的aop可以有针对性的解决上面的三个难题
1)采用切点(Pointcut),指定哪些类哪些方法织入增强逻辑
2)通过增强(Advice)描述横切逻辑和方法的具体织入点(方法前、方法后、两端等)
3)通过切面(Advisor)将1和2结合起来。