1. AOP
- 概念:面向方面编程
- 实现AOP的语言为AOL Aspect-Oriented language
1.1 静态AOP
- 特点 相应的横切关注点以Aspect形式实现之后,会通过特定的编译器,将实现后的Aspect编译并织入到系统的静态类中。比如,Aspect会使用ajc编译器将各个Aspect以Java字节码的形式编译到系统的各个功能模块中,以达到融合Aspect和Class的目的
- 优点 没有任何性能受损失
- 缺点 灵活性不够。如果横切关注点需要改变织入到系统的位置,就需要重新修改Aspect定义文件,p。
1.2 动态AOP
- 特点 AOP的织入过程在系统运行开始之后进行,而不是预先编译到系统类中,甚至在系统运行的时候,也可以动态更改织入逻辑。
- 优点 灵活易用
- 缺点 可以容忍的性能损失
1.3 java平台上AOP实现机制
-
动态代理
- Spring AOP默认情况下采用这种机制实现AOP机能
-
动态字节码增强
- 使用ASM或者CGLIB等Java工具库,在程序运行期间,动态构建字节码的class文件
-
Java代码生成
- 如 EJB容器根据部署描述符文件提供的织入信息,会为相应的功能模块类根据描述符所提供的信息生成对应的Java代码,然后通过部署工具或者部署接口编译Java代码生成相应的Java类。之后,部署到EJB容器的功能模块类就可以正常工作了。
-
自定义类加载器
- 通过自定义类加载器的方式完成横切逻辑到系统的织入,自定义类加载器通过读取外部文件规定的织入规则和必要信息,在加载class文件期间就可以将横切逻辑添加到系统模块类的现有逻辑中,然后将改动后的class交给Java虚拟机运行。
- 某些应用服务器会控制整个的类加载体系,所以,在这样的场景下使用可能会造成一定的问题。
-
AOL扩展
- AOL扩展是最强大、也最难掌握的一种方式,我们之前提到的Aspect就属于这种方式
1.4 AOP成员
- Joinpoint(进行织入操作的系统执行点)
- 常见Joinpoint类型:
- 方法调用 某个方法被调用的时候所处的程序执行点(调用对象)
- 方法执行 某个方法内部执行开始时点(被调用到的方法)
- 构造方法调用
- 字段设置
- 字段获取
- 异常处理执行
- 常见Joinpoint类型:
- Pointcut(Joinpoint的表述方式)
- 将横切逻辑织入当前系统的过程中,需要参照Pointcut规定的Joinpoint信息,才可以知道应该往系统的哪些Joinpoint上织入横切逻辑
- Pointcut的表达方式
- 直接指定Joinpoint所在方法名称
- 正则表达式
- 使用特定的Pointcut表述语言
- Pointcut运算符
- Advice
- dvice是单一横切关注点逻辑的载体,它代表将会织入到Joinpoint的横切逻辑。如果将Aspect比t作OOP中的Class,那么Advice就相当于Class中的Method
- 形式:
- Before Advice
- 前置通知,方法之前执行,可以通过在BeforeAdvice中抛出异常的方式来中断当前程序流程
- After Advice 具体分为三种
- After returning advice
- 当前Joinpoint处执行流程正常完成后, After returning Advice才会执行
- After throwing advice
- 当前Joinpoint执行过程中抛出异常的情况下,才会执行
- After Advice
- 不管Joinpoint处执行流程是正常终了还是抛出异常都会执行,就好像Java中的finally块一样
- After returning advice
- Around Advice
- 环绕通知,在Joinpoint之前和之后都指定相应的逻辑,甚至于中断或者忽略Joinpoint处原来程序流程的执行。
- Introduction
- 为原有的对象添加新的特性或者行为,这就好像你是一个普通公民,当让你穿军装,带军帽,添加了军人类型的Introduction之后,你就拥有军人的特性或者行为。
- Before Advice
2 spring AOP
- 描述:spring AOP 仅支持方法执行类型的joinpoint,SpringAop,设计哲学也是简单而强大。以有限的20%的AOP支持,来满足80%的AOP需求,如果SpringAOP无法满足你所需要的那80%之外的需求,SpringAOP对Aspecti也提供了很好的集成。
2.1 spring AOP 实现机制
-
描述: SpringAOP属于第二代AOP,采用动态代理机制和动态字节码生成技术实现。动态代理机制和字节码生成都是在运行期间为目标对象生成一个代理对象,而将横切逻辑织入到这个代理对象中,系统最终使用的是织入了横切逻辑的代理对象,而不是真正的目标对象
-
代理模式:: Spring AOP本质上就是采用代理机制实现的代理模式参考
-
动态代理
- JDK1.3以后引入动态代理机制,我们可以为指定的接口在系统运行期间动态的生成代理对象。
- InvocationHandler就是我们实现横切逻辑的地方,它是横切逻辑的载体,作用跟Advice是一样的。
- 动态代理的不足: 动态代理机制只能对实现了相应Interface的类使用如果某个类没有实现任何的Interface,就无法使用动态代理机制为其生成相应的动态代理对象
package com.mg.springjiemi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class SubjectInvocationHadndler implements InvocationHandler {
private Object target;
public SubjectInvocationHadndler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try{
System.out.println("-----Before Advice ------");
if(method.getName().equals("request")){
System.out.println("先跟秘书交谈,安全代理,虚拟代理,远程代理");
}
return method.invoke(target,args);
// System.out.println("----- After returning advice -----");
}catch (Exception e) {
System.out.println("-----After throwing advice ------");
return null;
} finally {
System.out.println("-----After advice ------");
}
}
}
package com.mg.springjiemi.proxy;
import java.lang.reflect.Proxy;
public class InvocationHandlerClient {
public static void main(String[] args) {
ISubject subject = (ISubject) Proxy.newProxyInstance(ISubject.class.getClassLoader(),new Class[]{ISubject.class},new SubjectInvocationHadndler(new SubjectImpl()));
String value = subject.request();
System.out.println(value);
}
}
控制台输出:
-----Before Advice ------
先跟秘书交谈,安全代理,虚拟代理,远程代理
-----After advice ------
跟老板交谈
- 默认情况下,如果SpringAOP发现目标对象实现了相应Interface,则采用动态代理机制为其生成代理对象实例。而如果目标对象没有实现任何Interface,SpringAOP会尝试使用一个称为CGLIB
- 动态字节码生成
- 原理: 对目标对象进行继承扩展,为其生成相应的子类,而子类可以通过覆写来扩展父类的行为,只要将横切逻辑的实现放到子类中,然后让系统使用扩展后的目标对象的子类,达到与代理模式相同效果。
- 缺点: 无法对final方法进行覆盖
- 代码示例
package com.mg.springjiemi.proxy.cglib;
public class SubjectImpl {
public String request() {
return "跟老板交谈";
}
}
package com.mg.springjiemi.proxy.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class SubjectCallback implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
try{
System.out.println("-----Before Advice ------");
if(method.getName().equals("request")){
System.out.println("先跟秘书交谈,安全代理,虚拟代理,远程代理");
}
return methodProxy.invokeSuper(o,objects);
// System.out.println("----- After returning advice -----");
}catch (Exception e) {
System.out.println("-----After throwing advice ------");
return null;
} finally {
System.out.println("-----After advice ------");
}
}
}
package com.mg.springjiemi.proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
public class Client {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SubjectImpl.class);
enhancer.setCallback(new SubjectCallback());
SubjectImpl proxy = (SubjectImpl) enhancer.create();
String value = proxy.request();
System.out.println(value);
}
}
控制台输出:
-----Before Advice ------
先跟秘书交谈,安全代理,虚拟代理,远程代理
-----After advice ------
跟老板交谈
3 三种Spring AOP的使用方式
- 根据当前应用环境、各种工具支持、整体团队对各种方式的熟悉程度等不同情况来权衡利弊,最终决定一种合适的方式
- 在2.x版本的AOP中,不论是对AspectJ集成还是基于Schema的AOP的实现,最终的底层实现还是基于第一代Spring AOP的各种理念和实体
3.1 Spring AOP 1.x版本发布的基于接口定义的Advice声明方式。
- 描述: 各种类型的Advice定义需要实现特定的接口,Advice的管理可以通过10C容器或者直接编程来进行。
3.2 基于Schema的AOP,Spring 2.0发布之后增加的Spring AOP使用方式
- 描述: Spring-AOP-1.x版本和@AspectJ形式的AOP优点,不但完全支持Spring-AOP-1.x的配置需求,而且在@AspectJ形式的AOP的基础之上进行了改装:也可以通过POJO声明Aspect和Advice定义。不过,通过相应注解表达的各种信息,转移到了XSD形式的容器配置文件中。
- 应用环境: Spring 2.x版本,但是却无法使用Java 5
3.3 @AspectJ形式的AOP 在Spring 2.0发布之后新增加的一种Spring AOP使用方式
- 描述: 只需要以普通bean的形式声明相应的Aspect和Advice,然后通过相应的注解标注一下即可
- 好处: 各种信息的管理统一到了一个位置,并且由于IDE的重构支持,AOP实现的管理更加方便高效。另外,只需要使用注解标注POJO中的Advice定义方法即可,而不用实现规定的接口来实现各种Advice类型。
- 应用环境: Java5以及更高版本的Java虚拟机之上
4 AspectJ形式的AOP使用
- Spring发布2.0版本之后,SpringAOP框架集成了Aspect的部分功能,这其中就包括Aspecti的Pointcut描述语言支持
4.1 @AspectJ形式Pointcut
- Pointcut Expression
- @Pointcut(标志符(表达式))
- Pointcut Signature 具体化为一个方法定义
- 返回类型必须是void
- public型的Pointcut Signature可以在其他Aspect定义中引用
- private则只能在当前Aspect定义中引用
- 可以将Pointcut Signature作为相应Pointcut-Expression的标志符,在Pointcut-Expression的定义中取代重复的Pointcut表达式定义
@Pointcut("execution(* com.lcw.one.util.basetkmybatis.BaseConsumerController+.*(..)) || execution(* com.lcw.one.main.controller.*.*(..))")
public void webLog(){}
@Pointcut("webLog()")
public void webLog2(){}
- Pointcut 标志符
- execution
- execution (modifiers-pattern? ret-type-pattern declaring-type-pattern?
name-pattern (param-pattern) throws-pattern?) - execution(<修饰符模式>?<返回类型模式><方法所在类的全路径名>?<方法名模式>(<参数模式>)<异常模式>?) 方法的返回类型、方法名以及参数部分的匹配模式是必须指定的
- execution通配符
- * 可以用于任何部分的匹配模式中,可以匹配相邻的多个字符,即一个Word
- .. 通配符可以在两个位置使用,在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
- + 配指定类型的子类型;仅能作为后缀放在类型模式后边。
- within 匹配指定类型下所有的Joinpoint
- SpringAOP只支持方法级别的Joinpoint,所以,在我们为within指定某个类后,它将匹配指定类所声明的所有方法执行
- this spring-aop匹配目标对象的代理对象, AspectJ中代调用方法一方所在的对象
- target spring-aop匹配目标对象,AspectJ中被调用方法所在的对象
- args 匹配拥有指定参数类型、指定参数数量的,会在运行期间动态检查参数的类型。
- @within 匹配持有指定注解类型内的方法,静态匹配
- @target 匹配目标对象持有指定的注解,动态匹配
- @args 匹配方法传入的参数持有指定注解的执行;
- @annotation:匹配方法持有指定注解的方法;
- execution (modifiers-pattern? ret-type-pattern declaring-type-pattern?
- execution
public boolean login (object user); 传入 mg.User 对象。
execution (* * (User)) 匹配不到 静态pointcut
args(mg.User) 可以 动态pointcut
4.2 @AspectJ形式的Advice
-
@Before
-
@AfterReturning
-
@AfterThrowing
- throwing 属性
- 可以限定Advice定义方法的参数名,并在方法调用的时候,将相应的异常绑定到具体方法参数上
- throwing 属性
-
@After
-
@Around
-
@DeclareRarents
- 用于标注Introduction类型的Advice,但该注解对应标注对象的域(Field),而不是方法
- 指定新接口定义的实现类以及将要加诸其上的目标对象
-
可能需要在Advice定义中访问Joinpoint处的方法参数
- 通过org.aspectj.lang.JoinPoint
- @Before,@AfterReturning,@AfterThrowing,@After JoinPoint必须是第一个参数
- 通过arg等标志符绑定
- 当args标志符接受的不是具体的对象类型,而是某个参数名称的时候,它会将这个参数名称对应的参数值绑定到对Advice方法的调用
- 通过org.aspectj.lang.JoinPoint
-
Advice的执行顺序
- 如果匹配同一个Joinpoint的多个Advice都声明在同一个Aspect定义中
- Advice的执行顺序,由它们在Aspect中的声明顺序决定。
- 对于AfterReturngingAdvice,拥有最高优先级的则是最后运行
- Advice声明在不同的Aspect内的时候。如果多个Advice声明所对应的Pointcut定义匹配同一个Joinpoin
- Ordered接口 注解 值越小优先级越高
- 如果匹配同一个Joinpoint的多个Advice都声明在同一个Aspect定义中
-
Aspect的实例化模式
- 使用perthis或者pertarget指定了Aspect的实例化模式之后,将这些Aspect注册到容器时,不能为其bean定义指定singleton的scope,否则会出现异常。毕容器先限定了只有一个实例,就不能为每一个代理对象或者目标对象实例化相应实例。
- singleton 切面只会有一个实例
- perthis 符合条件的代理对象 aop对象 创建一个切面实例
- pertarget 符合条件的每个目标对象创建一个切面实例
// 案例
@Aspect("perthis(this(mg.Apple))")
@Component
public class WebLogAspect {
private Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
@Pointcut("this(mg.Apple)")
public void webLog(){}
}
@Pointcut("webLog()")
public void webLog2(){}
5 AOP 实现
5.1 AnnotationAwareAspectJAutoProxyCreator
- aop的实现基本靠 AnnotationAwareAspectJAutoProxyCreator 完成。根据@Pointcut 注解定义的切点来自动代理相匹配的bean
- 创建代理主要包含了两个步骤
- 获取增强方法或者增强器
- 根据获取的增强进行代理
- 织入
- BeanPostProcessor 的 postProcessAfterInitialization 后置处理器 实现织入。
-
AnnotationAwareAspectJAutoProxyCreator 类图
image.png
- 继承了 InstantiationAwareBeanPostProcessor link
- 继承了 BeanPostProcessor
- 主要作用在于目标对象的实例化过程中需要处理的事情,包括实例化对象的前后过程以及实例的属性设置
- 继承了 InstantiationAwareBeanPostProcessor link
网友评论