Spring AOP官方文档学习笔记之基于xml的Spring AOP

Posted shame11

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring AOP官方文档学习笔记之基于xml的Spring AOP相关的知识,希望对你有一定的参考价值。

1.声明schema,导入命名空间

(1)如果我们想要使用基于xml的spring aop,那么,第一步,我们需要在xml配置文件中声明spring aop schema,导入命名空间,如下这是一个标准的模板

<?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 https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!-- //... -->

</beans>

(2)在xml配置文件中,所有的切面以及通知等都必须放置于<aop:config>标签内

2.声明一个切面

//定义一个切面类Logger,在其中声明一个前置通知
public class Logger 

    public void beforePrint() 
        System.out.println("before...");
    


<!-- xml配置文件 -->
<beans ....>
    <!-- 将切面类注册为spring的一个bean -->
    <bean id="logger" class="cn.example.spring.boke.Logger"></bean>

    <aop:config>
        <!-- 使用<aop:aspect>标签,来定义一个切面,其中id的值需唯一,ref用来引用切面类 -->
        <aop:aspect id="aspect" ref="logger">
            <!-- 在<aop:aspect>标签内部,我们可以定义5种通知,在这里使用<aop:before>标签来定义一个前置通知,其中method指定通知方法,它只能是Logger这个切面类中的方法,pointcut指定切入点表达式 -->
            <aop:before method="beforePrint" pointcut="execution(* cn.example.spring.boke.ExampleA.*(..))"></aop:before>
        </aop:aspect>
    </aop:config>

</beans>

3.声明一个切入点

<beans ....>

    <bean id="logger" class="cn.example.spring.boke.Logger"></bean>

    <aop:config>
        <!-- 使用<aop:pointcut>标签来定义一个切入点,其中id的值唯一,expression即为切入点表达式 -->
        <!-- 之后,在通知标签内部,使用pointcut-ref来引用这个切入点 -->
        <aop:pointcut id="common" expression="execution(* cn.example.spring.boke.ExampleA.*(..))"/>

        <!-- 在基于xml的切入点表达式中 &&, || 以及 ! 分别被替换为了 and, or 与 not,如下面这个例子 -->
        <aop:pointcut id="mix" expression="execution(public * *(..)) and @args(org.springframework.stereotype.Component)"/>

        <aop:aspect id="aspect" ref="logger">
            <aop:before method="beforePrint" pointcut-ref="common"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

4.声明一个通知

//切面类
public class Logger 
    public void beforePrint() 
        System.out.println("before...");
    

    public void afterReturningPrint(Object returnVal) 
        System.out.println(returnVal);
        System.out.println("afterReturning...");
    

    public void afterThrowingPrint(Throwable throwable) 
        System.out.println(throwable);
        System.out.println("afterThrowing...");
    

    public void afterPrint() 
        System.out.println("after...");
    

    public void aroundPrint(ProceedingJoinPoint joinPoint) 
        try 
            System.out.println("before...");
            joinPoint.proceed();
            System.out.println("after...");
         catch (Throwable throwable) 
            throwable.printStackTrace();
         finally 
            System.out.println("finally...");
        
    


<aop:config>
    <aop:pointcut id="common" expression="execution(* cn.example.spring.boke.ExampleA.*(..))"/>

    <aop:aspect id="aspect" ref="logger">
        <!-- 使用<aop:before>声明前置通知 -->
        <aop:before method="beforePrint" pointcut-ref="common"></aop:before>

        <!-- 使用<aop:after-returning>声明返回通知,其中使用returning属性来声明获取切入点执行后的返回值,与前面基于注解的示例相同 -->
        <aop:after-returning method="afterReturningPrint" pointcut-ref="common" returning="returnVal"></aop:after-returning>

        <!-- 使用<aop:after-throwing>声明异常通知,其中使用throwing属性来声明获取切入点执行异常后所抛出的异常,与前面基于注解的示例相同 -->
        <aop:after-throwing method="afterThrowingPrint" pointcut-ref="common" throwing="throwable"></aop:after-throwing>

        <!-- 使用<aop:after>声明后置通知 -->
        <aop:after method="afterPrint" pointcut-ref="common"></aop:after>
  
        <!-- 使用<aop:around>声明环绕通知,具体的注意事项与前面基于注解的示例相同 -->
        <aop:around method="aroundPrint" pointcut-ref="common"></aop:around>
    </aop:aspect>
