Spring AOP 基础概念
个人描述
- 编程:
- 可以理解为沿着一条线从上往下顺序执行的代码,代码的执行是存在多条直线的。
- 切面编程:
- 就像对顺序执行的代码按照一定的切面规则横切一刀,对切到的每条直线产生两个断点(这里区分为:上、下)
- 为了使原有的顺序执行的代码能顺利执行下去,需要对断点进行续接
- 续接的过程就是面向切面编程!
- 续接的代码就是通知,前接上断点,后接下断点
术语
- Aspect
- 切面,对要产生切面编程相关定义的模块化,Spring中体现为可以定义切入点、连接点、通知的类
- JoinPoint
- 连接点:横切后一个直线被切为为2断点,续接时就是接着上断点编程连接到下断点(因为编程都是从上往下顺序写的),这个上部分点就是连接点
- Pointcut
- 切入点,产生连接点的规则集合,Spring中体现为定义产生连接点的切入规则
- Advice
- 通知:围绕连接点产生的编程内容,Spring中分为:前置通知,后置通知,环绕通知,异常通知,返回通知等
- target
- 目标对象,采用代理方式实现AOP时,被代理的对象为target
- proxy
- 代理对象,AOP的实现方式,代理为其中一种,pointcut中使用this替代
- weaving
- 织入,将通知内容应用到目标的过程,分为静态织入与动态织入,Spring采用动态织入
概念
-
POP:面向过程编程
-
OOP:面向对象编程
-
BOP:面向Bean编程
-
AOP:面向切面编程
切面编程简易图示
注解式编程
- 切面类
- 用@Aspect在类上面,声明该类是一个切面类,内部可以定义切入点,连接点,通知
value | 描述 | 模式 |
单例模式,默认 | 单例 | |
perthis | 为每个切面的连接点对应的代理对象 都创建一个新的切面实例 | 多例,配合@Scope("prototype")使用 |
pertarget |
为每个切面的连接点对应的代理目标对象 都创建一个新的切面实例 | |
percflow | 不支持(5.2.6) | |
percflowbelow | 不支持(5.2.6) | |
pertypewithin | 不支持(5.2.6) |
- 切入点
- 用@Pointcut在void方法上,声明该方法是一个切入点,可以绑定参数,value为产生连接点的规则
- 在具体的通知中定义
根据表达式来匹配某方法来产生连接点 | execution | 表达式最终匹配的是方法 |
根据对象来产生连接点 | this | 匹配的是代理后的代理对象 |
target | 匹配的是代理前的目标对象 | |
within | 匹配的是代理前的对象 | |
args | 匹配的是方法的入参类型 | |
根据注解来产生连接点 | @target | 匹配的是代理前的目标对象上使用的注解 |
@args | 匹配的是方法的入参中使用的注解 | |
@within | 匹配的是代理前的对象上使用的注解 | |
@annotation | 匹配的是方法上使用的注解 | |
根据Bean名称来产生连接点 | bean | 匹配的是IOC中注册的Bean名称 |
组合模式 | &&、||、! | 以上产生方式组合使用 |
- 通知
@Before | 前置通知 | 在方法执行之前执行 |
@After | 后置通知 | 在方法执行之后执行 |
@Around | 环绕通知 | 可选择方法何时执行 |
@AfterThrowing | 异常通知 | 产生指定的异常时执行 |
@AfterReturning | 返回通知 | 方法执行完毕后通知 |
@DeclareParents | 引介增强 | 给目标类新增方法 |
- 通配符
* | 模糊匹配:表示任意数量的字符 |
.. | 模糊匹配:表示多个/多级,一般用于指定任意层级的包、任意个数的参数 |
+ |
模糊匹配:表示按类型匹配指定类的所有类,包含子类,仅能跟在类名后面 |
execution()、within() | * .. + |
args()、this()、target() | + |
@args()、@within()、@target()、@annotation() | 不支持 |
切入点定义
execution :匹配方法执行产生连接点(通过表达式匹配到某方法,然后产生连接点,这是主要的使用方式)
// execution(<修饰符模式>? <返回类型模式> <类路径模式> <方法名模式>(<参数模式>)<异常模式>?)
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
示例
@Pointcut("execution(* cn.tinyice.demo..AspectJTargetInterface.sayHello())")
public void pointCut() {
}
modifier-pattern? | 修饰符匹配,可选 |
ret-type-pattern | 返回值匹配,可以为*表示任何返回值,全路径的类名等 |
declaring-type-pattern? | 类路径匹配,可选 |
name-pattern | 方法名匹配,可以指定方法名 或者 *代表所有, set* 代表以set开头的所有方法 |
param-pattern | 参数匹配,可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“*”来表示匹配任意类型的参数 |
throws-pattern? | 异常类型匹配,可选 |
? | 表示可选 |
常见用法:注意最少2部分组成(中间一个空格隔开)
// 匹配多级子包中的所有方法
execution(* cn.tinyice..service.*(..))
// 匹配当前包中的所有public方法
execution(public * DemoService.*(..))
// 匹配指定包中的所有public方法,并且返回值是int类型的方法
execution(public int cn.tinyice.demo.service.*(..))
// 匹配指定包中的所有public方法,并且第一个参数是String,返回值是int类型的方法
execution(public int cn.tinyice.demo.service.*(String name, ..))
- within(Type、id),支持类型表达式
@Pointcut("within(cn.tinyice..DemoService)")
public void pointCut() {
}
Type | 支持类型表达式匹配,包括Class、Interface,支持类型表达式,不支持继承 |
id | XML配置中Bean的Id |
常见用法
// 匹配指定包中的所有方法,但不包括子包
within(cn.tinyice.demo.service.*)
// 匹配指定包中的 所有方法,包括子包
within(cn.tinyice.demo.service..*)
// 匹配当前包中的指定类中的方法
within(DemoService)
// 匹配一个接口的所有实现类中的实现的方法
within(IDemoService+)
- this(Type、id)、target(Type、id),Type支持类全限定名称
@Pointcut("this(cn.tinyice.demo.service.DemoService)")
public void pointCut() {
}
@Pointcut("target(cn.tinyice.demo.service.DemoService)")
public void pointCut() {
}
this | 匹配AOP代理对象的类型(CGLIB、JDK 代理之后的类型) |
target | 匹配AOP目标类的类型(类或接口的原始类型),支持继承关系 |
within | 匹配AOP目标类的类型(类或接口的原始类型),支持类型表达式、不支持继承关系(可以通过+来模糊匹配实现) |
- args(Type、id)
@Pointcut("args(java.lang.String, java.lang.String,java.lang.Integer)")
public void pointCut() {
}
- @annotation 、@within 、 @target 、@args : 将注解对象注入到增强方法中
@Pointcut("@annotation(cn.tinyice.demo.aspect.aspects.TyAnnotation))")
public void pointCut() {
}
@Pointcut("@within(cn.tinyice.demo.aspect.aspects.TyAnnotation)")
public void pointCut() {
}
@Pointcut("@target(cn.tinyice.demo.aspect.aspects.TyAnnotation)")
public void pointCut() {
}
@Pointcut("@args(cn.tinyice.demo.aspect.aspects.TyAnnotation)")
public void pointCut() {
}
- bean(name、id):根据IOC中的bean来产生连接点,支持*匹配
@Pointcut("bean(demoService)")
public void pointCut() {
}
@Pointcut("bean(*Service)")
public void pointCut2() {
}
- 组合模式:可以使用 或(||)、且(&&),非(!) 来处理以上结果集
@Pointcut("bean(demoService)")
public void pointCut() {
}
@Pointcut("bean(*Service)")
public void pointCut2() {
}
@Pointcut("bean(*Service) && pointCut()")
public void pointCut3() {
}
@Pointcut("bean(*Service) && bean(*Service)")
public void pointCut4() {
}
访问连接点信息
org.aspectj.lang.JoinPoint | 支持任何通知 |
org.aspectj.lang.ProceedingJoinPoint | 只支持环绕通知 @Around |
方法 | 说明 |
---|---|
Object[] getArgs() |
获取连接点方法运行时的入参列表。 |
Signature getSignature() |
获取连接点的方法签名对象。 |
Object getTarget() |
获取连接点所在的目标对象。 |
Object getThis() |
获取代理对象。 |
方法 | 说明 |
---|---|
Object proceed() throws Throwable |
通过反射执行目标对象连接点处的方法。 |
Object proceed(Object[] var1) throws Throwable |
使用新的入参(替换掉原来的入参),通过反射执行目标对象连接点处的方法。 |
参数传递
支持方式:this、target、@within、@target、@annotation、@args,当需要在通知编码中使用某些对象时,可以通过参数名称传递来获取对象
参数传递时通过反射无法获取参数具体名称,需要通过argNames属性配置参数名称,如果第一个参数是的JoinPoint,ProceedingJoinPoint或 JoinPoint.StaticPart类型可以不指定该从参数,其它参数必须指定
传递示例
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code and bean
}
排序
- 如果在同一个切面类中声明的增强,则按照增强在切面类中定义的顺序进行织入;
- 如果增强位于不同的切面类中,通过Order排序,无Order的顺序不固定
简单示例
接口
public interface IDemoService {
void print(String str);
}
要增强的方法
@Service
public class DemoService implements IDemoService {
@Override
@TyAnnotation
public void print(String str) {
System.out.print(str);
}
}
增强切面类
@Aspect
// @Aspect 只能修饰类
@Component
public class PointCutGroup {
@Pointcut(value = "args(str) && @annotation(cn.tinyice.demo.aspect.aspects.TyAnnotation)) ", argNames = "str")
// 保证参数能正确连接上
public void pointCut(String str) {
}
@Before(value = "pointCut(str)", argNames = "str")
public void beforeAdvice(String str) {
System.out.println("------------ beforeAdvice PointCutGroup args=" + str + "-------------------");
}
@After(value = "pointCut(str)", argNames = "str")
public void afterAdvice(String str) {
System.out.println("------------ afterAdvice PointCutGroup args=" + str + "-------------");
}
@Around(value = "pointCut(str)", argNames = "joinPoint,str")
// ProceedingJoinPoint 参数为@Around增强独有,表示连接点
public Object aroundAdvice(ProceedingJoinPoint joinPoint, String str) throws Throwable {
System.out.println("------------ aroundAdvice PointCutGroup args=" + str + "----------------");
Object obj = joinPoint.proceed();
System.out.println();
System.out.println("------------ aroundAdvice PPointCutGroup args=" + str + "-----------------");
return obj;
}
@AfterReturning(pointcut = "pointCut(str)", argNames = "str,retVal", returning = "retVal")
// value 可以自定义匿名切入点 或者pointCut引入具名切入点
// returning 为方法返回值对象参数名,参数在afterReturning中定义
// @AfterReturning 发生在@After之前
public void afterReturning(String str, Object retVal) {
System.out.println("------------ afterReturning PPointCutGroup retVal=" + retVal + "-------------");
}
@AfterThrowing(value = "args(str) && execution(* cn.tinyice..DemoService.*(..) )", argNames = "str,ex", throwing = "ex")
// throwing 为要增强的异常类型的参数名称,参数在afterThrowing中定义
public void afterThrowing(String str, RuntimeException ex) {
System.out.println("------------ afterThrowing PPointCutGroup exception=" + ex + "-------------");
}
}