SpringAction学习二、高级装配:面向切面的Spring

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MASORL/article/details/82668606

Spring提供了4种类型的AOP支持

(1)基于代理的经典Spring AOP; 书中介绍说经典比较笨重和复杂,所以不介绍,有兴趣以后可以看看
(2)纯POJO切面;
(3)@AspectJ注解驱动的切面;
(4)注入式AspectJ切面(适用于Spring各版本)

AOP的术语

通知:切面的工作被称为通知

连接点:连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。

切点:定义了哪些连接点会得到通知,有助于缩小切面所通知的连接点的范围,定义了通知在何处工作

切面:通知和切点共同定义了切面的全部内容----它是什么,在何时何处完成其功能

织入:把切面应用到目标对象并创建新的代理对象的过程。一共有3个时期可以织入

  1. 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
  2. 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-timeweaving,LTW)就支持以这种方式织入切面。
  3. 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的

重点为加粗的时期。这是我们所用到的方式!!!!换图片来表示的话就是:代理类会拦截目标对象的方法,执行通知。然后再把调用转发给目标对象。

稍微了解了Spring aop,接下来开始编写

(1)切点

(2)通知

编写切点

关于切点,我们必须使用Aspectj的切点表达式语言来定义

Spring AOP所支持的AspectJ切点指示器
AspectJ指示器 描 述
arg() 限制连接点匹配参数为指定类型的执行方法
@args() 限制连接点匹配参数由指定注解标注的执行方法
execution() 用于匹配是连接点的执行方法
this()  限制连接点匹配AOP代理的bean引用为指定类型的类
target 限制连接点匹配目标对象为指定类型的类
@target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类
型的注解
within()  限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方
法定义在由指定的注解所标注的类里)
@annotation 限定匹配带有指定注解的连接点

截图提看一下切点怎么编写

切点编完了,接下来声明通知

声明通知

通知一共有5种,AspectJ提供了五个注解来定义通知

AspectJ提的五个注解来定义通知
注 解 通 知
@After 通知方法会在目标方法返回或抛出异常后调用
@AfterReturning 通知方法会在目标方法返回后调用
@AfterThrowing 通知方法会在目标方法抛出异常后调用
@Around 通知方法会将目标方法封装起来
@Before 通知方法会在目标方法调用之前执行

示例代码

接口:该接口的aspectStart将会被切面拦截

package com.spring.chapter4.springaop;

public interface BeNotified {
    void aspectStart();
}

接口实现

package com.spring.chapter4.springaop;

import org.springframework.stereotype.Component;

@Component
public class BeNotifiedImpl implements BeNotified {
    public void aspectStart() {
        System.out.println("主体方法");
    }
}

配置类

@EnableAspectJAtuoProxy:中启用AspectJ注解的自动代理

package com.spring.chapter4.springaop;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AspectConfig {
}

切面类:

@Aspect定义切面类,并要为这个切面类创建bean

package com.spring.chapter4.springaop;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

@Component
@Aspect

public class AspectClass {
    @Before("execution(* com.spring.chapter4.springaop.BeNotified.aspectStart(..))")
    public void beforeNotice(){
        System.out.println("主体方法运行之前");
    }
    @AfterReturning("execution(* com.spring.chapter4.springaop.BeNotified.aspectStart(..))")
    public void fterReturningNotice(){
        System.out.println("主体方法运行之后");
    }
    @AfterThrowing("execution(* com.spring.chapter4.springaop.BeNotified.aspectStart(..))")
    public void aafterThrowingNotice(){
        System.out.println("主体方法运行抛出一异常后");
    }

}

测试:

package com.spring.chapter4.springaop;

import org.aspectj.lang.annotation.AfterThrowing;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AspectConfig.class)
public class AspectTest {
    @Autowired
    private BeNotified beNotified;

    @Test
    public void aspectTest(){
        beNotified.aspectStart();
    }
}

测试结果

主体方法运行之前
主体方法
主体方法运行之后

修改实现类,使其抛出一个异常

package com.spring.chapter4.springaop;

import org.springframework.stereotype.Component;

@Component
public class BeNotifiedImpl implements BeNotified {
    public void aspectStart() {
        System.out.println("主体方法");
        throw new RuntimeException();
    }
}

测试结果

WARNING: All illegal access operations will be denied in a future release
主体方法运行之前
主体方法
主体方法运行抛出一异常后

