代码改变世界

JavaEE学习之Spring AOP

2014-08-17 22:31  ps_zw  阅读(728)  评论(0编辑  收藏  举报

一、基本概念

  AOP——Aspect-Oriented Programming,面向切面编程,它是spring框架的一个重要组成部分。一般的业务逻辑都有先后关系,我们可以理解为纵向关系,而AOP关注的是横向关系,每一个关注点可以理解为一个横切面。例如我们的大部分代码都会涉及到日志记录,很多的数据库操作都会涉及到事务的创建和提交。那么从横向关注这些逻辑,他们都一个个的切面。

  AOP技术的具体实现,可以通过动态代理技术或者是在程序编译期间进行静态的"织入"方式。AOP经常使用的场景包括:日志记录,事务管理,异常处理,安全控制等方面。Spring 中 AOP 代理由 Spring 的 IoC 容器负责生成、管理,其依赖关系也由 IoC 容器负责管理。在spring中可以仅通过配置文件实现AOP,也可以使用注解实现。

  AOP相关概念:

  • Aspect(方面,切面):系统中需要实现的那些交叉功能,是系统模块化的一个切面,或领域。如日志记录。
  • Joinpoint(连接点):应用程序执行过程中,插入切面的地点,可以是方法调用,异常抛出,或者要修改的字段。    
  • Advice(通知):切面的实际实现,他通知系统新的行为。AOP通知大致上包括:前置通知(Before),环绕通知(Around),后置通知(After Returning),异常通知(Throws Advice) .。
  • Pointcut(切入点):定义了将通知应用到哪一个连接点。本质上是一个捕获连接点的结构。在AOP中,可以定义一个point cut,来捕获相关方法的调用。
  • Introduce(引入):为对象引入附加的方法或属性,从而达到修改对象结构的目的。
  • 目标对象:被通知的对象,既可以是你编写的类,也可以是第三方类。
  • 代理:将通知应用到目标对象后创建的对象,应用系统的其他部分不用为了支持代理对象而改变
  • Weaving(编织,织入):将切面应用到目标对象从而创建一个新代理对象的过程。织入发生在目标对象生命周期的多个点上:编译器,类装载期,运行期。

二、依赖jar包

 本文主要学习spring aop相关使用,所以需要的jar主要包括spring相关jar包和aop相关jar包,具体pom.xml文件如下:

 1 <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">
 2   <modelVersion>4.0.0</modelVersion>
 3   <groupId>wzhang</groupId>
 4   <artifactId>spring-aop</artifactId>
 5   <version>0.0.1-SNAPSHOT</version>
 6   <name>spring-aop</name>
 7   <description>Hello World</description>
 8   <dependencies>
 9       <dependency>
10           <groupId>org.springframework</groupId>
11           <artifactId>spring-aop</artifactId>
12           <version>3.2.3.RELEASE</version>
13       </dependency>
14       <dependency>
15           <groupId>org.springframework</groupId>
16           <artifactId>spring-beans</artifactId>
17           <version>3.2.3.RELEASE</version>
18       </dependency>
19       <dependency>
20           <groupId>org.springframework</groupId>
21           <artifactId>spring-context</artifactId>
22           <version>3.2.3.RELEASE</version>
23       </dependency>
24       <dependency>
25           <groupId>org.springframework</groupId>
26           <artifactId>spring-core</artifactId>
27           <version>3.2.3.RELEASE</version>
28       </dependency>
29       <dependency>
30           <groupId>org.springframework</groupId>
31           <artifactId>spring-expression</artifactId>
32           <version>3.2.3.RELEASE</version>
33       </dependency>
34       <dependency>
35           <groupId>aopalliance</groupId>
36           <artifactId>aopalliance</artifactId>
37           <version>1.0</version>
38       </dependency>
39       <dependency>
40           <groupId>log4j</groupId>
41           <artifactId>log4j</artifactId>
42           <version>1.2.17</version>
43       </dependency>
44       <dependency>
45           <groupId>org.aspectj</groupId>
46           <artifactId>aspectjrt</artifactId>
47           <version>1.8.2</version>
48       </dependency>
49       <dependency>
50           <groupId>org.aspectj</groupId>
51           <artifactId>aspectjweaver</artifactId>
52           <version>1.8.2</version>
53       </dependency>
54   </dependencies>
55 </project>
pom.xml

三、具体实现

任何技术都是为业务服务的,AOP也不例外。下面以经典的计算器业务为例,简单介绍下AOP的应用。

1.利用maven创建一个simple project,并添加相关依赖。

2.在src/main/resources目录下创建spring配置文件:applicationContext.xml,先放着,用的时候再去配置。

(***************上面两步都是准备工作,下面才是具体实现*********************)

3.定义业务类

定义接口:ICalculate,并提供int add(int,int)方法,再定义一个实现类CalculateImpl:

 1 /************** ICalculate接口 **************/
 2 
 3 package com.wzhang.service;
 4 
 5 public interface ICalculate {
 6 
 7     /**
 8      * add 加运算
 9      * @return 两数和
10      */
11     int add(int a,int b);
12 }
13 
14 /********* ICalculate 的实现类 **************/
15 
16 package com.wzhang.service.impl;
17 
18 import com.wzhang.service.ICalculate;
19 
20 public class CalculateImpl implements ICalculate {
21 
22     public int add(int a, int b) {
23         return a+b;
24     }
25 }

4.spring 实现AOP有两种方式,配置文件和注解方式。这里先通过配置文件实现方法调用前的权限检查:

1)定义类SecurityAspect,并提供方法checkAuthority.代码如下:

 1 package com.wzhang.aspect;
 2 /**
 3 * 安全检查切面(方面)
 4 */
 5 public class SecurityAspect {
 6     
 7     /**
 8      * 权限检查
 9      */
10     public void checkAuthority(){
11         System.out.println("方法调用前,先检查权限!");
12     }
13 }

