一、AOP概念
1. 什么是AOP?
- AOP:Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
- 主要功能是:日志记录、性能统计、安全控制、事务处理、异常处理等等。
2. AOP实现方式
- 预编译
-AspectJ - 运行期动态代理(JDK动态代理,CGLib动态代理)
-Spring AOP、Jboss AOP
3. AOP相关概念
| 名称| 说明 |
| ------------- |:-------------:| -----:|
| 切面(Aspect) |一个关注点的模块化,这个关注点可能会横切多个对象 |
| 连接点(Joinpoint) |程序执行过程中的某个特定的点|
| 通知(Advice) |在切面的某个特定的连接点上执行的动作|
| 切入点(PointCut) |匹配连接点的断言,在AOP中通知和一个切入点表达式关联|
| 引入(Introduction) |在不修改类代码的前提下,为类添加新的方法和属性|
| 目标对象(Target Object) |被一个或者多个切面所通知的对象|
| AOP代理(AOP proxy) |AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)|
| 织入(Weaving) |把切面连接到其他的应用程序类型或者对象上,并创建一个被通知的对象,分为:编译时织入、类加载时织入、执行时织入|
4. Advice的类型
| 名称| 说明 |
| ------------- |:-------------:| -----:|
| 前置通知(Before advice)|在某连接点(join point)之前执行的通知,但不能阻止连接点前的执行(除非它抛出一个异常】)|
| 返回后通知(After returning advice)|在某连接点(join point)正常完成后执行的通知|
| 抛出异常后通知(After throwing advice)|在方法抛出异常退出时执行的通知|
| 后通知(After(finally) advice)|当某连接点退出的时候执行的通知(不论时正常返回还是异常退出)|
| 环绕通知(Around advice)|包围一个连接点(join point)的通知|
5. Spring框架中AOP的用途
- 提供了声明式的企业服务,特别是EJB替代服务的声明
- 允许用户定制自己的方面,以完成AOP和OOP的互补使用
6. Spring的AOP实现
- 纯java实现,无需特殊的编译过程,不需要控制类加载器层次
- 目前只支持方法执行连接点(通过Spring Bean的方法执行)
- 不是为了提供最完整的AOP实现(尽管它非常强大);而是侧重于提供一种AOP实现和Spring IoC容器之间的整合,用于帮助企业应用中的常见问题。
- Spring AOP不会与AspectJ竞争,从而提供综合全面的AOP解决方案。
7. 有接口和无接口的Spring AOP实现区别
- 业务对象有接口, Spring AOP 默认使用标准的JavaSE动态代理作为AOP代理,这使得任何接口(或者接口集)都可以被代理。
- 业务对象没有接口,可以使用Spring AOP中CGLIB代理
二、配置切面aspect
1. 基于Schema-based的AOP实现
- Spring所有切面和通知器都必须放在<aop:config>内(可以配置包含多个<aop:config>元素),每一个<aop:config>可以包含pointcut、adviser和aspect元素(他们必须按照这个顺序进行声明)。
- <aop:config>风格的配置大量使用了Spring的自动代理机制。
示例
- 创建切面类MoocAspect :
package test14;
/**
* Created by amber on 2017/6/18.
* 切面类
*/
public class MoocAspect {
}
- 创建目标类
package test14;
/**
* Created by amber on 2017/6/18.
*/
public class AspectBiz {
}
- ApplicationContext中配置:
在<beans>中声明命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-context.xsd"
<aop:config>
<aop:aspect id="myAspect" ref="moocAspect"></aop:aspect>
</aop:config>
<bean id="aspectBiz" class="test14.AspectBiz"></bean>
<bean id="moocAspect" class="test14.MoocAspect"/>
2、配置切入点Pointcut
- 在AOP中通知(Advice)和一个切入点表达式关联。
execution(public **(..))
切入点为执行所有public方法时
execution(* set*(..))
切入点为执行所有set开始的方法时
execution(* com.xyz.service.AccountService.*(..))
切入点为执行AccountService类中所有方法时
execution(* com.xyz.service..(..))
切入点为执行 com.xyz.service包下所有方法时
execution(* com.xyz.service...(..))
切入点为执行 com.xyz.service包下及其子包下所有方法时
within(com.xyz.service.*) (仅SpringAOP)
within(com.xyz.service..*) (仅SpringAOP)
within用于匹配指定类型内的方法执行
this(com.xyz.service.AccountService) (仅SpringAOP)
this用于匹配当前AOP代理对象类型的执行方法
target(com.xyz.service.AccountService) (仅SpringAOP)
target用于匹配当前目标对象类型的执行方法
args(java.io.Serializable) (仅SpringAOP)
args用于匹配当前执行的方法传入的参数为指定类型的执行方法
@target(org.springframework.transacation.annotation.Transcactional) (仅SpringAOP)
@within(org.springframework.transacation.annotation.Transcactional) (仅SpringAOP)
@annotation(org.springframework.transacation.annotation.Transcactional) (仅SpringAOP)
按照注解类型匹配
@args(com.xyz.security.Classified)(仅SpringAOP)
bean(tradeService)(仅SpringAOP)
bean(*Service)(仅SpringAOP)
配置切入点:
<aop:config>
<aop:aspect id="myAspect" ref="moocAspect">
<aop:pointcut id="moocPointCut" expression="execution(* test14.*Biz(..))"/>
</aop:aspect>
</aop:config>
三、advice
** Before Advice(前置通知)**
在写测试之前呢,要现在下载好需要的jar包。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
- 创建了业务类AspectBiz:
package test14;
/**
* Created by amber on 2017/6/18.
* 业务类
*/
public class AspectBiz {
public void biz(){
System.out.println("AspectBiz biz.");
}
}
- 创建切面类MoocAspect:
package test14;
/**
* Created by amber on 2017/6/18.
* 切面类
*/
public class MoocAspect {
public void before(){
System.out.println("MoocAspect 前置通知,before");
}
}
- applicationContext中:
<bean id="aspectBiz" class="test14.AspectBiz"></bean>
<bean id="moocAspect" class="test14.MoocAspect"/>
<aop:config>
<aop:aspect id="moocAspectAop" ref="moocAspect">
<aop:pointcut id="moocPointCut" expression="execution(* test14.*Biz.*(..))"/>
<aop:before method="before" pointcut-ref="moocPointCut"/>
</aop:aspect>
</aop:config>
- 测试类:
@Test
public void test14() {
AspectBiz aspectBiz=super.getBean("aspectBiz");
aspectBiz.biz();
}
-
结果:
Paste_Image.png
** After returning advice(返回后通知)**
- 更新MoocAspect类:
package test14;
/**
* Created by amber on 2017/6/18.
* 切面类
*/
public class MoocAspect {
public void before(){
System.out.println("MoocAspect 前置通知,before");
}
public void afterReturning(){
System.out.println("MoocAspect 返回后通知,afterReturning");
}
}
- 更新applicationContext:
<aop:config>
<aop:aspect id="moocAspectAop" ref="moocAspect">
<aop:pointcut id="moocPointCut" expression="execution(* test14.*Biz.*(..))"/>
<aop:before method="before" pointcut-ref="moocPointCut"/>
<aop:after-returning method="afterReturning" pointcut-ref="moocPointCut"/>
</aop:aspect>
</aop:config>
-
结果:
Paste_Image.png
** After throwing advice(抛出异常后通知)**
注:可使用throwing属性指定可被传递的异常参数名称
- 修改业务类AspectBiz:
package test14;
/**
* Created by amber on 2017/6/18.
* 业务类
*/
public class AspectBiz {
public void biz(){
System.out.println("AspectBiz biz.");
throw new RuntimeException();
}
}
- 更新切面类MoocAspect :
package test14;
/**
* Created by amber on 2017/6/18.
* 切面类
*/
public class MoocAspect {
public void before(){
System.out.println("MoocAspect 前置通知,before");
}
public void afterReturning(){
System.out.println("MoocAspect 返回后通知,afterReturning");
}
public void afterThrowing(){
System.out.println("MoocAspect 抛出异常后通知,afterThrowing");
}
}
- 更新applicationContext:
<aop:config>
<aop:aspect id="moocAspectAop" ref="moocAspect">
<aop:pointcut id="moocPointCut" expression="execution(* test14.*Biz.*(..))"/>
<aop:before method="before" pointcut-ref="moocPointCut"/>
<aop:after-returning method="afterReturning" pointcut-ref="moocPointCut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="moocPointCut"/>
</aop:aspect>
</aop:config>
-
结果:
Paste_Image.png
** After(finally) advice(后通知)**
- 更新切面类MoocAspect:
package test14;
/**
* Created by amber on 2017/6/18.
* 切面类
*/
public class MoocAspect {
public void before(){
System.out.println("MoocAspect 前置通知,before");
}
public void afterReturning(){
System.out.println("MoocAspect 返回后通知,afterReturning");
}
public void afterThrowing(){
System.out.println("MoocAspect 抛出异常后通知,afterThrowing");
}
public void after(){
System.out.println("MoocAspect 后通知,after");
}
}
- 更新applicationContext:
<aop:config>
<aop:aspect id="moocAspectAop" ref="moocAspect">
<aop:pointcut id="moocPointCut" expression="execution(* test14.*Biz.*(..))"/>
<aop:before method="before" pointcut-ref="moocPointCut"/>
<aop:after-returning method="afterReturning" pointcut-ref="moocPointCut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="moocPointCut"/>
<aop:after method="after" pointcut-ref="moocPointCut"/>
</aop:aspect>
</aop:config>
- 结果:
** Around advice(环绕通知)**
注:通知方法的必须有参数,且第一个参数必须是ProceedingJoinPoint类型。
- 修改AspectBiz 业务类,讲抛出异常代码删掉。
package test14;
/**
* Created by amber on 2017/6/18.
* 业务类
*/
public class AspectBiz {
public void biz(){
System.out.println("AspectBiz biz.");
}
}
- 更新MoocAspect类:
package test14;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* Created by amber on 2017/6/18.
* 切面类
*/
public class MoocAspect {
public void before(){
System.out.println("MoocAspect 前置通知,before");
}
public void afterReturning(){
System.out.println("MoocAspect 返回后通知,afterReturning");
}
public void afterThrowing(){
System.out.println("MoocAspect 抛出异常后通知,afterThrowing");
}
public void after(){
System.out.println("MoocAspect 后通知,after");
}
public Object around(ProceedingJoinPoint pjp){
Object obj= null;
try {
System.out.println("MoocAspect 环绕通知,around1");
obj = pjp.proceed();
System.out.println("MoocAspect 环绕通知,around2");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return obj;
}
}
- applicationContext:
<aop:config>
<aop:aspect id="moocAspectAop" ref="moocAspect">
<aop:pointcut id="moocPointCut" expression="execution(* test14.*Biz.*(..))"/>
<aop:before method="before" pointcut-ref="moocPointCut"/>
<aop:after-returning method="afterReturning" pointcut-ref="moocPointCut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="moocPointCut"/>
<aop:after method="after" pointcut-ref="moocPointCut"/>
<aop:around method="around" pointcut-ref="moocPointCut"/>
</aop:aspect>
</aop:config>
- 结果:
** Advice parameters(通知参数)**
- 更新AspectBiz 业务类,增加一个带参的方法,init。
package test14;
import test12.StringStore;
/**
* Created by amber on 2017/6/18.
* 业务类
*/
public class AspectBiz {
public void biz(){
System.out.println("AspectBiz biz.");
}
public void init(String bizName, int times){
System.out.println("AspectBiz init: "+bizName+" "+times);
}
}
- 更新MoocAspect 切面类,增加带参数环绕通知方法aroundInit
package test14;
import org.aspectj.lang.ProceedingJoinPoint;
import test12.StringStore;
/**
* Created by amber on 2017/6/18.
* 切面类
*/
public class MoocAspect {
public void before(){
System.out.println("MoocAspect 前置通知,before");
}
public void afterReturning(){
System.out.println("MoocAspect 返回后通知,afterReturning");
}
public void afterThrowing(){
System.out.println("MoocAspect 抛出异常后通知,afterThrowing");
}
public void after(){
System.out.println("MoocAspect 后通知,after");
}
public Object around(ProceedingJoinPoint pjp){
Object obj= null;
try {
obj = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return obj;
}
public Object aroundInit(ProceedingJoinPoint pjp, String bizName,int times){
System.out.println("MoocAspect 环绕通知,bizName: "+bizName+" times: "+times);
Object obj= null;
try {
System.out.println("MoocAspect 环绕通知,around1");
obj = pjp.proceed();
System.out.println("MoocAspect 环绕通知,around2");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return obj;
}
}
- appclicationContext:
<aop:config>
<aop:aspect id="moocAspectAop" ref="moocAspect">
<aop:pointcut id="moocPointCut" expression="execution(* test14.*Biz.*(..))"/>
<aop:before method="before" pointcut-ref="moocPointCut"/>
<aop:after-returning method="afterReturning" pointcut-ref="moocPointCut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="moocPointCut"/>
<aop:after method="after" pointcut-ref="moocPointCut"/>
<aop:around method="around" pointcut-ref="moocPointCut"/>
<aop:around method="aroundInit" pointcut="execution(* test14.AspectBiz.init(String,int))
and args(bizName,times)"/>
</aop:aspect>
</aop:config>
- 测试类:
@Test
public void test14() {
AspectBiz aspectBiz=super.getBean("aspectBiz");
aspectBiz.init("advice parameters",1);
}
-
结果:
Paste_Image.png
四、Introductions
- Introductions允许一个切面声明一个实现指定接口的通知对象,并且提供了一个接口实现类来代表这些对象。
- 由<aop:aspect>中的<aop:declare-parents>元素声明,该元素用于声明所匹配的类型拥有一个新的parent(因此得名)
- 新增接口Fit:
package test15;
/**
* Created by amber on 2017/6/18.
*/
public interface Fit {
void filter();
}
- 新增实现类FitImpl :
package test15;
/**
* Created by amber on 2017/6/18.
*/
public class FitImpl implements Fit {
public void filter() {
System.out.print("FitImpl filter");
}
}
- 更新applicationContext:
<bean id="aspectBiz" class="test14.AspectBiz"></bean>
<bean id="moocAspect" class="test14.MoocAspect"/>
<aop:config>
<aop:aspect id="moocAspectAop" ref="moocAspect">
<aop:declare-parents types-matching="test14.*+"
implement-interface="test15.Fit"
default-impl="test15.FitImpl"/>
</aop:aspect>
</aop:config>
在<aop:declare-parents>标签中,types-matching是test14包下的所有类,implement-interface是Fit接口。
default-impl是默认的实现类FitImpl。
在测试类中 Fit fit=super.getBean("aspectBiz");
意思是给AspectBiz类增加了一个父类FitImpl 。
上面“提供了一个接口实现类来代表这些对象”,即用实现Fit接口的FitImpl类代表AspectBiz对象,
- 测试类:
@Test
public void test15() {
Fit fit=super.getBean("aspectBiz");
fit.filter();
}
Paste_Image.png
最后注意:所有基于配置文件的Aspect,只支持单例模式。
五、Advisors
- Advisors就像一个小的自包含的方面,只有一个advice。
- 切面自身通过一个bean表示,并且必须实现某个advice接口,同时,Advisor也可以很好的利用AspectJ的切入点表达式。
- Spring通过配置文件中的<aop:advisor>元素支持advisor。实际使用中,大多数情况下它会和transactional advice配合使用。
- 为了定义一个advisor的优先级以便让advice可以有序,可以使用order属性来定义advisor的顺序。
示例:
创建一个实现某个Advice的切面类:
package com.myspring.app.aop;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.springframework.aop.MethodBeforeAdvice;
/**
* 方法前置通知
* @author Michael
*/
@Component//如果是自动装配,在定义切面的时候直接写在ref属性里就可以了
public class MyAdvice implements MethodBeforeAdvice{
//如果使用aop:advisor配置,那么切面逻辑必须要实现advice接口才行!否则会失败!
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知");
}
//如果是<aop:aspect>配置,编写一般的方法就可以了,然后在切面配置中指定具体的方法名称!
public void doBefore(JoinPoint point) throws Throwable {
}
}
applicationContext(重点!!可以和<aop:aspect>对比着看):
<aop:config>
<aop:pointcut expression="(execution(* com.myspring.app.aop.TestPoint.*(..)))" id="mypoint"/>
<aop:advisor advice-ref="myAdvice " pointcut-ref="mypoint"/>
</aop:config>
六、验证可以通过切面控制目标执行次数的示例:
创建ConrrentOperationExecutor 切面类。
package schema.advisors;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.dao.PessimisticLockingFailureException;
import test12.StringStore;
/**
* Created by amber on 2017/6/18.
*/
public class ConrrentOperationExecutor {
private int maxRetries;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public Object doCurrentOperation(ProceedingJoinPoint pjp)throws Throwable{
int numAttempts=0;
PessimisticLockingFailureException lockingFailureException;
do{
numAttempts++;
System.out.println("Try times : "+numAttempts);
try {
return pjp.proceed();
} catch (PessimisticLockingFailureException throwable) {
lockingFailureException=throwable;
}
}while (numAttempts<this.maxRetries);
System.out.println("Try error : "+numAttempts+"\n");
throw lockingFailureException;
}
}
创建InvokeService业务类:
package schema.advisors.service;
import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.stereotype.Service;
/**
* Created by amber on 2017/6/18.
*/
@Service
public class InvokeService {
public void invoke() {
System.out.print("InvokeService invoke...");
}
public void invokeException() {
throw new PessimisticLockingFailureException("");
}
}
在ApplicationContext:
<context:component-scan base-package="schema"></context:component-scan>
<aop:config>
<aop:aspect id="conrrentOperationRetry" ref="conrrentOperationExecutor">
<aop:pointcut id="idempotentOperation" expression="execution(* schema.advisors.service.*.*(..))"/>
<aop:around method="doCurrentOperation" pointcut-ref="idempotentOperation"/>
</aop:aspect>
</aop:config>
<bean id="conrrentOperationExecutor" class="schema.advisors.ConrrentOperationExecutor">
<property name="maxRetries" value="3"/>
</bean>
测试方法:
@Test
public void schema() {
InvokeService service=super.getBean("invokeService");
service.invoke();
System.out.println();
service.invokeException();
}
结果:
Paste_Image.png
网友评论