AOP(Aspect Oriented Programming),即面向切面编程。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
AOP 术语
- Join point(连接点):目标对象中的方法,我们要关注和增强的方法,也就是我们要作用的点
- Pointcut(切点):连接点的集合,常用正则表达式定义所匹配的类和方法名称来指定这些切点。
- Advice(通知):通知定义了切面的位置,应用在某个方法被调用之前?之后?还是抛出异常时?等等。
- Aspect(切面):切面是通知和切点的结合,在spring bean通常是个类,xml中是个label。
- Target object(目标对象):原始对象
- AOP proxy(代理对象):AOP框架创建的对象,包含了原始对象的代码和增加后的代码的对象。
- Weaving(织入):把代理逻辑加入到目标对象上的过程
- Introduction(引入):向现有的类添加新方法或属性,从而无需修改这些现有类的情况下,让他们具有新的行为和状态。织入是对方法的增强,引入是对类的增强
通知的类型
- 前置通知: 在一个方法执行之前,执行通知。
- 后置通知: 在一个方法执行之后,不考虑其结果,执行通知。
- 返回后通知: 在一个方法执行之后,只有在方法成功完成时,才能执行通知。
- 抛出异常后通知:在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。
- 环绕通知:在方法调用之前和之后,执行通知。
Advice(通知):通知定义了切面的位置,应用在某个方法被调用之前?之后?还是抛出异常时?等等。
各个通知的使用
引入jar
首先需要引入下面的jar包:
compile(project(":spring-aop"))
compile("org.aspectj:aspectjweaver:1.9.6")
compile("org.aspectj:aspectjrt:1.9.6")
aop参数-JoinPoint
每一个通知都有可以使用org.aspectj.lang.JoinPoint
类型的参数作为方法的第一个参数,可以通过这个参数来查看连接点的信息。
getArgs(): 获取方法的参数.
getThis(): 获取代理对象.
getTarget(): 获取目标对象.
getSignature(): 获取方法签名.
toString(): 获取方法描述.
环绕通知可以使用JoinPoint的子类ProceedingJoinPoint获取更多的方法。
Object proceed() throws Throwable; // 执行目标方法
Object proceed(Object[] var1) throws Throwable; // 设置目标方法的参数后执行目标方法
切面的使用
package com.morris.spring.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.stream.Collectors;
@Component
@Aspect // 切面
@EnableAspectJAutoProxy
// 开启AOP
public class UserServiceAspect {
@Pointcut("execution(* com.morris.spring.service..*.*(..))") // 切点
public void pointCut() {
}
@Before("pointCut()") // 前置通知
public void before(JoinPoint joinPoint) {
System.out.println("---Before begin---");
System.out.println("getArgs: " + Arrays.asList(joinPoint.getArgs()).stream().map(Object::toString).collect(Collectors.joining(",")));
System.out.println("getThis: " + joinPoint.getThis());
System.out.println("getTarget: " + joinPoint.getTarget());
System.out.println("getSignature: " + joinPoint.getSignature());
System.out.println("toString: " + joinPoint.toString());
System.out.println("---Before end---");
}
@After("pointCut()") // 返回通知
public void after() {
System.out.println("After");
}
@AfterReturning("pointCut()") // 正常返回通知
public void returning() {
System.out.println("AfterReturning");
}
@AfterThrowing(value = "pointCut()", throwing = "throwing") // 异常返回通知
public void logThrowing(JoinPoint joinPoint, Throwable throwing) {
System.out.println("异常返回通知:" + joinPoint.getSignature().getDeclaringTypeName() + " 异常:");
throwing.printStackTrace();
}
@Around("pointCut()") // 环绕通知
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("---Around begin---");
proceedingJoinPoint.proceed();
System.out.println("---Around end---");
}
}
多个AOP之间的执行顺序
切面类1
package com.morris.spring.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect // 切面
@Order(3)
public class UserServiceAspect2 {
@Pointcut("execution(* com.morris.spring.service..*.*(..))") // 切点
public void pointCut() {
}
@Before("pointCut()") // 前置通知
public void before() {
System.out.println("---UserServiceAspect2.before begin---");
}
@Before("pointCut()") // 前置通知
public void before2() {
System.out.println("---UserServiceAspect2.before2 begin---");
}
}
切面类2
package com.morris.spring.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect // 切面
@Order(4)
public class UserServiceAspect3 {
@Before("com.morris.spring.aop.UserServiceAspect2.pointCut()") // 前置通知
public void before() {
System.out.println("---UserServiceAspect3.before begin---");
}
}
结论:order越小越是最先执行,但更重要的是最先执行的最后结束,Spring AOP就是一个同心圆,要执行的方法为圆心,最外层的order最小。@Order注解只能作用于类上,标记在方法上无效,同一个切面中,两个通知作用于同一个目标方法是无序(先按注解排序,再按方法名排序,没有优先级,这点可以从源码中分析得到)的。
简写
简写如下:
package com.morris.spring.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect // 切面
public class UserServiceAspect4 {
@Before("execution(* com.morris.spring.service..*.*(..))") // 切点+通知
public void before() {
System.out.println("---UserServiceAspect4.before begin---");
}
}
切点表达式
execution
用于匹配方法执行 join points连接点,最小粒度为方法级别,在aop中主要使用。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
这里问号表示当前项可以有也可以没有,其中各项的语义如下
- modifiers-pattern:方法的可见性,如public,protected;
- ret-type-pattern:方法的返回值类型,如int,void等;
- declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
- name-pattern:方法名类型,如buisinessService();
- param-pattern:方法的参数类型,如java.lang.String;
- throws-pattern:方法抛出的异常类型,如java.lang.Exception;
例子:
@Pointcut("execution(* com.chenss.dao.*.*(..))") // 匹配com.chenss.dao包下的任意接口和类的任意方法
@Pointcut("execution(public * com.chenss.dao.*.*(..))") // 匹配com.chenss.dao包下的任意接口和类的public方法
@Pointcut("execution(public * com.chenss.dao.*.*())") // 匹配com.chenss.dao包下的任意接口和类的public 无方法参数的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))") // 匹配com.chenss.dao包下的任意接口和类的第一个参数为String类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))") // 匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))") // 匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(public * *(..))") // 匹配任意的public方法
@Pointcut("execution(* te*(..))") // 匹配任意的以te开头的方法
@Pointcut("execution(* com.chenss.dao.IndexDao.*(..))") // 匹配com.chenss.dao.IndexDao接口中任意的方法
@Pointcut("execution(* com.chenss.dao..*.*(..))") // 匹配com.chenss.dao包及其子包中任意的方法
within
表达式的最小粒度为包
@Pointcut("within(com.chenss.dao.*)") // 匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.chenss.dao..*)") // 匹配com.chenss.dao包及其子包中的任意方法
args
匹配指定参数类型和指定参数数量的方法,与包名和类名无关。
args同execution不同的地方在于:args匹配的是运行时传递给方法的参数类型execution匹配的是方法在声明时指定的方法参数类型。
@Pointcut("args(java.io.Serializable)") // 匹配运行时传递的参数类型为指定类型的、且参数个数和顺序匹配
this
this表示代理对象。
@Pointcut("this(com.morris.spring.service.UserService)")
JDK代理时,this指向接口和代理类proxy,也就是说@Pointcut("this(com.morris.spring.service.UserServiceImpl)")
无法实现代理。
cglib代理时(@EnableAspectJAutoProxy(proxyTargetClass = true) ),this指向接口和子类,也就是说@Pointcut("this(com.morris.spring.service.UserServiceImpl)")
可以实现代理。
target
target代表目标对象。
@Before("target(com.morris.spring.aop.annotation.target.UserServiceImpl)")
@target
任何目标对象上面指定注解的类方法。
@Pointcut("@target(com.morris.spring.annotation.Abcd)") // @Abcd
@within
任何目标对象上面指定注解的类方法。
@Pointcut("@within(com.morris.spring.annotation.Abcd)")
注意@target和@within的区别:
- @target要求对象的运行时类型与被注解的类型是同一个类型。
- @within要求对象的运行时类型是被注解的类型的子类。
@args
任何方法参数对应的类上面有指定的注解(不是方法的参数前面有指定的注解)的方法。
@Pointcut("@args(com.morris.spring.annotation.Abcd)")
@annotation
目标方法上有指定注解的方法。
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
bean
作用于Spring中beanName为指定名称的类的方法。
@Pointcut("bean(userServiceImpl)")
@Pointcut("bean(*ServiceImpl)")