Spring Framework的核心:Spring AOP

1  概述

        AOP(Aspect Oriented Programming)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的横向多模块统一控制的一种技术。AOP是OOP的补充,是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP可以分为静态织入和动态织入,静态织入即在编译前将需织入内容写入目标模块中,这样成本非常高。动态织入则不需要改变目标模块。

1.1  AOP的基本概念

1.1.1

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。
  • 连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。
  • 通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。

  • 切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。

  • 引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。

  • 目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。

  • AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

  • 织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

1.1.2  通知类型:

  • 前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。

  • 后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

  • 异常通知(After throwing advice):在方法抛出异常退出时执行的通知。

  • 最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

  • 环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。

        环绕通知是最常用的通知类型。和AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,你不需要在JoinPoint上调用用于环绕通知的proceed()方法,就不会有调用的问题。

        在Spring 2.0中,所有的通知参数都是静态类型,因此你可以使用合适的类型(例如一个方法执行后的返回值类型)作为通知的参数而不是使用Object数组。

        通过切入点匹配连接点的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。 切入点使得通知可以独立对应到面向对象的层次结构中。例如,一个提供声明式事务管理的环绕通知可以被应用到一组横跨多个对象的方法上(例如服务层的所有业务操作)。

 2  基于XML配置的Spring AOP

2.1  新建一个Maven项目,pom.xml文件如下:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <modelVersion>4.0.0</modelVersion>
 6 
 7     <groupId>com.hmz</groupId>
 8     <artifactId>spring-aop</artifactId>
 9     <version>1.0-SNAPSHOT</version>
10 
11     <properties>
12         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
13         <spring.version>4.3.0.RELEASE</spring.version>
14     </properties>
15 
16     <dependencies>
17         <dependency>
18             <groupId>junit</groupId>
19             <artifactId>junit</artifactId>
20             <scope>test</scope>
21             <version>4.10</version>
22         </dependency>
23         <dependency>
24             <groupId>org.springframework</groupId>
25             <artifactId>spring-context</artifactId>
26             <version>${spring.version}</version>
27         </dependency>
28         <dependency>
29             <groupId>org.aspectj</groupId>
30             <artifactId>aspectjweaver</artifactId>
31             <version>1.8.9</version>
32         </dependency>
33         <dependency>
34             <groupId>cglib</groupId>
35             <artifactId>cglib</artifactId>
36             <version>3.2.4</version>
37         </dependency>
38         <dependency>
39             <groupId>org.testng</groupId>
40             <artifactId>testng</artifactId>
41             <version>RELEASE</version>
42         </dependency>
43     </dependencies>
44 
45 </project>
pom.xml

2.2  创建要被代理的Math类,代码如下:

 1 package com.hmz;
 2 
 3 /*
 4  *@Description:  被代理的目标类
 5  *@Author:  hmz
 6  *@CreateDate:  2018/9/16 10:30
 7  *@Version:  1.0
 8  */
 9 public class Math {
10 
11     public int add(int n1, int n2) {
12         int result = n1 + n2;
13         System.out.println(n1 + " + " + n2 + " = " + result);
14         return result;
15     }
16 
17     public int sub(int n1, int n2) {
18         int result = n1 - n2;
19         System.out.println(n1 + " - " + n2 + " = " + result);
20         return result;
21     }
22 
23     public int mut(int n1, int n2) {
24         int result = n1 * n2;
25         System.out.println(n1 + " * " + n2 + " = " + result);
26         return result;
27     }
28 
29     public int div(int n1, int n2) {
30         int result = n1 / n2;
31         System.out.println(n1 + " / " + n2 + " = " + result);
32         return result;
33     }
34 
35 }
Math.java

2.3  编辑AOP中需要使用到的通知类,代码如下:

 1 package com.hmz;
 2 
 3 /*
 4  *@Description:  通知类,横切逻辑
 5  *@Author:  hmz
 6  *@CreateDate:  2018/9/16 10:34
 7  *@Version:  1.0
 8  */
 9 public class Advices {
10 
11     public void before() {
12         System.out.println("------前置通知------");
13     }
14 
15     public void after() {
16         System.out.println("------最终通知------");
17     }
18 
19 }
Advices.java

2.4  配置容器初始化时需要的XML文件,代码如下:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
 4        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
 5 
 6     <!-- 被代理的对象 -->
 7     <bean id="math" class="com.hmz.Math"/>
 8 
 9     <!-- 通知 -->
10     <bean id="advices" class="com.hmz.Advices"/>
11 
12     <!-- aop配置 -->
13     <aop:config proxy-target-class="true">
14         <!-- 切面 -->
15         <aop:aspect ref="advices">
16             <!-- 切点 -->
17             <aop:pointcut id="pointcut" expression="execution(* com.hmz.Math.*(..))"/>
18             <!-- 连接通知方法和切点 -->
19             <aop:before method="before" pointcut-ref="pointcut"/>
20             <aop:after method="after" pointcut-ref="pointcut"/>
21         </aop:aspect>
22     </aop:config>
23 
24 </beans>
aop01.xml

