什么是AOP?
AOP:Aspect Oriented Programming,中文翻译为”面向切面编程“。面向切面编程是一种编程范式,它作为OOP面向对象编程的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、权限控制、缓存控制、日志打印等等。AOP采取横向抽取机制,取代了传统纵向继承体系的重复性代码
Join point:连接点,例如:servlet中的longin()就是连接点;所以连接点在spring中它永远是一个方法。也可以说'目标对象中的方法就是一个连接点。Join point:连接点,例如:servlet中的longin()就是连接点;所以连接点在spring中它永远是一个方法。也可以说'目标对象中的方法就是一个连接点
pointcut:切点,就是连接点的集合
Weaving:织入
Advice:通知,就字面意思,但是有2个部分组成,通知内容和通知到哪里去
Aspect:切面!包括连接点,切点,通知的一个载体。(如果用AspectJ它就是一个类,如果用springXML的时候它就是一个标签)并且交给spring管理
Target object:目标对象,原始对象
AOP proxy:代理对象, 包含了原始对象的代码和增强后的代码的那个对象
Introduction:引入,个人理解为实现接口
使用场景
日志记录,权限验证,效率检查,事务管理......
通知类型:
Before :在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常
After :在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容
AfterReturning:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行
AfterThrowing:在连接点抛出异常后执行
Around:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行
实现方式
方法拦截式实现AOP
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
1、用javaconfig启动AOP首先需要在Spring配置类中添加@EnableAspectJAutoProxy来使工程支持处理带有AspectJ的注解的组件
@Configuration
@EnableAspectJAutoProxy
public class ApplicationConfig {
}
2.定义切面,对符合方法规则的类进行拦截处理
@Aspect
@Component
public class TestAspect {
private final static Logger log = LoggerFactory.getLogger(TestAspect.class);
@Pointcut("execution(* com.example.demo3.service.*.*(..))")
public void logPointCut() {
}
@Before("logPointCut()")
public void beforeLog(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println((method.getName() + "前置增强"));
}
@After("logPointCut()")
public void afterLog(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println(method.getName() + "后置增强");
}
@AfterThrowing("logPointCut()")
public void exceptionLog(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println(method.getName() + "异常增强");
}
@Around("logPointCut()")
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println(method.getName() + "环绕增强");
return joinPoint.proceed();
}
@AfterReturning("logPointCut()")
public void afterRunningLog(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println(method.getName() + "执行正常的后置增强");
}
}
使用注解式配置切面
定义拦截规则的注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
// 拦截规则类型
String type() default "";
}
定义关于此注解的切面
@Aspect
@Component
public class TestAspect2 {
private final static Logger log = LoggerFactory.getLogger(TestAspect2.class);
@Pointcut("@annotation(com.example.demo3.annotation.TestAnnotation")
public void logPointCut() {
}
@Before("logPointCut()")
public void beforeLog(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println((method.getName() + "前置增强"));
}
@After("logPointCut()")
public void afterLog(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println(method.getName() + "后置增强");
}
@AfterThrowing("logPointCut()")
public void exceptionLog(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println(method.getName() + "异常增强");
}
@Around("logPointCut()")
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println(method.getName() + "环绕增强");
return joinPoint.proceed();
}
@AfterReturning("logPointCut()")
public void afterRunningLog(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println(method.getName() + "执行正常的后置增强");
}
}
可以看到由于程序没有发生异常,所以异常增强没有执行,我们接着看
此时程序报错,我们可以看到执行正常的后置增强没有执行,异常增强在异常情况下执行
方法拦截式与注解式的比较
方法规则式拦截
优点
- 方法规则式侵入性低,在分工开发的环境中,编写业务方法代码的人并不需要知道他编写的业务方法的AOP处理逻辑,只要按照统一的命名规则对方法命名即可,然后由编写AOP的人根据统一的方法命名来选择对哪些方法实施拦截。
- 适用于业务代码开发时不考虑AOP处理,业务代码开发完成后再开发AOP的,可以实现无缝接入。
缺点
- 方法规则式灵活性稍差一点,在设计切面时,如果要实现精确的方法拦截处理或者对不同的方法实现不同的处理,execution表达式会写的比较多。
注解式拦截
优点
- 注解式AOP灵活性高,只要定义好注解,想对哪个业务方法做拦截,只需要加上注解就可以了,A方法用A注解,B方法用B注解,不同的业务逻辑使用不同的拦截处理逻辑,这些工作只需要在切面中声明即可。
缺点
- 正因为较高的侵入性,需要在业务代码中加上AOP注解,需要在设计阶段就考虑清楚AOP的拦截逻辑。
在开发当中小编比较推荐注解式拦截,用法灵活方便,特别是接手他人代码时。
AOP切点表达式
1、execution 表达式
execution(modifiers-pattern?ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
语法格式: execution(返回类型.包名.类名.方法名(参数表))
exection(.com.xxx.AService.(..))
com.xxx.AService 类型中的任意方法,任意类型返回结果,参数表不限定,都增加切面
应用:最常用,也是相对最通用。根据方法执行的标准,定义切点,如事务日志
2.@annotation 表达式 语法格式: @annotation(注解的完全限定名)
@annotation(com.example.demo3.annotation.TestAnnotation)
表示匹配使用@annotation指定注解标注的方法将会被环绕
小编列举了两种比较常用的,还有其他的切点表达式,有兴趣的同学可以自行百度了解。