spring aop理解及使用:我想这回应该可以说清楚了吧

2022-04-06 16:04:16 浏览数 (1)

AOP能干啥?

  • 解决代码重复性问题 将公共的事情通过切面去完成
  • 解决关注点分离
    • 水平分离 展示层 > 服务层 > 持久层
    • 垂直分离 模块划分(订单、库存、用户等)
    • 切面分离(aop) 分离功能性需求和非功能性需求; aop到底能做什么呢?如:权限控制缓存控制事务控制审计日志性能监控分布式追踪异常处理数据认证都可以使用aop; 为什么这些可以使用aop呢?这一部分功能他与业务没有啥关系,但是他们的公用性非常的强,不管啥操作、啥业务,可能都需要这些;举个很常用的例子,当我们做java web开发的时候,如果我想打印出所有接口请求及响应的数据日志,我们要怎么打?这个关注点(通用需求)它与业务无关,任何业务、任何请求都得打;那我们要怎么做?在每个Controller的每一个方法里面都去打印?可是可以,但是这很显然是一个苦逼、无聊且没有任何营养的体力活;这个时候,我们就可以通过AOP去完美的解决这个问题,具体怎么做,后面再说。

支持AOP的常用编程语音

  • Java
  • Python
  • .NET
  • c/c
  • PHP
  • Ruby
  • ......

AOP的原理

当我们剥开aop的外衣的时候,其实他的核心设计思想就是代理模式;spring中大量用到了代理模式;如果你不太了解代理模式,其实也不影响你对aop的使用;这里我举个生活中的例子,带你了解一下什么代理模式;当你在美团、饿了么点餐的时候,其实就是一个典型的代理模式,美团(代理对象)代理了餐馆(目标对象)将美食(方法)卖给你,同时对你的消费进行了增强(帮你配送、送你优惠券等);帮你配送、送你赠品并不是餐厅做的;而是美团(代理对象)做的;但是这一切并没有影响到你就餐、也没有影响餐厅对商品的销售;aop同样也使用的这个方式,在不影响目标对象的前提下对他的功能进行增强。

Spring AOP与AspectJ的关系

Spring AOP只是引用了AspectJ相关的注解,借鉴了他的编程风格,具体的实现是由Spring自己做的,并没有依赖AspectJ编译器,因此核心的东西他们没有直接的关系。spring和aspect在实现上还存在本质上的区别,spring是在运行时动态生成的代理对象,aspect是在编译的时候就生成了代理对象;所以同样的面容,不同的灵魂...

Spring AOP关键词

  • Aspect(切面) 对多个类的一个关注点的表达;什么是关注点,通俗一点就是说,大家都要做或者都关心的事情;比如说:系统的权限校验,可能登录、下单、操作都涉及到用户权限的校验,那么权限校验就是关注点,在切面中,要明确的表达出切入的点、切入的时机,织入什么等等!。所以切面是对以下所有知识点的一个综合的理解。
  • Join point(连接点) 程序执行过程中aop要作用的一个点(如:方法的执行、异常处理)。spring中连接点始终是代表着方法的执行。通俗点理解,连结点就代表这一个方法;如果还是觉得概念有点空可以看了下面的pointcut之后再来理解一下连接点;
  • Pointcut(切入点) 通过表达式匹配出来的某一类(些)连接点(方法)的集合;切入点和连接点(Join point)是相辅相成的;简单理解就是,切入点通过表达式描述来匹配出对应的方法(连接点);
代码语言:javascript复制

/**
 * 所有的public方法
 * 以下public * *(..)这个表达式匹配到的所有方法 都称之为连接点
 */
@Pointcut("execution(public * *(..))")
public void ex_pc1() {
}
  • Advice(通知) 通知要分两个维度去理解:1、增强什么?2、什么时候增强(执行时机)?切面在特定的连接点(Spring下表示某个方法的执行)处基于某个时机采取的操作我们称之为通知;通知作用于连接点的时机有以下5种:Before(之前)、After(之后)、AfterReturning(成功执行之后)、AfterThrowing(执行异常)、Around(环绕,该执行时机可以包含前面四种执行时机);实例如下:
