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

art

通过切点选择连接点:

切入点表达式解释 :

编写切点:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值