</aop:config>

5.优先级

<beans ....>

    <bean id="logger" class="cn.example.spring.boke.Logger"></bean>

    <bean id="exampleA" class="cn.example.spring.boke.ExampleA"></bean>

    <aop:config>
        <aop:pointcut id="common" expression="execution(* cn.example.spring.boke.ExampleA.*(..))"/>
        <!-- 可使用<aop:aspect/>标签中的order属性来声明不同切面类的优先级 -->
        <aop:aspect id="aspect" ref="logger" order="1">
            <!-- 在同一切面类中,不同切面的优先级与切面声明的顺序有关,如下由于<aop:before/>标签声明于<aop:around/>标签之前,因此before的优先级高于around -->
            <aop:before method="beforePrint" pointcut-ref="common"></aop:before>

            <aop:around method="aroundPrint" pointcut-ref="common"></aop:around>
        </aop:aspect>
    </aop:config>

</beans>

6.声明一个引介

public class ExampleA



//希望向ExampleA中添加方法doSomething()
public interface Extention 
    void doSomething();


//doSomething()方法默认的实现
public class ExtentionImpl implements Extention

    @Override
    public void doSomething() 
        System.out.println("doSomething...");
    


<beans ....>

    <bean id="logger" class="cn.example.spring.boke.Logger"></bean>

    <bean id="exampleA" class="cn.example.spring.boke.ExampleA"></bean>

    <aop:config>

        <aop:aspect id="aspect" ref="logger">
            <!-- 在<aop:aspect/>标签中,使用<aop:declare-parents/>标签便可声明一个引介,其中types-matching属性值对应@DeclareParents注解中的value属性值,default-impl属性值对应@DeclareParents注解中的defaultImpl属性值,implement-interface表明父类型,与基于注解的配置一致 -->
            <aop:declare-parents types-matching="cn.example.spring.boke.*" implement-interface="cn.example.spring.boke.Extention" default-impl="cn.example.spring.boke.ExtentionImpl"></aop:declare-parents>
        </aop:aspect>
    </aop:config>

</beans>

//使用引介,与基于注解的配置一致
Extention exampleA = (Extention)ctx.getBean("exampleA");

7.Advisors

(1) 除了使用<aop:aspect/>标签外,我们还可以使用<aop:advisor/>标签来声明一个切面,不过使用<aop:advisor/>时,其所指向的bean必须要实现对应的Advice接口,如下

//若要定义前置通知,则必须实现MethodBeforeAdvice接口,其他相应的通知也有对应的接口
public class Logger implements MethodBeforeAdvice 
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable 
        System.out.println("advisor before...");
    


<beans ....>

    <bean id="logger" class="cn.example.spring.boke.Logger"></bean>

    <bean id="exampleA" class="cn.example.spring.boke.ExampleA"></bean>

    <aop:config>
        <aop:pointcut id="common" expression="execution(* cn.example.spring.boke.ExampleA.*(..))"/>
        <!-- 使用<aop:advisor/>标签来定义一个切面,与前面的<aop:aspect/>标签相比,不需要在其内部声明具体的通知标签了,在底层原理上,<aop:advisor/>与<aop:aspect/>是相似的,只是<aop:advisor/>的使用方式变了而已,且该标签一般专用于事物管理上 -->
        <aop:advisor advice-ref="logger" pointcut-ref="common"></aop:advisor>
    </aop:config>

</beans>

Spring AOP官方文档学习笔记之AOP概述

1.AOP简介

(1) Spring的关键组件之一就是AOP框架,它是对Spring IoC的补充(这意味着如果我们的IOC容器不需要AOP的话就不用引入AOP),此外,AOP亦是对OOP的补充,OOP的关注点在于类,而AOP的关注点在于切面,它可以将分散在不同类不同方法中重复的代码逻辑抽取出来,称之为通知(Advice),然后在运行时通过动态代理技术将“通知”组合进原有对象中,这样就能在实现原有预期效果的情况下达到减少代码冗余的目的

