第4章 ◄Spring之AOP►

在上一章节中,我们大致了解了Spring核心容器,了解了IOC思想在Spring中的具体应用Bean容器以及Bean的配置与使用,这一章我们将开始学习AOP在Spring框架中的具体应用。

本章主要涉及的知识点:
● AOP的引入:从传统处理方法到AOP处理,一步一步引入。
● AOP的实现方式:通过实践了解AOP的3种实现方式。

4.1 AOP基础

​ 在第2章中也有介绍AOP,不过侧重点是AOP的原理和动态代理,本节主要介绍AOP的基础知识点。

4.1.1 AOP的引入

​ 这里可以把单个模块当作一个圆柱,假如没有AOP,那么在做日志处理的时候,我们就会在每个模块中添加日志或者权限处理,日志或权限类似圆柱体的部分圆柱,如图4-1所示。

图4-1

​ 一般大多数的日志或权限处理代码是相同的,为了实现代码复用,我们可能把日志处理抽离成一个新的方法,如图4-2所示。

图4-2

​ 即使这样,我们仍然必须手动插入这些方法,而且这两个方法是强耦合的。假如此时我们不需要这个功能了,或者想换成其他功能,就必须一个个修改。
​ 通过动态代理,可以在指定位置执行对应流程。这样就可以将一些横向的功能抽离出来,形成一个独立的模块,然后在指定位置插入这些功能。这样的思想被称为面向切面编程,亦即AOP,如图4-3所示。

4.1.2 AOP主要概念

4.1.1小节介绍了引入AOP的好处,本小节来了解一下AOP的几个核心概念。

  • (1)横切关注点
    AOP把一个业务流程分成几部分,例如权限检查、业务处理、日志记录,每个部分单独处理,然后把它们组装成完整的业务流,每部分被称为切面或关注点。

  • (2)切面
    类是对物体特征的抽象,切面就是对横切关注点的抽象。可以将每部分抽象成一叠纸一样,一层一层的,那么每张纸都是一个切面。

  • (3)连接点
    因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。其实,Spring只支持方法类型的连接点包含字段和构造器。因为字段通过get、set方法得到,构造器其实也是方法。Spring只支持方法类型的连接点和连接点是字段或者构造器是包含关系。

  • (4)切入点
    对连接点进行拦截的定义。连接点可以有很多,但并不一定每个连接点都进行操作,比如莲藕,藕段与藕段之间是有连接点的,但不一定都切开。

  • (5)通知

    通知指的就是指拦截到连接点之后要执行的代码,分为前置、后置、异常、最终、环绕通知5类。这个有点类似于把藕段与藕段断开之后要做的事情,是往里面加蜂蜜还是做什么。

  • (6)目标对象
    代理的目标对象,就是动态代理的target,在实际操作中一般会先实现AOP的接口,然后配置这些接口作用到哪些对象上,被作用的对象就是目标对象。

  • (7)织入
    切面是独立的,目标对象也是独立的,它们是不耦合的,那它怎么把切面放到目标对象中呢?这时就需要进行织入操作,就类似这种的,怎么把target和打印日志联系到一起呢?使用动态代理。在Spring中,aop.framework.ProxyFactory用作织入器,进行横切逻辑的织入。

  • (8)引入
    不改代码的同时,为类动态地添加方法或字段。

4.2 AOP实现

​ 4.1节主要介绍了AOP的理论知识,本节通过示例进一步理解Spring中AOP的使用,主要介绍AOP的3种实现方式:经典的基于代理的AOP、AspectJ基于XML的配置、AspectJ基于注解的配置。

1.经典的基于代理的AOP

​ 基于代理的AOP主要介绍MethodBeforeAdvice、AfterReturningAdvice、ThrowsAdvice三个接口的使用。

​ ● MethodBeforeAdvice:见名知意,通过方法名就可以猜到它的作用。方法前拦截器在执行指定方法前调用,参数分别为被调用的方法、执行时被传入的参数、被拦截的Bean。
​ ● AfterReturningAdvice:返回后拦截器,在执行完指定方法并返回之后调用。如果有返回值可以获取到返回值,否则为null。参数分别为方法返回值、被调用的方法、执行时被传入的参数、被拦截的Bean。
​ ● ThrowsAdvice:异常拦截器,在指定方法抛出异常时被调用。该接口并未定义方法,因此不需要实现任何方法。那它是怎么拦截的呢?我们可以查看该接口的定义,在定义类文档中有如图4-4所示的说明。例如,在实现该接口的类中定义了public void afterThrowing(Exception ex)、public void afterThrowing(Method method, Object[] args, Object target, Exception ex)方法抛出异常时就会被调用。
[插图]
图4-4

