Spring AOP
Spring AOP
简介
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
白话(自我理解):就是对符合切入点表示的方法进行功能扩展
核心概念
- 连接点:程序执行过程中的任意位置,在Spirng中是方法
- 切入点:哪些方法需要追加功能的,匹配通知的方法,叫切入点,符合切入点表达式的方法
- 通知:各个方法共用的功能,叫通知。通知存在于通知类中。
- 切面:切面描述的是通知共用的功能与所对应切入点的关系。在哪些切入点上执行哪些通知叫切面
- 通知类:定义通知处理类
- 目标(Target):原对象,被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
代码实现
1.导入maven依赖
因为这里使用的SpringBoot项目所以导入SpringBoot中的依赖
<!-- SpringBoot测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 开启web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- aop和aspect -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.业务层和实现层代码
业务层,继承mybatis-plus
package com.sky.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.sky.pojo.SysUser;
/**
* @author 尹稳健~
* @version 1.0
* @time 2022/9/6
*/
public interface SysUserService extends IService<SysUser> {
void add();
void delete();
void updateUser();
SysUser queryById(Integer id);
}
实现层
package com.sky.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sky.mapper.SysUserMapper;
import com.sky.pojo.SysUser;
import com.sky.service.SysUserService;
import org.springframework.stereotype.Service;
/**
* @author 尹稳健~
* @version 1.0
* @time 2022/9/6
*/
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
implements SysUserService {
@Override
public void add() {
System.out.println("add方法调用");
}
@Override
public void delete() {
System.out.println("delete方法调用");
}
@Override
public void updateUser() {
System.out.println("updateUser方法调用");
}
@Override
public SysUser queryById(Integer id) {
System.out.println("queryById方法调用");
return null;
}
}
3.在业务层的方法上进行AOP功能扩展
3.1 切入点表达式
动作关键字(访问修饰符 返回值 包名.类/接口.方法名(参数)异常名)
- 动作关键词:描述切入点的行为动作,例如execution表示执行到指定切入点
- 访问修饰符:public ,private等等,可以省略
- 返回值
- 包名
- 类/接口名
- 方法名
- 参数
- 异常名:方法定义中抛出指定异常,可以省略
如果把包名写死,那么就需要写特别多的切入点表达式,所以一般我们都是使用通配符来描述切入点,可以快速大量匹配连接点方法
- *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.sky.*.UserService.select*(*))
- … : 多个连续的任意符号,可以独立出现,冲用于简化包名和参数的书写
execution(public * com.sky..UserService.select*(..))
- +:专用于匹配子类类型(了解)
execution(public * com..*Service+.select*(..))
书写技巧
- 所有代码按照标准规范开发,否则一下技巧全部失效
- 描述切入点通常描述接口,而不是描述实现类
- 访问控制修饰符针对接口开发均采用public描述(一般可以省略访问控制修饰符)
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配符来快速描述
- 包名书写尽量不使用 …匹配,效率过低,常用*做当个包描述,或精准匹配
- 接口名/类名书写名称与模块相关的采用星号匹配,例如UserService书写成*Service,绑定业务层接口名
- 方法名书写以动词进行精准匹配,名称采用星号匹配,例如getById书写成getBy*,
- 参数规则较为复杂,根据业务方法灵活调整
- 通常不使用异常作为匹配规则
3.2通知类型
通知类型 | 连接点 |
---|---|
前置通知 | 方法前 |
后置通知 | 方法后,发生异常不执行 |
环绕通知 | 方法前后 |
返回后通知 | 方法后,如果发生异常不会执行 |
抛出异常后通知 | 只有发生异常后会通知 |
前置通知
package com.sky.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author 尹稳健~
* @version 1.0
* @time 2022/9/6
*/
@Aspect
@Component
public class LogAspect {
/** 切入点表达式 */
@Pointcut("execution(* com.sky.service.*Service.*(..))")
public void logPointCut(){}
/** 前置通知 */
@Before("logPointCut()")
public void before(){
System.out.println("前置通知");
}
}
测试:
package com.sky;
import com.sky.service.SysUserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author 尹稳健~
* @version 1.0
* @time 2022/9/6
*/
@SpringBootTest
public class ApplicationTest {
@Autowired
private SysUserService sysUserService;
@Test
public void testBefore(){
sysUserService.add();
}
}
结果:
前置通知
add方法调用
后置通知
package com.sky.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author 尹稳健~
* @version 1.0
* @time 2022/9/6
*/
@Aspect
@Component
public class LogAspect {
/** 切入点表达式 */
@Pointcut("execution(* com.sky.service.*Service.*(..))")
public void logPointCut(){}
/** 前置通知 */
@Before("logPointCut()")
public void before(){
System.out.println("前置通知");
}
/** 后置通知 */
@After("logPointCut()")
public void after(){
// int i = 1/0;
System.out.println("后置通知");
}
}
结果:
前置通知
add方法调用
后置通知
如果我们在后置通知中埋了一个异常,那么会怎么样呢?
package com.sky.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author 尹稳健~
* @version 1.0
* @time 2022/9/6
*/
@Aspect
@Component
public class LogAspect {
/** 切入点表达式 */
@Pointcut("execution(* com.sky.service.*Service.*(..))")
public void logPointCut(){}
/** 前置通知 */
@Before("logPointCut()")
public void before(){
System.out.println("前置通知");
}
/** 后置通知 */
@After("logPointCut()")
public void after(){
int i = 1/0;
System.out.println("后置通知");
}
}
结果:发生异常不执行
前置通知
add方法调用
java.lang.ArithmeticException: / by zero
环绕通知
package com.sky.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author 尹稳健~
* @version 1.0
* @time 2022/9/6
*/
@Aspect
@Component
public class LogAspect {
/** 切入点表达式 */
@Pointcut("execution(* com.sky.service.*Service.*(..))")
public void logPointCut(){}
/** 环绕通知 */
@Around("logPointCut()")
public Object around(){
System.out.println("环绕通知");
return null;
}
}
调用:
package com.sky;
import com.sky.service.SysUserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author 尹稳健~
* @version 1.0
* @time 2022/9/6
*/
@SpringBootTest
public class ApplicationTest {
@Autowired
private SysUserService sysUserService;
@Test
public void testBefore(){
sysUserService.add();
}
}
结果:
环绕通知
为什么这里的结果只有环绕通知中的方法呢,而add方法没有执行呢?
因为环绕通知和其他的通知不同,需要自己调用
环绕通知的正确写法:
package com.sky.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author 尹稳健~
* @version 1.0
* @time 2022/9/6
*/
@Aspect
@Component
public class LogAspect {
/** 切入点表达式 */
@Pointcut("execution(* com.sky.service.*Service.*(..))")
public void logPointCut(){}
/** 环绕通知 */
@Around("logPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知前置");
Object result = joinPoint.proceed();
System.out.println("环绕通知后置");
return result;
}
}
记住一定要将结果返回,不然是没有执行结果的
Object result = joinPoint.proceed();
return result;
介绍ProceedingJoinPoint:
getArgs:可以获取方法的参数
getTarget: 可以获取目标对象,通过反射可以获取其他信息
Object[] args = joinPoint.getArgs();
System.out.println(args);
Object target = joinPoint.getTarget();
System.out.println(target.getClass().getName());
结果:
环绕通知前置
add方法调用
[Ljava.lang.Object;@3af36922
com.sky.service.impl.SysUserServiceImpl
环绕通知后置
返回后通知
和后置通知类似,略
异常通知
发生异常时,触发:
package com.sky.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sky.mapper.SysUserMapper;
import com.sky.pojo.SysUser;
import com.sky.service.SysUserService;
import org.springframework.stereotype.Service;
/**
* @author 尹稳健~
* @version 1.0
* @time 2022/9/6
*/
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
implements SysUserService {
@Override
public void add() {
System.out.println("add方法调用");
int i = 1 /0;
}
@Override
public void delete() {
System.out.println("delete方法调用");
}
@Override
public void updateUser() {
System.out.println("updateUser方法调用");
}
@Override
public SysUser queryById(Integer id) {
System.out.println("queryById方法调用");
return null;
}
}
切面类
package com.sky.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author 尹稳健~
* @version 1.0
* @time 2022/9/6
*/
@Aspect
@Component
public class LogAspect {
/** 切入点表达式 */
@Pointcut("execution(* com.sky.service.*Service.*(..))")
public void logPointCut(){}
/** 异常通知 */
@AfterThrowing(value = "logPointCut()",throwing = "t")
public void afterThrowing(Throwable t){
System.out.println("异常通知"+t.getMessage());
}
}
调用:
package com.sky;
import com.sky.service.SysUserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author 尹稳健~
* @version 1.0
* @time 2022/9/6
*/
@SpringBootTest
public class ApplicationTest {
@Autowired
private SysUserService sysUserService;
@Test
public void testBefore(){
sysUserService.add();
}
}
结果:
add方法调用
异常通知/ by zero
java.lang.ArithmeticException: / by zero