代码源地址:https://github.com/haijiao12138/Spring.git
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
当然也可以使用 AspectJ ,Spring AOP 已经集成了AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。
AOP是Spring框架的重要特性。
通知类型有:前置通知、后置最终通知、后置返回通知、后置异常通知、环绕通知
通过通知类型实现Aop的步骤
一:在创建的SpringBoot项目中添加maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
二:创建切面类
在项目com.haijiao12138.demo.spring.aop0813包中创建Controller类AopController1:
package com.haijiao12138.demo.spring.aop0813;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: 海角12138
* @ClassName: AopController
* @description: TODO
* @date: 2021/8/13 0:07
*
*/
@RestController
@RequestMapping("/aop1")
public class AopController1 {
@RequestMapping("/test")
public String testAop(String key){
return "testAop: key = " + key;
}
@RequestMapping("testAfterThrowing")
public String testAfterThrowing(String key){
throw new NullPointerException(key);
}
@RequestMapping("/testAround")
public String testAround(String key){
return "环绕通知:key = " + key;
}
}
在com.haijiao12138.demo.spring.aop0813包中创建切面类:WebAspect1:
package com.haijiao12138.demo.spring.aop0813;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author: haijiao12138
* @ClassName: WebAspect
* @description: TODO
* @date: 2021/8/13 0:05
*
*/
@Aspect
@Component
public class WebAspect1 {
/**
* 切入点
* 匹配top.alanlee.template.controller包及其子包下的所有类的所有方法
* 切入点方法不用写代码,返回类型为void
* execution:用于匹配表达式 将符合条件下的方法和合适的参数作为切面引入
*/
@Pointcut("execution(* com.haijiao12138.demo.spring.aop0813.*.*(..))")
public void pointCut(){
}
/**
* 前置通知,目标方法调用前被调用
*/
@Before("pointCut()")
public void beforeAdvice(JoinPoint joinPoint){
System.out.println("----------- 前置通知 -----------");
Signature signature = joinPoint.getSignature();
System.out.println("返回目标方法的签名:" + signature);
System.out.println("代理的是哪一个方法:" + signature.getName());
Object[] args = joinPoint.getArgs();
System.out.println("获取目标方法的参数信息:" + Arrays.asList(args));
}
/**
* 最终通知,目标方法执行完之后执行
*/
@After("pointCut()")
public void afterAdvice(){
System.out.println("----------- 最终通知 -----------");
}
/**
* 后置返回通知
* 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
* 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
* returning 只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行
* @param joinPoint
* @param keys
*/
@AfterReturning(value = "execution(* com.haijiao12138.demo.spring.aop0813.AopController1..*.*(..))", returning = "keys")
public void afterReturningAdvice(JoinPoint joinPoint, String keys){
System.out.println("----------- 后置返回通知 -----------");
System.out.println("后置返回通知的返回值:" + keys);
}
/**
* 后置异常通知
* 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
* throwing 只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
* @param joinPoint
* @param e
*/
@AfterThrowing(value = "pointCut()", throwing = "e")
public void afterThrowingAdvice(JoinPoint joinPoint, NullPointerException e){
System.out.println("----------- 后置异常通知 -----------");
}
/**
* 环绕通知
* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
* @param proceedingJoinPoint
*/
@Around("execution(* com.haijiao12138.demo.spring.aop0813.AopController1.testAround(..))")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("----------- 环绕通知 -----------");
System.out.println("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
try {
Object proceed = proceedingJoinPoint.proceed();
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
System.out.println("---------- 环绕通知结束 -------------");
}
return null;
}
}
注意:JoinPoint:
除@Around外,每个方法里都可以加或者不加参数JoinPoint。
JoinPoint包含了类名、被切面的方法名、参数等属性。
@Around参数必须为ProceedingJoinPoint。
三.测试
1-测试testAop,访问:http://localhost:8080/aop1/test?key=hello
代码运行结果是:
2-测试testAfterThrowing,访问:http://localhost:8080/aop1/testAfterThrowing?key=hello
运行结果是:
3-测试环绕通知testAround,访问:http://localhost:8080/aop1/testAround?key=hello
代码运行结果是:
四:比较Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。
Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。