Spring in action 4 剑指Spring - (四)面向切面的Spring
面向切面的Spring
aop: Aspect oriented Programming 面向切面编程,面向切面编程是面向对象编程的补充,而不是替代品。在运行时,动态地将代码切入到类的指定方法,指定位置上的编程思想就是面向切面编程。
Aop中的专业术语:
- 1.通知(Advice):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
- before advice, 在 join point前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
- after return advice, 在一个 join point 正常返回后执行的 advice
- after throwing advice, 当一个 join point 抛出异常后执行的 advice
- after(final) advice, 无论一个 join point是正常退出还是发生了异常, 都会被执行的 advice.
- around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.
- introduction,introduction可以为原有的对象增加新的属性和方法。
- 2.连接点(JoinPoint): 连接点就是在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。这个点可以是一个方法、一个属性、构造函数、类静态初始化块,甚至一条语句。 而对于 Spring 2.0 AOP 来说,连接点只能是方法。每一个方法都可以看成为一个连接点。
- 3.切入点(Pointcut):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
- 4.切面(Aspect):切面是通知和切入点的结合,通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
- 5.引入(introduction): 引入是指给一个现有类添加方法或字段属性,引入还可以在不改变现有类代码的情况下,让现有的 Java 类实现新的接口 (以及一个对应的实现 )。相对于 Advice 可以动态改变程序的功能或流程来说,引介 (Introduction) 则用来改变一个类的静态结构 。
- 6.目标(target):是要被通知(Advice)的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
- 7.代理(proxy):实现整套aop机制的,都是通过代理。
- 8.织入(weaving):把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时,为什么是运行时,后面解释。
Spring对Aop的支持:
Spring提供了4种类型的AOP支持:
- 基于代理的经典Spring AOP;
- 纯POJO切面;
- @AspectJ注解驱动的切面;
- 注入式AspectJ切面(适用于Spring各版本)。
前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。
Spring在运行时通知对象:
通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中,如图所示,代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。
通过切点选择连接点:
切入点表达式解释 :
编写切点:
execution(* chendongdong.bean.phone.sleep(…)) = 任何返回值的phone类下的sleep方法
再切点中选择bean:
execution(* chendongdong.bean.phone.sleep(…) and bean(‘persion’)) = 任何返回值的phone类下的sleep方法,但是指定bean的ID为persion的对象生效。
使用注解创建切面:
AspectJ注解
注解 | 通知 |
---|---|
@After | 通知方法会在目标方法返回或抛出异常后调用 |
@AfterReturning | 通知方法会在目标方法返回后调用 |
@AfterThrowing | 通知方法会在目标方法抛出异常后调用 |
@Around(ProceedingJoinPoint joinPoint) | 通知方法会将目标方法包裹起来 |
@Before | 通知方法会再目标方法调用之前执行 |
正常情况:
接口:
public interface ElectronicA {
void sleep();
}
实现类:
//手机
@Component
@Primary
public class PhoneA implements ElectronicA {
public void play(){
System.out.println("phone");
}
@Override
public void sleep() {
System.out.println("phone sleep");
}
}
//电池
@Component
public class BatteryA implements ElectronicA {
public void play(){
System.out.println("battery");
}
@Override
public void sleep() {
System.out.println("battery sleep");
}
}
切面:
@Aspect
@Component
public class eleAspect {
//切点
@Pointcut("execution(* *.*(..))")
public void pointCut(){}
@Before(value = "pointCut()")
public void beforeEle(){
System.out.println("==== 前置 ====");
}
@After(value = "pointCut()")
public void afterEle(){
System.out.println("==== 后置 ====");
}
@Around(value = "pointCut()")
public Object aroundEle(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("==== 前环绕 ====");
Object s = joinPoint.proceed();
System.out.println("==== 后环绕 ====");
return s;
}
@AfterReturning(value = "pointCut()" , returning = "result")
public void returningEle(JoinPoint joinPoint, Object result){
System.out.println(result);
}
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void refund(JoinPoint joinPoint, Exception exception){
System.out.println(exception.getMessage());
}
}
配置类:
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "chendongdong.spring.test.bean.test4")
public class Test4config {
}
测试类:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Test4config.class);
ElectronicA bean = context.getBean(ElectronicA.class);
bean.sleep();
测试结果:
==== 前环绕 ====
==== 前置 ====
phone sleep
==== 后环绕 ====
==== 后置 ====
返回值 = phone sleep
异常情况:
手机实现类增加 int i = 1 / 0;
//手机
@Component
@Primary
public class PhoneA implements ElectronicA {
public void play(){
System.out.println("phone");
}
@Override
public void sleep() {
int i = 1 / 0;
System.out.println("phone sleep");
}
}
测试结果:
==== 前环绕 ====
==== 前置 ====
==== 后置 ====
异常 = / by zero
java.lang.ArithmeticException: / by zero
正常情况下,不会执行异常通知(AfterTrowing)。
异常情况下,不会执行环绕通知目标方法后的代码(Around after),也不会执行返回通知(AfterReturning)。
使用xml创建切面:
xml注解元素
Aop配置元素 | 用途 |
---|---|
aop:advisor | 定义Aop通知器 |
aop:after | 定义Aop后置通知(不管被通知的方法是否成功执行) |
aop:after-returning | 定义Aop返回通知 |
aop:after-throwing | 定义Aop异常通知 |
aop:around | 定义Aop环绕通知 |
aop:aspect | 定义一个切面 |
aop:aspectj-autopoxy | 启用@Aspect注解驱动的切面 |
aop:before | 定义Aop前置通知 |
aop:config | 顶层的Aop配置元素,大多数的aop:*元素必须包含在元素类 |
aop:pointcut | 定义一个切点 |
正常情况:
接口:
public interface ElectronicA {
void sleep();
}
实现类:
//手机
@Component
@Primary
public class PhoneA implements ElectronicA {
public void play(){
System.out.println("phone");
}
@Override
public void sleep() {
System.out.println("phone sleep");
}
}
//电池
@Component
public class BatteryA implements ElectronicA {
public void play(){
System.out.println("battery");
}
@Override
public void sleep() {
System.out.println("battery sleep");
}
}
切面:
public class XmlLoggerAspectA {
public void before(){
System.out.println("--->前置");
}
public void after(){
System.out.println("--->后置");
}
public void afterReturning(Object returnVal){
System.out.println("--->返回值 : " + returnVal);
}
public void afterThrowing(Exception exception){
System.out.println("--->异常:"+exception.getMessage());
}
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("--->环绕 前置");
Object proceed = joinPoint.proceed();
System.out.println("--->环绕 后置");
return proceed;
}
}
配置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" xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="phone" class="chendongdong.spring.test.bean.test4.PhoneA"></bean>
<bean id="battery" class="chendongdong.spring.test.bean.test4.BatteryA" primary="true"></bean>
<bean id="logger" class="chendongdong.spring.test.bean.test4.XmlLoggerAspectA"></bean>
<aop:config>
<aop:aspect ref="logger">
<aop:pointcut id="pointCut" expression="execution(* *.*(..))"/>
<aop:after method="after" pointcut-ref="pointCut"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointCut" returning="returnVal"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="exception"/>
<aop:before method="before" pointcut-ref="pointCut"/>
<aop:around method="around" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
</beans>
测试类:
ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("test4.xml");
ElectronicA bean1 = context1.getBean(ElectronicA.class);
bean1.sleep(4);
测试结果:
--->前置
--->环绕 前置
battery sleep
--->后置
--->返回值 : battery sleep
--->环绕 后置
异常情况:
电池实现类增加 int i = 1 / 0;
//电池
@Component
public class BatteryA implements ElectronicA {
public void play(){
System.out.println("battery");
}
@Override
public String sleep(int num) {
int i = 1 / 0;
System.out.println("battery sleep");
return "battery sleep";
}
}
测试结果:
--->前置
--->环绕 前置
--->后置
--->异常:/ by zero
java.lang.ArithmeticException: / by zero
JoinPoint 对象:
JoinPoint
JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法的JoinPoint对象。
常用API:
方法名 | 功能 |
---|---|
Signature getSignature() | 获取封装了署名信息的对象,在该对象中可以获取目标方法的方法名,所属类的Class等信息。 |
Object[] getArgs() | 获取传入目标方法的参数对象 |
Object[] getTarget() | 获取被代理的对象 |
Object[] getThis() | 获取代理对象 |
ProceedingJoinPoint
ProceedingJoinPoint 对象是JoinPoint的子接口,该对象只用在@Around的切面方法中,添加了两个方法:
Object proceed() trows Trowable //执行目标方法
Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法