代码语言:javascript复制

/**
 * Before的执行时机是在方法执行之前执行
 * 这里可以通过运算符去匹配多个切入点
 */
@Before("ex_pc1() || ex_pc2()")
public void advice1() {
  System.out.println("aop before..........");
}
  • Introduction 用于声明其他的方法或者属性,在Spring Aop中可以通过一个接口及其接口的实现将其添加到目标对象中,让目标对象具备这个接口实现的功能。
  • target object(目标对象) 通过切入点的表达式匹配出来的所有类我们称之为目标对象,再由Spring Aop通过运行时代理实现,因此对于使用者来说他看到的始终是一个代理对象。
  • Aop Proxy(Aop代理) Aop框架基于目标对象创建的对象称之为目标对象,其目的用于执行通过切面添加的方法;在Spring中AOP代理使用的JDK动态代理或者CGLIB代理
  • Weaving(织入) 将某个增强基于切入点在特定的时机作用于连接点的过程叫做织入;

Spring AOP的使用方式

  • 第一种 通过XML配置,目前开发都用的少了,因为用起来没有注解的方式简单,所以也就不推荐了;如果因为历史原因需要用到,可以去官网进行查看;
  • 第二种 通过注解的方式进行配置 不管那种方式,其实目的就是切面的表达式、切面的内容

Spring AOP的使用

  • mavne依赖(如果使用的springboot 测试)
代码语言:javascript复制

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  • spring 依赖(spring源码进行测试)
代码语言:javascript复制

// 这里使用的是5.1.x的spring源码进行的测试
// 由于spring源码是使用的gradle构建的 所以下面是gradle的引入
compile(project(":spring-aop"))
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.6'
  • 所有的测试代码如下
代码语言:javascript复制

// 自定义注解
package com.lupf.aop.anno;

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

@Retention(RetentionPolicy.RUNTIME)
public @interface Lupf {
}

// ==========================================================
// 业务测试bean
package com.lupf.aop.service;

import com.lupf.aop.anno.Lupf;

@Lupf
public class BusiBean {
}

// ==========================================================
// 接口I
package com.lupf.aop.service;

public interface I {
 public String busi1();

 public void busi2(String arg1,Integer arg2);
}

// ==========================================================
// ServiceA
package com.lupf.aop.service.dir;

import com.lupf.aop.anno.Lupf;
import com.lupf.aop.service.BusiBean;
import org.springframework.stereotype.Component;

@Lupf
@Component
public class ServiceA {

 public String busi1(BusiBean busiBean){
    System.out.println("ServiceA busi1.....");
 return "bbb";
  }

 public void busi2(@Lupf String arg1){
    System.out.println("ServiceA busi2.....");
  }
}

// ==========================================================
// Service B
package com.lupf.aop.service;

import com.lupf.aop.anno.Lupf;
import org.springframework.stereotype.Component;

@Component
public class ServiceB implements I{

 @Override
 @Lupf
 public String busi1(){
    System.out.println("ServiceB busi1.....");
 //System.out.println(1/0);
 return "bbb";
  }

 @Override
 public void busi2(String arg1,Integer arg2){
    System.out.println("ServiceB busi2.....");
  }
}

// ==========================================================
// 配置类
package com.lupf.aop;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

@Component
@ComponentScan("com.lupf.aop")
@EnableAspectJAutoProxy//(proxyTargetClass = true)
public class AppConfig {
}

// ==========================================================
// 项目启动类 及测试代码
package com.lupf.aop;

import com.lupf.aop.service.I;
import com.lupf.aop.service.dir.ServiceA;
import com.lupf.aop.service.ServiceB;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {
 public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    ac.register(AppConfig.class);
    ac.refresh();

    ServiceA serviceA = ac.getBean(ServiceA.class);
    serviceA.busi1(null);
    serviceA.busi2("a");

    I serviceB = ac.getBean(I.class);
    System.out.println(serviceB.busi1());;
    serviceB.busi2("a",);
  }
}
  • 结构如下,可以在你的项目下创建一个com.lupf.aop的目录,将上面的代码按一下的目录结构拷贝到项目中即可用于测试了。

