Spring AOP理解与应用

一 什么是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,简单的示意图如下
middle

三 如何使用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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值