天天看点

Spring切面

AOP基本概念

  • 通知(Advice):定义了一个切面该干什么(what)以及什么时候应用(when).spring的通知类型有五种:
    • before:在方法调用之前.
    • after:在方法调用之后,无论方法是否正常返回.
    • after-returning:在方法正常返回之后应用.
    • after-throwing:在方法出现异常时调用.
    • around:该通知将包住整个方法.
  • 连接点(join point):所有可能应用切面的点,包括方法调用,实例域改变等等.
  • 切点(pointcut):定义了切面的具体应用位置,哪些方法使用了通知之类的.(where)
  • 切面(aspect):是advice与pointcut的结合,它定义了对哪些方法(where)在哪些情况下(when)执行什么操作(what).
  • 引入(INTRODUCTION):用于向已有类中加入新的实例域/方法,使用动态代理实现.
  • 织入(WEAVING):将切面/引入应用到目标对象,并创建代理对象的过程.织入的时机主要有以下3个:
    • 编译期:在目标类编译时将切面织入,需要使用特别的编译器,AspectJ使用该方式.
    • 类加载时期:在类加载器加载字节码时,对字节码进行修改,实现织入.需要特殊的ClassLoader,AspectJ 5支持该织入方式.
    • 运行期:通过创建动态代理(JDK/CGLib),在运行期基于目标对象动态生成代理类,并最终创建代理实例,更多的是复合的意味.这是spring的织入模式.

Spring AOP支持

spring对于AOP的支持主要有以下三种方式:

1. 利用xml的aop命名空间,将一个纯pojo转换为切面.

2. 利用@AspectJ驱动的切面.

3. 直接注入AspectJ的切面.

tips:1,2两种方式,Spring是通过动态代理来实现的.

jdk动态代理的应用如下:将Advice对象以及目标对象注入到InvocationHandler实例中,并在invoke方法中实现切面.(该InvocationHandler实例会传递给代理对象).

Spring切面

spring只支持使用方法作为切点,如果需要对构造器/实例域应用切面只能使用3的AspectJ.

Spring AOP

Spring使用AspectJ表达式定义切点,但是只支持AspectJ的子集。

Spring支持的AspectJ符号如下表

AspectJ符号 含义
execution(expression) 用于真正筛选方法切点,其他符号基于该结果进行进一步限定,所以是必须的。
this(fullClassName) 用于选择那些代理是基于fullClassName的bean(切点/代理对象)
bean(id) 限定只对特定id的bean应用切面
@args(AnnotationTypes) 用于限定那些参数的类型使用AnnotationTypes进行注解的切点
args(name) 用于传递实际参数到通知
within(package.*) 限定切点的bean的类型在package.*的范围内
@within(AT)/@target(AT) 限定切点所在类必须由@AT进行注解
@annotation(AT) 限定切点对应的方法必须有@AT注解
target(targetClass) 限定切点所在的类必须是targetClass

基于注解的Spring AOP

  • 必须在配置类中使用@EnableAspectJAutoProxy,允许自动使用切面代理。
  • 通过@Aspect声明一个切面并通过@Component/@Bean创建该切面的实例,使其可以使用。
package high.perform;

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Aspect
@Component
public class Audience {
    //这里声明了切点表达式,用于匹配切点
    //*表示匹配任意返回类型
    //+表示匹配Perform及其子类
    //..表示参数任意
    @Pointcut(value = "execution(* high.perform.Perform+.play(..)) && " +
            // args用于传递来自切点的参数,在这里即为Perform.play的参数,
            // 需要与playX的参数同名传递(出于多参数考虑)
            "args(song) && " +
            // 限定为被@T注解的类作为切点所在的类
            "@target(TX) && " +
            // 选择id为mp的bean,若id不存在则无论bean(id)/!bean(id)过滤结果都为false.
            "bean(mp) && " +
            // 用于选择那些被@TX注解的切点(方法)
            "@annotation(TX)",
            // 当使用在非debug模式编译,需要使用argNames属性,该属性必须与playX的参数名一致
            argNames = "song")
    public void playX(String song){}

    //在这里声明一个前置通知,使用前面定义的切点
    @Before("playX(song)")
    public void beforePerform(String song){
        System.out.println(song+" is ready to play.");
        System.out.println("Please sit down.");
    }
}

interface Perform {
    void play(String x);
}
@Component("mp")
@TX
//这里是一个切点
class MusicPerform implements Perform{
    @Override
    @TX
    public void play(@TX String x) {
        System.out.println("Sing "+x+" out.");
    }
}
@Target({ElementType.TYPE,ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface TX{}
           
  • 环绕通知(Aroud)是最强大的通知,其对应的实现方法需要有一个ProceedingJoinPoint参数.
package high.perform;

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Aspect
@Component
public class Audience {
    @Pointcut("execution(* *.play(..)) && args(song)")
    public void playX(String song){}

    @Around("playX(song)")
    //ProceedingJoinPoint不需要加入到args中
    public void aroundPerform(ProceedingJoinPoint pjp,String song){
        try{
            System.out.println("this is before advice.");
            Object[] params = {song};
            //目标方法调用
            pjp.proceed(params);
            System.out.println("this is after advice.");
        }catch (Throwable e){
            System.out.println("this is after-throwing.");
        }
    }

}

interface Perform {
    void play(String x);
}
@Component("mp")
@TX
//这里是一个切点
class MusicPerform implements Perform{
    @Override
    @TX
    public void play(@TX String x) {
        System.out.println("Sing "+x+" out.");
    }
}
@Target({ElementType.TYPE,ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface TX{}
           

利用切面实现引入

@Aspect
@Component()
public class IAIB{
    //这里的value必须是一个全限定类名,否则不成功.
    //对所有B的子类进行代理,引入接口A的功能,具体实现使用AI.
    //这部分引入的功能基于动态代理很容易实现,只要在创建代理时传进去引入的接口即可.
    @DeclareParents(value = "high.perform.B+",defaultImpl = AI.class)
    public static A a;
}
           

注入AspectJ切面

wait for need.