【Spring入门-04】AOP

Spring AOP

AOP(Aspect Oriented Programming):面向切面编程,可以把各类的公共行为封装到一个可重用模块,并将其命名为 Aspect,即切面,减少系统的重复代码,降低模块之间的耦合。

常用的主要功能:日志记录,性能统计,安全控制,事务处理,异常处理。

相关术语

  • 通知(Advice):指拦截到连接点之后要执行的代码,通知又分为五种:
    • 前置通知(Before):在目标方法被调用之前调用通知
    • 后置通知(After):在目标方法完成之后调用通知
    • 返回通知(After-returning):在目标方法成功执行之后调用通知
    • 异常通知(After-throwing):在目标方法抛出异常后调用通知
    • 环绕通知(Around):在目标方法被调用前后执行通知
  • 连接点(Join point):指被拦截到的方法
  • 切点(Pointcut):对连接点进行拦截的定义
  • 切面(Aspect):是通知和切点的结合,是对横切关注点的抽象
  • 引入(Introduction):允许添加新方法或属性到现有的类中
  • 织入(Weaving):将切面应用到目标对象并创建新的代理对象的过程
    • 编译器:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ 就是以这种方式织入切面
    • 类加载期:切面在目标类加载到 JVM 时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5 的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面
    • 运行期:切面在应用运行的某个时刻被织入。一般在织入切面时,AOP 容器会为目标对象动态创建一个代理对象。Spring AOP 就是以这种方式织入切面
  • 目标对象(Target object):被一个或者多个切面所通知的对象,这个对象永远是一个被代理对象

AspectJ 切点表达式

AspectJ 指示器 描述
arg() 限制连接点匹配参数为指定类型的执行方法
@args() 限制连接点匹配参数由指定注解标注的执行方法
execution() 用于匹配是连接点的执行方法
this 限制连接点匹配 AOP 代理的 bean 引用为指定类型的类
target 限制连接点匹配目标对象为指定类型的类
@target() 限制连接点匹配由指定注解标注的执行对象
within() 限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解标注的类型
@annotation 限定匹配带有指定注解的连接点

通配符

  • * 匹配任意数量的字符
  • + 匹配指定类及其子类
  • .. 匹配任意数的子包或参数

运算符

  • &&and 与操作符
  • ||or 或操作符
  • !not 非操作符

使用注解声明切面

注解 描述
@EnableAspectJAutoProxy 启用自动代理功能
@AspectJ 标注切面
@Pointcut 标注可重用的切点
@After 通知方法会在目标方法调用之后执行
@AfterReturning 通知方法在目标方法返回后调用
@AfterThrowing 通知方法在目标方法抛出异常后调用
@Around 通知方法将目标方法封装起来
@Before 通知方法会在目标方法调用之前执行

1. 声明切面

@Aspect:表明类 Audience 是一个切面。
@Pointcut:在切面内定义可重用的切点。
@Around:环绕通知必须接收一个 ProceedingJointPoint 参数。通过调用 ProceedingJointPointproceed() 方法调用被通知的目标方法,否则会阻塞对被通知目标方法的访问。

@Aspect
@Component
public class Audience {

    @Pointcut("execution(* com.chen.aop.Concert.perform(..))")
    public void performance(){
    }

    @Before("performance()")
    public void before(){
        System.out.println("Before performance.");
    }

    @After("performance()")
    public void after(){
        System.out.println("After performance.");
    }

    @AfterReturning("performance()")
    public void applause(){
        System.out.println("CLAP CLAP CLAP!!!");
    }

    @AfterThrowing("performance()")
    public void demandRefund(){
        System.out.println("Demanding a refund.");
    }
    
    @Around("performance()")
    public void log(ProceedingJoinPoint joinPoint){
        System.out.println("Log info.");
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            System.out.println("Log error.");
        }
    }
}

定义 Performance 接口及其实现类 Concert

public interface Performance {
    void perform();
}
@Component
public class Concert implements Performance {
    @Override
    public void perform() {
        System.out.println("Concert perform.");
        // throw new RuntimeException();
    }
}

2. 自动化装配 Bean

@EnableAspectJAutoProxy:启动自动代理功能。proxyTargetClass 默认为 false,表示使用JDK动态代理技术织入通知;当配置为 true 时,表示使用CGLib动态代理技术织入通知。

@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig {
}

3. 测试

public class PerformanceTest {

    @Test
    public void test() {
        ApplicationContext context = new AnnotationConfigApplicationContext(ConcertConfig.class);
        Performance performance = context.getBean(Performance.class);
        performance.perform();
    }
}

使用 XML 配置声明切面

AOP 配置元素 用途
<aop:advisor> 定义 AOP 通知
<aop:after> 定义 AOP 后置通知
<aop:after-returning> 定义 AOP 返回通知
<aop:after-throwing> 定义 AOP 异常通知
<aop:around> 定义 AOP 环绕通知
<aop:aspect> 定义一个切面
<aop:aspectj-autoproxy> 启用 @AspectJ 注解驱动的切面
<aop:before> 定义 AOP 前置通知
<aop:config> 顶层的 AOP 配置元素
<aop:declare-parents> 以透明的方式为被通知的对象引入额外的接口
<aop:pointcut> 定义一个切点
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="com.chen.aop.Concert"/>
    <bean id="audience" class="com.chen.aop.Audience"/>

    <aop:aspectj-autoproxy/>

    <aop:config>
        <aop:aspect ref="audience">
            <aop:pointcut id="performance" expression="execution(* com.chen.aop.Performance.perform(..))"/>

            <aop:before pointcut-ref="performance" method="before"/>
            <aop:after pointcut-ref="performance" method="after"/>
            <aop:after-returning pointcut-ref="performance" method="applause"/>
            <aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
            <aop:around pointcut-ref="performance" method="log"/>
        </aop:aspect>
    </aop:config>

</beans>

Spring AOP 的两种代理方式

  1. 基于 JDK 接口的动态代理
  2. 基于 CGLib 动态生成子类的代理

如果目标对象实现了接口,则默认采用 JDK 动态代理
如果目标对象没有实现接口,则采用 CGLib 动态代理
如果目标对象实现了接口,且强制 CGLib 代理,则使用 CGLib 代理

Spring AOP 与 AspectJ 区别

Spring 在运行时通知对象
代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标类。
当代理拦截到方法调用时,在调用目标对象方法之前,会执行切面逻辑。

Spring 只支持方法级别的连接点
Spring 基于 JDK 动态代理,因此只支持方法连接点
AspectJ、JBoss 支持方法、字段和构造器连接点

应用

  • 事务 @Transational
  • 安全校验 @PreAuthorize
  • 缓存 @Cacheable

参考

  1. GitHub 代码
  2. Spring框架小白的蜕变
  3. 探秘Spring AOP

猜你喜欢

转载自blog.csdn.net/leifchen90/article/details/93158497
今日推荐