Spring中的AOP(上)--AspectJ

本文详细介绍了Spring中的AOP概念,包括切面、连接点、建议、切入点等,并重点讲解了@AspectJ的支持,如启用、声明切面、声明切入点和Advice。内容涵盖切入点表达式的使用、Advice类型以及AOP代理机制,帮助读者理解Spring AOP在企业级应用中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring中的AOP(上)

软件环境

名称版本号
jdk1.8
springboot2.1.6.RELEASE
spring document5.1.8.RELEASE

以下jar包springboot已集成,无需单独引入,只为对照说明版本号

名称版本号
spring-aop5.1.8.RELEASE
aspectjrt1.9.4
aspectjtools1.9.4
aspectjweaver1.9.4

1. AOP简介

面向切面编程(AOP-Aspect-Oriented Programming)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP-Object-Oriented Programming). OOP中模块化的关键单元是类,而在AOP中, 模块化单元是切面.切面实现了跨越多种类型和对象的关注点(例如事务管理)的模块化.(在AOP中通常被称为"横切"问题.)

Spring的一个关键组件是AOP框架.虽然Spring IOC容器不依赖于AOP,但AOP补充了Spring IOC以提供非常强大的中间件解决方案.并且AOP结合IOC将使我们的程序变的更加简单.


本文讨论基于架构和基于@AspectJ的AOP支持.

具有AspectJ切入点的Spring AOP

Spring通过两种方式实现AOP
1. 基于xml配置 ①
2. @AspectJ ②
使用了AspectJ切入点语言,同时仍可以使用Spring AOP进行编织
AOP在Spring中的应用
  1. 声明式事务管理
  2. 用户自定义切面

1.1.AOP概念

  • Aspect(切面): 多个类的关注点的抽象. (把分布于不同业务但功能相同的代码从业务逻辑中抽离出来, 组成独立的模块,这些模块称为切面. ) 事务管理是横切关注点的一个很好的例子. 在Spring AOP中, 切面是通过使用常规类(基于xml配置)或使用@Aspect的常规类来实现的.

  • Join point(连接点): 程序执行期间的一个点, 例如执行方法或处理异常. 在Spring AOP中, 连接点始终表示方法执行(实际上连接点还可以是字段或者构造器).

  • Advice(建议): 针对特定连接点的采取的操作. 如"Around", “Before"和"After”. 许多AOP框架(包括Spring)将Advice建模为拦截器并在连接点周围维护一系列拦截器.

  • Pointcut(切入点): 要切入的连接点(方法). Advice与Pointcut表达式相关联, 并在切入点匹配的任何连接点处运行(例如, 执行具有特定名称的方法). 切入点表达式匹配的连接点的定义是AOP的核心, Spring默认使用AspectJ切入点表达式语言.

  • Introduction(简介): 代表类声明额外的方法或字段. Spring AOP允许您向任何Advice的对象引入新接口(以及相应的实现). 例如, 您可以使用Introduction使bean实现 IsModified接口, 以简化缓存. (Introduction在AspectJ社区中称作inter-type declaration(类型间声明).)

  • Target Object(目标对象): 由一个或多个切面Advice的对象. 也称为"Advice对象"(包含增强方法的对象). (由于Spring AOP是使用运行时代理实现的, 因此该对象始终是代理对象.)

  • AOP Proxy(AOP代理): 由AOP框架创建的对象, 包括目标对象和为目标对象加上的Advice. (在Spring Framework中, AOP代理是JDK动态代理或CGLIB代理.)

  • Weaving(编织): 连接切面与其他应用程序类型或对象去创建一个Advice对象的过程.这可以在编译时(例如,使用AspectJ编译器), 类加载时或在运行时完成.与其他纯Java AOP框架一样,Spring AOP在运行时执行编织.

Spring AOP包括以下类型的Advice:

  • Before: 在连接点之前运行但无法阻止执行流程进入连接点(除非它抛出异常).

  • AfterReturning: 在连接点正常完成后运行(方法不会抛出异常,正常返回).

  • AfterThrowing: 方法通过抛出异常退出,则执行.

  • After: 无论连接点怎样退出(正常或异常返回),都会执行.

  • Around: 围绕连接点. 这是最有力的Advice. Around可以在方法调用之前和之后执行自定义行为.它还可以选择是继续执行连接点, 还是返回自定义返回值或抛出异常来结束方法.

