Spring AOP的介绍与实现
1. AOP的介绍
AOP(Aspect OrientedProgramming)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
在不改变原有的逻辑的基础上,增加一些额外的功能。代理也是这个功能,读写分离也能用aop来做。 AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(crosscutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
2. AOP的相关概念
-
横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
-
Aspect(切面):通常是一个类,里面可以定义切入点和通知
-
JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用。被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
-
Advice(通知):AOP在特定的切入点上执行的增强处理,有before(前置),after(后置),afterReturning(最终),afterThrowing(异常),around(环绕)
-
Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
-
weave(织入):将切面应用到目标对象并导致代理对象创建的过程
-
introduction(引入):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
-
AOP代理(AOPProxy):AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
-
目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO
3. Advice通知类型:
-
(1)Before:
在目标方法被调用之前做增强处理,
@Before只需要指定切入点表达式即可 -
(2)AfterReturning:
在目标方法正常完成后做增强,
@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值 -
(3)AfterThrowing:
主要用来处理程序中未处理的异常,
@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象 -
(4)After:
在目标方法完成之后做增强,无论目标方法时候成功完成。
@After可以指定一个切入点表达式 -
(5)Around:
环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是ProceedingJoinPoint
@Around可以指定一个切入点表达式
4. AOP的实现方式
通过接口来实现
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
/**
* 前置通知:运行目标方法前时,运行的通知;
* 后置通知:运行目标方法后时,运行的通知;
* 异常通知:运行目标方法发生异常时,运行的通知;
* 环绕通知:在环绕通知中可以定义,前置通知、后置通知、异常通知和最终通知,比较全面;
* 最终通知:运行方法后,都会运行的通知;
* <p>
* 通过接口实现通知
* 前置通知:继承MethodBeforeAdvice接口,并重写before()方法;
* 后置通知:继承AfterReturningAdvice接口,并重写afterReturning()方法;
* 异常通知:继承ThrowsAdvice接口,无重写方法;
* 环绕通知:继承MethodInterceptor接口,并重写invoke()方法;
* 最终通知;
*/
public class AspectImpl implements MethodBeforeAdvice, AfterReturningAdvice, AfterAdvice, MethodInterceptor, ThrowsAdvice {
/**
* 前置通知
* @param method 目标方法对象
* @param args 目标方法参数
* @param target 目标方法的类对象
* @throws Throwable
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.printf("前置通知:%s%n参数:%s%n", method.getName(), (Integer) args[0]);
}
/**
* 后置通知
* @param returnValue 目标方法的返回值
* @param method 目标方法对象
* @param args 目标方法参数
* @param target 目标方法的类对象
* @throws Throwable
*/
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.printf("后置通知:%s%n返回值:%s%n%n", method.getName(), returnValue);
}
/**
* 异常通知
* @param method 目标方法对象
* @param args 目标方法参数
* @param target 目标方法的类对象
* @param ex 异常类型
*/
public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
System.out.printf("异常通知:%s%n%n", ex.toString());
}
/**
* 环绕通知 拦截器
* @param invocation 连接点信息
* @return 目标方法执行后的结果
* @throws Throwable
*/
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = null;
try {
System.out.printf("环绕通知-前置通知:%s%n", invocation.getMethod().getName());
result = invocation.proceed();
System.out.printf("环绕通知-后置通知:%s%n", invocation.getMethod().getName());
} catch (Exception ex) {
System.out.printf("环绕通知-异常通知:%s%n", invocation.getMethod().getName());
} finally {
System.out.printf("环绕通知-最终通知:%s%n", invocation.getMethod().getName());
}
return result;
}
}
配置文件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
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="cn.sivan.aop.impl.UserServiceImpl"/>
<bean id="aspectImpl" class="cn.sivan.aop.aspect.AspectImpl"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* cn.sivan.aop.impl.*.*(..))"/>
<aop:advisor advice-ref="aspectImpl" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试
public class UserServiceTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.updateUser(1);
}
}
环绕通知-前置通知:updateUser
前置通知:updateUser
参数:1
updateUser:更新-用户
后置通知:updateUser
返回值:6059
环绕通知-后置通知:updateUser
环绕通知-最终通知:updateUser
通过配置实现通知
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 通过配置实现通知
* before 前置通知
* afterReturning 后置通知
* after 最终通知
* afterThrowing 异常通知
* invoke 环绕通知
* <p>
* JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象
* Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
* Object[] getArgs(); 获取传入目标方法的参数对象
* Object getTarget(); 获取被代理的对象
* Object getThis(); 获取代理对象
*/
public class AspectConfig {
public void before(JoinPoint jp) {
System.out.println("目标方法名为:" + jp.getSignature().getName());
System.out.println("目标方法所属类的简单类名:" + jp.getSignature().getDeclaringType().getSimpleName());
System.out.println("目标方法所属类的类名:" + jp.getSignature().getDeclaringTypeName());
//获取传入目标方法的参数
Object[] args = jp.getArgs();
for (int i = 0; i < args.length; i++) {
System.out.println("第" + (i + 1) + "个参数为:" + args[i]);
}
System.out.println("被代理的对象:" + jp.getTarget());
System.out.println("代理对象自己:" + jp.getThis());
}
public void after(JoinPoint jp) {
System.out.println("最终通知");
}
public void afterReturning(JoinPoint jp, Object returnValue) {
System.out.println("后置通知:" + returnValue);
}
public void afterThrowing(JoinPoint jp, NullPointerException ex) {
System.out.println("异常通知:" + ex.toString());
}
public Object invoke(ProceedingJoinPoint pjp) {
System.out.println("环绕通知");
Object result = null;
try {
result = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
}
配置文件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
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="cn.sivan.aop.impl.UserServiceImpl"/>
<bean id="aspectConfig" class="cn.sivan.aop.aspect.AspectConfig"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* cn.sivan.aop.impl.UserServiceImpl.*(..))"/>
<aop:aspect ref="aspectConfig">
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturning" returning="returnValue" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowing" throwing="ex" pointcut-ref="pointcut"/>
<aop:around method="invoke" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
通过注解实现通知
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 通过注解实现通知
* 前置通知: 注解为 @Before
* 后置通知: 注解为 @AfterReturning
* 异常通知: 注解为 @AfterThrowing
* 环绕通知: 注解为 @Around
* 最终通知: 注解为 @After
* 在applicationContext.xml中开启注解对AOP的支持
* <p>
* 两个注意点:
* @Aspect 声明该类是一个通知;
* @Component("annotationAdvice") 将AnnotationAdvice纳入到SpringIoc容器中。
*/
@Aspect
@Component("aspectAnnotation")
public class AspectAnnotation {
@Before("execution(* cn.sivan.aop.impl.*.*(..))")
public void myBefore(JoinPoint jp) {
System.out.println("前置通知:" + jp.getSignature().getName());
}
@After("execution(* cn.sivan.aop.impl.*.*(..))")
public void myAfter(JoinPoint jp) {
System.out.println("最终通知");
}
@AfterReturning(pointcut = "execution(* cn.sivan.aop.impl.*.*(..))", returning = "returnValue")
public void myAfterReturning(JoinPoint jp, Object returnValue) {
System.out.println("后置通知:" + returnValue);
}
@AfterThrowing(pointcut = "execution(* cn.sivan.aop.impl.*.*(..))", throwing = "ex")
public void myThrows(JoinPoint jp, NullPointerException ex) {
System.out.println("异常通知:" + ex.toString());
}
@Around("execution(* cn.sivan.aop.impl.*.*(..))")
public Object myAround(ProceedingJoinPoint pjp) {
System.out.println("环绕通知");
Object result = null;
try {
result = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
}
配置文件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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解对aop的支持-->
<aop:aspectj-autoproxy/>
<!-- 里面放包的名字,可以放多个包。放在里面之后运行会在相关包中找相关的注解,找到了就将他们纳入到SpringIoc容器中 -->
<context:component-scan base-package="cn.sivan.aop"/>
</beans>
常见使用场景
| Advice | |
|---|---|
| 环绕通知 | 控制事务 权限控制 |
| 后置通知 | 记录日志(方法已经成功调用) |
| 异常通知 | 异常处理 控制事务 |
| 最终通知 | 记录日志(方法已经调用,但不一定成功) |
指定每个 aspect 的执行顺序
- 实现org.springframework.core.Ordered接口,实现它的getOrder()方法
- 给aspect添加@Order注解,该注解全称为:org.springframework.core.annotation.Order
不管采用上面的哪种方法,都是值越小的 aspect 越先执行。
比如,我们为 apsect1 和 aspect2 分别添加 @Order 注解,如下:
@Order(5)
@Component
@Aspect
public class Aspect1 {
// ...
}
@Order(6)
@Component
@Aspect
public class Aspect2 {
// ...
}
这样修改之后,可保证不管在任何情况下, aspect1 中的 advice 总是比 aspect2 中的 advice 先执行.

浙公网安备 33010602011771号