spring AOP 之一:spring AOP功能介绍

一、AOP简介

  AOP:是一种面向切面的编程范式,是一种编程思想,旨在通过分离横切关注点,提高模块化,可以跨越对象关注点。Aop的典型应用即spring的事务机制,日志记录。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。主要功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等;主要的意图是:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

1.1、AOP几个相关的概念:

名称说明
切面(Aspect)一个关注点的模块化,这个关注点可能会横切多个对象
连接点(Joinpoint)程序执行过程中的某个特定的点。例如类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点。
通知(Advice)在切面的某个特定的连接点上执行的动作。(通知定义了切面是什么以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。)
切入点(Pointcut)匹配连接点的断言,在AOP中通知和一个切入点的表达式。(例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定)
引入(Introduction)再不修改类代码的前提下,为类添加新的方法和属性。(也称为内部类型声明,为已有的类添加额外新的字段或方法,Spring允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象))
目标对象(Target Object)被一个或多个切面所通知的对象。(需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为“被通知对象”;由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象)
AOP代理(AOP Proxy)AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)
织入(Weaving)把切面连接到其他的应用程序类型或者对象上,并创建一个被通知的对象,     分为:编译时织入、类加载时织入、执行时织入。(将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期、类装载期、运行期进行)

把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码
(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术

1.2、Advice的类型

名称说明
前置通知(Before advice)在某个连接点(join point)之前执行的通知,但不能阻止连接点前的执行(除非它抛出异常)
返回后通知(After returning advice)在某个连接点(join point)正常完成后执行的通知
抛出异常后通知(After throwing advice)在方法抛出异常退出时执行的通知
后通知(After(finally) advice)当某个连接点退出的时候执行的通知(无论是正常返回还是异常退出)
环绕通知(Around advice)包围一个连接点(join point)的通知

 

 

 

 

 

 

 

 

 

 

 

二、Spring的AOP实现

AOP实现方案:AspectJ和Spring AOP。
AspectJ:Aspectj是aop的java实现方案,AspectJ是一种编译期的用注解形式实现的AOP。
(1)AspectJ是一个代码生成工具(Code Generator),其中AspectJ语法就是用来定义代码生成规则的语法。基于自己的语法编译工具,编译的结果是JavaClass文件,运行的时候classpath需要包含AspectJ的一个jar文件(Runtime lib),支持编译时织入切面,即所谓的CTW机制,可以通过一个Ant或Maven任务来完成这个操作。
(2)AspectJ有自己的类装载器,支持在类装载时织入切面,即所谓的LTW机制。使用AspectJ LTW有两个主要步骤,第一,通过JVM的-javaagent参数设置LTW的织入器类包,以代理JVM默认的类加载器;第二,LTW织入器需要一个 aop.xml文件,在该文件中指定切面类和需要进行切面织入的目标类。
(3)AspectJ同样也支持运行时织入,运行时织入是基于动态代理的机制。(默认机制)

见《AspectJ入门

Spring AOP:Spring AOP是AOP实现方案的一种,它支持在运行期基于动态代理的方式将aspect织入目标代码中来实现AOP。但是spring aop的切入点支持有限,而且对于static方法和final方法都无法支持aop(因为此类方法无法生成代理类);另外spring aop只支持对于ioc容器管理的bean,其他的普通java类无法支持aop。现在的spring整合了aspectj,在spring体系中可以使用aspectj语法来实现aop。

2.1、有接口无接口的Spring AOP 实现区别

  1. Spring AOP默认使用标准的javaSE动态代理作为AOP代理,这使得任何接口(或者接口集)都可以被代理
  2. Spring AOP中也可以使用CGLib代理(如果一个业务对象并没有实现一个接口)

2.2、Spring提供了4种实现AOP的方式:

1.经典的基于代理的AOP
2.@AspectJ注解驱动的切面 《spring AOP 之二:@Aspect注解的3种配置
3.AOP标签的纯POJO切面
4.注入式AspectJ切面(编译期注入)见《AspectJ入门

前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。

 

示例1

首先写一个接口叫Sleepable,这是一个牛X的接口,所有具有睡觉能力的东西都可以实现该接口(不光生物,包括关机选项里面的休眠)

package com.dxz.aop.demo1;

public interface Sleepable {

    void sleep();
}

然后写一个Human类,他实现了这个接口 

package com.dxz.aop.demo1;

public class Human implements Sleepable {
    public void sleep() {
        System.out.println("睡觉了!梦中自有颜如玉!");
    }
}

好了,这是主角,不过睡觉前后要做些辅助工作的,最基本的是脱穿衣服,失眠的人还要吃安眠药什么的,但是这些动作与纯粹的睡觉这一“业务逻辑”是不相干的,如果把这些代码全部加入到sleep方法中,是不是有违单一职责呢?,这时候我们就需要AOP了。
编写一个SleepHelper类,它里面包含了睡觉的辅助工作,用AOP术语来说它就应该是通知了,我们需要实现上面的接口。 

package com.dxz.aop.demo1;
import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;

public class SleepHelper implements MethodBeforeAdvice,AfterReturningAdvice{

    public void before(Method mtd, Object[] arg1, Object arg2)
            throws Throwable {
        System.out.println("通常情况下睡觉之前要脱衣服!");
    }

    public void afterReturning(Object arg0, Method arg1, Object[] arg2,
            Object arg3) throws Throwable {
        System.out.println("起床后要先穿衣服!");
    }
    
}

 然后在spring配置文件applicationContext-aop1.xml中进行配置:

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd 
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="human" class="com.dxz.aop.demo1.Human">
    </bean>
    
    <bean id="sleepHelper" class="com.dxz.aop.demo1.SleepHelper">
    </bean>
    
    <bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
        <property name="pattern" value=".*sleep" />
    </bean>

    <bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="sleepHelper" />
        <property name="pointcut" ref="sleepPointcut" />
    </bean>

    <bean id="humanProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="human" />
        <property name="interceptorNames" value="sleepHelperAdvisor" />
        <property name="proxyInterfaces" value="com.dxz.aop.demo1.Sleepable" />
    </bean>
</beans>

测试类: 

package com.dxz.aop.demo1;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args){
        ApplicationContext appCtx = new ClassPathXmlApplicationContext("applicationContext-aop1.xml");
        Sleepable sleeper = (Sleepable)appCtx.getBean("humanProxy");
        sleeper.sleep();
    }
}