2) 在applicationContext.xml中配置AOP:

 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     xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
 5     xsi:schemaLocation="http://www.springframework.org/schema/beans 
 6     http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
 7     http://www.springframework.org/schema/context     
 8     http://www.springframework.org/schema/context/spring-context-3.2.xsd
 9     http://www.springframework.org/schema/tx 
10     http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
11     http://www.springframework.org/schema/aop 
12     http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
13 
14     <!-- 扫描com.wzhang下的所有包 -->
15     <context:component-scan base-package="com.wzhang" />
16 
17     <!-- 定义业务类 -->
18     <bean id="calculate" class="com.wzhang.service.impl.CalculateImpl" />
19 
20     <!-- 定义一个安全控制切面 -->
21     <bean id="authority" class="com.wzhang.aspect.SecurityAspect" />
22 
23     <!-- 通过配置文件实现 -->
24     <aop:config>
25         <!-- 定义切面 -->
26         <aop:aspect id="security" ref="authority">
27             <!-- 指定切入点 -->
28             <aop:pointcut expression="execution(* com.wzhang.service.*.*(..))"
29                 id="securityPointcut" />
30             <!-- 配置前置通知 -->
31             <aop:before method="checkAuthority" pointcut-ref="securityPointcut" />
32             <!-- 还可以配置环绕通知,后置通知,异常通知 -->
33         </aop:aspect>
34     </aop:config>
35 </beans>

注意事项:

  • 需要配置AOP相关的schema;
  • 在<aop:config></aop:config>节点中配置切面,也就是我们之前定义的处理类;
  • 指定切入点,使用切入点表达式。
  • 通知(Advice),可以通过pointcut-ref指定已定义切入点;要定义内置切入点,可将 pointcut-ref 属性替换为 pointcut 属性。

经过以上几步,使用配置文件实现的主体代码就完成了,剩下的就是测试了。我们这里和用注解实现AOP一起测试

5. 通过注解实现方法调用前后的日志记录

1)通过注解实现AOP需要在配置文件中配置:  <aop:aspectj-autoproxy />节点,启动对@AspectJ注解的支持。配置如下:

 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     xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
 5     xsi:schemaLocation="http://www.springframework.org/schema/beans 
 6     http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
 7     http://www.springframework.org/schema/context     
 8     http://www.springframework.org/schema/context/spring-context-3.2.xsd
 9     http://www.springframework.org/schema/tx 
10     http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
11     http://www.springframework.org/schema/aop 
12     http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
13     
14     <!-- 扫描com.wzhang下的所有包 -->
15     <context:component-scan base-package="com.wzhang" />
16 
17     <!-- 自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。(启动对@AspectJ注解的支持) -->
18     <aop:aspectj-autoproxy />
19 </beans>

