spring aop 理解

本文详细解析了AOP(面向切面编程)的概念,对比了JDK动态代理和CGLib动态代理的区别,深入探讨了SpringAOP的工作原理,包括切点、增强、切面的定义,以及动态代理的实现方式。同时,文章还介绍了如何在Spring中配置AOP,以及如何强制使用CGLib代理。

最近一直在面试,aop这个还是永远的热点,看不了不少大佬的关于aop的文章,感觉还是下面不错

常问的点 :  两种动态代理 区别 , 怎么强制使用CGLIB代理  切点和切面定义  

 底层原理,自己动手写怎么写实现

动态代理

  • JDK动态代理:只能为接口创建动态代理实例,而不能针对类 。
  • CGLib(Code Generation Library)动态代理:可以为任何类创建织入横切逻辑代理对象,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final。

原理对比:

  • JDK动态代理:JDK动态代理技术。通过需要代理的目标类的getClass().getInterfaces()方法获取到接口信息(这里实际上是使用了Java反射技术。getClass()和getInterfaces()函数都在Class类中,Class对象描述的是一个正在运行期间的Java对象的类和接口信息),通过读取这些代理接口信息生成一个实现了代理接口的动态代理Class(动态生成代理类的字节码),然后通过反射机制获得动态代理类的构造函数,并利用该构造函数生成该Class的实例对象(InvokeHandler作为构造函数的入参传递进去),在调用具体方法前调用InvokeHandler来处理。
  • CGLib动态代理:字节码技术。利用asm开源包,把代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。采用非常底层的字节码技术,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。

Spring AOP

  • Pointcut(切点):指定在哪些类的哪些方法上织入横切逻辑
  • Advice(增强):描述横切逻辑和方法的具体织入点(方法前、方法后、方法的两端等)
  • Advisor(切面):将Pointcut和Advice两者组装起来。有了Advisor的信息,Spring就可以利用JDK或CGLib的动态代理技术采用统一的方式为目标Bean创建织入切面的代理对象了

AOP的动态代理:

我们所说的Spring AOP,它包括基于XML配置的AOP和基于@AspcetJ注解的AOP,这两种方法虽然在配置切面时的表现方式不同,但底层都是使用动态代理技术(JDK代理或CGLib代理)。

Spring可以继承AspcetJ,但AspcetJ本身并不属于Spring AOP的范畴:

  • AspectJ:

AspectJ 在编译时“自动”编译得到了一个新类,这个新类增强了原有的 Hello.java 类的功能,因此 AspectJ 通常被称为编译时增强的 AOP 框架

与 AspectJ 相对的还有另外一种 AOP 框架,它们不需要在编译时对目标类进行增强,而是运行时生成目标类的代理类,该代理类要么与目标类实现相同的接口,要么是目标类的子类——总之,代理类的实例可作为目标类的实例来使用。一般来说,编译时增强的 AOP 框架在性能上更有优势——因为运行时动态增强的 AOP 框架需要每次运行时都进行动态增强。

  • Spring AOP:

与 AspectJ 相同的是,Spring AOP 同样需要对目标类进行增强,也就是生成新的 AOP 代理类;与 AspectJ 不同的是,Spring AOP 无需使用任何特殊命令对 Java 源代码进行编译,它采用运行时动态地、在内存中临时生成“代理类”的方式来生成 AOP 代理。
Spring 允许使用 AspectJ Annotation 用于定义方面(Aspect)、切入点(Pointcut)和增强处理(Advice),Spring 框架则可识别并根据这些 Annotation 来生成 AOP 代理。Spring 只是使用了和 AspectJ 5 一样的注解,但并没有使用 AspectJ 的编译器或者织入器(Weaver),底层依然使用的是 Spring AOP,依然是在运行时动态生成 AOP 代理,并不依赖于 AspectJ 的编译器或者织入器。
简单地说,Spring 依然采用运行时生成动态代理的方式来增强目标对象,所以它不需要增加额外的编译,也不需要 AspectJ 的织入器支持;而 AspectJ 在采用编译时增强,所以 AspectJ 需要使用自己的编译器来编译 Java 文件,还需要织入器。

@AspectJ的使用:

如果不打算使用 Spring 的 XML Schema 配置方式,则应该在 Spring 配置文件中增加如下片段来启用 @AspectJ 支持:

<!-- 启动 @AspectJ 支持 -->
<bean class="org.springframework.aop.aspectj.annotation. 
    AnnotationAwareAspectJAutoProxyCreator"/>

