Spring AOP你会用吗?一看就明白
SpringBoot 2.1.5.RELEASE
java version 1.8.0_231
一、什么是Spring AOP?
对这个名词的概念解释相信大家都不陌生,Spring AOP即是Spring的核心特性之一,AOP 全拼 Aspect Oriented Program,面向切面编程。
从字面意思理解切面编程的意思就好像是我们用“刀子”把一个顺序执行的代码从某一部位切开,然后我们在这个切面上写上我们的代码。AOP技术的目的是为了方便开发者,在切面编程的思想中,开发人员需要将核心业务和周边业务区分开。
核心业务就是涉及系统中核心部分的业务逻辑,比如操作数据库,修改重要用户数据等,这些业务不具有普适性,每个业务方法都有各自不同的处理逻辑;
周边业务就是一些共性逻辑操作,比如一些通用日志打印,方法耗时统计等等
几个名词概念
- 连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候,即在什么时间,在Spring中连接点概念就是代表着通知的五种类型:before、around、after、afterReturning、afterThrowing
什么时间:方法执行前
- 通知(Advice):在切面的某个特定的连接点上执行的动作。即你在某个时间(连接点)想做具体什么事情(功能),具体事情就是你实际的业务,比如打印日志,提交事务等。
具体什么事情:打印日志
- 切入点(Pointcut):匹配连接点的断言。可以理解为一个选择器,即我的功能是要在哪些类的哪些方法执行,可以使用通配符来选择类和方法
哪些类的哪些方法:* site.teamo.mall.service.impl...(..))
理解为:在site.teamo.mall.service.impl包下的所有类的所有方法
- 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。就是通知和切入点结合,即在哪些类的哪些方法上,在什么时间,做什么事情。
将通知和切入点结合:在site.teamo.mall.service.impl包下的所有类的所有方法在方法执行前打印日志
-
引入(Introduction):用来给一个类型声明额外的方法或属性,即将切面引入切入点匹配的对应类中。
-
目标对象(Target Object): 被一个或者多个切面所通知的对象,即切入点匹配的对应类的实例就是目标对象。
-
代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约。AOP实现的一种方式,对目标对象进行代理,以实现切面通知
-
织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。即对目标对象进行代理,将切面加入目标对象的过程
整体流程理解
在Spring AOP核心的四点是:通知、切入点、切面、织入。一个完整的流程可以描述为:编写你的增强功能(通知),确定这个功能的执行时间(连接点),确定这个功能在什么地方生效(切入点),经过以上三步就形成了一个切面,使用Spring的动态代理机制对目标对象进行代理,将切面引入目标对象的执行逻辑,这样切面就被织入了目标对象
Spring AOP中五种通知类型
- before:在方法调用之前执行的通知
- around:在方法调用之前和之后,都分别可以执行的通知
- after:在方法正常调用之后执行的通知
- afterThrowing:在方法调用过程中发生异常时执行的通知
- afterReturning:在方法调用之后执行的通知,不论方法是否执行异常
二、一个场景
找出慢方法:现有系统A,系统A中的代码中存在调用外部服务接口的逻辑,对此需要统计系统A服务层中各个方法的执行时长,找出执行慢的方法对其统计,以便后续优化。
一般逻辑
methodA(){
long start = currentTime();
doSomething;//方法A核心业务逻辑
...
if(currentTime-start>500){
LoG.info(方法执行时长过长,记为慢方法);
}
}
methodB(){
long start = currentTime();
doSomething;//方法B的核心业务逻辑
...
if(currentTime-start>500){
LoG.info(方法执行时长过长,记为慢方法);
}
}
...
...
这样的一个逻辑中就是将核心业务和周边业务没有进行区分,在这样的一个场景中核心业务就是各自方法自己具体的业务逻辑(methodA和methodB自己的功能逻辑),而周边业务就是计算一个各自核心业务的执行时间执行较慢时则打印日志。在这种实现方式中,虽然满足了当前需求即找出慢方法,可以当周边业务需求发生变更时(比如要将慢方法的执行时间记录进数据库),会带来较多的代码改动,存在风险。
切面编程逻辑
avatar记录开始时间和计算耗时是周边业务,methodA的功能逻辑是核心业务,将周边业务写在切面中,就将核心业务和周边业务分开来编程了,耦合性也就降低
三、具体实现
- pom中引入spring aop的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 编写切面代码
切面表达式eg:execution(* site.teamo.mall.service.impl...(..))
avatar
- 1——execution 所要执行的表达式主体
- 2——代表方法返回值类型, *代表匹配所有类型
- 3——切入点的类在site.teamo.mall.service.impl包及其子包
- 4——代表匹配切入点包下的类,*代表匹配所有类
- 5——切入点类的方法,*代表所有方法,(..)代表方法入参,结合起来即所有入参的所有方法
@Aspect
@Component
public class SlowMethodStatistics {
private static final Logger LOGGER = LoggerFactory.getLogger(SlowMethodStatistics.class);
@Around("execution(* site.teamo.mall.service.impl..*.*(..))")
public Object slowMethodLog(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
// 执行目标 service
Object result = joinPoint.proceed();
// 记录结束时间
long cost = System.currentTimeMillis() - start;
if (cost > 1) {
LOGGER.info("======{}.{} 执行耗时:{}======",
joinPoint.getTarget().getClass(),
joinPoint.getSignature().getName(),
cost);
}
return result;
}
}
- 测试代码
@SpringBootTest
@RunWith(SpringRunner.class)
@EnableAutoConfiguration
@MapperScan(basePackages = {"site.teamo.mall.dao"})
public class AopTest {
@Autowired
private UserService userService;
@Test
public void aopTest() throws Exception {
userService.queryUsernameIsExist("mall");
userService.queryUserForLogin("test","12345");
}
}
执行效果
avatar
文章欢迎转载,转载请注明出处,个人公众号【爱做梦的锤子】,全网同id,个站 http://te-amo.site,欢迎关注,里面会分享更多有用知识,还有我的私密照片