切面表达式(expression)

通配符(wildcards)
  • *(用于匹配任意字符)
  • ..(匹配指定类及其子类,类似于java的泛型)
  • (表达任意数的子包或者参数) 通配符操作,因为有时候我们希望通过一个切面去切一类的方法,如果我们逐一去写的话,就会显的很麻烦,因此使用通配符的话会大大降低我们的代码量
运算符(operators)

用来表达希望同时满足多个条件的表达式

  • &&(与-两个都必须满足)
  • ||(或) 有一个满足就行
  • !(非) 取反
指示器(designators)

主要用于描述,通过什么样的方式去匹配你想要操作的类对应的方法;

  • 测试类AspectJDemo 为了方便测试,这里创建一个AspectJDemo的java类用于写测试用例(文末给出了完整的类文件)
代码语言:javascript复制

@Component // 交由spring管理
@Aspect // 指定当前类为一个aop切面
@Order(0) // 当有多个切面的时候,指定执行顺序,越小优先级越高
public class AspectJDemo {
 // 写测试用例
}
  • exection
代码语言:javascript复制

/**
 * 完整的格式如下
 * exection(
 *     modifier-pattern?   // 修饰符   允许不配置
 *     ret-type-pattern   // 返回值
 *     declaring-type-pattern?  // 描述包名    允许不配置
 *     name-pattern(param-pattern)   // 描述方法名 方法参数
 *     throws-pattern? // 匹配抛出的异常   允许不配置
 * )
 */
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

// 以下为测试用例
/**
 * 所有的public方法
 */
@Pointcut("execution(public * *(..))")
public void ex_pc1() {
}

/**
 * 只有带了特定返回值的类型
 */
@Pointcut("execution( String *(..))")
public void ex_pc2() {
}

/**
 * 指定指定的包名 带特定请求参数的
 */
@Pointcut("execution(public * com.lupf.aop..*(String,..))")
public void ex_pc3() {
}

/**
 * 匹配执行的方法名 所以这里只有方法名为busi1的才会被代理
 */
@Pointcut("execution(public * com.lupf.aop..*1(..))")
public void ex_pc4() {
}
  • within within为execution的一种简化模式 使用起来更便捷 但是他没有execution功能强大;其只能指明具体的类或具体目录下类中的方法
代码语言:javascript复制

/**
 * 指定具体的类ServiceB下的所有方法
 */
@Pointcut("within(com.lupf.aop.service.ServiceB)")
public void w_pc2(){
}

/**
 * 通配service目录及子目录的所有类
 * com.lupf.aop.service..* 会匹配当前目录及子目录
 * com.lupf.aop.service.* 只会匹配当前目录  子目录不会去匹配
 */
@Pointcut("within(com.lupf.aop.service..*)")
public void w_pc1() {
}
  • args 匹配指定的参数
代码语言:javascript复制

/**
 * 指明包含特定入参的方法,可以通过..进行多个参数的统配
 * args(String,Integer) 只能是第一个参数为String 第二个为Integer的才能匹配
 * args(String,Integer,..) 只要是前两个参数为String和Integer的都可以匹配上
 * args(..,Integer) 最后一个参数为Integer的都可以匹配上
 */
@Pointcut("args(String,Integer)")
public void arg_pc1() {
}
  • this 用于匹配代理对象的类;这一部分是相对不好理解的,可以参考注释详细的测试一下
代码语言:javascript复制