上面配置文件中的 AnnotationAwareAspectJAutoProxyCreator 是一个 Bean 后处理器(BeanPostProcessor),该 Bean 后处理器将会为容器中 Bean 生成 AOP 动态代理。

使用 @Aspect 标注一个 Java 类,该 Java 类将会作为方面 Bean(也是可以被Spring容器管理的Bean),如下面代码片段所示:

// 使用 @Aspect 定义一个方面类
@Aspect 
public class LogAspect 
{ 
    // 定义该类的其他内容
    ... 
}

当我们使用 @Aspect 来修饰一个 Java 类之后,Spring 将不会把该 Bean 当成组件 Bean 处理,因此负责自动增强的后处理 Bean 将会略过该 Bean,不会对该 Bean 进行任何增强处理。开发时无须担心使用 @Aspect 定义的方面类被增强处理,当 Spring 容器检测到某个 Bean 类使用了 @Aspect 标注之后,Spring 容器不会对该 Bean 类进行增强。

CGLib代理与JDK动态代理:

1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP 
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

如何强制使用CGLIB实现AOP? :

1、添加CGLIB库
2、在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

AOP自动代理原理:

Spring提供了自动代理机制,让容器为我们自动生成代理。在内部,Spring使用BeanPostProcessor自动地完成这项工作。

这些基于BeanPostProcessor的自动代理创建器的实现类,将根据一些规则自动在容器实例化Bean时为匹配的Bean生成代理实例

  • 基于Bean配置名规则的自动代理创建器:允许为一组特定配置名的Bean自动创建代理实例的代理创建器,实现类为BeanNameAutoProxyCreator;
  • 基于Advisor匹配机制的自动代理创建器:它会对容器中所有的Advisor进行扫描,自动将这些切面应用到匹配的Bean中(即为目标Bean创建代理实例),实现类为DefaultAdvisorAutoProxyCreator;
  • 基于Bean中AspjectJ注解标签的自动代理创建器:为包含AspectJ注解的Bean自动创建代理实例,它的实现类是AnnotationAwareAspectJAutoProxyCreator,该类是Spring 2.0的新增类。

注:

通过Spring IOC原理可知:BeanPostProcessor 是容器级生命周期接口方法 ,该后处理器接口不由 Bean 本身实现,它们独立于 Bean,实现类以容器附加装置的形式注册到 Spring 容器中并通过接口反射为 Spring 容器预先识别。当Spring 容器创建任何 Bean 的时候,这些后处理器都会发生作用,所以这些后处理器的影响是全局性的。当然,用户可以通过合理地编写后处理器,让其仅对感兴趣Bean 进行加工处理。

 

 

 

在正确配置了Spring事务管理后,或许在某些场景下,你可以写出如下代码:

class T {

    public int createFirst(){
       //dosometing....
       try {
           this.createSecond();
       }catch (Exception e){
          throw e;
       }
       //dosometing....
       return 0;
    }


    @Transactional
    public int createSecond(){
       //dosometing with db....

    }
}


然后在外部通过Spring容器获取T的实例t,当你调用t.createFirst()方法的时候,你会发现添加了@Transactional注解的createSecond()方法并没有运行在事务中。

为了了解这个问题的原理,首先先需要了解Spring 是如何处理注解的(@Transaction 或 @Async)。

AOP

面向切面编程,通俗一点将即是在要执行的方法前或后执行一段代码。类使用Spring MVC中的过滤器,在请求前后执行额外的逻辑,并且该逻辑对实际的方法是透明的。

AOP的实现原理

编译时织入:在代码编译时,把切面代码融合进来,生成完整功能的Java字节码,这就需要特殊的Java编译器了,AspectJ属于这一类

类加载时织入:在Java字节码加载时,把切面的字节码融合进来,这就需要特殊的类加载器,AspectJ和AspectWerkz实现了类加载时织入

运行时织入:在运行时,通过动态代理的方式,调用切面代码增强业务功能,Spring采用的正是这种方式。动态代理会有性能上的开销,但是好处就是不需要神马特殊的编译器和类加载器啦,按照写普通Java程序的方式来就行了!

Spring AOP 属于运行时织入,即使用代理模式。或许你看到Spring AOP依赖了AspectJ包会以为Spring AOP是AspectJ来实现的。但其他并不是,Spring AOP只是使用了和AspectJ一样的注解但并没有使用 AspectJ 的编译器或者织入器(Weaver),底层AOP实现与AspectJ是不同的。Spring AOP 无需使用任何特殊命令对 Java 源代码进行编译,它采用运行时动态地、在内存中临时生成“代理类”的方式来生成 AOP 代理。