程序运行产生结果: 

十月 23, 2017 5:12:05 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@5197848c: startup date [Mon Oct 23 17:12:05 CST 2017]; root of context hierarchy
十月 23, 2017 5:12:05 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext-aop1.xml]
通常情况下睡觉之前要脱衣服!
睡觉了!梦中自有颜如玉!
起床后要先穿衣服!

OK!这是我们想要的结果,但是上面这个过程貌似有点复杂,尤其是配置切点跟通知,Spring提供了一种自动代理的功能,能让切点跟通知自动进行匹配,修改配置文件如下: 

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd 
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="sleepHelper" class="com.dxz.aop.demo1.SleepHelper">
    </bean>
    
    <bean id="sleepAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice" ref="sleepHelper" />
        <property name="pattern" value=".*sleep" />
    </bean>
    
    <bean id="human" class="com.dxz.aop.demo1.Human">
    </bean>
    
    <bean
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
</beans>

执行程序: 

public static void main(String[] args){
        ApplicationContext appCtx = new ClassPathXmlApplicationContext("applicationContext-aop11.xml");
        Sleepable sleeper = (Sleepable)appCtx.getBean("human");
        sleeper.sleep();
    }

 成功输出结果跟前面一样!
只要我们声明了org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator就能为方法匹配的bean自动创建代理!

但是这样还是要有很多工作要做,有更简单的方式吗?有!

一种方式是使用AspectJ提供的注解:

package com.dxz.aop.demo2;

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


/**
 * @Aspect的注解来标识切面
 */
@Aspect
//@Component 
public class SleepHelper {

    public SleepHelper() {

    }

    @Pointcut("execution(* *.sleep())")
    public void sleeppoint() {
    }

    @Before("sleeppoint()")
    public void beforeSleep() {
        System.out.println("睡觉前要脱衣服!");
    }

    @AfterReturning("sleeppoint()")
    public void afterSleep() {
        System.out.println("睡醒了要穿衣服!");
    }

}

用@Aspect的注解来标识切面,注意不要把它漏了,否则Spring创建代理的时候会找不到它,@Pointcut注解指定了切点,@Before和@AfterReturning指定了运行时的通知,注意的是要在注解中传入切点的名称。
然后我们在Spring配置文件上下点功夫,首先是增加AOP的XML命名空间和声明相关schema,见配置文件applicationContext-aop2.xml: 