(2) 在Spring中,AOP主要用于两大方面,一是提供了声明式服务(比如声明式事物管理:@Transactional注解),二是让用户实现自定义切面,实现代码解偶,用于作为OOP的补充,一个简单的例子如下

//我们想在ExampleA中的每个方法中记录该方法的开始执行时间和结束执行时间
@Component
public class ExampleA 
    //在每个方法业务代码执行前和执行后,都有一个System.out.println用于打印执行时间
    public void register() 
        System.out.println(System.currentTimeMillis() + " 开始执行...");
        //注册相关的业务逻辑代码...
        try 
            Thread.sleep(100);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        System.out.println(System.currentTimeMillis() + " 结束执行...");
    

    public void sendEmail() 
        System.out.println(System.currentTimeMillis() + " 开始执行...");
        //发送邮件相关的业务逻辑代码...
        try 
            Thread.sleep(200);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        System.out.println(System.currentTimeMillis() + " 结束执行...");
    


//现在,我们期望把"记录该方法的开始执行时间和结束执行时间"这一段代码逻辑提取出来,这样,之后再往ExampleA中添加了新的方法后,我们就不需要再手动添加两个 System.out.println 语句了,而被提取的这一段代码逻辑,被称之为通知(Advice),而通知加上要被增强的业务代码(比如ExampleA中的注册,发送邮件相关的业务逻辑代码)就形成了一个切面
//使用@Aspect注解定义切面类,用于声明定义一个个切面,你可能会疑惑为啥这个注解是由org.aspectj.aspectjrt提供的,那是因为虽然名字叫Spring AOP,给人的感觉好像是Spring单独开发的,但其实是Spring整合了AspectJ AOP框架(用AspectJ AOP的写法和定义方式,底层由Spring封装实现)一同实现的这个AOP功能
@Aspect
@Component
public class Logger 
    //1.重复的代码逻辑(System.out.println),即通知的提取:对应(3)和(5),用于记录方法的开始执行时间和结束执行时间
    //2.指出要被增强的业务代码:假如有两个类ExampleA和ExampleB,我们需要记录ExampleA中每个方法的开始执行时间和结束执行时间,而ExampleB类不用,这就是(1)所发挥的作用
    //3.执行要被增强的业务代码:仅仅指出要被增强的业务代码有哪些还不行,我们还需要调用这些业务代码,从而使它真正的被执行,这是(2)和(4)发挥的作用,(2)中的joinPoint称之为切入点,我们可以将它视为要被增强的业务代码(register,sendEmail)的抽象,而(4)joinPoint.proceed() 就代表着业务代码的执行,如同 thread.start() 代表着线程执行一样
    //4.将通知和被增强的业务代码整个组合起来,称之为切面,即下面的recordTime方法,它就代表一个切面
    @Around("execution(public * cn.example.spring.boke.ExampleA.*(..))")           //(1)
    public void recordTime(ProceedingJoinPoint joinPoint) throws Throwable        //(2)
        System.out.println(System.currentTimeMillis() + " 开始执行...");            //(3)
        joinPoint.proceed();                                                       //(4)
        System.out.println(System.currentTimeMillis() + " 结束执行...");            //(5)
    


//之后,使用@EnableAspectJAutoProxy开启注解AOP功能
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("cn.example.spring.boke")
public class Config 



//然后我们就可以去掉ExampleA的方法中的开始和结束时执行时间打印,因为我们已经把它们抽象提取出来了
@Component
public class ExampleA 
    public void register() 
        //注册相关的业务逻辑代码...
        try 
            Thread.sleep(100);
         catch (InterruptedException e) 
            e.printStackTrace();
        
    

    public void sendEmail() 
        //发送邮件相关的业务逻辑代码...
        try 
            Thread.sleep(200);
         catch (InterruptedException e) 
            e.printStackTrace();
        
    


