AOP概述
AOP 场景概述
-
在OOP面对对象以及软件重构的理念,如果多个类中出现相同的代码,则应该考虑定义父类。如Pig、Horse这些对象都有run()和eat()方法,则可以通过定义一个包含着两个方法的Animal父类,Pig、Horse 继承这个父类实现两个方法
-
有一些情况的公共方法或者相同的代码没有办法使用OOP的思路抽象解决,如:下面的代码,所有的业务实现类方法都需要加入监控方法。
# 监控类
public class AopMonitor {
public static void start() {
System.out.println("~~ 监控启动 ~~");
}
public static void end() {
System.out.println("++ 监控结束 ++");
}
}
# 业务接口
public interface AopService {
void add();
void update();
}
# 业务实现类
public class AopServiceImpl implements AopService {
@Override
public void add() {
AopMonitor.start(); # 加入监控 start
System.out.println("** 操作新增的业务** ");
AopMonitor.end(); # 加入监控 end
}
@Override
public void update() {
AopMonitor.start(); # 加入监控 start
System.out.println("** 操作修改的业务** ");
AopMonitor.end(); # 加入监控 end
}
}
- AOP 面向切面编程。其另辟蹊径,通过横向抽取机制解决无法纵向继承的问题。抽取抽象一部分内容是比较很容易的,困难在于怎么把这些抽取的部分集成到业务逻辑中以完成和原来一样的业务流程,这才是关键。而这也就时AOP需要完成的内容。
# 把这部分抽取出来,然后集成到需要的业务代码中
AopMonitor.start();
......
......
AopMonitor.end();
AOP 实现者
- AspectJ:声明式AOP,语言级的AOP实现,扩展了Java语言。在【编译期】提供横切代码织入。
- Spring AOP:称为编程式AOP,纯Java实现,【运行期】通过代理的方式实现。
- AspectWerkz
- JBoss AOP
- AOP 联盟:
aopalliance
是众多开源AOP项目的联合组织,定义了一套AOP规范与标准接口(如org.aopalliance.aop.Advice
)
AspectJ 是目前用的最多,一般spring项目的日常也会使用声明式的实现。Spring的最大的价值并不是提供最好的AOP实现以及最完整AOP功能,其侧重于提供一种和IOC整合的AOP实现,使得在Spring中可以无缝的将Spring AOP、IOC和AspectJ整合在一起。
AOP 术语
-
连接点-Joinpoint:指程序执行的某个位置,如类初始化前、类初始化后、方法调用前/调用后、方法抛出异常后等。
-
切点-Pointcut:一个类会有多个连接点,如上面的
AopServiceImpl
类,每个方法都是一个连接点。AOP通过切点定位特定的连接点,如通过名字匹配其中一个连接点add()
方法,只对这个方法进行织入,忽略其他方法。 -
增强-Advice:是指织入到连接点上的一段程序代码。如上例中的监控类
AopMonitor
中的start()、end()
方法,被织入到业务方法的调用前后。在Spring中定义了5个增强,如前置(BeforeAdvice)、后置(AfterAdvice)、引介等 -
目标-Target:把Advice织入的目标类,如上例中的业务处理类
AopServiceImpl
-
引介-Introduction:一种特殊的Advice,可以为目标类添加一些属性和方法。如业务类原本没有实现某个接口,通过引介功能,可以动态的为改业务类添加接口的实现逻辑,增强业务类的功能
-
织入-Weaving:将Advice添加到目标(Target)类的具体连接点上的过程。
- 编译期织入
- 类装载期织入
- 动态代理织入(运行期)
spring 采用动态代理织入(运行期),AspectJ采用编译期织入和类装载期织入
-
代理-Proxy:一个类被AOP织入Advice后,就产生了一个结果类,其是融合了原类和Advice增强逻辑的代理类
-
切面-Aspect:由切点(Pointcut)和Advice组成,即包含横切逻辑的定义,也包括切点的定义。
Spring AOP 原理
具体的例子还是上面监控的例子,如果没有AOP,需要硬编码实现,每个业务(AopServiceImpl)方法中都需要手动调用监控类(AopMonitor)的方法。Spring Aop 使用JDK的动态代理和CGLib设计这一块,改善其实现。
JDK 动态代理
动态代理详解
详细的动态代码不赘述,详见博客其他相关文章 JDK进阶(7):动态代理
使用动态代理改造
- 从业务代码中移除监控的横切部分,使得
AopServiceProxyImpl
只负责业务。代码如下:
# 监控类
public class AopMonitor {
public static void start() {
System.out.println("~~ 监控启动 ~~");
}
public static void end() {
System.out.println("++ 监控结束 ++");
}
}
# 业务接口
public interface AopService {
void add();
void update();
}
# 业务实现类,只负责业务(去掉监控代码)
public class AopServiceImpl implements AopService {
@Override
public void add() {
System.out.println("** 操作新增的业务** ");
}
@Override
public void update() {
System.out.println("** 操作修改的业务** ");
}
}
- 把横切的监控部分,放入动态代理的
InvocationHandler
的invoke()
中,如新增类ProxyHandler
。横切代码只出现了一次,这样通过invoke()
就把业务diamante与横切代码编织在一起。
# 动态代理
public class ProxyHandler implements InvocationHandler {
# 希望被代理的目标对象
private Object target;
public ProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
# 横切放入这边
AopMonitor.start();
// 业务方法调用,这边生成的为代理实例
Object invoke = method.invoke(target, args);
# 横切放入这边
AopMonitor.end();
# 返回代理实例
return invoke;
}
}
- 通过动态代理的 Proxy 工具类创建业务的代理类,代码如下
# 目标对象
AopService target = new AopServiceProxyImpl();
# InvocationHandler 实现类实例,需要传入目标对象
ProxyHandler proxyHandler = new ProxyHandler(target);
# 创建代理。从这边的实现也可以看出,JDK的动态代理针对接口编程
AopService proxy = (AopService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), // 目标类实现的接口
proxyHandler);
# 调用代理对象的 add() 方法
proxy.add();
CGLib 实现
- 如果目标类没有实现接口,JDK动态代理没有办法帮其创建代理,实现织入,需要 CGLib 的支持。CGLib 采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并织入横切逻辑
- 编写一个可以为任何类创建织入性能监控横切逻辑代理对象的创建器:实现
MethodInterceptor
接口
public class CglibHandler implements MethodInterceptor {
# 获取代理
public Object getProxy(Class clazz) {
Enhancer enhancer = new Enhancer();
# 设置需要创建子类的类
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 {
# 织入横切监控
AopMonitor.start();
// 业务方法调用,返回代理
Object invoke = methodProxy.invokeSuper(o, objects);
AopMonitor.end();
return invoke;
}
}
- 创建代理类实现
CglibHandler cglibHandler = new CglibHandler();
# 创建代理类
AopServiceCglib proxy = (AopServiceCglib) cglibHandler.getProxy(AopServiceCglib.class);
proxy.add();
proxy.update();
总结
- JDK 动态代理与 CGLib 性能
- CGLib创建的代理类执行效率比动态代理创建的代理对象性能高
- CGLib创建代理对象所花费的时间比动态代理高
- 结果:创建次数少的使用CGLib,如Spring中的单例bean;需多次创建的使用动态代理。
- 问题
就以上的实现,有一些问题需要解决:
- Advice 增强没有分类。通过硬编码的方式指定织入横切逻辑,在目标方法的开始前、开始后织入代码,没有灵活实现各个不同的横切,如方法前、方法后、异常等。
- Pointcut 不灵活。目标类的所有方法都添加了监控的横切逻辑,要不所有方法都监控,要不都不监控,没有实现灵活的切点。
- 代理创建麻烦。手工编写代码获取实例的过程,对于不同的类创建代理时,需要使用不同的方式(动态代理和 CGLib )
Spring 编程式 AOP 的主要工作就是围绕以上三点问题展开的。
- 通过 Advice 描述横切逻辑和方法的具体织入点(方法前、方法后、方法的两端等)
- 通过 Pointcut 指定在哪些类的哪些方法上织入 Advice
- 通过 Advisor 切面将 Pointcut 和 Advice 组装起来