<?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:context="http://www.springframework.org/schema/context"
    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/context 
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

    <aop:aspectj-autoproxy/> 
    
    <bean id="human" class="com.dxz.aop.demo2.Human">
    </bean>
    
    <bean id="sleepHelper" class="com.dxz.aop.demo2.SleepHelper">
    </bean>
</beans>

记得加上这个标签:
<aop:aspectj-autoproxy/> 有了这个Spring就能够自动扫描被@Aspect标注的切面了。

最后是运行,很简单方便了: 

public static void main(String[] args){
    ApplicationContext appCtx = new ClassPathXmlApplicationContext("applicationContext-aop2.xml");
    Sleepable human = (Sleepable)appCtx.getBean("human");
    human.sleep();
}

下面我们来看最后一种常用的实现AOP的方式:使用Spring来定义纯粹的POJO切面

AOP标签
前面我们用到了<aop:aspectj-autoproxy/>标签,Spring在aop的命名空间里面还提供了其他的配置元素:
<aop:advisor> 定义一个AOP通知者
<aop:after> 后通知
<aop:after-returning> 返回后通知
<aop:after-throwing> 抛出后通知
<aop:around> 周围通知
<aop:aspect>定义一个切面
<aop:before>前通知
<aop:config>顶级配置元素,类似于<beans>这种东西
<aop:pointcut>定义一个切点

我们用AOP标签来实现:

package com.dxz.aop.demo3;

public class SleepHelper {

    public void beforeSleep()
            throws Throwable {
        System.out.println("通常情况下睡觉之前要脱衣服!");
    }

    public void afterSleep() throws Throwable {
        System.out.println("起床后要先穿衣服!");
    }
    
}

代码就不用继承啥了,只是修改配置文件,加入AOP配置即可: 

<?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:context="http://www.springframework.org/schema/context"
    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/context 
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

    <!-- 配置AOP  《方式一》 -->
    <aop:config>
        <!-- 配置切面及通知 -->
        <aop:aspect ref="sleepHelper">
            <aop:before method="beforeSleep" pointcut="execution(public * *..Sleepable.sleep(..))" />
            <aop:after method="afterSleep" pointcut="execution(public * *..Sleepable.sleep(..))" />
        </aop:aspect>
    </aop:config>
    
    
    <!-- 配置AOP  《方式二》-->
    <aop:config>
        <!-- 配置切点表达式 -->
        <aop:pointcut id="pointcut"
            expression="execution(public * *..Sleepable.sleep(..))" />
        <!-- 配置切面及通知 -->
        <aop:aspect ref="sleepHelper">
            <aop:before method="beforeSleep" pointcut-ref="pointcut" />
            <aop:after method="afterSleep" pointcut-ref="pointcut" />
        </aop:aspect>
    </aop:config>
    <bean id="human" class="com.dxz.aop.demo3.Human">
    </bean>

    <bean id="sleepHelper" class="com.dxz.aop.demo3.SleepHelper">
    </bean>
</beans>

4 注入AspectJ切面

虽然Spring AOP能够满足许多应用的切面需求,但是与AspectJ相比,Spring AOP 是一个功能 比较弱的AOP解决方案。AspectJ提供了Spring AOP所不能支持的许多类型的切点。 例如,当我们需要在创建对象时应用通知,构造器切点就非常方便。不像某些其他面向对象语 言中的构造器,Java构造器不同于其他的正常方法。这使得Spring基于代理的AOP无法把通知 应用于对象的创建过程。 对于大部分功能来讲,AspectJ切面与Spring是相互独立的。虽然它们可以织入到任意的Java应 用中,这也包括了Spring应用,但是在应用AspectJ切面时几乎不会涉及到Spring。 但是精心设计且有意义的切面很可能依赖其他类来完成它们的工作。如果在执行通知时,切 面依赖于一个或多个类,我们可以在切面内部实例化这些协作的对象。但更好的方式是,我们 可以借助Spring的依赖注入把bean装配进AspectJ切面中。

示例见《AspectJ入门》中的示例

三、Spring AOP 原理剖析

通过前面介绍可以知道:AOP 代理其实是由 AOP 框架动态生成的一个对象,该对象可作为目标对象使用。AOP 代理包含了目标对象的全部方法,但 AOP 代理中的方法与目标对象的方法存在差异:AOP 方法在特定切入点添加了增强处理,并回调了目标对象的方法。

AOP 代理所包含的方法与目标对象的方法示意图如图 3 所示。