java.lang.RuntimeException
	at com.spring.chapter4.springaop.BeNotifiedImpl.aspectStart(BeNotifiedImpl.java:9)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)

XML方式:

接口:

package com.spring.chapter4.xmlaspect;

public interface BeNotified {
    void aspectStart();
    void aspectStart(int b);
}

实现

package com.spring.chapter4.xmlaspect;

import org.springframework.stereotype.Component;

public class BeNotifiedImpl implements BeNotified {
    public void aspectStart() {
        System.out.println("xmlaspect+主体方法");
    }
    public void aspectStart(int b) {

    }
}

切面类

package com.spring.chapter4.xmlaspect;

public class AspectClass {
    public void beforeNotice(){
        System.out.println("xmlaspect:主体方法运行之前");
    }
    public void fterReturningNotice(){
        System.out.println("xmlaspect:主体方法运行之后");
    }
    public void aafterThrowingNotice(){
        System.out.println("xmlaspect:主体方法运行抛出一异常后");
    }

}

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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="beNotified" class="com.spring.chapter4.xmlaspect.BeNotifiedImpl"/>
    <bean id="aspectClass" class="com.spring.chapter4.xmlaspect.AspectClass"/>
    <aop:config>
       <aop:aspect ref="aspectClass">
           <aop:before
                   pointcut="execution(* com.spring.chapter4.xmlaspect.BeNotified.aspectStart(..))"
                   method="beforeNotice"/>
           <aop:after-returning
                   pointcut="execution(* com.spring.chapter4.xmlaspect.BeNotified.aspectStart(..))"
                   method="fterReturningNotice"/>
           <aop:after-throwing
                   pointcut="execution(* com.spring.chapter4.xmlaspect.BeNotified.aspectStart(..))"
                   method="aafterThrowingNotice"/>
       </aop:aspect>
    </aop:config>
</beans>

补充点一:使用@Pointcut注解声明频繁使用的切点表达式

通过@Pointcut把切点表达式赋值到一个方法种,其他注解通过引用这个方法就可以引用通用的表达式

package com.spring.chapter4.springaop;

import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

@Component
@Aspect
@EnableAspectJAutoProxy
public class AspectClass2 {
    @Pointcut("execution(* com.spring.chapter4.springaop.BeNotified.aspectStart(..))")
    public void aspectStart(){}

    @Before("aspectStart()")
    public void beforeNotice(){
        System.out.println("主体方法运行之前2");
    }

    @AfterReturning("aspectStart()")
    public void fterReturningNotice(){
        System.out.println("主体方法运行之后2");
    }

    @AfterThrowing("aspectStart()")
    public void aafterThrowingNotice(){
        System.out.println("主体方法运行抛出一异常后2");
    }

}

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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="beNotified" class="com.spring.chapter4.xmlaspect.BeNotifiedImpl"/>
    <bean id="aspectClass" class="com.spring.chapter4.xmlaspect.AspectClass"/>
    <aop:config>
       <aop:aspect ref="aspectClass">
           <aop:pointcut
                   id="aspectStart"
                   expression="execution(* com.spring.chapter4.xmlaspect.BeNotified.aspectStart(..))"/>
           <aop:before
                   pointcut-ref="aspectStart"
                   method="beforeNotice"/>
           <aop:after-returning
                   pointcut-ref="aspectStart"
                   method="fterReturningNotice"/>
           <aop:after-throwing
                   pointcut-ref="aspectStart"
                   method="aafterThrowingNotice"/>
       </aop:aspect>
    </aop:config>
</beans>

补充点二:环绕通知

@Around相当于所有通知的集合

必须要有joinPoint.proceed(),将控制权转交到被通知方,不然会发生堵塞

package com.spring.chapter4.springaop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AspectClass3 {
    @Pointcut("execution(* com.spring.chapter4.springaop.BeNotified.aspectStart(..))")
    public void aspectStart(){}

    @Around("aspectStart()")
    public void around(ProceedingJoinPoint joinPoint){
        try {
            System.out.println("主体方法运行之前3");
            joinPoint.proceed();//控制器转让,需要捕获Throable异常
            System.out.println("主体方法运行之后3");
        }catch (Throwable e){
            System.out.println("主体方法运行抛出一异常后2");
        }
    }

}

xml方式:

切面类:无注解

package com.spring.chapter4.xmlaspect;

import org.aspectj.lang.ProceedingJoinPoint;

