AOP: Aspect Oriented Programing 面向切面编程

使用动态代理实现AOP

传统方法:

  • 基于接口的动态代理:Proxy.newProxyInstance
  • 基于子类的动态代理:Enhancer.create

术语:

  • JoinPoint(连接点):所代理类的所有方法。
  • Pointcut(切入点):被增强的方法是切入点。
  • Advice(通知/增强):拦截到JoinPoint之后要做的事情。
    • 前置通知
      • 事务前
    • 后置通知
      • 事务后
    • 异常通知
      • catch中
    • 最终通知
      • finally中
    • 环绕通知
      • 整个invoke
  • Target(目标对象\被代理对象)
  • Introduction(引介):特殊的通知
  • Weaving(织入):根据目标对象创建代理对象的过程
  • Proxy(代理):织入增强后产生的代理类
  • Aspect(切面):切入点和通知的结合

基于Spring的AOP编程

基于XML的AOP

配置XML文件

<bean id="logger" class="xyz.aiinirii.spring.utils.Logger"/>

<aop:config>
    <aop:aspect id="logAdvice" ref="logger">
        <!-- 类比在事务之前所做的事情 -->
        <aop:before method="beforePrintLog"
                    pointcut="execution(* xyz.aiinirii.spring.service.impl.*.*(..))"/>
        <!-- 类比在事务成功之后所做的事情 例如 commit -->
        <aop:after-returning method="afterReturningPrintLog"
                             pointcut="execution(* xyz.aiinirii.spring.service.impl.*.*(..))"/>
        <!-- 例如回滚 -->
        <aop:after-throwing method="afterThrowingPrintLog"
                            pointcut="execution(* xyz.aiinirii.spring.service.impl.*.*(..))"/>
        <!-- 例如关闭链接 -->
        <aop:after method="afterPrintLog"
                   pointcut="execution(* xyz.aiinirii.spring.service.impl.*.*(..))"/>
    </aop:aspect>
</aop:config>

运行效果,saveAccount方法得到了增强:

image-20200610151556148

或者这样:

<bean id="logger" class="xyz.aiinirii.spring.utils.Logger"/>

<aop:config>
    <aop:pointcut id="serviceImpl" expression="execution(* xyz.aiinirii.spring.service.impl.*.*(..))"/>
    <aop:aspect id="logAdvice" ref="logger">
        <aop:before method="beforePrintLog"
                    pointcut-ref="serviceImpl"/>
        <aop:after-returning method="afterReturningPrintLog"
                             pointcut-ref="serviceImpl"/>
        <aop:after-throwing method="afterThrowingPrintLog"
                            pointcut-ref="serviceImpl"/>
        <aop:after method="afterPrintLog"
                   pointcut-ref="serviceImpl"/>
    </aop:aspect>
</aop:config>

环绕通知?

<bean id="logger" class="xyz.aiinirii.spring.utils.Logger"/>

<aop:config>
    <aop:pointcut id="serviceImpl" expression="execution(* xyz.aiinirii.spring.service.impl.*.*(..))"/>
    <aop:aspect id="logAdvice" ref="logger">
        <aop:around method="aroundPrintLog" pointcut-ref="serviceImpl"/>
    </aop:aspect>
</aop:config>

image-20200610152558086

发现并没有执行被增强的函数。切入点方法没有执行,而通知方法执行了。

原因分析:并没有在环绕通知中调用切入点方法。

public Object aroundPrintLog(ProceedingJoinPoint joinPoint){
    Object rtValue = null;
    try {
        System.out.println("Before Print Log...");
        rtValue = joinPoint.proceed();
        System.out.println("After Return Print Log...");
    } catch (Throwable throwable) {
        System.out.println("After Throwing Print Log...");
        throwable.printStackTrace();
    } finally {
        System.out.println("After Print Log...");
    }
    return rtValue;
}
<bean id="logger" class="xyz.aiinirii.spring.utils.Logger"/>

<aop:config>
    <aop:pointcut id="serviceImpl" expression="execution(* xyz.aiinirii.spring.service.impl.*.*(..))"/>
    <aop:aspect id="logAdvice" ref="logger">
        <aop:around method="aroundPrintLog" pointcut-ref="serviceImpl"/>
    </aop:aspect>
</aop:config>

基于注解的AOP

<aop:aspectj-autoproxy/>

Logger 类,通知类:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @author AIINIRII
 */
@Component("logger")
@Aspect
public class Logger {

    @Before("execution(* xyz.aiinirii.spring.service.impl.*.*(..))")
    public void beforePrintLog() {
        System.out.println("Before Print Log...");
    }

    @AfterReturning("execution(* xyz.aiinirii.spring.service.impl.*.*(..))")
    public void afterReturningPrintLog() {
        System.out.println("After Return Print Log...");
    }

    @AfterThrowing("execution(* xyz.aiinirii.spring.service.impl.*.*(..))")
    public void afterThrowingPrintLog() {
        System.out.println("After Throwing Print Log...");
    }

    @After("execution(* xyz.aiinirii.spring.service.impl.*.*(..))")
    public void afterPrintLog() {
        System.out.println("After Print Log...");
    }

    // @Around("execution(* xyz.aiinirii.spring.service.impl.*.*(..))")
    public Object aroundPrintLog(ProceedingJoinPoint joinPoint){
        Object rtValue = null;
        try {
            System.out.println("Before Print Log...");
            rtValue = joinPoint.proceed();
            System.out.println("After Return Print Log...");
        } catch (Throwable throwable) {
            System.out.println("After Throwing Print Log...");
            throwable.printStackTrace();
        } finally {
            System.out.println("After Print Log...");
        }
        return rtValue;
    }
}

AccountServiceImpl 类,Target 类:

import org.springframework.stereotype.Service;
import xyz.aiinirii.spring.service.IAccountService;

/**
 * @author AIINIRII
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService {

    public void saveAccount() {
        System.out.println("Successfully save the account.");
    }
}

总结

提取重复代码:arrow_right:创建代理对象

从而实现AOP

提示,尽量用@Around()否则最终和异常/后置顺序将会出错。