2)定义切面。代码如下:

 1 package com.wzhang.aspect;
 2 
 3 import org.aspectj.lang.annotation.AfterReturning;
 4 import org.aspectj.lang.annotation.Aspect;
 5 import org.aspectj.lang.annotation.Before;
 6 import org.aspectj.lang.annotation.Pointcut;
 7 
 8 /**
 9  * 记录日志切面
10  * @author wzhang
11  *
12  */
13 @Aspect
14 public class LogAspect {
15     
16     /**
17      * 前置切入点,
18      */
19     @Pointcut("execution(* com.wzhang.service.*.*(..))")
20     public void beforePointcut(){}
21     
22     /**
23      * 后置通知,日志记录
24      */
25     @AfterReturning("execution(* com.wzhang.service.*.*(..))")
26     public void log(){
27         System.out.println("方法调用后,记录操作日志!");
28     }
29     
30     /**
31      * 前置通知
32      */
33     @Before("beforePointcut()")
34     public void beforeLog(){
35         System.out.println("使用@Pointcut注解,实现方法调用前记录操作日志!");
36     }
37 
38 }

3)配置bean:<bean id="log" class="com.wzhang.aspect.LogAspect" />,最终配置(包括第4步的配置)如下:

 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     xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
 5     xsi:schemaLocation="http://www.springframework.org/schema/beans 
 6     http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
 7     http://www.springframework.org/schema/context     
 8     http://www.springframework.org/schema/context/spring-context-3.2.xsd
 9     http://www.springframework.org/schema/tx 
10     http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
11     http://www.springframework.org/schema/aop 
12     http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
13 
14     <!-- 扫描com.wzhang下的所有包 -->
15     <context:component-scan base-package="com.wzhang" />
16 
17     <!-- 定义业务类 -->
18     <bean id="calculate" class="com.wzhang.service.impl.CalculateImpl" />
19 
20     <!-- 定义一个安全控制切面 -->
21     <bean id="authority" class="com.wzhang.aspect.SecurityAspect" />
22     
23     <bean id="log" class="com.wzhang.aspect.LogAspect" />
24 
25     <!-- 通过配置文件实现 -->
26     <aop:config>
27         <!-- 定义切面 -->
28         <aop:aspect id="security" ref="authority">
29             <!-- 指定切入点 -->
30             <aop:pointcut expression="execution(* com.wzhang.service.*.*(..))"
31                 id="securityPointcut" />
32             <!-- 配置前置通知 -->
33             <aop:before method="checkAuthority" pointcut-ref="securityPointcut" />
34             <!-- 还可以配置环绕通知,后置通知,异常通知 -->
35         </aop:aspect>
36     </aop:config>
37 
38 
39     <!-- 自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。(启动对@AspectJ注解的支持) -->
40     <aop:aspectj-autoproxy />
41 </beans>
applicationContext.xml

几点说明:

通过@Aspect注解声明切面;

通过@Before,@Around,@AfterReturning,@AfterThrowing等定义通知,可以通过表达式execution(xxx)定义切入点;

还可以在方法上@Pointcut定义切入点,方便同类中其他方法使用此处配置的切入点。

6.测试,在src/test/java目录下创建测试类:AppTest.代码如下:

 1 package com.wzhang.test;
 2 
 3 import org.junit.Assert;
 4 import org.junit.Test;
 5 import org.springframework.context.ApplicationContext;
 6 import org.springframework.context.support.ClassPathXmlApplicationContext;
 7 
 8 import com.wzhang.domain.UserBean;
 9 import com.wzhang.service.ICalculate;
10 import com.wzhang.service.IUser;
11 
12 public class AopTest {
13 
14     static ApplicationContext ctx;
15     static {
16         ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
17     }
18     
19     @Test
20     public void calculateTest(){
21         ICalculate cal =  (ICalculate) ctx.getBean("calculate");
22         int result =  cal.add(5, 6);
23         Assert.assertEquals(11, result);
24     }
25 }

运行测试,输入以下结果:

第一行说明使用配置文件实现AOP成功,第二行说明使用@Pointcut注解实现前置通知成功。

四、切入点表达式的相关说明

1.切入点表达式中:"*"-匹配所有字符;".."-用于匹配多个包,多个参数;"+"-表示类及其子类;

2.切入点表达式支持逻辑运算符:&&,||,!;

3.使用execution用于匹配子表达式;

4.AOP的注解不仅仅局限于例子中的几个,还有@args,@within,@this,@target等注解,需要我们深入研究。

 

示例代码下载:spring-aop.zip