AOP的基本概念
1.Advice(通知、切面): 某个连接点所采用的处理逻辑,也就是向连接点注入的代码, AOP在特定的切入点上执行的增强处理。
@Aspect | 切面声明,标注在类、接口(包括注解类型)或枚举上。 |
@Pointcut | 切入点声明,即切入到哪些目标类的目标方法。 value 属性指定切入点表达式,默认为 "",用于被通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式 |
@Before | 前置通知, 在目标方法(切入点)执行之前执行。 value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式 注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知。 |
@After | 后置通知, 在目标方法(切入点)执行之后执行 |
@AfterRunning | 返回通知, 在目标方法(切入点)返回结果之后执行,在 @After 的后面执行 pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 "" |
@AfterThrowing | 异常通知, 在方法抛出异常之后执行, 意味着跳过返回通知 pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 "" 注意:如果目标方法自己 try-catch 了异常,而没有继续往外抛,则不会进入此回调函数 |
@Around | 环绕通知:目标方法执行前后分别执行一些代码,发生异常的时候执行另外一些代码 |
2.JointPoint(连接点):程序运行中的某个阶段点,比如方法的调用、异常的抛出等。
3.Pointcut(切入点): JoinPoint的集合,是程序中需要注入Advice的位置的集合,指明Advice要在什么样的条件下才能被触发,在程序中主要体现为书写切入点表达式。
4.Advisor(增强): 是PointCut和Advice的综合体,完整描述了一个advice将会在pointcut所定义的位置被触发。
5.@Aspect(切面): 通常是一个类的注解,里面可以定义切入点和通知
6.AOP Proxy:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。
<aop:aspectj-autoproxy/>
<aop:config proxy-target-class="true">
<aop:pointcut id="servicePointcut"
expression="execution(* com.cpic..*Service.*(..))" />
<aop:advisor pointcut-ref="servicePointcut" advice-ref="txAdvice"
order="3" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="list*" read-only="true" />
<!-- log方法会启动一个新事务 -->
<tx:method name="log*" propagation="REQUIRES_NEW"
isolation="READ_COMMITTED" />
</tx:attributes>
</tx:advice>
//OK所以一个Spring增强(advisor)=切面(advice)+切入点(PointCut)
Pointcut
表示式(expression)和签名(signature)
//Pointcut表示式
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
//Point签名
private void log(){}
由下列方式来定义或者通过 &&、 ||、 !、 的方式进行组合:
-
execution:用于匹配方法执行的连接点;
-
within:用于匹配指定类型内的方法执行;
-
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
-
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
-
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
-
@within:用于匹配所以持有指定注解类型内的方法;
-
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
-
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
-
@annotation:用于匹配当前执行方法持有指定注解的方法;
格式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
其中后面跟着“?”的是可选项
括号中各个pattern分别表示:
- 修饰符匹配(modifier-pattern?)
- 返回值匹配(ret-type-pattern): 可以为*表示任何返回值, 全路径的类名等
- 类路径匹配(declaring-type-pattern?)
- 方法名匹配(name-pattern):可以指定方法名 或者 *代表所有, set* 代表以set开头的所有方法
- 参数匹配((param-pattern)):可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用"*" 来表示匹配任意类型的参数,".."表示零个或多个任意参数。
如(String)表示匹配一个String参数的方法;(*,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型。 - 异常类型匹配(throws-pattern?)
eg.
- 任意公共方法的执行:execution(public * *(..))
- 任何一个以“set”开始的方法的执行:execution(* set*(..))
- AccountService 接口的任意方法的执行:execution(* com.xyz.service.AccountService.*(..))
- 定义在service包里的任意方法的执行: execution(* com.xyz.service.*.*(..))
- 定义在service包和所有子包里的任意类的任意方法的执行:execution(* com.xyz.service..*.*(..))
第一个*表示匹配任意的方法返回值, ..(两个点)表示零个或多个,第一个..表示service包及其子包,第二个*表示所有类, 第三个*表示所有方法,第二个..表示方法的任意参数个数 - 定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))")
- pointcutexp包里的任意类: within(com.test.spring.aop.pointcutexp.*)
- pointcutexp包和所有子包里的任意类:within(com.test.spring.aop.pointcutexp..*)
- 实现了Intf接口的所有类,如果Intf不是接口,限定Intf单个类:this(com.test.spring.aop.pointcutexp.Intf)
当一个实现了接口的类被AOP的时候,用getBean方法必须cast为接口类型,不能为该类的类型 - 带有@Transactional标注的所有类的任意方法:
- @within(org.springframework.transaction.annotation.Transactional)
- @target(org.springframework.transaction.annotation.Transactional)
- 带有@Transactional标注的任意方法:@annotation(org.springframework.transaction.annotation.Transactional)
@within和@target针对类的注解,@annotation是针对方法的注解 - 参数带有@Transactional标注的方法:@args(org.springframework.transaction.annotation.Transactional)
- 参数为String类型(运行是决定)的方法: args(String)
JoinPoint
常用的方法:
- Object[] getArgs:返回目标方法的参数
- Signature getSignature:返回目标方法的签名
- Object getTarget:返回被织入增强处理的目标对象
- Object getThis:返回AOP框架为目标对象生成的代理对象
当使用@Around处理时,需要将第一个参数定义为ProceedingJoinPoint类型,该类是JoinPoint的子类。
织入
@AfterReturning(
pointcut="execution(* com.abc.service.*.access*(..)) && args(time, name)",
returning="returnValue")
public void access(Date time, Object returnValue, String name) {
System.out.println("目标方法中的参数String = " + name);
System.out.println("目标方法中的参数Date = " + time);
System.out.println("目标方法的返回结果returnValue = " + returnValue);
}
表达式中增加了args(time, name)部分,意味着可以在增强处理的签名方法(access方法)中定义"time"和"name"两个属性。这两个形参的类型可以随意指定(access方法中指定),但一旦指定了这两个参数的类型,则这两个形参类型将用于限制该切入点只匹配第一个参数类型为Date,第二个参数类型为String的方法(方法参数个数和类型若有不同均不匹配);access方法只需要满足"time", "name"参数的顺序和pointcut中args(param1, param2)的顺序相同即可,"returnValue"位置顺序无所谓。
//将被access方法匹配
public String accessAdvice(Date d, String n) {
System.out.println("方法:accessAdvice");
return "aa";
}
切面执行顺序
一个方法只被一个Aspect类拦截
正常:
异常:
同一个方法被多个Aspect类拦截
优先级高的切面类里的增强处理的优先级总是比优先级低的切面类中的增强处理的优先级高。
在“进入”连接点时,最高优先级的增强处理将先被织入(eg.给定的两个不同切面类Before增强处理中,优先级高的那个会先执行);
在“退出”连接点时,最高优先级的增强处理会最后被织入(eg.给定的两个不同切面类After增强处理中,优先级高的那个会后执行)。
eg.优先级为1的切面类Bean1包含了@Before,优先级为2的切面类Bean2包含了@Around,虽然@Around优先级高于@Before,但由于Bean1的优先级高于Bean2的优先级,因此Bean1中的@Before先被织入。
Spring提供了如下两种解决方案指定不同切面类里的增强处理的优先级:
- 让切面类实现org.springframework.core.Ordered接口:实现该接口的int getOrder()方法,该方法返回值越小,优先级越高
- 直接使用@Order注解来修饰一个切面类:使用这个注解时可以配置一个int类型的value属性,该属性值越小,优先级越高
同一个切面类里的两个相同类型的增强处理在同一个连接点被织入时,Spring AOP将以随机的顺序来织入这两个增强处理,没有办法指定它们的织入顺序。即使给这两个 advice 添加了 @Order 这个注解,也不行!
eg.
spring的xml开启AOP配置:
<aop:config proxy-target-class="false" />
<aop:aspectj-autoproxy />
<bean id="opLogAspectj" class="com.noob.aspectj.OpLogAspectj"></bean>
package com.noob.aspectj;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import com.noob.annotation.OpLog;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OpLog {
/**
*
* 操作类型
*/
int opModule();
/**
*
* 是否批量
*/
String batch();
/**
*
* 操作来源(页面+操作)
*/
String source();
}
@Aspect
@Slf4j
public class OpLogAspectj {
@Pointcut(value = "@annotation(com.noob.annotation.OpLog)")
public void methodPointcut(){}
/**
* 对带注解@OpLog的方法进行切面,并获取到注解的属性值
*/
@Around(value= "methodPointcut() && @annotation(opLog)" , argNames="opLog")
public Object around(ProceedingJoinPoint point, OpLog opLog) throws Throwable{
Object obj = null;
Object[] args = point.getArgs();
try {
obj = point.proceed(args);
} catch (Throwable e) {
log.error("方法执行异常", e);
}
long endTime = System.currentTimeMillis();
MethodSignature signature = (MethodSignature) point.getSignature();
String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
return obj;
}
@Pointcut(value = "@annotation(org.apache.shiro.authz.annotation.RequiresPermissions)")
public void methodPointcut2(){}
/**
* 配置指定位置指定类型的参数
*/
@Around(value= "methodPointcut2() && (args(request, ..) || args(.., request))")
public Object around2(ProceedingJoinPoint point, HttpServletRequest request) throws Throwable{
Object obj = null;
Object[] args = point.getArgs();
try {
SsoUser ssoUser = SsoSession.getCurrentUser(request);
obj = point.proceed(args);
} catch (Throwable e) {
log.error("方法执行异常", e);
}
return obj;
}
}
进入切面的方法:
@OpLog(batch = YesOrNo.NO, source = FuncPointEnum.GROUP_ADD)
public Response<Integer> add(RuleGroupModifyReq addModel) {
// do somethings
}
@RequestMapping(value = "/delete")
@ResponseBody
@RequiresPermissions("rule:delete")
public Response<Integer> delete(
@RequestBody Map<String, String> params,
HttpServletRequest request) {
// do somethings
}
@Aspect 快速入门
1、@Aspect 常见用于记录日志、异常集中处理、权限验证、Web 参数校验、事务处理等等
2、要想把一个类变成切面类,只需3步:
1)在类上使用 @Aspect 注解使之成为切面类
2)切面类需要交由 Sprign 容器管理,所以类上还需要有 @Service、@Repository、@Controller、@Component 等注解
3)在切面类中自定义方法接收通知
3、AOP 的含义就不再累述了,下面直接上示例:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.SourceLocation;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 切面注解 Aspect 使用入门
* 1、@Aspect:声明本类为切面类
* 2、@Component:将本类交由 Spring 容器管理
* 3、@Order:指定切入执行顺序,数值越小,切面执行顺序越靠前,默认为 Integer.MAX_VALUE
*
* @author wangMaoXiong
* @version 1.0
* @date 2020/8/20 19:22
*/
@Aspect
@Order(value = 999)
@Component
public class AspectHelloWorld {
/**
* @Pointcut :切入点声明,即切入到哪些目标方法。value 属性指定切入点表达式,默认为 ""。
* 用于被下面的通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式
* <p>
* 切入点表达式常用格式举例如下:
* - * com.wmx.aspect.EmpService.*(..)):表示 com.wmx.aspect.EmpService 类中的任意方法
* - * com.wmx.aspect.*.*(..)):表示 com.wmx.aspect 包(不含子包)下任意类中的任意方法
* - * com.wmx.aspect..*.*(..)):表示 com.wmx.aspect 包及其子包下任意类中的任意方法
* </p>
*/
@Pointcut(value = "execution(* com.wmx.aspect.EmpServiceImpl.*(..))")
private void aspectPointcut() {
}
/**
* 前置通知:目标方法执行之前执行以下方法体的内容。
* value:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
* <br/>
*
* @param joinPoint:提供对连接点处可用状态和有关它的静态信息的反射访问<br/> <p>
* Object[] getArgs():返回此连接点处(目标方法)的参数
* Signature getSignature():返回连接点处的签名。
* Object getTarget():返回目标对象
* Object getThis():返回当前正在执行的对象
* StaticPart getStaticPart():返回一个封装此连接点的静态部分的对象。
* SourceLocation getSourceLocation():返回与连接点对应的源位置
* String toLongString():返回连接点的扩展字符串表示形式。
* String toShortString():返回连接点的缩写字符串表示形式。
* String getKind():返回表示连接点类型的字符串
* </p>
*/
@Before(value = "aspectPointcut()")
public void aspectBefore(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
Object target = joinPoint.getTarget();
Object aThis = joinPoint.getThis();
JoinPoint.StaticPart staticPart = joinPoint.getStaticPart();
SourceLocation sourceLocation = joinPoint.getSourceLocation();
String longString = joinPoint.toLongString();
String shortString = joinPoint.toShortString();
System.out.println("【前置通知】");
System.out.println("\targs=" + Arrays.asList(args));
System.out.println("\tsignature=" + signature);
System.out.println("\ttarget=" + target);
System.out.println("\taThis=" + aThis);
System.out.println("\tstaticPart=" + staticPart);
System.out.println("\tsourceLocation=" + sourceLocation);
System.out.println("\tlongString=" + longString);
System.out.println("\tshortString=" + shortString);
}
/**
* 后置通知:目标方法执行之后执行以下方法体的内容,不管目标方法是否发生异常。
* value:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
*/
@After(value = "aspectPointcut()")
public void aspectAfter(JoinPoint joinPoint) {
System.out.println("【后置通知】");
System.out.println("\tkind=" + joinPoint.getKind());
}
/**
* 返回通知:目标方法返回后执行以下代码
* value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
* pointcut 属性:绑定通知的切入点表达式,优先级高于 value,默认为 ""
* returning 属性:通知签名中要将返回值绑定到的参数的名称,默认为 ""
*
* @param joinPoint :提供对连接点处可用状态和有关它的静态信息的反射访问
* @param result :目标方法返回的值,参数名称与 returning 属性值一致。无返回值时,这里 result 会为 null.
*/
@AfterReturning(pointcut = "aspectPointcut()", returning = "result")
public void aspectAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("【返回通知】");
System.out.println("\t目标方法返回值=" + result);
}
/**
* 异常通知:目标方法发生异常的时候执行以下代码,此时返回通知不会再触发
* value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
* pointcut 属性:绑定通知的切入点表达式,优先级高于 value,默认为 ""
* throwing 属性:与方法中的异常参数名称一致,
*
* @param ex:捕获的异常对象,名称与 throwing 属性值一致
*/
@AfterThrowing(pointcut = "aspectPointcut()", throwing = "ex")
public void aspectAfterThrowing(JoinPoint jp, Exception ex) {
String methodName = jp.getSignature().getName();
System.out.println("【异常通知】");
if (ex instanceof ArithmeticException) {
System.out.println("\t【" + methodName + "】方法算术异常(ArithmeticException):" + ex.getMessage());
} else {
System.out.println("\t【" + methodName + "】方法异常:" + ex.getMessage());
}
}
}
如上所示在不修改原来业务层代码的基础上,就可以使用 AOP 功能,在目标方法执行前后或者异常时都能捕获然后执行。
execution 切点表达式
1、@Pointcut 切入点声明注解,以及所有的通知注解都可以通过 value 属性或者 pointcut 属性指定切入点表达式。
2、切入点表达式通过 execution 函数匹配连接点,语法:execution([方法修饰符] 返回类型 方法路径(参数类型) [异常类型])
3、切入点表达式的写法比较灵活,比如:* 号表示任意一个,.. 表示任意多个,还可以使用 &&、||、! 进行逻辑运算,不过实际开发中通常用不到那么多花里胡哨的,掌握以下几种就基本够用了。
切入点表达式常用举例
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(Integer)) | 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,且带有一个 Integer 类型参数。 |
---|---|
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(*)) | 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,且带有一个任意类型参数。 |
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(..)) | 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,参数不限。 |
execution(* grp.basic3.se.service.SEBasAgencyService3.editAgencyInfo(..)) || execution(* grp.basic3.se.service.SEBasAgencyService3.adjustAgencyInfo(..)) | 匹配 editAgencyInfo 方法或者 adjustAgencyInfo 方法 |
execution(* com.wmx.aspect.EmpService.*(..)) | 匹配 com.wmx.aspect.EmpService 类中的任意方法 |
execution(* com.wmx.aspect.*.*(..)) | 匹配 com.wmx.aspect 包(不含子包)下任意类中的任意方法 |
execution(* com.wmx.aspect..*.*(..)) | 匹配 com.wmx.aspect 包及其子包下任意类中的任意方法 |
转载于:https://blog.csdn.net/weixin_33676492/article/details/92388130
转载于:https://wangmaoxiong.blog.csdn.net/article/details/108164388