​ 在软件开发中推荐面向接口的编程,所以这里创建一个IAOPServices接口,并定义两个方法:withAopMethod方法将使用拦截器拦截的方法,withNoAopMethod方法不会被拦截器拦截。接口代码如下:

​ 在软件开发中推荐面向接口的编程,所以这里创建一个IAOPServices接口,并定义两个方法:withAopMethod方法将使用拦截器拦截的方法,withNoAopMethod方法不会被拦截器拦截。接口代码如下:

public interface IAOPService {public String withAopMethod() throws Exception;public String withNoAopMethod() throws Exception;
}

​ 在AOPServicesImpl类中实现了该接口,并在该类中定义了String类型的description属性,以及对应的getter、setter方法。两个接口方法将返回该description属性的值。

public class AOPServicesImpl implements IAOPService {private String description;public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}public String withAopMethod() throws Exception {System.out.println("AOP函数运行方法:withAopMethod");if(description.trim().length()==0){throw new Exception("description为空");}return description;}public String withNoAopMethod() throws Exception {System.out.println("无AOP函数运行方法:withNoAopMethod");return description;}
}

​ 上面把要使用AOP拦截的方法准备好了,下面就是定义AOP拦截器方法了。这里在AOPInterceptor类中实现了上面的AfterReturningAdvice、MethodBeforeAdvice、ThrowsAdvice三个接口。

public class AOPInterceptor  implements AfterReturningAdvice,MethodBeforeAdvice,ThrowsAdvice{public void afterReturning(Object value, Method method, Object[] args, Object instance) throws Throwable {System.out.println("方法"+method.getName()+"运行结束,返回值为:"+value);}public void before(Method method, Object[] args, Object instance) throws Throwable {System.out.println("ִ执行MethodBeforeAdvice,即将执行的方法:"+method.getName());if(instance instanceof AOPServicesImpl){String description=((AOPServicesImpl)instance).getDescription();if(description==null){throw new NullPointerException("description为 null");}}}public void afterThrowing(Exception ex){System.out.println("抛出异常:"+ex.getMessage());}public void afterThrowing(Method method, Object[] args, Object target, Exception ex){System.out.println("方法"+method.getName()+"抛出异常:"+ex.getMessage());}
}

​ 这里要拦截的方法和拦截器都准备好了,怎么将拦截器用于拦截该方法呢?这里就需要进行配置了。首先在pom.xml中引入依赖,这里引入spring-aop、spring-context。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>chapter4</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring.version>5.0.0.RELEASE</spring.version></properties><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>${spring.version}</version></dependency></dependencies>
</project>

​ 实际上Spring无法将Services实现类与拦截器直接组装,因为没有对应的setter、getter方法。安装时,先借助Spring中的代理类将自定义拦截器注入NameMatchMethodPointcutAdvisor类的advice属性中,再将定义好的NameMatchMethodPointcutAdvisor对象注入ProxyFactoryBean中。这里将自定义的AOPInterceptor拦截器注入NameMatchMethodPointcutAdvisor中,然后将NameMatchMethodPointcutAdvisor对象注入ProxyFactoryBean中。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="aopInterceptor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"><property name="advice"><bean class="spring.tutorial.chapter4.aop.AOPInterceptor"></bean></property><property name="mappedName" value="withAopMethod"></property></bean><bean id="aopService" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="interceptorNames"><list><value>aopInterceptor</value></list></property><property name="target"><bean class="spring.tutorial.chapter4.service.impl.AOPServicesImpl"><property name="description" value="basicAop"></property></bean></property></bean>
</beans>

​ 从Spring容器中获取IAOPServices对象,并分别执行IAOPServices中的两个方法。Spring会在withAopMethod()方法前后添加拦截器,在withNoAopMethod()方法前后并不会添加。