//启动容器,执行register和sendEmail方法,可以看见我们的执行时间日志打印了出来,之后添加进ExampleA类中的新方法,也都会进行时间的打印,而无需我们手动的添加两个System.out.println语句,这便是AOP的强大功能
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
ctx.getBean(ExampleA.class).register();
ctx.getBean(ExampleA.class).sendEmail();

2.AOP术语

(1) Aspect:切面 = 通知 + 切入点,如我们例子中所示的recordTime方法

(2) Join point:连接点,可以理解为类中的一个一个方法

(3) Advice:通知,即某个切面要在某个切入点上所要采取的行动,通常而言就是各个类各个方法中重复逻辑的抽象,比如上面这个例子中开始ExampleA中的2个方法中都有开始和结束时间打印,它们是重复的逻辑,因此我们可将它提取出来,称之为通知

(4) Pointcut:切入点,会被增强的连接点,比如上面的例子中我们对ExampleA中的方法register和sendEmail进行了增强,那么这些被增强的方法就有了一个新的称谓,即切入点,而其它的类中的方法没有被增强,还是一个个普通的方法,而这些普通的方法称之为连接点

(5) Introduction:引介,它的概念同通知差不多,只不过通知是针对切入点所提供的增强的逻辑,而引介是针对Class类,它可以在不修改原有类的代码的前提下,在运行期为原始类动态添加新的属性/方法

(6) Target object:目标对象,即会被增强的方法所属的对象,如上面例子中的ExampleA对象

(7) AOP proxy:AOP代理对象,是在目标对象上被增强了过后所产生的新对象,Spring采用动态代理技术来实现AOP,底层实现为JDK动态代理或CGLIB动态代理

(8) Weaving:织入,它代表一个动作,即将Advice组合进Target object中,从而产生AOP proxy这么的一个过程

3.AOP通知类型

(1) 根据通知与切入点的执行关系,Spring提供了5种通知类型,如下:

  • Before advice:前置通知,即通知在切入点执行之前执行

  • After returning advice:返回通知,即通知在切入点"正常"执行之后执行

  • After throwing advice:异常通知,即通知在切入点触发异常之后执行

  • After (finally) advice:后置通知,即无论切入点以何种方式执行(正常或异常),通知都会执行

  • Around advice:环绕通知,既可以在切入点之前执行通知,又可以在切入点之后执行,甚至可以不用执行切入点,它是最为强大的通知,我们上面例子中的recordTime就使用的是环绕通知

注意:After returning advice与After throwing advice两者是互斥的,因为如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值,因此这两个通知只会有一个执行

(2) Spring建议能使用具体的通知就去使用具体的通知,比如能用Before advice的情况下就不用去使用Around advice

4.Spring AOP特征

(1) Spring AOP是基于纯java实现的,不需要特殊的编译过程也不需要去控制类加载器的层次结构

(2) Spring AOP目前的切入点类型只能是方法,不能是变量或其它的类型,如果我们想要切入变量,可以使用AspectJ AOP框架,Spring与AspectJ无缝集成

(3) Spring AOP与IOC容器紧密集成,如果我们只是想要单独使用一个AOP框架,那么可以使用AspectJ

(4) Spring AOP支持基于注解的配置,也支持基于xml文件的配置,同IOC一样

(5) Spring AOP默认使用jdk动态代理,因此只要一个类实现了某个接口,那么它就能被代理,但如果我们的某个类没有实现接口,则会采用cglib动态代理来生成代理对象,同时我们也可以强制使用cglib动态代理作为默认选项

以上是关于Spring AOP官方文档学习笔记之基于xml的Spring AOP的主要内容,如果未能解决你的问题,请参考以下文章

Spring AOP官方文档学习笔记之AOP概述

Spring AOP官方文档学习笔记之Spring AOP的其他知识点

Spring IOC官方文档学习笔记之基于注解的容器配置

Spring IOC官方文档学习笔记之基于Java的容器配置

Spring 4 官方文档学习核心技术之Spring AOP

Spring4.0学习笔记009——AOP的配置(基于XML文件)