图 3.AOP 代理的方法与目标对象的方法
图 3.AOP 代理的方法与目标对象的方法

Spring 的 AOP 代理由 Spring 的 IoC 容器负责生成、管理,其依赖关系也由 IoC 容器负责管理。因此,AOP 代理可以直接使用容器中的其他 Bean 实例作为目标,这种关系可由 IoC 容器的依赖注入提供。

纵观 AOP 编程,其中需要程序员参与的只有 3 个部分:

  • 定义普通业务组件。
  • 定义切入点,一个切入点可能横切多个业务组件。
  • 定义增强处理,增强处理就是在 AOP 框架为普通业务组件织入的处理动作。

上面 3 个部分的第一个部分是最平常不过的事情,无须额外说明。那么进行 AOP 编程的关键就是定义切入点和定义增强处理。一旦定义了合适的切入点和增强处理,AOP 框架将会自动生成 AOP 代理,而 AOP 代理的方法大致有如下公式:

代理对象的方法 = 增强处理 + 被代理对象的方法

在上面这个业务定义中,不难发现 Spring AOP 的实现原理其实很简单:AOP 框架负责动态地生成 AOP 代理类,这个代理类的方法则由 Advice 和回调目标对象的方法所组成。

对于前面提到的图 2 所示的软件调用结构:当方法 1、方法 2、方法 3 ……都需要去调用某个具有“横切”性质的方法时,传统的做法是程序员去手动修改方法 1、方法 2、方法 3 ……、通过代码来调用这个具有“横切”性质的方法,但这种做法的可扩展性不好,因为每次都要改代码。

于是 AOP 框架出现了,AOP 框架则可以“动态的”生成一个新的代理类,而这个代理类所包含的方法 1、方法 2、方法 3 ……也增加了调用这个具有“横切”性质的方法——但这种调用由 AOP 框架自动生成的代理类来负责,因此具有了极好的扩展性。程序员无需手动修改方法 1、方法 2、方法 3 的代码,程序员只要定义切入点即可—— AOP 框架所生成的 AOP 代理类中包含了新的方法 1、访法 2、方法 3,而 AOP 框架会根据切入点来决定是否要在方法 1、方法 2、方法 3 中回调具有“横切”性质的方法。

简而言之:AOP 原理的奥妙就在于动态地生成了代理类,这个代理类实现了图 2 的调用——这种调用无需程序员修改代码。接下来介绍的 CGLIB 就是一个代理生成库,下面介绍如何使用 CGLIB 来生成代理类。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOP中,拦截器责任链处理过程是指当目标方法被多个通知匹配到时,Spring通过引入拦截器链来保证每个通知的正常执行。拦截器链是由一系列的拦截器组成的,每个拦截器都负责在目标方法的前后执行特定的逻辑。 在源码中,拦截器责任链的处理过程主要通过MethodInvocation接口来实现。MethodInvocation接口提供了proceed()方法,用于执行拦截器链中下一个拦截器的逻辑。当调用proceed()方法时,会按照拦截器链的顺序依次执行每个拦截器的逻辑,直到达到链的末尾或者某个拦截器决定终止链的执行。 在拦截器责任链处理过程中,每个拦截器可以在目标方法的调用前后执行自定义的逻辑。拦截器可以对方法的参数进行检查、修改方法的返回值,或者在方法执行前后记录日志等操作。通过拦截器责任链的处理,Spring AOP能够实现面向切面编程的功能。 需要注意的是,拦截器链的执行顺序是根据拦截器的配置顺序确定的。在Spring的配置文件中,可以通过配置拦截器的顺序来控制拦截器链的执行顺序。这样可以确保每个拦截器按照预期的顺序执行,从而达到期望的功能效果。 总结起来,Spring AOP源码的拦截器责任链处理过程主要通过MethodInvocation接口实现,它通过调用proceed()方法来依次执行拦截器链中每个拦截器的逻辑。拦截器链的执行顺序可以通过配置文件来控制,从而实现面向切面编程的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Spring AOP源码:拦截器责任链处理过程](https://blog.csdn.net/weixin_45031612/article/details/128806966)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Spring AOP 自动代理源码 DefaultAdvisorAutoProxyCreator](https://download.csdn.net/download/weixin_38530536/14854229)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [【SpringSpring AOP 源码分析-拦截器链的执行过程(四)](https://blog.csdn.net/qq_46514118/article/details/121912507)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值