Spring建议使用可以实现所需行为的最不强大的Advice类型. 例如, 如果只需要使用方法的返回值更新缓存,那么最好实现AfterReturning而不是Around, 尽管Around可以完成同样的事情. 使用最具体的Advice类型可以提供更简单的编程模型, 减少错误的可能性.

由切入点匹配的连接点的概念是AOP的关键,它将其与仅提供拦截的旧技术区分开来.

这些术语不是特定于Spring的. AOP术语不是特别直观.

SpringAOP的目的不是提供最完整的AOP实现, 而是利用AOP实现和Spring IOC之间的紧密集成, 以帮助解决企业应用程序中的常见问题. 如果需要处理非常细粒度的对象(连接点不止方法还包括字段和构造器), 在这种情况下, AspectJ是最佳选择.

1.2 AOP代理

Spring AOP默认使用AOP代理的标准JDK动态代理. ③
Spring AOP也可以使用CGLIB代理. 这是代理类而不是接口所必需的. 默认情况下, 如果业务对象未实现接口, 则使用CGLIB.

1.3 @AspectJ支持

Spring使用AspectJ提供的库; 使用与AspectJ 5相同的注释, 用于切入点解析和匹配. 但是, AOP运行时仍然是纯Spring AOP, 并且不依赖于AspectJ编译器或weaver.

1.3.1 启用@AspectJ支持

如果Spring确定bean被一个或多个切面Advice, 它会自动为该bean生成一个代理来拦截方法调用, 并确保根据需要执行Advice.

  • 使用Java配置启用@AspectJ支持
    @Configuration
    @EnableAspectJAutoProxy
    public class AppConfig {
    }
  • 使用XML配置启用@AspectJ支持
    <aop:aspectj-autoproxy/>
1.3.2 声明切面
  • 使用注释
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
@Component // 还可以使用XML配置Bean的方式 ④
public class NotVeryUsefulAspect {
}
Tips: 在Spring AOP中, 切面本身不能成为其他切面Advice的目标. 
@Aspect类上的注释将其标记为切面, 因此将其从自动代理中排除.
1.3.3 声明切入点

切入点声明由两部分组成:一个是包含名称和任何参数的签名,另一个是切入点表达式.

// 切入点表达式使用的是AspectJ 5的切入点表达式
@Pointcut("execution(* transfer(..))")
private void anyOldTransfer() {} // 签名

切入点指示符根据搜索空间可以分为三组

  • kinded 特定类型指示符 execution, get, set, call, handler
  • scoping 范围界定指示符 within, withincode
  • contextual 基于上下文语境指示符 this, target, @annotation

编写正确的切入点至少应包括前两种类型(kinded和scoping).范围界定指示符的匹配非常快,使用它们意味着AspectJ可以非常迅速地排除不应进一步处理的连接点组.一个好的切入点应尽可能包括一个.

AspectJ切入点指示符未在Spring中提供支持的有:call,get,set,preinitialization, staticinitialization,initialization,handler,adviceexecution,withincode,cflow,cflowbelow,if,@this,和@withincode.
在Spring AOP的切入点表达式中使用这些切入点指示符会导致IllegalArgumentException异常.
Spring AOP可能会在将来的版本中扩展,以支持更多的AspectJ切入点指示符.
在编译期间, AspectJ处理切入点以优化匹配性能. 检查代码并确定每个连接点是否(静态地或动态地)匹配给定切入点, 这是一个【【代价高昂】】的过程.
(动态匹配意味着无法通过静态分析完全确定匹配,并且在代码中放置测试以确定代码运行时是否存在实际匹配).
为了获得最佳匹配性能, 要尽可能缩小匹配的搜索空间. scoping指示符非常快速匹配,使用它们意味着AspectJ可以非常快速地解除不应进一步处理的连接点组.
如果可能,一个好的切入点应该总是包含一个.
支持的切入点指示符

Spring AOP支持以下在切入点表达式中使用的AspectJ切入点指示符(PCD):

execution:用于匹配方法执行的连接点.这是使用Spring AOP时要使用的主要切入点指示符.

within:将匹配限制为某些类型内的连接点(使用Spring AOP时,在匹配类型内声明的方法的执行).

this:限制匹配到连接点(使用Spring AOP时方法的执行)的匹配,其中bean引用(Spring AOP代理)是给定类型的实例.

target:在目标对象(代理的应用程序对象)是给定类型的实例的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行).

args:在参数是给定类型的实例的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行).