/**
 * 这里指明了com.lupf.aop.service.ServiceB用于匹配代理后的对象
 * <p>
 * 为了方便测试 这里定义了一个接口I ServiceB实现了I
 * <p>
 * 第一次测试,
 * 使用 @EnableAspectJAutoProxy 表示默认使用jdk动态代理
 * 那么spring获取对象是 I serviceB = ac.getBean(ServiceB.class)
 * 这样serviceB对象调用方法的时候,不会被增强
 * 原因是因为jdk代理是基于接口,那么生成的代理对象如下
 * public $ServiceBByJDK implements I {
 *     publis ServiceB serviceB;
 *     public void busi1(){
 *        service.busi();
 *     }
 *
 *     // other....
 * }
 * 所以$ServiceBByJDK无法匹配上ServiceB,增强失败
 * <p>
 * <p>
 * ===============================================================
 * <p>
 * 第二次测试
 *
 * @EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用cglib
 * 那么就会得到一个基于cglib的代理实现
 * 最终的代理对象效果如下
 * public ServiceB$$EbhabcerBySpringCGLIB&&xxxx extend ServiceB implements I
 * {
 * // do something
 * }
 * 因为cglib代理是基于继承 因此生成的代理对象是ServiceB的子类,所以这里就可以匹配上
 * 第二次测试执行的ServiceB中的所有方法都是可以增强的
 */
@Pointcut("this(com.lupf.aop.service.ServiceB)")
public void this_pc1() {
}
  • target 匹配动态代理之前 目标对象为指定对象的类
代码语言:javascript复制

/**
 * 由于是匹配的目标对象 因此就不会区分使用的什么代理方式了
 * 因此jdk和cglib代理的对象都是可以增强的
 */
@Pointcut("target(com.lupf.aop.service.ServiceB)")
public void target_pc1() {
}
  • @within 匹配加了指定注解的类
代码语言:javascript复制

/**
 * 类上面加了指定注解,那么该类下所有的方法执行都增强
 * <p>
 * 注意:该方式只作用于类上面 如果注解只写在方法上没加在类上面,那么增强是无法生效的
 */
@Pointcut("@within(com.lupf.aop.anno.Lupf)")
public void anno_within_pc() {
}
  • @target 目标对象是否添加指定的注解
代码语言:javascript复制

/**
 * 代理对象的时候 目标对象添加了指定注解类的方法执行都会进行增强
 */
@Pointcut("@target(com.lupf.aop.anno.Lupf)")
public void anno_target_pc() {
}

@开头的都是作用于注解

  • @args
代码语言:javascript复制

/**
 * 方法参数带有指定注解的
 * <p>
 * 如下测试,定义一个测试对象
 *
 * @Lupf public class BusiBean {
 * }
 * <p>
 * 如下的方法是会进行增强的
 * public String busi1(BusiBean busiBean)
 * <p>
 * 下面这种方式是不会进行增强的
 * public void busi2(@Lupf String arg1)
 */
@Pointcut("@args(com.lupf.aop.anno.Lupf)")
public void anno_args_pc() {
}
  • @annotation 加了指定注解的方法
代码语言:javascript复制

/**
 * 加了指定注解的方法调用的时候都会进行增强
 * <p>
 * 注意:这里只有方法,加在类上面是没有用的,和@within是对立的
 */
@Pointcut("@annotation(com.lupf.aop.anno.Lupf)")
public void anno_pc1() {
}
  • 特别说明 需要匹配多个表达式的时候,可以使用运算符进行匹配
代码语言:javascript复制

// 实例 表示添加了指定注解且只有一个String参数的方法
@Pointcut("@annotation(com.lupf.aop.anno.Lupf) && args(String)")

AOP的5种通知时机

  • @Before 在目标方法执行之前执行增强的代码
代码语言:javascript复制

/**
 * Before的执行时机是在方法执行之前执行
 * 这里可以通过运算符去匹配多个切入点
 */
@Before("w_pc1()")
public void advice1() {
  System.out.println("aop before..........");
}
  • @AfterReturning 在目标方法成功执行之后执行增强
代码语言:javascript复制

/**
 * 这个通知相对于After就更加明确了
 * 只有在执行成功之后才会通知
 * <p>
 * 如果不需要管结果,那么我们只需要指定好切点就好了
 * 如果我们需要拿到想要结果,就可以通过returning指定一个字段名称,并通过名称拿到响应数据
 * 同时这里是可以指定具体的响应数据类型,当指定具体类型之后,就只有返回指定类型的数据才会触发通知
 * <p>
 * 这里是否需要返回 并不会影响到调用方
 *
 * @param re 响应数据
 */
