Spring AOP
Author 阿飞
@date2016-11-06
1.1 AOP介绍
一般意义上的AOP请参考笔者的另一篇笔记《AOP.docx》
1.2 Spring AOP介绍
spring aop注解采用的是AspectJ的注解规则。
1.2.1几个基础概念(Aspect,Joinpoint,Pointcut…)
1. Aspect(切面):切面是一个抽象概念,它是由切入点(Pointcut)、增强(Advice)组成的一个模块,其实就是一个class,这个类中包含了横切(干扰)其它代码(目标对象)的内容。
2. Join point(连接点):指所有的方法。
3. Advice(通知/增强):通过切面在一个特定的连接点处执行的动作。
4. Pointcut(切入点):可以插入增强处理的连接点(通过配置指定),一个切入点包含两部分:
1) the pointcut signature(切入点签名):其实就是声明一个普通的方法
2) the pointcut expression(切入点表达式):指@Pointcut注解的参数,其参数是切入点表达式。如下示例:
@Pointcut("execution(*transfer(..))")// the pointcut expression
privatevoid anyOldTransfer() {}// the pointcut signature
5. Introduction(引入):
6. Target object(目标对象):指被增强处理的对象。
7. AOP proxy(AOP代理):指AOP框架创建的目标对象的代理对象。
8. Weaving(织入):是一个动作,代表将增强内容和原始目标对象进行结合的过程,织入的结果是产生了一个AOP代理对象。
1.2.2切入点表达式
我们以常用的execution切入点指示符为例,说明一下它的切入点表达式规则,如下:
execution(modifiers-pattern?ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
括号内的各项含义如下:
1)modifiers-pattern 方法访问修饰符,可省略。
2)ret-type-pattern 方法返回值类型,使用“*”代表任意返回值类型。
3)declaring-type-pattern 方法所在类的完全限定性名称。
4)name-pattern 方法名,使用“*”代表任意方法名。
5)param-pattern 方法参数列表,“*”代表匹配只含有一个任意类型参数的方法,“..”代表匹配含有任意多个参数的方法。可空,比如使用()代表匹配无参方法。
6)throws-pattern 方法抛出的异常,“*”表示匹配任意一个异常,可省略。
注:切入点表达式的目的是匹配目标对象的特定方法。
1.2.3切入点指示符
spring切入点表达式有多种,但是常用的是execution。另外还有:within,target,agrs。可以从附件中资料获取详细信息。
1.2.4 AOP代理(AOP proxies)
SpringAOP默认采用JDK动态代理作为AOP代理,来为目标接口生成代理对象。但是Spring也支持使用CGLIB代理,CGLIB代理是为类而非接口生成代理对象,它是一种运行时代理,不会对原始目标对象的字节码产生影响。
在笔者的测试代码中有这么一个配置<aop:aspectj-autoproxy/>,不要被名字误解,该配置只是告诉Spring AOP使用AspectJ的注解功能,并不会使用AspectJ的AOP功能,即不会使用AspectJ去改变目标对象的字节码,这一点在Spring官方文档中有提及:
注意到配置<aop:aspectj-autoproxy/>,此处是没有配置使用何种代理,那么默认使用JDK动态代理,要想强制使用CGLIB代理,可以在aop:aspectj-autoproxy元素中添加属性proxy-target-class="true",即,
<aop:aspectj-autoproxy proxy-target-class="true"/>
1.2.5增强(Advice)的种类
说明:1.2.5.1 – 1.2.5.5中的例子来自附录参考资料[1]
1.2.5.1 Beforeadvice
@Before("com.xyz.myapp.System.dataOperation()")
public voiddoAccessCheck() {
// ...
}
在目标方法执行之前执行的操作,若要访问目标方法的参数,需要将Before advice中方法的第一个参数设置为JoinPoint类型的。
请注意:1、在Before advice中是无法执行目标方法的。
2、若目标方法未执行,则Before advice中的操作也不会被执行。
1.2.5.2 After returningadvice
@AfterReturning("com.xyz.myapp.System.dataOperation()")
public voiddoAccessCheck() {
// ...
}
Afterreturning advice中的操作只有在目标方法被正常执行或是不执行目标对象方法时才会被执行。
1.2.5.3 After throwingadvice
@AfterThrowing("com.xyz.myapp.System.dataOperation()")
public voiddoRecoveryActions() {
// ...
}
After throwingadvice中的操作在目标方法正常执行时不会被执行,在不执行目标方法时也不会被执行,它只会在目标方法执行发生异常时才会被执行。
请注意:若是包裹目标方法的代码片段的异常被处理了,那么视为目标方法没有发生异常。
1.2.5.4 After(finally)advice
@After("com.xyz.myapp.System.dataOperation()")
public voiddoReleaseLock() {
// ...
}
Afteradvice中的操作无论何时都会被执行,不管是正常执行目标方法,还是执行发生异常,还是未执行目标方法。
1.2.5.5 Aroundadvice
@Around("com.xyz.myapp.System.businessService()")
public Object aroundM(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
Aroundadvice中的操作无论何时都会被执行。
1.2.6增强(Advice)的执行顺序
1.2.6.1 在同一个切面类中,增强的执行顺序
在同一个切面类中,增强的执行顺序是(以在Around advice中调用目标方法为例):
1.2.6.2 在不同切面类中,对同一个目标方法增强的执行顺序
参考附件中资料。
1.3 Spring AOP开发
1.3.1使用注解开发
1.3.1.1基本开发步骤
前提:使用注解开发spring项目的基本配置要具备。
Step-1. 启用@AspectJ 注解支持(Enabling @AspectJ Support) Step-2. 声明切面(Declaring an aspect) Step-3. 声明切入点 (Declaring a pointcut) Step-4. 声明增强(Declaring advice) |
Step-1.启用@AspectJ 注解支持(Enabling@AspectJ Support)
1)aspectjweaver.jar(1.6.8及之后)
2)在spring配置文件中添加aop schema:
<?xmlversion="1.0" encoding="UTF-8"?>
<beansxmlns="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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- beandefinitions here -->
</beans>
3)在spring配置文件中配置 <aop:aspectj-autoproxy/>
Step-2.声明切面(Declaring an aspect)
1)在类上面使用注解@Aspect,示例:
@Aspect
publicclass NotVeryUsefulAspect {
}
说明:同时要在xml文件或使用注解(比如@Component)使得切面类由spring负责实例化。
Step-3.声明切入点(Declaring a pointcut)
1)在切面类中使用@Pointcut(切入点表达式)声明切入点,示例:
@Pointcut("execution(*com.xyz.someapp..service.*.*(..))")
public void businessService() {}
2) 关于切入点表达式的规则参见本文档中的1.2.2 section。
Step-4.声明增强(Declaring advice)
1)在切面类中使用@Before、@After等增强注解声明增强,示例:
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
1.3.1.2代码示例
以下示例是笔者基于maven、spring mvc注解、spring aop注解的测试Demo中的核心部分:
pom.xml文件:
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.xtu.spring.aop</groupId> <artifactId>spring-aop-test-Maven</artifactId> <packaging>war</packaging> <version>0.0.1</version> <name>spring-aop-test-Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <!-- Spring AOP核心包 aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.0</version> <type>jar</type> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>4.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.0.1.RELEASE</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.0.1.RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.0</version> <configuration> <update>true</update> <charset>utf-8</charset> <uriEncoding>UTF-8</uriEncoding> <url>http://localhost/manager/text</url> <server>tomcat7</server> <port>80</port> <path>/</path> </configuration> </plugin> </plugins> </build>
</project>
|
web.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>spring mvc test</display-name>
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
<servlet> <servlet-name>spring-aop</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-aop</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
<welcome-file-list> <welcome-file>/index.jsp</welcome-file> </welcome-file-list>
</web-app> |
spring-aop-servlet.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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 启用AOP,此处只是使用了aspectj的注解功能(这样我们便可以使用类似@Aspect,@Pointcut等注解了),具体AOP实现还是采用spring原本的运行时增强处理 --> <!-- 此处没有配置使用何种代理,那么默认使用JDK动态代理 --> <!-- 要想强制使用CGLIB代理,可以在aop:aspectj-autoproxy元素中添加属性 proxy-target-class="true" --> <aop:aspectj-autoproxy/> <!-- 自动扫描该包,实现bean的自动创建 --> <context:component-scan base-package="com.xtu"/> <context:annotation-config />
<!-- 启动SpringMVC的注解功能,完成请求和注解POJO的映射 --> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> </bean> <!-- 定义跳转的文件的前后缀,视图模式配置--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 这里的配置我的理解是自动给后面action的方法return的字符串加上前缀和后缀,变成一个可用的url地址 --> <property name="prefix" value="/WEB-INF/" /> <property name="suffix" value=".jsp" /> </bean>
<!-- 此配置甚为关键,没有annotation-driven,注解便不起作用 --> <mvc:annotation-driven></mvc:annotation-driven>
</beans> |
切面类:
package com.xtu.aop.aspect;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;
/** * 定义一个切面,名称为"Aspect" * * @author阿飞 * @date 2016-11-03 */ @Component @org.aspectj.lang.annotation.Aspect public class Aspect {
/** * 定义切入点 * 切入点包含两部分:切入点表达式,方法声明。 * 通常方法名字一般代表“切入点” */ @Pointcut("execution(public * com.xtu.aop.controller.*.*(..))") public void pointCutMethod() { }
/** * 定义Before advice * * @Description:@Before注解中的参数既可以是一个已经定义好的切入点(如"pointCutMethod()"), * 也可以是一个切入点表达式(如"execution(public * com.xtu.aop.controller.*.*(..))") * * @Notice "before advice"只能在目标方法执行之前做一些事情,它不能传入ProceedingJoinPoint参数。 * * @param joinPoint 当需要访问目标方法的参数时,第一个参数只能是JoinPoint类型 */ @Before("pointCutMethod()") public void beforeMethod(JoinPoint joinPoint) {
System.out.println("beforeMethod...");
}
/** * 定义After advice * * @param joinPoint 当需要访问目标方法的参数时,第一个参数只能是JoinPoint类型 */ @After("pointCutMethod()") public void afterMethod(JoinPoint joinPoint) {
System.out.println("afterMethod");
}
/** * 定义Around advice * * @param pJoinPoint 当需要执行目标方法时,第一个参数只能是ProceedingJoinPoint类型 * @throws Throwable */ @Around("pointCutMethod()") public void aroundMethod(ProceedingJoinPoint pJoinPoint) throws Throwable {
System.out.println("aroundMethod guaguo..."); pJoinPoint.proceed(); System.out.println("aroundMethod guaguo proceed end"); }
}
|
目标类:
package com.xtu.aop.controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
/** * 目标类 * * @author阿飞 * @date 2016-10-23 */ @Controller @RequestMapping("/userController") public class UserController {
public UserController (){ super(); System.out.println("UserController create..."); }
@RequestMapping(value = "/getUser1") public String getUser1(){ System.out.println("Method getUser1 in ..."); return "userInfoList"; }
}
|
附录
参考资料:
[1]. http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html
(Spring关于AOP的官方资料)
[2]. 轻量级javaEE 企业应用(第三版) 李刚 中的AOP部分
[3]. http://blog.csdn.net/chenmeng2192089/article/details/7970255
(spring对AOP的支持 JDK动态代理和CGLIB的区别)