@target:在执行对象的类具有给定类型的注释的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行).

@args:限制匹配的连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释.

@within:将匹配限制为具有给定注释的类型内的连接点(使用Spring AOP时,使用给定注释的类型中声明的方法的执行).

@annotation:将匹配限制在连接点的主题(Spring AOP中正在执行的方法)具有给定注释的连接点上.

Spring AOP还支持附加的切入点指示符(PCD) bean.

使用PCD,可以将连接点的匹配限制为特定的命名Spring Bean或一组命名Spring Bean(使用通配符时).该beanPCD具有下列形式:

bean(idOrNameOfBean)

该切入点指示符(PCD)bean只在Spring AOP中支持.它是AspectJ定义的标准PCD的特定于Spring的扩展,因此不适用于@Aspect模型中声明的切面.

组合切入点表达式
@Pointcut("execution(public * (..))")
private void anyPublicOperation() {} 

@Pointcut("within(com.xyz.someapp.trading..)")
private void inTrading() {} 

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

anyPublicOperation 如果方法执行连接点表示任何公共方法的执行,则匹配.

inTrading 如果交易模块中有方法执行,则匹配.

tradingOperation 如果方法执行代表交易模块中的任何公共方法,则匹配.

最佳实践是从较小的命名组件中构建更复杂的切入点表达式

例子

常用PCD(切入点指示符) execution 格式如下:

execution(权限修饰符? 方法返回类型 类的reference路径?方法名(参数) throws 异常类型?)
  • 任何公共的方法
execution(public * *(..))
  • 方法名以set开头的方法
execution(* set*(..))
  • AccountService接口定义的任何方法
execution(* com.xyz.service.AccountService.*(..))
  • service包中定义的任何方法
execution(* com.xyz.service..*(..))
  • service包或其子包中定义的任何方法
execution(* com.xyz.service..*.*(..))
  • service包中的任何连接点(仅在Spring AOP中是指方法)
within(com.xyz.service.*)
  • service包或其子包中的任何连接点(仅在Spring AOP中是指方法)
within(com.xyz.service..*)
  • 代理实现AccountService接口的任何连接点(仅在Spring AOP中是指方法)
this(com.xyz.service.AccountService)
  • 目标对象实现AccountService接口的任何连接点(仅在Spring AOP中是指方法)
target(com.xyz.service.AccountService)
  • 任何采用单个参数且运行时传递的参数为的连接点(仅在Spring AOP中是指方法)Serializable
args(java.io.Serializable)

请注意,此示例中给出的切入点不同于execution(* *(java.io.Serializable)).如果在运行时传递的参数为Serializable,则args版本匹配 ,如果方法签名声明单个type类型的参数,则execution匹配Serializable。

  • 目标对象带有@Transactional注释的任何连接点(仅在Spring AOP中是指方法)
@target(org.springframework.transaction.annotation.Transactional)
  • 目标对象的声明类型具有@Transactional注释的任何连接点(仅在Spring AOP中是指方法)
@within(org.springframework.transaction.annotation.Transactional)
  • 执行方法带有@Transactional注释的任何连接点(仅在Spring AOP中是指方法)
@annotation(org.springframework.transaction.annotation.Transactional)
  • 任何采用单个参数的连接点(仅在Spring AOP中是指方法),并且传递的参数带有@Classified注释(注解生命周期为@Retention(RetentionPolicy.RUNTIME),否则会报错)
@args(com.xyz.security.Classified)
  • 名为tradeService的Spring bean上的任何连接点(仅在Spring AOP中是指方法)
bean(tradeService)
  • 名称与通配符表达式匹配的Spring bean上的任何连接点(仅在Spring AOP中是指方法)*Service
bean(*Service)
1.3.4 声明Advice

Advice与切入点表达式关联,并且可以在切入点匹配的方法执行之前,之后或周围运行.切入点表达式可以是对命名切入点的简单引用,也可以是直接声明的切入点表达式.

@Before

在切入点表达式匹配的方法执行前

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {
    
    // 包含切入点表达式的方法
    @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}

直接使用切入点表达式

    @Before("execution(* com.xyz.myapp.dao..(..))")
    public void doAccessCheck() {
        // ...
    }
@AfterReturning

在切入点表达式匹配的方法正常执行返回时(方法必须正常返回,不能抛异常)

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}

可以对同一个切面添加多个Advice

获取方法返回的实际值

    @AfterReturning(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }

returning属性中使用的名称必须与建议方法中的参数名称一致.当方法执行返回时,返回值将作为相应的参数值传递到通知方法.returning也限制了只能匹配指定类型的值(Object可以匹配任何返回值,不可能返回完全不同的类型).

@AfterThrowing

在切入点表达式匹配的方法抛出异常后

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }

}

如果想要指定异常类型,需要在方法参数中定义,请使用一下方式

    @AfterThrowing(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }

throwing属性中使用的名称必须与建议方法中的参数名称一致.

@After

在切入点表达式匹配的方法执行后(无论抛不抛异常都会执行)

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }

}
@Around

围绕在切入点表达式匹配的方法,可以再方法执行前后做处理,并确定何时,如何以及甚至根本不执行该方法(在其他Advice满足要求的前提下,请勿使用@Around)

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    // 方法的第一个参数必须为类型ProceedingJoinPoint
    @Around("com.xyz.myapp.SystemArchitecture.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }

}

@Around返回的值是该方法的调用者看到的返回值

Advice获取方法参数

ProceedingJoinPoint,它是的子类JoinPoint.该 JoinPoint接口提供了许多有用的方法

  • getArgs():返回方法参数
  • getSignature():返回建议使用的方法的描述(返回值 类.方法(参数))
  • toString():打印切入点表达式
  • …(其余参见官网)
传递参数给Advice

要使参数值可用于Advice,可以使用的args.如果在args表达式中使用参数名称和Advice方法的参数名称一致,则在调用时会将相应参数的值作为参数传递.

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
    // ...
}

args(account,…)将匹配限制为方法至少有一个参数且为Account.其次,通过account使实际参数可用于建议方法中.

另一种写法

@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
    // ...
}

匹配注释示例:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
    AuditCode value();
}

@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
    AuditCode code = auditable.value();
    // ...
}

匹配泛型

public interface Sample<T> {
    void sampleGenericMethod(T param);
    void sampleGenericCollectionMethod(Collection<T> param);
}

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
    // Advice implementation
}

这种方法不适用于通用集合.因此,您不能按以下方式定义切入点:

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
    // Advice implementation
}

要实现与此类似的功能,您必须在上键入参数Collection<?>并手动检查元素的类型.

@Around传递参数

@Around("execution(List<Account> find*(..)) && " +
        "com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
        String accountHolderNamePattern) throws Throwable {
    String newPattern = preProcess(accountHolderNamePattern);
    return pjp.proceed(new Object[] {newPattern});
}

确保建议签名按顺序绑定每个方法参数

Advice顺序

当在不同Aspect定义的两条Advice都需要在同一连接点上运行时,除非另行指定,否则执行顺序是不确定的.可以通过指定优先级来控制执行顺序.通过org.springframework.core.Ordered在Aspect类中实现接口或使用注释对其进行Order注释,可以以正常的Spring方式完成此操作.给定两个方面,从中返回较低值Ordered.getValue()(或注释值)的Aspect具有较高的优先级.
当在相同Aspect定义的两条Advice都需要在同一连接点上运行时,其顺序是未定义的(因为无法通过反射为javac编译的类检索声明顺序).考虑将这些Advice方法拆为一个,或将Advice重构为单独的Aspect,您可以在Aspect级别使用org.springframework.core.Ordered

1.3.5 Introductions

切面不仅可以为现有的方法增加额外的功能,还可以为对象增加新的方法

@Aspect
public class UsageTracking {

    // value里的参数是需要增强的类,defaultImpl是UsageTracked接口的实现类
    @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;// 新增的方法需要定义在一个新的接口中

    // com.xyz.myapp.SystemArchitecture.businessService() 切入点方法
    @Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();// 调用新增接口增加的方法
    }

}
1.3.6 一个AOP示例

用AOP写一个重试操作,有时由于并发问题(例如,死锁失败),业务服务的执行可能会失败.如果重试该操作,则很可能在下一次尝试中成功.对这种情况下重试的业务服务,我们希望重试该操作以避免客户端看到PessimisticLockingFailureException.

@Aspect
public class ConcurrentOperationExecutor implements Ordered {//实现Ordered接口,进行加载优先级配置,这样我们可以将优先级提高到事务之上(这样每次操作都是一个新事务)

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;// 优先级设置,越小优先级越高

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Around("com.xyz.myapp.SystemArchitecture.businessService()")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }

}