// @AfterReturning("anno_pc1()")
@AfterReturning(pointcut = "anno_pc1()", returning = "re")
public void advice3(Object re) {
  System.out.println("aop after reutrn........re:"   re);
}
  • @AfterThrowing 目标方法执行异常之后执行增强
代码语言:javascript复制

/**
 * 当方法执行出现异常之后,就会触发这个通知
 * <p>
 * 如果不需要管异常是什么,那么我们只需要指定好切点就好了
 * 如果我们需要拿到异常,就可以通过throwing指定一个字段名称,并通过名称拿到相应的异常
 * 同时这里是可以指定具体的异常类型,当指定具体类型之后,就只有抛出指定类型的异常才会触发通知
 *
 * @param ex
 */
//@AfterThrowing("anno_pc1()")
@AfterThrowing(pointcut = "anno_pc1()", throwing = "ex")
public void advice4(Exception ex) {
  System.out.println("aop after exception msg:"   ex.getMessage());
}
  • @After 目标方法不管执行成功还是失败都会执行增强
代码语言:javascript复制

/**
 * 这个通知相对于After就更加明确了
 * 只有在执行成功之后才会通知
 * <p>
 * 如果不需要管结果,那么我们只需要指定好切点就好了
 * 如果我们需要拿到想要结果,就可以通过returning指定一个字段名称,并通过名称拿到响应数据
 * 同时这里是可以指定具体的响应数据类型,当指定具体类型之后,就只有返回指定类型的数据才会触发通知
 * <p>
 * 这里是否需要返回 并不会影响到调用方
 *
 * @param re 响应数据
 */
// @AfterReturning("anno_pc1()")
@AfterReturning(pointcut = "anno_pc1()", returning = "re")
public void advice3(Object re) {
  System.out.println("aop after reutrn........re:"   re);
}
  • @Around 环绕通知囊括了上面的4种情况,包含了所有的执行时机;所以这种方式是最常用的
代码语言:javascript复制

/**
 * 坏绕通知 囊括了上面4种通知时机
 *
 * @param joinPoint
 * @return 响应数据
 * @throws Throwable
 */
@Around("anno_pc1()")
public Object advice4(ProceedingJoinPoint joinPoint) throws Throwable {
 //在这里相当于@Before

  System.out.println("aop around before.........");

 // 获取所有参数
  Object[] args = joinPoint.getArgs();

 try {
 // 执行目标方法并获取响应对象
    Object o = joinPoint.proceed(args);

 // 到这里相当于@AfterReturning
    System.out.println("aop around after returning.........");

 // 返回数据
 // 这里不同于@Before 这里如果不返回,调用方将拿不到响应数据
 return o;
  } catch (Throwable t) {

 // 这里相当于@AfterThrowing
    System.out.println("aop around after throwing.........");
 throw t;
  } finally {
 // 到这里相当于@After 异常和正常只会出现其中一种情况
    System.out.println("aop around after.........");
  }
}
  • 特别说明,执行时机同样可以通过算术运算符匹配对各切入点,如:
代码语言:javascript复制

@Before("w_pc1() && w_pc2()")

AOP执行优先级

当多个切面作用于同一个方法的的时候,可以通过在切面类上加@Order(0)来指定优先级,数越小,优先级越高。

  • 完整的测试用例 为了不占用篇幅,放在了最后
代码语言:javascript复制

package com.lupf.aop;

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

@Component // 交由spring管理
@Aspect // 指定当前类为一个aop切面
@Order() // 当有多个切面的时候,指定执行顺序,越小优先级越高
public class AspectJDemo {

 /**
   * exection(
   *     modifier-pattern?   // 修饰符   允许不配置
   *     ret-type-pattern   // 返回值
   *     declaring-type-pattern?  // 描述包名    允许不配置
   *     name-pattern(param-pattern)   // 描述方法名 方法参数
   *     throws-pattern? // 匹配抛出的异常   允许不配置
   * )
   */

