文章目录
一 什么是Spring AOP
AOP的相关概念
1 切入点(PointCut)
需要添加功能性代码的方法集合,如某个java包下所有的doBusiness方法。
2 通知(Advice)
对业务代码加功能性代码这件事情,就是通知。比如:在执行XX方法之前,都进行打印日志操作。Spring中的通知共有五种类型,分别是:
(1) 前置通知(Before):在目标方法被调用之前调用通知功能。
(2) 后置通知(After):在目标方法完成之后调用通知,无论该方法是否发生异常。
(3) 后置返回通知(After-returning):在目标方法成功执行之后调用通知。
(4) 后置异常通知(After-throwing):在目标方法抛出异常后调用通知。
(5) 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
3 切面(Aspect)
即功能性代码,如打印日志,统计接口响应时长等。
4 连接点(Join Point)
代码中能够插入切面的点,比如某个类的某个方法调用前、调用后、抛出异常后等等。切面代码可以利用这些点插入到正常的流程之中,从而添加行为。
二 为什么需要Spring AOP
在实际开发的过程中,我们编写的业务代码很多时候会包含一些重复的与业务无关的“逻辑”,比如打印日志、统计接口性能等等。
这些功能性代码分布在服务的各个位置,有时它们的代码量甚至超过核心的业务逻辑。
所以,就有人想,能不能把这些通用的功能性代码抽象出来,从而将业务代码与功能代码进行解耦,优化代码结构。
为了解决上述的问题,有些设计模式应运而生,比如模板模式、装饰者模式。
模板模式,通过父类写死功能性代码和业务代码的相对顺序,子类继承父类后,只需实现相应的业务接口,即可以在业务代码前后插入关心的功能,示例如下:
package src.com.lukeye.template;
public abstract class AbstractBaseFunc {
public void excute(){
long startTime = System.currentTimeMillis();
doBussiness();
long endTime = System.currentTimeMillis();
}
public abstract void doBussiness();
}
package src.com.lukeye.template;
public class Func extends AbstractBaseFunc{
@Override
public void doBussiness() {
long sum = 0;
for (int i = 0; i < 10000000; i++) {
sum += i;
}
System.out.println(sum);
}
}
可以看出子类的功能性代码逻辑必须得和父类的完全一致,但有时候不同的业务代码需要定制不同的功能,此时模板模式就不太好用了。
装饰者模式,为了解决模板模式的问题,引入了装饰者模式。装饰者模式将功能性代码模块分成很多个类,每个类只实现一种功能,比如打印日志、统计接口性能。不同的业务代码只需要用不同的功能模块进行装饰,就能够获取定制化的功能。示例代码如下:
package src.com.lukeye.decorator;
/**
* 业务接口
* @author lukeye
*/
public interface IBase {
void excute();
}
package src.com.lukeye.decorator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 日志装饰者
* @author lukeye
*/
public class LoggerDecorator implements IBase {
public static final Logger LOGGER = LoggerFactory.getLogger(LoggerDecorator.class);
@Autowired
private IBase base;
@Override
public void excute() {
LOGGER.info("begin...");
base.excute();
LOGGER.info("end...");
}
}
package src.com.lukeye.decorator;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 统计量装饰者
* @author lukeye
*/
public class MetricDecorator implements IBase {
@Autowired
private IBase base;
@Override
public void excute() {
long startTime = System.currentTimeMillis();
base.excute();
long endTime = System.currentTimeMillis();
}
}
装饰者模式同样也有缺点
1 功能性代码需要实现业务接口,这在概念上就行不通
2 对于没有实现IBase接口的业务代码无法使用装饰者
AOP:针对上述的问题,我们发现功能性代码和业务代码应该是完全正交的关系,用功能性代码切开业务代码,这种方式就是AOP,简单的示意图如下
三 如何使用Spring AOP
示例代码
3.1 基于注解
业务代码,切点&连接点
package src.lukeye.aop;
public interface IBankService {
boolean transfer(String from, String to, int amount);
}
package src.lukeye.aop;
import org.springframework.stereotype.Component;
@Component
public class BankService implements IBankService {
@Override
public boolean transfer(String from, String to, int amount) {
if (amount < 1) {
throw new IllegalArgumentException("transfer amount must be a positive number");
}
System.out.println("[" + from + "]向[" + to + "]转账金额" + amount);
return false;
}
}
通知&切面
package src.lukeye.aop;
import com.google.common.collect.Lists;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.List;
@Order(1)
@Aspect
@Component
public class TransferLogAdvice {
/**
* 切点1
*/
@Pointcut("execution(* src.lukeye.aop.BankService.*(..))")
public void myPointCut(){}
/**
* 切点2
*/
@Pointcut("execution(* src.lukeye.aop.*Service.*(..))")
public void lukePointCut(){}
/**
* 前置通知:在方法执行前执行的代码
* @param joinPoint
*/
@Before(value = "myPointCut() || lukePointCut()")
public void beforeExecute(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Lists.newArrayList(joinPoint.getArgs());
System.out.println(this.getClass().getSimpleName()+ " before execute:"+methodName+ " begin with "+args);
}
/**
* 后置通知:在方法执行后执行的代码(无论该方法是否发生异常),注意后置通知拿不到执行的结果
* @param joinPoint
*/
@After(value = "myPointCut()()")
public void afterExecute(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println(this.getClass().getSimpleName()+ " after execute:"+methodName+" end!");
}
/**
* 后置返回通知:在方法正常执行后执行的代码,可以获取到方法的返回值
* @param joinPoint
*/
@AfterReturning(value = "myPointCut()()",
returning="result")
public void afterReturning(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println(this.getClass().getSimpleName()+ " afterReturning execute:"+methodName+" end with result:"+result);
}
/**
* 后置异常通知:在方法抛出异常之后执行,可以访问到异常信息,且可以指定出现特定异常信息时执行代码
* @param joinPoint
*/
@AfterThrowing(value = "myPointCut()()",
throwing="exception")
public void afterThrowing(JoinPoint joinPoint, Exception /**NullPointerException*/ exception){
String methodName = joinPoint.getSignature().getName();
System.out.println(this.getClass().getSimpleName()+ " afterThrowing execute:"+methodName+" occurs exception:"+exception);
}
/**
* 环绕通知, 围绕着方法执行
*/
@Around(value = "myPointCut()()")
public Object around(ProceedingJoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println(this.getClass().getSimpleName()+ " around:"+methodName+" execute start");
Object result = null;
try {
result = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println(this.getClass().getSimpleName()+ " around:"+methodName+" execute end");
return result;
}
}
主方法:
package src.lukeye.aop;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringAOPTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.config/*.xml");
IBankService bankService = ctx.getBean(IBankService.class);
bankService.transfer("jordan", "kobe", 2000);
System.out.println("*********************");
bankService.transfer("jordan", "kobe", 0);
ctx.close();
}
}
3.2 基于XML配置
参考文献
[1] https://www.jianshu.com/p/007bd6e1ba1b
[2] https://wemp.app/posts/d0186e9a-ec80-404b-bd66-1c97b1a8c2de