Spring AOP的代理实现

JDK动态代理:JDK动态代理技术。通过需要代理的目标类的getClass().getInterfaces()方法获取到接口信息(这里实际上是使用了Java反射技术。getClass()和getInterfaces()函数都在Class类中,Class对象描述的是一个正在运行期间的Java对象的类和接口信息),通过读取这些代理接口信息生成一个实现了代理接口的动态代理Class(动态生成代理类的字节码),然后通过反射机制获得动态代理类的构造函数,并利用该构造函数生成该Class的实例对象(InvokeHandler作为构造函数的入参传递进去),在调用具体方法前调用InvokeHandler来处理。

CGLib动态代理:字节码技术。利用asm开源包,把代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。采用非常底层的字节码技术,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。

在我们实际使用Spring AOP时,它包括基于XML配置的AOP和基于@AspcetJ注解的AOP,这两种方法虽然在配置切面时的表现方式不同,但底层都是使用动态代理技术(JDK代理或CGLib代理)。

现在回头来看本文开头的代码:

createFirst()方法里面直接调用createSecond(……)方法,这里还隐含一个关键字,那就是this,实际上这里调用是这样的:this.createSecond(),this是当前对象。当前对象是T,问题就出在这里,因为要想用事务执行createSecond(……),必须用代理对象执行,因为代理对象会拦截到@Transactional来执行相关的增强,但是此时却直接用T对象调用,绕过了代理对象增强的部分,也就是说代理增强部分失效,@Transactional注解失效。

如果在外部通过Spring容器获取T实例,然后直接调用createSecond方法,此时createSecond()是被代理的,在代理对象中判断到该方法有@Transactional,则会执行该注解需要的前置增强后(开启事务),然后通过invoke,用实际T对象来调用addOrder()方法执行业务逻辑,然后执行后置增强(回滚or提交)。

特殊场景的解决办法:

没有用代理对象执行createSecond(……),被T对象抢占了先机。那么解决就是要让代理对象来执行createSecond(……)。

T t = (T) AopContext.currentProxy(); 
//获取代理对象
t.createSecond(); 
//通过代理对象调用createSecond

//需要在@EnableAspectJAutoProxy添加属性值。
//@EnableAspectJAutoProxy(exposeProxy = true)

T t = (T) AopContext.currentProxy(); 
//获取代理对象
t.createSecond(); 
//通过代理对象调用createSecond

//需要在@EnableAspectJAutoProxy添加属性值。
//@EnableAspectJAutoProxy(exposeProxy = true)

 

参考:https://zhuanlan.zhihu.com/p/29483023

参考:https://zhuanlan.zhihu.com/p/29483023https://blog.youkuaiyun.com/canot/article/details/80855439

 

Spring AOP(面向切面编程)是Spring框架中的一个重要模块,用于实现横切关注点的模块化。它通过在程序运行期间动态地将代码织入到应用程序的特定位置,从而实现对横切关注点的处理。 在Spring AOP中,通过定义切面(Aspect)和连接点(Join Point)来实现对横切关注点的处理。切面定义了在何处以及如何进行横切关注点的处理,而连接点则表示在应用程序中可以插入切面的位置。 Spring AOP的核心概念是切面、连接点、通知(Advice)、切点(Pointcut)和引入(Introduction): 1. 切面(Aspect):切面是一个模块化的单元,它包含了一组通知和切点。通知定义了在何时、何地以及如何进行横切关注点的处理,而切点定义了在应用程序中哪些连接点可以插入切面。 2. 连接点(Join Point):连接点是在应用程序中可以插入切面的位置。例如,方法调用、方法执行、异常抛出等都可以作为连接点。 3. 通知(Advice):通知定义了在连接点上执行的操作。常见的通知类型有前置通知(Before)、后置通知(After)、返回通知(After Returning)和异常通知(After Throwing)等。 4. 切点(Pointcut):切点是一个表达式,用于定义哪些连接点可以插入切面。通过切点表达式,可以选择性地将切面应用于特定的连接点。 5. 引入(Introduction):引入允许在现有的类中添加新的方法和属性。 Spring AOP的优点在于它能够将横切关注点与业务逻辑分离,提高了代码的可维护性和可重用性。同时,它也提供了一种非侵入式的方式来实现横切关注点的处理,不需要修改原有的代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值