对于一些没有做幂等的操作,我们不执行重试操作.定义幂等标识注解,放在可以进行幂等操作的方法上

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}

使用幂等注解修饰后代码修改如下

@Around("com.xyz.myapp.SystemArchitecture.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
    // ...
}

这里给推荐大家一个Guava的Retry框架,十分好用

1.4 Advisors

"Advisors(顾问)"是Spring AOP中的新支持,顾问就像一个独立的切面,只有一条建议.
Spring通过使用aop命名空间标签在xml配置文件中配置,使用aop:advisor元素.通常会与事务结合

<aop:config>
    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>
    <aop:advisor
        pointcut-ref="businessService"
        advice-ref="tx-advice"/>
</aop:config>
<!-- 需要添加事务的命名空间 -->
<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

1.5 代理机制

想要强制使用CGLIB,那么就设置aop:config下面的proxy-target-class属性为true

<aop:config proxy-target-class="true">
        <!-- other beans defined here... -->
</aop:config>

要是使用@AspectJ强制使用CGLIB的话,可以配置aop:aspectj-autoproxy下的proxy-target-class属性为true

<aop:aspectj-autoproxy proxy-target-class="true"/>

要是使用@AspectJ强制使用CGLIB的话,向@EnableAspectJAutoProxy注解中添加属性proxyTargetClass = true

@EnableAspectJAutoProxy(proxyTargetClass = true)
1.5.1 代理问题

Spring AOP是基于代理的.所以在调用被切面表达式匹配的方法的时候其实是先调用代理方法,然后调用实际方法.这个地方有一个问题,如果我们在代理的方法中调用了对象自身的另一个方法,那么这个方法是不会命中匹配的.

public class SimplePojo implements Pojo {
    public void foo() {
        // 当前对象调用,非代理对象
        this.bar();
    }
    public void bar() {
        // ...
    }
}

public class Main {
    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        Pojo pojo = (Pojo) factory.getProxy();
        // 代理调用
        pojo.foo();
    }
}

如果想要使用代理请使用如下方式

public class SimplePojo implements Pojo {

    public void foo() {
        // 使用代理对象调用
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // ...
    }
}

AspectJ没有此自调用问题,因为它不是基于代理的AOP框架.

这个问题还发生在事务嵌套调用的时候,在主事务中直接调用当前对象的子事务方法,导致事务失效

1.6 在Spring应用程序中使用AspectJ

当Spring AOP不满足需求的情况下,在Spring中也可以使用AspectJ的weaver.此处主要涉及AspectJ的知识,本篇不再详述.


后记:

① 在配置文件中加入aop的引用

<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:aop="http://www.springframework.org/schema/aop"
	   xsi:schemaLocation="
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--  aop configuration  -->
	    <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

② AspectJ是一个面向切面的框架,它扩展了Java语言.AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件.

③ 代理分为静态代理和动态代理

  • 静态代理: 由程序员或者自动生成工具生成代理类,然后进行代理类的编译和运行. 在代理类/委托类运行之前,代理类已经以.class的格式存在.
  • 动态代理: 在程序运行时,由反射机制动态创建而成.

JDK代理和CGLIB代理都是动态代理.

JDK的动态代理机制只能代理实现了接口的类, 而没有实现接口的类就不能实现JDK的动态代理;它的原理是继承代理类Proxy并实现要代理的接口.

CGLIB是针对类来实现代理的,它的原理是对指定的目标类生成一个子类, 并覆盖其中方法实现增强.

  • 因为采用的是继承,所以不能对final修饰的类进行代理.
  • 从Spring 4.0开始,由于CGLIB代理实例是通过Objenesis创建的,因此不再调用您的代理对象的构造函数.仅当您的JVM不允许绕过构造函数时,您才可能从Spring的AOP支持中看到相应的调试日志.
  • 在Spring Boot 2.0 中,Spring Boot现在默认使用CGLIB动态代理(基于类的动态代理),包括AOP. 如果需要基于接口的动态代理(JDK基于接口的动态代理), 需要设置spring.aop.proxy-target-class属性为false.
    ④ 使用XML配置
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect"></bean>

⑤ AopConfigException: Could not generate CGLIB subclass. Cannot subclass final class

当你在测试的时候出现以上异常:
原因:CGLIB是继承目标类(见③),final修饰的类不能进行代理;这个时候需要限制PCD的范围,一般都是由于条件过于宽泛造成的.或者你可以修改类的访问修饰符.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值