public class Chapter4Controller {public static void main(String[] args) throws Exception {ApplicationContext context=new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});BeanFactory factory=context;IAOPService services=(IAOPService)context.getBean("aopService");services.withAopMethod();services.withNoAopMethod();}
}

运行结果如下:

ִ执行MethodBeforeAdvice,即将执行的方法:withAopMethod
AOP函数运行方法:withAopMethod
方法withAopMethod运行结束,返回值为:basicAop
无AOP函数运行方法:withNoAopMethod

2.AspectJ基于XML的配置

​ AspectJ是一个面向切面的框架,扩展了Java语言。AspectJ定义了AOP语法,有一个专门的编译器,用来生成遵守Java字节编码规范的Class文件。我们还是利用IAOPServices接口、AOPServicesImpl类实现AspectJ基于XML的AOP编程。表4-1给出AspectJ主要的配置元素。使用AspectJ时需要引入两个依赖项,即aopalliance、aspectjweaver。在引入这两个依赖项时需要注意,有时会报错,更新两个依赖项的版本就好了。

 <dependency><groupId>aopalliance</groupId><artifactId>aopalliance</artifactId><version>1.0</version></dependency><!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.11</version></dependency>

表4-1 AspectJ主要的配置元素

​ 这里里定义了XMLAdvice拦截器方法,用于演示前置、后置、成功返回、异常返回、环绕通知。
[插图]
上面把拦截器定义完了,之后就是把定义好的拦截器与Services关联在一起。使用AOP配置元素将Services与拦截器中的方法关联上。

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;public class XMLAdvice {public void beforeAdvice() {System.out.println("前置通知执行了");}public void afterAdvice() {System.out.println("后置通知执行了");}public void afterReturnAdvice(String result) {System.out.println("返回通知执行了" + "运行业务方法返回的结果为" + result);}public String aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {String result = "";try {System.out.println("环绕通知开始执行了");long start = System.currentTimeMillis();result = (String) proceedingJoinPoint.proceed();long end = System.currentTimeMillis();System.out.println("环绕通知执行结束了");System.out.println("执行业务方法共计:" + (end - start) + "毫秒。");} catch (Throwable e) {}return result;}public void throwingAdvice(JoinPoint joinPoint, Exception e) {StringBuffer stringBuffer = new StringBuffer();stringBuffer.append("异常通知执行了.");stringBuffer.append("方法:").append(joinPoint.getSignature().getName()).append("出现了异常.");stringBuffer.append("异常信息为:").append(e.getMessage());System.out.println(stringBuffer.toString());}
}

​ 上面把拦截器定义完了,之后就是把定义好的拦截器与Services关联在一起。使用AOP配置元素将Services与拦截器中的方法关联上。

配置完之后还是和经典的基于代理的AOP一样,运行代码从Spring容器中获取IAOPServices对象,并分别执行IAOPServices中的两个方法。Spring会在withAopMethod()方法前后添加拦截器,在withNoAopMethod()方法前后并不会添加。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="serviceImplA" class="spring.tutorial.chapter4.service.impl.AOPServicesImpl"><property name="description" value="basicAop"></property></bean><bean id="serviceAspectBean" class="spring.tutorial.chapter4.service.impl.XMLAdvice" /><aop:config><aop:aspect id="serviceAspect" ref="serviceAspectBean"><aop:pointcut id="servicePointcut" expression="execution(* spring.tutorial.chapter4.service.*.withAop*(..))" /><aop:before pointcut-ref="servicePointcut" method="beforeAdvice" /><aop:after pointcut-ref="servicePointcut" method="afterAdvice" /><aop:after-returning pointcut-ref="servicePointcut" method="afterReturnAdvice" returning="result" /><aop:around pointcut-ref="servicePointcut" method="aroundAdvice" /><aop:after-throwing pointcut-ref="servicePointcut" method="throwingAdvice" throwing="e" /></aop:aspect></aop:config>
</beans>

​ 配置完之后还是和经典的基于代理的AOP一样,运行代码从Spring容器中获取IAOPServices对象,并分别执行IAOPServices中的两个方法。Spring会在withAopMethod()方法前后添加拦截器,在withNoAopMethod()方法前后并不会添加。

