读《Spring实战》:面向切面

AOP术语

通知(Advice)

在AOP中,切面的工作被称为通知,也就是通知就是具体要干的工作。
spring中有5中通知:

  • 前置通知: 在目标方法之前调用通知功能
  • 后置通知: 在目标方法之后调用通知功能
  • 返回通知: 在目标方法成功执行之后调用通知功能
  • 异常通知: 在执行目标方法抛出异常后调用通知功能
  • 环绕通知: 通知包裹了目标方法,在目标方法调用之前和调用之后执行自定义的行为。

连接点(Join point)

应用执行过程中的一个时机,可以体现为方法调用时,抛出异常时,方法返回时,甚至修改一个字段,通常和方法有关。

切点(Poincut)

切点可以描述为在哪里,在代码中就表现为明确的类和方法名称,以及方法参数来明确在哪里调用通知功能。

切面(Aspect)

切面是通知和切点的结合,通知和切点共同组成了切面的定义: 切面要做什么,在哪里做,在什么时候做。

引入(Introduction)

引入可以向现有的类添加新方法和属性。例如,可以新建一个通知类,这个通知类用来记录一个对象的最后修改时间,这个通知类只需要一个LocalDatTime属性,和一个setLastUpdateTime(LocalDateTime updateTime)方法即可。在目标对象发生修改的时候,这个通知类中的记录最后修改时间的属性和方法就可以加到目标对象中,从而可以在无需修改现有类的情况下让目标对象具有新的行为和状态。(但是这种可读性就是不太好,暂时没遇到)

织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点(when)被织入到目标对象中,在目标对象的生命周期中有多个连接点可以织入:

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

Spring AOP的支持

基础认识

并不是所有的AOP框架都是相同的,有些允许在字段修饰符级别应用通知,有些只支持与方法调用相关的连接点。Spring AOP构建在动态代理基础之上,因此Spring对于AOP的支持局限于方法拦截,Spring AOP提供了4中了类型的AOP:

  • 基于代理的经典Spring AOP
  • 纯POJO切面
  • @AspectJ注解驱动的切面
  • 注入式AspectJ切面(适用于Spring各版本)

关于Spring AOP的AspectJ切点,最重要的一点就是Spring仅支持AspectJ切点指示器的一个子集,这里的指示器我理解就是定位目标对象方法的一种表达式,根据这些指示器能找到织入切面的目标对象的方法在哪里,Spring AOP所支持的AspectJ切点指示器:

  • arg(): 限制目标方法的参数有哪些,是什么类型的 使用 (…)表示不限制
  • execution(): 用于匹配是连接点的执行方法
  • this: 限制执行目标方法的上下文对象,在Spring AOP中指的是代理对象,而不是被代理的对象
  • target: 限制执行目标方法的被代理对象的类型
  • within(): 限制 目标对象所在的位置(包,类,方法都可以)

在Spring 中尝试使用AspectJ其他指示器的时候,将会抛出IllegalArgumentException异常。上面的指示器中只有execution指示器时实际执行匹配的,而其他的指示器都是用来显示匹配的,说明execution是最主要的指示器,其他的都是用来辅助匹配的。并且在指示器的表达式中还可以使用逻辑词(&& ! || )。

Spring中使用AspectJ注解来声明通知方法:

  • @After: 在目标方法返回之后或者抛出异常之后
  • @AfterReturning: 在目标方法返回之后
  • @AfterThrowing: 在目标方法抛出异常之后
  • @Around: 通知方法将包裹目标方法,相当于前置和后置
  • @Before: 在调用目标方法之前

测试代码:

public interface IDay0404Target {

     void rain();

      void rain(String name);

      void rain(String name,String age);
}
@Component("day0404TargetImpl")
public class Day0404TargetImpl implements IDay0404Target{
    @Override
    public void rain() {
        System.out.println("无参的rain方法");
        rainOne();
    }

    @Override
    public void rain(String name) {
        System.out.println("一个String参数的rain方法");
    }

    @Override
    public void rain(String name,String age) {
        System.out.println("两个String参数的rain方法");
    }

    public  Integer rainOne(){
        System.out.println(" 返回值是Integer的rain方法");
        return 1;
    }
}
@Component("day0404TargetTwoImpl")
public class Day0404TargetTwoImpl implements IDay0404Target{
    @Override
    public void rain() {
        System.out.println("day0404TargetTwoImpl 无参的rain方法");
    }

    @Override
    public void rain(String name) {
        System.out.println("day0404TargetTwoImpl 一个String参数的rain方法");
    }

