Spring AOP
前段时间使用AOP做日志拦截,由于很长时间没有使用过AOP了,在开发过程中遇到各种各样的问题,都快要郁闷死了。。。 现重新温习,做个记录。如有不正确指出还请指点!
首先,在OO设计中,由类组成了模块化,而AOP的出现,弥补了OO中的不足点。原因是:在AOP中模块化的单元是切面。切面能对关注点进行模块化,例如:横切多个类型和对对象的事务管理,而每个关注点被称作为横切关注点。
一 AOP的基本概念
1. 切面(Aspect):
一个关注点的模块化,可以横切多个对象,例如:事务管理。在Spring AOP中,切面可以使用基于模式或者基于Aspect注解的方式来实现。
2. 连接点(Jointpoint)
在程序执行过程中某个特定的点。比如某个方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总表示一个方法的执行。
3. 通知(advice)
许多AOP框架都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
在切面的某个特定连接点上执行的动作。
4. 切入点(Pointcut)
匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如:当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心。Spring缺省使用AspectJ切入点语法。
5. 引入(Introduction)
用来给一个类型声明额外的方法或属性(也被称为连接类型声明)。Spring允许引入一个新的接口以及对应的实现到任何被代理的对象。例如:你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。
6. 目标对象(Target Object)
被一个或多个切面通知的对象,也被称为被通知对象。而在Spring AOP中,目标对象是通过运行时代理实现的,所以这个对象永远是被代理(Proxied)对象。
7. AOP代理(AOP Proxy)
AOP框架创建的对象,用来实现某个契约。在AOP中,AOP代理可以是JDK的Proxy或者CGLib。
8. 织入(Weaving)
把切面连接到其它应用程序类型或对象上,并创建一个被通知的对象。这些可以在编译时(如使用AspectJ编译器),类加载时或运行时完成。Spring和其它的AOP都是在运行时织入。
二 通知(Advice)的类型
主要包含以下五种通知:
1) Before Advice
在连接点执行的通知。
2) After returning Advice
在连接点之后执行的通知,如果没有任何异常,那就返回。
3) After throwing Advice
执行连接点时抛出异常退出时执行的通知。
4) After Advice
在连接点退出的时候执行的通知,不管任何时候,都会被执行。
5) Around Advice
包围一个连接点的通知,如方法调用,这是最强大的通知类型。可以在连接点前后完成自定义的行为,也可以选择是否继续执行连接点或者直接返回值或抛出异常来终止执行。
Spring对AOP的支持有以下4种情况:
经典的基于代理的aop(各版本spring)
@AspectJ注解驱动的切面(spring2.0后)
纯pojo切面(spring2.0后)
注入式AspectJ切面(各版本spring)
工程目录结构如下
一、基于XML文件的配置
Spring文件applicationContext.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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <bean id="personServiceBean" class="service.impl.PersonServiceBean"></bean> <!-- 开启aspectj注解 --> <aop:aspectj-autoproxy /> <bean id="springAOP" class="aop.SpringAOP"></bean> <!-- 基于XML配置 --> <bean id="springAOPBean" class="aop.SpringAOPXML"></bean> <aop:config> <aop:aspect id="aspect" ref="springAOPBean"> <!-- 定义了返回值类型!!!String --> <!-- <aop:pointcut id="mycut" expression="execution(java.lang.String service.impl.PersonBean.*(..))" />--> <!-- 定义了输入参数类型!!!String --> <!--<aop:pointcut id="mycut" expression="execution(* service.impl.PersonBean.*(java.lang.String,..))" />--> <!-- 返回所有不是void类型的所有方法 --> <!--<aop:pointcut id="mycut" expression="execution( !void service.impl.PersonBean.*(..))" />--> <!-- service包下的类和子包 <aop:pointcut id="mycut" expression="execution( * service.*.*(..))" /> <aop:before pointcut-ref="mycut" method="doAccessCheck" /> <aop:after-returning pointcut-ref="mycut" method="doAfterReturningString" returning="personId" /> <aop:after-returning pointcut-ref="mycut" method="doAfterReturningObject" returning="person" /> <aop:after-throwing pointcut-ref="mycut" method="doAfterThrowing" />--> <!-- <aop:after pointcut-ref="mycut" method="doAfter"/> --> <!-- <aop:around pointcut-ref="mycut" method="doAround"/> --> </aop:aspect> </aop:config> </beans>
log4j.properties
og4j.rootLogger=DEBUG, stdout
log4j.category.service.impl=DEBUG
#stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %5p [%t] - %m%n
#logfile 后置通知并返回值(String)
log4j.logger.afterReturnString= INFO, ars
log4j.appender.ars=org.apache.log4j.DailyRollingFileAppender
log4j.appender.ars.DatePattern=yyyy-MM-dd HH:mm:ss
log4j.appender.ars.layout=org.apache.log4j.PatternLayout
log4j.appender.ars.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.ars.File=D:/logs/afterReturnS.log
#logfile 后置通知并返回值(Object)
log4j.logger.afterReturnObject= INFO, aro
log4j.appender.aro=org.apache.log4j.DailyRollingFileAppender
log4j.appender.aro.DatePattern=yyyy-MM-dd HH:mm:ss
log4j.appender.aro.layout=org.apache.log4j.PatternLayout
log4j.appender.aro.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.aro.File=D:/logs/afterReturnO.log
#logfile 前置通知
log4j.logger.before= INFO, bef
log4j.appender.bef=org.apache.log4j.DailyRollingFileAppender
log4j.appender.bef.DatePattern=yyyy-MM-dd HH:mm:ss
log4j.appender.bef.layout=org.apache.log4j.PatternLayout
log4j.appender.bef.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.bef.File=D:/logs/before.log
切面定义
personServiceBean.java
package service.impl;
import model.Person;
import service.PersonService;
public class PersonServiceBean implements PersonService{
private Person person = null;
private String user = null;
public PersonServiceBean(){}
public PersonServiceBean(String user) {
this.user = user;
}
public String getUser() {
return user;
}
public Person getPerson(){
person = new Person();
person.setName("wy");
person.setAddress("bj");
person.setPhone("110");
return person;
}
public String getPersonName(Integer personId) {
return (personId.intValue()+1)+"";
}
public void save(String name) {
//throw new RuntimeException("例外通知!!!");
System.out.println("执行save()方法");
}
public void update(String name, Integer personId) {
System.out.println("执行update()方法");
}
}
单元测试类JunitSpringAopTest
package junit.test;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.PersonService;
public class JunitSpringAopTest {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
}
@Test
public void interceptor() {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"applicationContext.xml");
PersonService person = (PersonService) ctx.getBean("personServiceBean");
// person.save("xxx");
// person.getPersonName(2);
person.getPerson();
}
}
注意需要说明的地方:对每个拦截的方法打出日志并放到对应的日志文件中,详细看注释。
二、基于注解
注解配置AOP,大致分为三步:
1. 使用注解@Aspect来定义一个切面,在切面中定义切入点(@Pointcut),通知类型(@Before, @AfterReturning,@After,@AfterThrowing,@Around).
2. 开发需要被拦截的类。
3. 将切面配置到xml中,当然,我们也可以使用自动扫描Bean的方式。这样的话,那就交由Spring AoP容器管理。
切面定义
SpringAOP.java
package aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @Aspect 此类是一个 切面
* @Component 将此类纳入到Spring容器中
*/
@Component
@Aspect
public class SpringAOP {
@SuppressWarnings("unused")
@Pointcut("execution(* service.impl.PersonServiceBean.*(..))")
private void anyMethod(){}//声明一个切点
@Before("anyMethod() && args(name)")
public void doAccessCheck(String name){
System.out.println("前置通知!!!"+name);
}
@AfterReturning("anyMethod()")
public void doAfterReturning(){
System.out.println("后置通知!!!");
}
@AfterReturning(pointcut="anyMethod()",returning="result")
public void doAReturn(String result){
System.out.println(result);
}
@AfterThrowing("anyMethod()")
public void doAfterThrowing() {
System.out.println("例外通知!!!");
}
@AfterThrowing( pointcut="anyMethod()" ,throwing="e")
public void doAfterThrow(Exception e) {
System.out.println("例外通知:"+e+" "+e.getMessage());
}
@Around("anyMethod()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
//非常适合做权限的判断
//使用了环绕通知就可以不适用前面的通知
System.out.println("进入方法!!!");
Object result = pjp.proceed();
System.out.println("退出方法!!!");
return result;
}
}
一、什么是 AOP。
AOP(Aspect Orient Programming),也就是面向切面编程 。可以这样理解,面向对象编程(OOP)是从静态角度考虑程序结构,面向切面编程(AOP)是从动态角度考虑程序运行过程 。
二、AOP 的作用。
常常通过 AOP 来处理一些具有横切性质的系统性服务 ,如事物管理、安全检查、缓存、对象池管理 等,AOP 已经成为一种非常常用的解决方案。
三、AOP 的实现原理。
如图:AOP 实际上是由目标类的代理类实现的 。AOP 代理其实是由 AOP 框架动态生成的一个对象,该对象可作为目标对象使用 。AOP 代理包含了目标对象的全部方法,但 AOP 代理中的方法与目标对象的方法存在差异,AOP 方法在特定切入点添加了增强处理,并回调了目标对象的方法 。
四、Spring 中对 AOP 的支持
Spring 中 AOP 代理由 Spring 的 IoC 容器负责生成、管理,其依赖关系也由 IoC 容器负责管理 。因此,AOP 代理可以直接使用容器中的其他 Bean 实例作为目标,这种关系可由 IoC 容器的依赖注入提供。Spring 默认使用 Java 动态代理来创建 AOP 代理 , 这样就可以为任何接口实例创建代理了。当需要代理的类不是代理接口的时候, Spring 自动会切换为使用 CGLIB 代理,也可强制使用 CGLIB 。
AOP 编程其实是很简单的事情。纵观 AOP 编程, 其中需要程序员参与的只有三个部分:
- 定义普通业务组件。
- 定义切入点,一个切入点可能横切多个业务组件。
- 定义增强处理,增强处理就是在 AOP 框架为普通业务组件织入的处理动作。
所以进行 AOP 编程的关键就是定义切入点和定义增强处理。一旦定义了合适的切入点和增强处理,AOP 框架将会自动生成 AOP 代理,即:代理对象的方法 = 增强处理 + 被代理对象的方法 。
五、Spring 中 AOP 的实现。
Spring 有如下两种选择来定义切入点和增强处理。
- 基于 Annotation 的“零配置”方式:使用@Aspect、@Pointcut等 Annotation 来标注切入点和增强处理。
- 基于 XML 配置文件的管理方式:使用 Spring 配置文件来定义切入点和增强点。
1、基于 Annotation 的“零配置”方式。
(1)、首先启用 Spring 对 @AspectJ 切面配置的支持。
- <?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
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">
- <!-- 启动对@AspectJ 注解的支持 -->
- <aop:aspectj-autoproxy/>
- </beans>
如果不打算使用 Spring 的 XML Schema 配置方式,则应该在 Spring 配置文件中增加如下片段来启用@AspectJ 支持。
- <!-- 启用 @AspectJ 支持 -->
- <bean class = "org.springframeword.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
(2)、定义切面 Bean。
当启动了@AspectJ 支持后,只要在 Spring 容器中配置一个带@Aspect 注释的 Bean, Spring 将会自动识别该 Bean 并作为切面处理。
- // 使用@Aspect 定义一个切面类
- @Aspect
- public class LogAspect {
- // 定义该类的其他内容
- ...
- }
(3)、定义 Before 增强处理。
- // 定义一个切面
- @Aspect
- public class BeforeAdviceTest {
- // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
- @Before ( "execution(* com.wicresoft.app.service.impl.*.*(..))" )
- public void authorith(){
- System.out.println("模拟进行权限检查。" );
- }
- }
上面使用@Before Annotation 时,直接指定了切入点表达式,指定匹配 com.wicresoft.app.service.impl包下所有类的所有方法执行作为切入点。
关于这个表达式的规则如下图。
(4)、定义 AfterReturning 增强处理。
- // 定义一个切面
- @Aspect
- public class AfterReturningAdviceTest {
- // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
- @AfterReturning (returning= "rvt" , pointcut= "execution(* com.wicresoft.app.service.impl.*.*(..))" )
- public void log(Object rvt) {
- System.out.println("模拟目标方法返回值:" + rvt);
- System.out.println("模拟记录日志功能..." );
- }
- }
(5)、定义 AfterThrowing 增强处理。
- // 定义一个切面
- @Aspect
- public class AfterThrowingAdviceTest {
- // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
- @AfterThrowing (throwing= "ex" , pointcut= "execution(* com.wicresoft.app.service.impl.*.*(..))" )
- public void doRecoverActions(Throwable ex) {
- System.out.println("目标方法中抛出的异常:" + ex);
- System.out.println("模拟抛出异常后的增强处理..." );
- }
- }
(6)、定义 After 增强处理。
After 增强处理与AfterReturning 增强处理有点相似,但也有区别:
-
AfterReturning 增强处理处理只有在目标方法成功完成后才会被织入。
- After 增强处理不管目标方法如何结束(保存成功完成和遇到异常中止两种情况),它都会被织入。
- // 定义一个切面
- @Aspect
- public class AfterAdviceTest {
- // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
- @After ( "execution(* com.wicresoft.app.service.impl.*.*(..))" )
- public void release() {
- System.out.println("模拟方法结束后的释放资源..." );
- }
- }
(7)、Around 增强处理
Around 增强处理近似等于 Before 增强处理和 AfterReturning 增强处理的总和。它可改变执行目标方法的参数值,也可改变目标方法之后的返回值。
- // 定义一个切面
- @Aspect
- public class AroundAdviceTest {
- // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
- @Around ( "execution(* com.wicresoft.app.service.impl.*.*(..))" )
- public Object processTx(ProceedingJoinPoint jp) throws java.lang.Throwable {
- System.out.println("执行目标方法之前,模拟开始事物..." );
- // 执行目标方法,并保存目标方法执行后的返回值
- Object rvt = jp.proceed(new String[]{ "被改变的参数" });
- System.out.println("执行目标方法之前,模拟结束事物..." );
- return rvt + "新增的内容" ;
- }
- }
(8)、访问目标方法的参数。
访问目标方法最简单的做法是定义增强处理方法时将第一个参数定义为 JoinPoint 类型,当该增强处理方法被调用时,该 JoinPoint 参数就代表了织入增强处理的连接点。JoinPoint 里包含了如下几个常用方法。
- Object[] getArgs(): 返回执行目标方法时的参数。
- Signature getSignature(): 返回被增强的方法的相关信息。
- Object getTarget(): 返回被织入增强处理的目标对象。
- Object getThis(): 返回 AOP 框架为目标对象生成的代理对象。
提示 :当时使用 Around 处理时,我们需要将第一个参数定义为 ProceedingJoinPoint 类型,该类型是 JoinPoint 类型的子类 。
(9)、定义切入点。
所谓切入点,其实质就是为一个切入点表达式起一个名称,从而允许在多个增强处理中重用该名称。
Spring 切入点定义包含两个部分:
- 一个切入点表达式。
- 一个包含名字和任意参数的方法签名。
- // 使用@Pointcut Annotation 时指定切入点表达式
- @pointcut ( "execution * transfer(..)" )
- // 使用一个返回值为void,方法体为空的方法来命名切入点
- private void anyOldTransfer(){}
- // 使用上面定义的切入点
- @AfterReturning (pointcut= "anyOldTransfer()" , returning= "reVal" )
- public void writeLog(String msg, Object reVal){
- ...
- }
2、基于 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:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">
- <aop:config>
- <!-- 将 fourAdviceBean 转换成切面 Bean, 切面 Bean 的新名称为:fourAdviceAspect,指定该切面的优先级为2 -->
- <aop:aspect id="fourAdviceAspect" ref= "fourAdviceBean" order= "2" >
- <!-- 定义个After增强处理,直接指定切入点表达式,以切面 Bean 中的 Release() 方法作为增强处理方法 -->
- <aop:after pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method= "release" />
- <!-- 定义个Before增强处理,直接指定切入点表达式,以切面 Bean 中的 authority() 方法作为增强处理方法 -->
- <aop:before pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method= "authority" />
- <!-- 定义个AfterReturning增强处理,直接指定切入点表达式,以切面 Bean 中的 log() 方法作为增强处理方法 -->
- <aop:after-returning pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method= "log" />
- <!-- 定义个Around增强处理,直接指定切入点表达式,以切面 Bean 中的 processTx() 方法作为增强处理方法 -->
- <aop:around pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method= "processTx" />
- </aop:aspect>
- </aop:config>
- <!-- 省略各个Bean 的配置 -->
- <!-- ... -->
- </beans>
- 配置切入点
- <?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
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">
- <aop:config>
- <!-- 定义一个切入点,myPointcut,直接知道它对应的切入点表达式 -->
- <aop:pointcut id="myPointcut" expression= "execution(* com.wicresoft.app.service.impl.*.*(..))" method= "release" />
- <aop:aspect id="afterThrowingAdviceAspect" ref= "afterThrowingAdviceBean" order= "1" >
- <!-- 使用上面定于切入点定义增强处理 -->
- <!-- 定义一个AfterThrowing 增强处理,指定切入点以切面 Bean 中的 doRecovertyActions() 方法作为增强处理方法 -->
- <aop:after-throwing pointcut-ref="myPointcut" method= "doRecovertyActions" throwing= "ex" />
- </aop:aspect>
- </aop:config>
- <!-- 省略各个Bean 的配置 -->
- <!-- ... -->
- </beans>
上一篇: AOP
下一篇: Spring AOP