ApplicationContext context =new ClassPathXmlApplicationContext(new String[] {"ajApplicationContext.xml"});BeanFactory factory = context;IAOPService services = (IAOPService) context.getBean("serviceImplA");services.withAopMethod();services.withNoAopMethod();

运行结果如下

前置通知执行了
环绕通知开始执行了
AOP函数运行方法:withAopMethod
环绕通知执行结束了
执行业务方法共计:0毫秒。
返回通知执行了运行业务方法返回的结果为basicAop
后置通知执行了
无AOP函数运行方法:withNoAopMethod

3.AspectJ基于注解的配置

​ AspectJ基于XML的配置还是需要在XML中配置AOP元素。现在一般提倡使用注解来进行编程,AspectJ也提供了基于注解的实现方式。基于注解的AOP配置其实和基于XML的一样,可以参照基于XML的来进行理解。这里定义了AnnontationAdvice,并用@Aspect注解定义切面。在XML中的配置元素改成了注解关键字。

package spring.tutorial.chapter4.service.impl;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Component
public class AnnontationAdvice {@Before("execution(* spring.tutorial.chapter4.service.*.withAop*(..))")public void beforeAdvice() {System.out.println("前置通知执行了");}@After("execution(* spring.tutorial.chapter4.service.*.withAop*(..))")public void afterAdvice() {System.out.println("后置通知执行了");}@AfterReturning(value = "execution(* spring.tutorial.chapter4.service.*.withAop*(..))",returning = "result")public void afterReturnAdvice(String result) {System.out.println("返回通知执行了" + "运行业务方法返回的结果为" + result);}@Around(value = "execution(* spring.tutorial.chapter4.service.*.withAop*(..))")public String aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {String result = "";try {System.out.println("环绕通知开始执行了");long start = System.currentTimeMillis();result = (String) proceedingJoinPoint.proceed();long end = System.currentTimeMillis();System.out.println("环绕通知执行结束了");System.out.println("执行业务方法共计:" + (end - start) + "毫秒。");} catch (Throwable e) {}return result;}@AfterThrowing(value = "execution(* spring.tutorial.chapter4.service.*.withAop*(..))",throwing = "e")public void throwingAdvice(JoinPoint joinPoint, Exception e) {StringBuffer stringBuffer = new StringBuffer();stringBuffer.append("异常通知执行了.");stringBuffer.append("方法:").append(joinPoint.getSignature().getName()).append("出现了异常.");stringBuffer.append("异常信息为:").append(e.getMessage());System.out.println(stringBuffer.toString());}
}

​ 在配置文件中只需配置一下自动扫描的包名,并配置<aop:aspectj-autoproxy>即可,比XML的配置简单一些。

<!-- 配置自动扫描的包 -->
<context:component-scan base-package="spring.tutorial.chapter4.service"></context:component-scan>
<!-- 自动为切面方法中匹配的方法所在的类生成代理对象。 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

​ 最后从Spring容器中获取IAOPServices对象,并分别执行IAOPServices中的两个方法。运行结果如下,从打印的日志可以看到拦截器拦截了withAopMethod()方法,并未拦截withNoAopMethod():

环绕通知开始执行了
前置通知执行了
AOP函数运行方法:withAopMethod
环绕通知执行结束了
执行业务方法共计:0毫秒。
后置通知执行了
返回通知执行了运行业务方法返回的结果为basicAop
无AOP函数运行方法:withNoAopMethod

4.3 小结

​ 本章主要从传统处理方式一步一步地引入AOP,介绍AOP的主要概念,并列举了AOP的3种实现。通过本章的学习,对AOP思想有了更深入的认识。其实,实现AOP还有其他方式,这里就不一一列举了。