    @Override
    public void rain(String name,String age) {
        System.out.println("day0404TargetTwoImpl 两个String参数的rain方法");
    }
}

@RequestMapping("/day0404")
@RestController
public class Day0404Controller {

    @Autowired
    @Qualifier("day0404TargetImpl")
    private IDay0404Target day0404Target;

    @Autowired
    @Qualifier("day0404TargetTwoImpl")
    private IDay0404Target two;
    @GetMapping("/test")
    public void test(){
            day0404Target.rain();
            day0404Target.rain("ddd");
            day0404Target.rain("1231","12313");
            two.rain("一个");
    }
}


//  第一个 * 表示 不限制返回值,目标方法是Day0404TargetImpl类的rain()方法
//  使用@PointCut注解可以减少很多切点的命名  下面通知的注解直接使用这个修饰的方法即可。
@Pointcut("execution(* com.example.reactor_test.day0404.Day0404TargetImpl.rain())")
public void rainTest(){};


// 表示 Day0404TargetImpl 类中方法参数只有一个String的方法
@Pointcut("execution(* com.example.reactor_test.day0404.Day0404TargetImpl.*(String))")
public void rainTest2(){

}

// 表示 Day0404TargetImpl 类中方法名为rain,并且方法参数有两个String的方法
@Pointcut("execution(* com.example.reactor_test.day0404.Day0404TargetImpl.rain(..)) && args(String,String)")
public void rainTest3(){

}

// execution(* com.example.reactor_test.day0404.IDay0404Target.*(..)) 表示是 IDay0404Target 接口的实现类 并且任意方法
// this(com.example.reactor_test.day0404.Day0404TargetTwoImpl)  限制了执行方法的对象类型要是  Day0404TargetTwoImpl
@Pointcut("execution(* com.example.reactor_test.day0404.IDay0404Target.*(..)) && this(com.example.reactor_test.day0404.Day0404TargetTwoImpl)")
public void rainTest4(){

}

// target(com.example.reactor_test.day0404.Day0404TargetTwoImpl)  限制了执行方法的被代理对象的类型,也就是实际执行对象类型  要是  Day0404TargetTwoImpl
@Pointcut("execution(* com.example.reactor_test.day0404.IDay0404Target.*(..)) && target(com.example.reactor_test.day0404.Day0404TargetTwoImpl)")
public void rainTest5(){

}

// within(com.example.reactor_test.day0404.Day0404TargetImpl)  限制了执行方法的类
//  && execution(* rain(String)) 限制了方法名称是rain,并且参数只有一个String
@Pointcut("within(com.example.reactor_test.day0404.Day0404TargetImpl) && execution(* rain(String))")
public void rainTest6(){

}

//  Integer  限制了方法返回值是 Integer,并且方法是rainOne()
@Pointcut("execution(Integer com.example.reactor_test.day0404.Day0404TargetImpl.rainOne())")
public void rainTest7(){};

@Before("rainTest()")
public void beforeRain(){
    System.out.println("rainTest 的   @Before 方法");
}


@Before("rainTest2()")
public void afterRain(){
    System.out.println("rainTest2 的  @Before方法");
}

@Before("rainTest3()")
public void afterRain2(){
    System.out.println("rainTest3 的  @Before方法");
}

@Before("rainTest4()")
public void afterRain3(){
    System.out.println("rainTest4 的  @Before方法");
}

@Before("rainTest5()")
public void test5(){
    System.out.println("rainTest5 的  @Before方法");
}

@Before("rainTest6()")
public void test6(){
    System.out.println("rainTest6 的  @Before方法");
}

@Before("rainTest7()")
public void test7(){
    System.out.println("rainTest7 的  @Before方法");
}

}

执行结果:


rainTest 的   @Before 方法
无参的rain方法
rainTest2 的  @Before方法
rainTest6 的  @Before方法
一个String参数的rain方法
rainTest3 的  @Before方法
两个String参数的rain方法
rainTest4 的  @Before方法
rainTest5 的  @Before方法
day0404TargetTwoImpl 一个String参数的rain方法

Spring中还引入了一个新的bean执行器:

execution(*.com.cn.test.one.two.perform()) && bean('one')

这个指示器其实和this有点像,它不是AspectJ中原生的,而是Spring AOP中的概念,限制的对象:

所匹配的是IoC容器中具有指定ID或名称的Bean。由于Spring AOP默认采用代理模式进行增强,因此实际上在进行AOP代理时,Spring容器返回给客户端的将会是代理对象,而非原始的被代理对象。