 /**
   * 所有的public方法
   * 以下public * *(..)这个表达式匹配到的所有方法 都称之为连接点
   */
 @Pointcut("execution(public * *(..))")
 public void ex_pc1() {
  }

 /**
   * 只有带了特定返回值的类型
   */
 @Pointcut("execution( String *(..))")
 public void ex_pc2() {
  }

 /**
   * 指定指定的包名 带特定请求参数的
   */
 @Pointcut("execution(public * com.lupf.aop..*(String,..))")
 public void ex_pc3() {
  }

 /**
   * 匹配执行的方法名 所以这里只有方法名为busi1的才会被代理
   */
 @Pointcut("execution(public * com.lupf.aop..*1(..))")
 public void ex_pc4() {
  }

 // within 指明具体的类或者具体的包路径
 // within 为execution的一种简化模式 使用起来更便捷 但是他没有execution功能强大

 /**
   * 指定具体的类ServiceB下的所有方法
   */
 @Pointcut("within(com.lupf.aop.service.ServiceB)")
 public void w_pc1() {
  }

 /**
   * 通配service目录及子目录的所有类
   * com.lupf.aop.service..* 会匹配当前目录及子目录
   * com.lupf.aop.service.* 只会匹配当前目录  子目录不会去匹配
   */
 @Pointcut("within(com.lupf.aop.service..*)")
 public void w_pc2() {
  }

 /**
   * 指明包含特定入参的方法,可以通过..进行多个参数的统配
   * args(String,Integer) 只能是第一个参数为String 第二个为Integer的才能匹配
   * args(String,Integer,..) 只要是前两个参数为String和Integer的都可以匹配上
   * args(..,Integer) 最后一个参数为Integer的都可以匹配上
   */
 @Pointcut("args(String,Integer)")
 public void arg_pc1() {
  }

 // this 表示匹配代理对象
 // spring aop 默认使用的jdk动态代理
 // 可以在启动类添加@EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用cglib

 /**
   * 这里指明了com.lupf.aop.service.ServiceB用于匹配代理后的对象
   * <p>
   * 为了方便测试 这里定义了一个接口I ServiceB实现了I
   * <p>
   * 第一次测试,
   * 使用 @EnableAspectJAutoProxy 表示默认使用jdk动态代理
   * 那么spring获取对象是 I serviceB = ac.getBean(ServiceB.class)
   * 这样serviceB对象调用方法的时候,不会被增强
   * 原因是因为jdk代理是基于接口,那么生成的代理对象如下
   * public $ServiceBByJDK implements I {
   * publis ServiceB serviceB;
   * public void busi1(){
   * service.busi();
   * }
   * <p>
   * // other....
   * }
   * 所以$ServiceBByJDK无法匹配上ServiceB,增强失败
   * <p>
   * <p>
   * ===============================================================
   * <p>
   * 第二次测试
   *
   * @EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用cglib
   * 那么就会得到一个基于cglib的代理实现
   * 最终的代理对象效果如下
   * public ServiceB$$EbhabcerBySpringCGLIB&&xxxx extend ServiceB implements I
   * {
   * // do something
   * }
   * 因为cglib代理是基于继承 因此生成的代理对象是ServiceB的子类,所以这里就可以匹配上
   * 第二次测试执行的ServiceB中的所有方法都是可以增强的
   */
 @Pointcut("this(com.lupf.aop.service.ServiceB)")
 public void this_pc1() {
  }

 // 匹配动态代理之前 目标对象为指定对象的类

 /**
   * 由于是匹配的目标对象 因此就不会区分使用的什么代理方式了
   * 因此jdk和cglib代理的对象都是可以增强的
   */
 @Pointcut("target(com.lupf.aop.service.ServiceB)")
 public void target_pc1() {
  }

 /**
   * 类上面加了指定注解,那么该类下所有的方法执行都增强
   * <p>
   * 注意:该方式只作用于类上面 如果注解只写在方法上没加在类上面,那么增强是无法生效的
   */
 @Pointcut("@within(com.lupf.aop.anno.Lupf)")
 public void anno_within_pc() {
  }

