欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring in action 4 剑指Spring - (四)面向切面的Spring

程序员文章站 2024-02-15 21:51:59
...

面向切面的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方法之前,会执行切面逻辑。

Spring in action 4 剑指Spring - (四)面向切面的Spring

通过切点选择连接点:

切入点表达式解释 :

Spring in action 4 剑指Spring - (四)面向切面的Spring

编写切点:

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 //传入的新的参数去执行目标方法