2.5  测试代码如下:

 1 package com.hmz;
 2 
 3 import org.springframework.context.ApplicationContext;
 4 import org.springframework.context.support.ClassPathXmlApplicationContext;
 5 import org.testng.annotations.Test;
 6 
 7 /**
 8  * Created by 黄茂展 on 2018/9/17.
 9  */
10 public class Test1 {
11 
12     @Test
13     public void test() {
14         ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop01.xml");
15         Math math = applicationContext.getBean("math", Math.class);
16         int n1 = 100, n2 = 5;
17         math.add(n1, n2);
18         math.sub(n1, n2);
19         math.mut(n1, n2);
20         math.div(n1, n2);
21     }
22 
23 }
Test1.java

2.6  在控制台的运行结果如下:

        ------前置通知------
        100 + 5 = 105
        ------最终通知------
        ------前置通知------
        100 - 5 = 95
        ------最终通知------
        ------前置通知------
        100 * 5 = 500
        ------最终通知------
        ------前置通知------
        100 / 5 = 20
        ------最终通知------

3  使用注解配置AOP

3.1  修改要被代理的Math类,代码如下:

 1 package com.hmz;
 2 
 3 import org.springframework.stereotype.Service;
 4 
 5 /*
 6  *@Description:  被代理的目标类
 7  *@Author:  hmz
 8  *@CreateDate:  2018/9/16 10:30
 9  *@Version:  1.0
10  */
11 @Service("math")
12 public class Math {
13 
14     public int add(int n1, int n2) {
15         int result = n1 + n2;
16         System.out.println(n1 + " + " + n2 + " = " + result);
17         return result;
18     }
19 
20     public int sub(int n1, int n2) {
21         int result = n1 - n2;
22         System.out.println(n1 + " - " + n2 + " = " + result);
23         return result;
24     }
25 
26     public int mut(int n1, int n2) {
27         int result = n1 * n2;
28         System.out.println(n1 + " * " + n2 + " = " + result);
29         return result;
30     }
31 
32     public int div(int n1, int n2) {
33         int result = n1 / n2;
34         System.out.println(n1 + " / " + n2 + " = " + result);
35         return result;
36     }
37 
38 }
Math.java

3.2  修改AOP中需要使用到的通知类,代码如下:

 1 package com.hmz;
 2 
 3 import org.aspectj.lang.annotation.After;
 4 import org.aspectj.lang.annotation.Aspect;
 5 import org.aspectj.lang.annotation.Before;
 6 import org.springframework.stereotype.Component;
 7 
 8 /*
 9  *@Description:  通知类,横切逻辑
10  *@Author:  hmz
11  *@CreateDate:  2018/9/16 10:34
12  *@Version:  1.0
13  */
14 @Component
15 @Aspect
16 public class Advices {
17 
18     @Before("execution(* com.hmz.Math.*(..))")
19     public void before() {
20         System.out.println("------前置通知------");
21     }
22 
23     @After("execution(* com.hmz.Math.*(..))")
24     public void after() {
25         System.out.println("------最终通知------");
26     }
27 
28 }
Advices.java

3.3  修改配置容器初始化时需要的XML文件,代码如下:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xmlns:context="http://www.springframework.org/schema/context"
 5        xmlns:aop="http://www.springframework.org/schema/aop"
 6        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.xsd">
 7 
 8     <context:component-scan base-package="com.hmz"/>
 9 
10     <aop:aspectj-autoproxy proxy-target-class="true"/>
11 
12 </beans>
aop02.xml

3.4  测试代码如下:

 1 package com.hmz;
 2 
 3 import org.springframework.context.ApplicationContext;
 4 import org.springframework.context.support.ClassPathXmlApplicationContext;
 5 import org.testng.annotations.Test;
 6 
 7 /**
 8  * Created by 黄茂展 on 2018/9/17.
 9  */
10 public class Test1 {
11 
12     @Test
13     public void test() {
14         ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop02.xml");
15         Math math = applicationContext.getBean("math", Math.class);
16         int n1 = 100, n2 = 5;
17         math.add(n1, n2);
18         math.sub(n1, n2);
19         math.mut(n1, n2);
20         math.div(n1, n2);
21     }
22 
23 }
Test1.java

3.5  在控制台的运行结果如下:

        ------前置通知------
        100 + 5 = 105
        ------最终通知------
        ------前置通知------
        100 - 5 = 95
        ------最终通知------
        ------前置通知------
        100 * 5 = 500
        ------最终通知------
        ------前置通知------
        100 / 5 = 20
        ------最终通知------

猜你喜欢

转载自www.cnblogs.com/HNewa/p/HMZ_Spring_AOP.html