环绕通知

直接上代码:

@Aspect
@Component
public class Day0404AroundAspect {

    @Pointcut("execution(* com.example.reactor_test.day0404.Day0404TargetImpl.rain())")
    public void rain(){

    }

    @Around("rain()")
    public void test(ProceedingJoinPoint joinPoint){
        System.out.println("第一次打印+"+ LocalDateTime.now().toString());
        try {
            Thread.sleep(5000);
            joinPoint.proceed();
        }catch (Throwable throwable){
            System.out.println("发生错误");
        }

        System.out.println("第二次打印"+ LocalDateTime.now().toString());
    }
}
打印结果:


第一次打印+2024-04-04T15:27:04.544536400
rainTest 的   @Before 方法
无参的rain方法
 返回值是Integer的rain方法
第二次打印2024-04-04T15:27:09.553697300
rainTest2 的  @Before方法
rainTest6 的  @Before方法
一个String参数的rain方法
rainTest3 的  @Before方法
两个String参数的rain方法
rainTest4 的  @Before方法
rainTest5 的  @Before方法
day0404TargetTwoImpl 一个String参数的rain方法

根据打印结果也可以看到,如果不执行joinPoint.proceed()的话是会阻塞的,可以用来做失败尝试操作;并且第一次打印和第二次打印就相当于调用了@Before注解和@After注解,将这两个注解放在了一个方法等于。

传递参数

@Aspect
@Component
public class Day0404ReceiveParamAspect {


    @Pointcut("execution(* com.example.reactor_test.day0404.Day0404TargetImpl.rain(String)) && args(name)")
    public void rainTest2(String name){

    }

    @Before("rainTest2(name)")
    public void test(String name){
        System.out.println("接收到的参数的值是:"+name);
    }
}

引入

之前在术语那块有提到引入,使用引入可以给目标对象加入新的方法和属性。在使用@After,@Before这些有关AOP注解来包装某些bean的时候,Spring会生成这些bean对象的代理对象实例(通过JDK代理或者cglib代理的方式),这些代理对象会实现这些bean实现的接口(没有实现接口就会继承bean),从而在间接调用被代理对象的原始方法的时候进行增强,添加自定义逻辑。如果这些代理对象可以暴露出某个接口,对调用者而言就是被代理对象增加了新的方法和属性。

在这里插入图片描述

调用者去调用目标对象(也就是被代理对象,切面应用的对象)的方法的时候,代理会把此调用委托给目标对象,当调用引入的方法的时候,会委托给引入的代理对象。也就是一个bean的实现被拆分到了多个类中,由被代理对象和引入代理队形来共同完成。

public interface IDay0404Target {

     void rain();

      void rain(String name);

      void rain(String name,String age);
}
@Component
public class IDay0404ImportImpl implements IDay0404ImportInterface{
    @Override
    public void rainProcessor() {
        System.out.println("引入的对象的方法 ");
    }
}
@Component
@Aspect
public class Day0404ImportConfig {

    @DeclareParents(value = "com.example.reactor_test.day0404.impl.Day0404TargetImpl",defaultImpl = IDay0404ImportImpl.class)
    public  static IDay0404ImportInterface day0404ImportInterface;

    @DeclareParents(value = "com.example.reactor_test.day0404.impl.Day0404TargetTwoImpl",defaultImpl = IDay0404ImportImpl.class)
    public  static IDay0404ImportInterface two;
}
    @Autowired
    @Qualifier("day0404TargetImpl")
    private IDay0404Target day0404Target;

    @Autowired
    @Qualifier("day0404TargetTwoImpl")
    private IDay0404Target two;
    @GetMapping("/test")
    public void test(){
            day0404Target.rain();
            day0404Target.rain("ddd");
            day0404Target.rain("1231","12313");
            two.rain("一个");
        IDay0404ImportInterface t1 = (IDay0404ImportInterface) day0404Target;
        IDay0404ImportInterface t2 = (IDay0404ImportInterface) two;
        t1.rainProcessor();
        t2.rainProcessor();
    }
打印结果:

。。。。。。。。
。。。。。。。。
引入的对象的方法 
引入的对象的方法 

本来是想用 @DeclareParents(value = “com.example.reactor_test.day0404.IDay0404Target+”,defaultImpl = IDay0404ImportImpl.class),但是有了+号会启动失败,理论说又了+号表示不包含自身,只包含子类,但是没好使,就放弃了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值