面向切面编程AOP
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是Spring框架中的一个重要内容,它通过对既有程序定义一个切入点,然后在其前后切入不同的执行内容,比如常见的有:打开数据库连接/关闭数据库连接、打开事务/关闭事务、记录日志等。基于AOP不会破坏原来程序逻辑,因此它可以很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
引入AOP场景starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
AOP实现日志切面
实现AOP的切面主要有以下几个要素:
- 使用
@Aspect
注解将一个java类定义为切面类 - 使用
@Pointcut
定义一个切入点,可以是一个规则表达式,比如下例中某个package下的所有函数,也可以是一个注解等。 - 根据需要在切入点不同位置的切入内容
- 使用
@Before
在切入点开始处切入内容 - 使用
@After
在切入点结尾处切入内容 - 使用
@AfterReturning
在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理) - 使用
@Around
在切入点前后切入内容,并自己控制何时执行切入点自身的内容 - 使用
@AfterThrowing
用来处理当切入内容部分抛出异常之后的处理逻辑
- 使用
AOP概念
-
连接点(Join point)
就是spring中允许使用通知的地方,基本上每个方法前后抛异常时都可以是连接点
-
切点(Poincut)
其实就是筛选出的连接点,一个类中的所有方法都是连接点,但又不全需要,会筛选出某些作为连接点做为切点。如果说通知定义了切面的动作或者执行时机的话,切点则定义了执行的地点
-
切面(Aspect)
其实就是通知和切点的结合,通知和切点共同定义了切面的全部内容,它是干什么的,什么时候在哪执行
-
引入(Introduction)
在不改变一个现有类代码的情况下,为该类添加属性和方法,可以在无需修改现有类的前提下,让它们具有新的行为和状态。其实就是把切面(也就是新方法属性:通知定义的)用到目标类中去
-
目标(target)
被通知的对象。也就是需要加入额外代码的对象,也就是真正的业务逻辑被组织织入切面。
-
织入(Weaving)
把切面加入程序代码的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入:
- 编译期:切面在目标类编译时被织入,这种方式需要特殊的编译器
- 类加载期:切面在目标类加载到JVM时被织入,这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码
- 运行期:切面在应用运行的某个时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP就是以这种方式织入切面的。
execution表达式
表达式
访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
例:public void com.huge.service.impl.AccountServiceImpl.save()
全通配写法
*..*.*(..)
切入点表达式写法:
参数包括:execution("修饰符 返回值类型 包.类.方法名(参数..) throws异常")
修饰符(举例):一般省略
* 任意
public 公共访问
返回值(举例):
void 无返回值
String 返回值是字符串类型
* 返回值任意
包(举例):
com.xx.user.dao 固定包
com.xx.*.dao com.xx下的任意包中的dao包
com.xx.user.dao.. 包括dao下所有子包中
类(举例):
UserDaoImpl 具体类
User* 以User开头类
*User 以User结尾类
* 任意类
方法(举例):
addUser 具体方法
* 任意方法
*User 以add结尾方法
add* 以add开头方法
参数(无参):
() 无参
(..) 任意参数
(String,int) 1个String和1个int类型的参数
(int) 1个int类型参数
throws,可省略一般不写
访问修饰符可以省略
void com.huge.service.impl.AccountServiceImpl.save()
返回值可以使用通配符,表示任意返回值
* com.huge.service.impl.AccountServiceImpl.save()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
* *.*.*.*.AccountServiceImpl.save()
包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.save()
定义在service 包里,及其子类的任意类的任意方法。
* com.huge.service.*.*()
类名和方法名都可以使用*来实现通配
* *..*.*()
指定只有一级包下的service中方法
* *.service..*()
所有包下的service,及其子包
* *..service..*()
IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意,方法;若为类,则为该类及其子类中的任意方法。
* com.huge.service.IAccountService+.*()
以s开头的任意方法
* com.huge.service.*.s*()
示例
@Service
public class AOPServiceImpl implements AOPService {
@Override
public void printAOP() {
System.out.println("printAOP");
}
}
@RestController
public class AOPController {
@Autowired
AOPService aopService;
@GetMapping("/aop")
public String aopTest(){
aopService.printAOP();
return "func : aopTest";
}
}
@Component
@Aspect //切面类注解
@Order(2)
public class AOPTest {
@Pointcut("execution(* com.top.springbootweb.service..*(..))")
public void test(){
}
@Before("test()")
public void before(JoinPoint joinPoint){
System.out.println("@Before");
System.out.println("当前方法名: "+joinPoint.getSignature().getName());
}
@After("test()")
public void after(JoinPoint joinPoint){
System.out.println("@After");
System.out.println("当前方法名: "+joinPoint.getSignature().getName());
}
//环绕通知
@Around("test()")
public void round(ProceedingJoinPoint joinPoint) throws Throwable { //环绕增强必须要获取ProceedingJoinPoint参数,
System.out.println("@Around");
System.out.println("当前方法名: "+joinPoint.getSignature().getName());
}
}