 /**
   * 代理对象的时候 目标对象添加了指定注解类的方法执行都会进行增强
   */
 @Pointcut("@target(com.lupf.aop.anno.Lupf)")
 public void anno_target_pc() {
  }

 /**
   * 方法参数带有指定注解的
   * <p>
   * 如下测试,定义一个测试对象
   *
   * @Lupf public class BusiBean {
   * }
   * <p>
   * 如下的方法是会进行增强的
   * public String busi1(BusiBean busiBean)
   * <p>
   * 下面这种方式是不会进行增强的
   * public void busi2(@Lupf String arg1)
   */
 @Pointcut("@args(com.lupf.aop.anno.Lupf)")
 public void anno_args_pc() {
  }

 /**
   * 加了指定注解的方法调用的时候都会进行增强
   * <p>
   * 注意:这里只有方法,加在类上面是没有用的,和@within是对立的
   */
 @Pointcut("@annotation(com.lupf.aop.anno.Lupf)")
 public void anno_pc1() {
  }

 /**
   * Before的执行时机是在方法执行之前执行
   * 这里可以通过运算符去匹配多个切入点
   */
 @Before("w_pc1() && w_pc2()")
 public void advice1() {
    System.out.println("aop before..........");
  }

 /**
   * After执行时机是在方法执行完之后
   * 这个执行并不会管执行成功还是失败,都会触发这个通知
   */
 @After("anno_pc1()")
 public void advice2() {
    System.out.println("aop after.............");
  }

 /**
   * 这个通知相对于After就更加明确了
   * 只有在执行成功之后才会通知
   * <p>
   * 如果不需要管结果,那么我们只需要指定好切点就好了
   * 如果我们需要拿到想要结果,就可以通过returning指定一个字段名称,并通过名称拿到响应数据
   * 同时这里是可以指定具体的响应数据类型,当指定具体类型之后,就只有返回指定类型的数据才会触发通知
   * <p>
   * 这里是否需要返回 并不会影响到调用方
   *
   * @param re 响应数据
   */
 // @AfterReturning("anno_pc1()")
 @AfterReturning(pointcut = "anno_pc1()", returning = "re")
 public void advice3(Object re) {
    System.out.println("aop after reutrn........re:"   re);
  }

 /**
   * 当方法执行出现异常之后,就会触发这个通知
   * <p>
   * 如果不需要管异常是什么,那么我们只需要指定好切点就好了
   * 如果我们需要拿到异常,就可以通过throwing指定一个字段名称,并通过名称拿到相应的异常
   * 同时这里是可以指定具体的异常类型,当指定具体类型之后,就只有抛出指定类型的异常才会触发通知
   *
   * @param ex
   */
 //@AfterThrowing("anno_pc1()")
 @AfterThrowing(pointcut = "anno_pc1()", throwing = "ex")
 public void advice4(Exception ex) {
    System.out.println("aop after exception msg:"   ex.getMessage());
  }

 /**
   * 坏绕通知 囊括了上面4种通知时机
   *
   * @param joinPoint
   * @return 响应数据
   * @throws Throwable
   */
 @Around("anno_pc1()")
 public Object advice4(ProceedingJoinPoint joinPoint) throws Throwable {
 //在这里相当于@Before

    System.out.println("aop around before.........");

 // 获取所有参数
    Object[] args = joinPoint.getArgs();

 try {
 // 执行目标方法并获取响应对象
      Object o = joinPoint.proceed(args);

 // 到这里相当于@AfterReturning
      System.out.println("aop around after returning.........");

 // 返回数据
 // 这里不同于@Before 这里如果不返回,调用方将拿不到响应数据
 return o;
    } catch (Throwable t) {

 // 这里相当于@AfterThrowing
      System.out.println("aop around after throwing.........");
 throw t;
    } finally {
 // 到这里相当于@After 异常和正常只会出现其中一种情况
      System.out.println("aop around after.........");
    }
  }
}

关于理解及使用就写到这里,以上纯属个人理解,如果存在不对的地方,欢迎留言指正!!!后续会一起来分析一下spring是如何去实现aop的;

0 人点赞