public class AspectAroundClass {
    public void aroundAspect(ProceedingJoinPoint proceedingJoinPoint){
        try{
            System.out.println("xmlaspect:主体方法运行之前");
            proceedingJoinPoint.proceed();
            System.out.println("xmlaspect:主体方法运行之后");
        }catch (Throwable e){
            System.out.println("xmlaspect:主体方法运行抛出一异常后");
        }
    }
}

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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="beNotified" class="com.spring.chapter4.xmlaspect.BeNotifiedImpl"/>
    <bean id="aspectClass" class="com.spring.chapter4.xmlaspect.AspectAroundClass"/>
    <aop:config>
       <aop:aspect ref="aspectClass">
           <aop:pointcut
                   id="aspectStart"
                   expression="execution(* com.spring.chapter4.xmlaspect.BeNotified.aspectStart(..))"/>
          <aop:around
              pointcut-ref="aspectStart"
              method="aroundAspect"/>
       </aop:aspect>
    </aop:config>
</beans>

补充三:切点的限制匹配

(1)within,限定类型

*.aspectStart(..)表示任何的aspectStart(..)方法都可以成为切点

exection最小粒度是方法

within最小粒度为类级别,通过BeNotfiedImpl 限定aspectStart为该类的方法,如果是其他类的方法,则不会出发通知

@Before("execution(* *.aspectStart(..)) && within(BeNotifiedImpl)")

也可以这么写,表示限制的类为com.spring.chapter4.springaop下的所有类,不包括下一级包的类

  @Before("execution(* *.aspectStart(..)) && within(com.spring.chapter4.springaop.*)")

或者这么写,包含springaop下所有包种的类

  @Before("execution(* *.aspectStart(..)) && within(com.spring.chapter4.springaop..*)")

(2)bean,限定特定的bean

限制只能通知bean id为testid 的aspectStart方法

  @Before("execution(* *.aspectStart(..)) && bean(testid)")

也可以这样,通知除了bean id为testid以外的所有bean方法

    @Before("execution(* *.aspectStart(..)) && !bean(testid)")

补充点四:处理通知中的参数

先来看下怎么声明参数以及传入到通知方法中

具体示例:

args()中的number,要与aspectStart的参数number对应

aspectStart中的number,要与方法中的nubmer对应

做到以上两点,就可以把被通知方法中参数,使用到通知方法中了

package com.spring.chapter4.springaop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AspectClass4 {
    @Pointcut("execution(* com.spring.chapter4.springaop.BeNotified.aspectStart(int)) && args(number) && within(BeNotifiedImpl2)")
    public void aspectStart(int number){}

    @Around("aspectStart(number)")
    public void around(ProceedingJoinPoint joinPoint,int number){
        try {
            System.out.println("主体方法运行之前4" + "携带的参数" + number);
            joinPoint.proceed();//控制器转让,需要捕获Throable异常
            System.out.println("主体方法运行之后4"  + "携带的参数" + number);
        }catch (Throwable e){
            System.out.println("主体方法运行抛出一异常后4"  + "携带的参数" + number);
        }
    }

}

xml方式

切面类

package com.spring.chapter4.xmlaspect;

public class AspectArgsClass {

    public void beforeNotice(int number){
        System.out.println("xmlaspect:主体方法运行之前" + number);
    }
    public void fterReturningNotice(int number){
        System.out.println("xmlaspect:主体方法运行之后" + number);
    }
    public void aafterThrowingNotice(int number){
        System.out.println("xmlaspect:主体方法运行抛出一异常后" + number);
    }
}

配置文件:配置类,注意number名要和切面类的参数number名对应

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="beNotified" class="com.spring.chapter4.xmlaspect.BeNotifiedImpl"/>
    <bean id="aspectArgsClass" class="com.spring.chapter4.xmlaspect.AspectArgsClass"/>
    <aop:config>
       <aop:aspect ref="aspectArgsClass">
           <aop:pointcut
                   id="aspectStart"
                   expression="execution(* com.spring.chapter4.xmlaspect.BeNotified.aspectStart(int)) and args(number)"/>
           <aop:before
                   pointcut-ref="aspectStart"
                   method="beforeNotice"/>
           <aop:after-returning
                   pointcut-ref="aspectStart"
                   method="fterReturningNotice"/>
           <aop:after-throwing
                   pointcut-ref="aspectStart"
                   method="aafterThrowingNotice"/>
       </aop:aspect>
    </aop:config>
</beans>

猜你喜欢

转载自blog.csdn.net/MASORL/article/details/82668606