[Spring 深度解析]第4章 Spring之AOP相关推荐

  1. [Spring 深度解析]第6章 Spring的IoC容器系列

    6. Spring的IoC容器系列 ​ IoC容器为开发者管理对象之间的依赖关系提供了很多便利和基础服务.有许多IoC容器供开发者选择,SpringFramework的IoC核心就是其中一个,它是开源 ...

  2. [Spring 深度解析]第5章 Spring之DAO

    第5章 ◄Spring之DAO► ​ 在上一章节中,我们了解了Spring框架中的AOP模块,这一章节我们开始学习Spring框架中的DAO模块. 本章主要涉及的知识点: ​ ● JDBC基本用法:S ...

  3. [Spring 深度解析]第2章 Spring基础

    第2章 ◄Spring基础► ​ 在上一章节中,我们学习了Java的注解与反射,在这一章节我们将了解一下Spring框架,并学习Spring框架中重要的编程思想控制反转(IOC).面向切面编程(AOP ...

  4. [Spring 深度解析]第1章 Java基础

    第1章 ◄Java基础► 在学习Spring之前我们需要对Java基础语法有一定的了解,Java中最重要的两个知识点是注解和反射.注解和反射在Spring框架中应用的最广泛.掌握注解和反射,有助于后面 ...

  5. [Spring 深度解析]第3章 核心容器

    第3章 ◄核心容器► ​ 在上一章节中,我们大致了解了Spring框架,并学习了控制反转(IOC)和面向切面编程(AOP)两个重要的编程思想,这一章我们将开始学习Spring框架中的核心容器. 本章主 ...

  6. [Spring 深度解析]第7章 IoC容器的初始化过程

    7. IoC容器的初始化过程 ​ 简单来说,IoC容器的初始化是由前面介绍的refresh()方法来启动的,这个方法标志着IoC容器的正式启动.具体来说,这个启动包括BeanDefinition的Re ...

  7. Spring源码深度解析(郝佳)-学习-Spring Boot体系原理

      Spring Boot是由Pivotal团队提供的全新框架,其设计目的用来简化新Spring应用初始化搭建以及开发过程,该框架使用了我写的方式进行配置,从而开发人员不再需要定义样板化的配置,通过这 ...

  8. Spring入门篇——第6章 Spring AOP的API介绍

    第6章 Spring AOP的API介绍 主要介绍Spring AOP中常用的API. 6-1 Spring AOP API的Pointcut.advice概念及应用 映射方法是sa开头的所有方法 如 ...

  9. spring in action学习-第一章 spring之旅

    首先我先吐槽一下这本书的封面图,我能理解成一个包着头巾的男人举着个水壶昂首挺胸,最后给你个眼神....开玩笑的这幅插图是约旦西南部卡拉克省的居民,那里的山顶有座城堡,对死海和平原有极佳的视野,这幅图出 ...

最新文章

  1. 每天一个linux命令(50):crontab命令
  2. qt文件逐行读取_qt读取txt文件并绘图 qt逐行读取txt文件
  3. 快速计算文件的MD5/SHA1/SHA256等校验值(Windows/Linux)
  4. layer绑定回车事件(转)
  5. 构造函数和实例化原理
  6. Nike Hyperdunk 2012 Men's Basketball Shoes Black/Gorge Green
  7. go html template 数据怎么加减乘除_Go 视图模板篇(五):模板布局和继承
  8. Python简单GUI(录音机)
  9. LinQ—Lambda表达式
  10. mysql的简单实用_MySQL的简单实用 手把手教学
  11. Jersey the RESTful Web Services in Java
  12. 在Linux中编译jrtplib
  13. 雷士灯wifi控制方法_一种wifi无线控制的灯具系统的制作方法
  14. python数字时钟
  15. python tokenize_model_如何将关键字放入NLTK tokenize中?
  16. 下一代云计算架构,VMware要占“半壁江山”
  17. PHP弹出对话框的方法
  18. 远程监控Wifi与4G蜂窝流量连接有什么不同
  19. OpenGL学习04_点画模式(点画线)
  20. 【牛客网-前端笔试题】——Javascript专项练习6

热门文章

  1. ISME:长期进化实验揭示脱硫弧菌的硝酸盐耐受机制
  2. PPT绘制示意图视频+文字版本-一篇就学会
  3. 植物微生物组专题:研究方法、当前热点及未来方向
  4. R语言数据包自带数据集之mtcars数据集字段解释、数据导入实战
  5. R语言dplyr包的top_n函数返回dataframe或tibble的前N行数据、dplyr包的top_frac函数返回dataframe或tibble的前百分之N(N%)的数据
  6. R语言使用compareGroups包绘制单因素分析表实战:基于survival包的colon数据集
  7. pyinstaller打包之后运行出现:Could not find the matplotlib data files
  8. python基于条件、规则构建已有字典的子集
  9. transformer工程实现笔记
  10. Single Molecule Real-Time Sequencing