最开始知道面向切面编程(AOP)的时候,觉得这个中文名非常难以理解。
实际上,AOP是一种简单的思想,试图通过把业务代码与非业务代码分开,减少代码之间的耦合性,同时减少了代码的重复。
优点:1.减少业务代码与非业务代码之间的耦合性
2.减少代码重复
一、没有使用AOP思想的代码
假设一个场景,现在有一个业务是计算数组中元素的最大值,代码如下
public void getMaxMethod(int a[]){
int max = a[0];
for(int i = 1; i < a.size(); i++){
if(max < a[i]) max = a[i];
}
}
后期由于数组元素很多,希望能够加上统计这个业务运行花费时间的功能(非业务代码)。
public void getMaxMethod(int a[]){
//获取开始时间
long startTime = System.currentTimeMillis();
int max = a[0];
for(int i = 1; i < a.size(); i++){
if(max < a[i]) max = a[i];
}
//获取结束时间
long endTime = System.currentTimeMillis();
long costTime = endTime - startTime;
log.info("cost time is " + costTime);
}
可以看到,加上非业务代码后,这个方法里既有了业务代码也有了非业务代码,随着后续需求继续增加,这个方法势必更加臃肿,包含很多功能,这增加了代码的耦合性。同时,若向多个不同业务功能(类似获取最大值这种)中增加统计时间的功能的话,统计时间的代码也需要写多份,很不方便。
二、增加AOP思想,使用Springboot对项目进行改造
下面提供两种方案使用AOP实现上述同样的功能,方案一使用execution表达式,方案二使用注解的方式。
package com.example.demo.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* 切面类,统计方法执行时间
* @Author cody
* @Date
*/
@Aspect
@Component
public class Timing {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 方法一:使用execution表达式限定切面影响到的方法,该表达式表示com.example.demo.job.Math类下的所有方法都添加该切面
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("execution(* com.example.demo.job.Math.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
logger.info("method is {}, time cost {} ms", joinPoint.getSignature().getName(), end-start);
return result;
}
/**
* 方法二:使用execution表达式限定切面影响到的方法,该表达式表示com.example.demo.job.Math类下的所有方法都添加该切面
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("@annotation(com.example.demo.annotation.TimeStat)")
public Object doAround2(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
logger.info("method is {}, time cost {} ms", joinPoint.getSignature().getName(), end-start);
return result;
}
}
package com.example.demo.job;
import com.example.demo.annotation.TimeStat;
import org.springframework.stereotype.Component;
/**
* 业务类
* @Author cody
* @Date 2020.11.20
*/
@Component
public class Math {
//如果使用方案一,则不需要@TimeStat注解
@TimeStat
public int getMaxMethod(int[] a){
int max = a[0];
for(int i = 1; i < a.length; i++){
if(max < a[i]) {
max = a[i];
}
}
return max;
}
}
package com.example.demo;
import com.example.demo.job.Math;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* 测试AOP
* @Author cody
* @Date 2020.11.20
*/
@RunWith(SpringRunner.class)
@SpringBootTest
class DemoApplicationTests {
@Autowired
Math math;
@Test
void contextLoads() {
int a[] = {1,2,45,3,2,65,3,6};
System.out.println(math.getMaxMethod(a));
}
}
2020-11-20 15:56:26.783 INFO 20620 --- [ main] com.example.demo.aspect.Timing : method is getMaxMethod, time cost 17 ms
65
可以直接运行的项目代码链接我放到了github仓库。
同时这段代码只使用了环绕通知(Around Advice) 的方法。除此之外,springboot提供了更多注解,这些注解可以对切面处理时间前后进行更加精细的操作,我看到一篇文章写的非常好,所以这部内容不重复了。