Spring中AOP的简单使用

路漫漫其修远兮, 吾将上下而求索!

AOP

Aspect Oriented Programming 意思是面向切面编程, 可能是因为java的面向对象编程在处理一些日志, 授权等操作时, 为了减少重复代码, 降低耦合程度. 所以引入了一种切面编程的概念. 通俗的理解切面编程 可以理解为对多个方法的增强操作.

基础知识

本文对基础不过多讲解, 提供以下学习地址, 比较全面, 但是强烈推荐各位在学习Spirng相关知识的时候去阅读官网文档, 一般的博客都是一些程序猿自己理解整理的知识, 很多都有一大推的问题, 因为他们也没有理解清楚, 所以一定要通过官网文档去学习, 然后辅助博客去理解一些难点

资料地址
Spring官网https://docs.spring.io/spring-framework/docs/current/reference/html/core.html
AOP基础https://blog.csdn.net/qq_41701956/article/details/84427891

引入依赖

        <!-- AOP支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <scope>test</scope>
        </dependency>
        <!--aspectJ织入 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

一. 开启@AspectJ支持

官方文档:
To enable @AspectJ support with Java @Configuration, add the @EnableAspectJAutoProxy annotation, as the following example shows:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

意思是: 使用java注解方式配置时, 要在配置类上加上 @EnableAspectJAutoProxy 开启Sping对AspectJ的默认支持

定义切面

切面中定义了两个切点, 切点内连接点有重叠, 为了验证通知的优先级

/**
 * @description: 定义一个切面
 *     注意:
 *     1. 最高优先级到最低优先级:@Around、@Before、@After、@AfterReturning、@afterhrowing。
 *    但是,请注意,@After-advice方法将在同一方面的任何@afterreturn或@afterhrowing-advice方法之后有效地调用
 *    2. 一个切面内定义了两个重叠的切点时, 如果此时再分别定义通知, 此时顺序无法判定, 官网推荐我们把这两个切点合并
 *    成一个切点, 或者再定义一个切面 通过@Order 指定优先级(数据越小, 优先级越高)
 * @author: wangml
 * @date: 2021/8/11 09:00
 */
@Aspect
@Order(100)
@Component
public class NotVeryUsefulAspect {

    private static final Logger logger = LoggerFactory.getLogger(NotVeryUsefulAspect.class);

    @Pointcut("execution(* com.huntkey.rx..service.*.*(..))")
    public void pointMed1() {};

    @Pointcut("execution(* com.huntkey.rx..service.UserService.delUser(..))")
    public void pointMed2() {};

    @Before("pointMed1()")
    public void beforeMedExecute() {
        logger.info("----切面方法1被执行前调用----");
    }

    @AfterReturning("pointMed1()")
    public void afterReturnMedExecute() {
        logger.info("----切面方法1执行成功后调用----");
    }


    @After("pointMed1()")
    public void afterMedExecute() {
        logger.info("----切面方法1被执行后调用--------");
    }

    @Around("pointMed2()")
    public Object aroundMed2Execute(ProceedingJoinPoint pjp) throws Throwable {
        logger.info("******切面方法2被调用********");
        // 修改入参
        Object[] args = pjp.getArgs();
        logger.info("初始参数: {}", args);

        // 获取方法签名
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        // 获取方法的参数列表
        Class[] parameterTypes = methodSignature.getParameterTypes();
        if (String.class.isAssignableFrom(parameterTypes[0])) {
            // 方法参数第一个参数类型时 String
            args[0] = "88888";
        }
        logger.info("修改后的参数: {}", args);
        logger.info("########环绕, 方法执行前处理#########");

        Object result = pjp.proceed(args);

        logger.info("########环绕, 方法执行后处理#########");
        logger.info("返回的数据: {}", result);
        return result;
    }
}

定义一个UserService

/**
 * @description: UserService
 * @author: wangml
 * @date: 2021/8/11 09:25
 */
@Service
public class UserService {

    public void addUser() {
        System.out.println("insert a user into user table");
    }

    public String delUser(String id) {
        System.out.println("delete a user from user table where id = " + id);
        return "delete user success!";
    }
}

新建测试类

/**
 * @description:AOP使用测试类
 * @author: wangml
 * @date: 2021/8/11 09:32
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class AopTest {

    @Autowired
    private UserService userService;

//    @Autowired
//    private EmployeeService employeeService;

    @Test
    public void addUser() {
        userService.addUser();
    }

    @Test
    public void delUser() {
        userService.delUser("1111");
    }

//    @Test
//    public void addEmployee() {
//        employeeService.addEmployee();
//    }
//
//    @Test
//    public void delEmployee() {
//        employeeService.delEmployee("111");
//    }
}

分析: 当执行addUser 时, 由于方法在切点pointMed1的连接点表达式范围内, 所以会执行 切点pointMed1的通知, beforeMedExecute, afterReturnMedExecute, afterMedExecute里面内容都会被执行, 执行结果如下, 符合预期

2021-08-11 17:38:03.200  INFO 13388 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----切面方法1被执行前调用----
insert a user into user table
2021-08-11 17:38:03.216  INFO 13388 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----切面方法1执行成功后调用----
2021-08-11 17:38:03.217  INFO 13388 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----切面方法1被执行后调用--------

当执行delUser时, 两个切点的连接点表达式都满足, 都会执行, 但是由于 @Around > @Before > @AfterReturning > @After 所以 切点pointMed2 会先执行, 结果如下, 符合预期

2021-08-11 17:39:28.614  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ******切面方法2被调用********
2021-08-11 17:39:28.614  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : 初始参数: 1111
2021-08-11 17:39:28.617  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : 修改后的参数: 88888
2021-08-11 17:39:28.617  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ########环绕, 方法执行前处理#########
2021-08-11 17:39:28.617  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----切面方法1被执行前调用----
delete a user from user table where id = 88888
2021-08-11 17:39:28.632  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----切面方法1执行成功后调用----
2021-08-11 17:39:28.632  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----切面方法1被执行后调用--------
2021-08-11 17:39:28.632  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ########环绕, 方法执行后处理#########
2021-08-11 17:39:28.632  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : 返回的数据: delete user success!

特别注意:

  1. 环绕可以控制实际方法是否执行, 通过 proceed来实现, 如果没有写, 则方法不会执行, 而且调用过程是链式调用, 和过滤器FilterChain的处理相似.
  2. 环绕可以用来在 proceed前 处理在方法传递参数, proceed后 处理方法返回数据, 如果方法是void 类型默认返回null.
  3. 一般优先使用环绕(官网说法)

定义另一个切面

主要用来验证不同切面优先级, 以及使用自定义注解 定义切点的使用

/**
 * @description: 切面拦截所有使用了注解的方法
 * @author: wangml
 * @date: 2021/8/11 14:16
 */
@Aspect
@Order(10)
@Component
public class VeryUsefulAspect {

    public static final Logger logger = LoggerFactory.getLogger(VeryUsefulAspect.class);

    @Pointcut("@annotation(com.huntkey.rx.sceo.anno.Idempotent)")
    public void pointCutMed(){};

    @Around("pointCutMed()")
    public Object aroundAnn(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取注解中传递的数据
        Object[] args = joinPoint.getArgs();
        logger.info("不处理方法参数, 参数值为: {}", args);

        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Method method = methodSignature.getMethod();

        Idempotent annotation = method.getAnnotation(Idempotent.class);
        String value = annotation.value();
        String name = annotation.name();
        if (!"".equals(value) || !"".equals(name)) {
            // 当注解内方法返回值不是默认值时, 有重新赋值
            logger.info("*****注解数据操作********");
            logger.info("Idempotent注解数据, value: {} name: {}", value, name);
        }

        // 具体执行的方法
        Object proceed = joinPoint.proceed();

        // proceed 是 方法执行后返回的数据, 若方法为void 则默认null.
        logger.info("***方法执行后返回数据: {}", proceed);
        return proceed;
    }
}

自定义注解@Idempotent

/**
 * @description: 方法注解
 * @author: wangml
 * @date: 2021/8/11 14:12
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {

    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";
}

定义一个EmployeeService

/**
 * @description: 员工Service(测试AOP注解使用)
 * @author: wangml
 * @date: 2021/8/11 14:48
 */
@Service
public class EmployeeService {

    private static final Logger logger = LoggerFactory.getLogger(EmployeeService.class);

    public void addEmployee() {
        logger.info("insert a employee into employee table");
    }

    @Idempotent(name = "wangml")
    public String delEmployee(String id) {
        logger.info("delete a employee from employee table where id = {}", id);
        return "delete employee success!";
    }
}

分析: addEmployee 没有添加@Idempotent 所以只有NotVeryUsefulAspect 中的切点pointMed1
中连接点表达式满足, 执行方法, 输出如下, 符合预期

2021-08-11 18:04:37.884  INFO 5228 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----切面方法1被执行前调用----
2021-08-11 18:04:37.899  INFO 5228 --- [           main] c.h.rx.sceo.service.EmployeeService      : insert a employee into employee table
2021-08-11 18:04:37.900  INFO 5228 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----切面方法1执行成功后调用----
2021-08-11 18:04:37.900  INFO 5228 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----切面方法1被执行后调用--------

delEmployee 添加了@Idempotent, 所以新定义的切面VeryUsefulAspect 中 pointCutMed切点的连接点表达式满足 和 NotVeryUsefulAspect 中的切点pointMed1中连接点表达式满足, 但是VeryUsefulAspect 的切面优先级 高于NotVeryUsefulAspect 切面, 因此 VeryUsefulAspect 中的通知先于 NotVeryUsefulAspect 执行. 执行方法, 输入如下, 符合预期

2021-08-11 18:09:41.215  INFO 7796 --- [           main] c.huntkey.rx.sceo.aop.VeryUsefulAspect   : 不处理方法参数, 参数值为: 111
2021-08-11 18:09:41.219  INFO 7796 --- [           main] c.huntkey.rx.sceo.aop.VeryUsefulAspect   : *****注解数据操作********
2021-08-11 18:09:41.219  INFO 7796 --- [           main] c.huntkey.rx.sceo.aop.VeryUsefulAspect   : Idempotent注解数据, value:  name: wangml
2021-08-11 18:09:41.219  INFO 7796 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----切面方法1被执行前调用----
2021-08-11 18:09:41.236  INFO 7796 --- [           main] c.h.rx.sceo.service.EmployeeService      : delete a employee from employee table where id = 111
2021-08-11 18:09:41.236  INFO 7796 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----切面方法1执行成功后调用----
2021-08-11 18:09:41.236  INFO 7796 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----切面方法1被执行后调用--------
2021-08-11 18:09:41.236  INFO 7796 --- [           main] c.huntkey.rx.sceo.aop.VeryUsefulAspect   : ***方法执行后返回数据: delete employee success!

总结

  • aop虽然功能强大, 但是最好不要定义太多, 不然检查问题的时候会晕死, 因为可能会有层层嵌套, 每次都可以对数据进行处理.
  • 适用于减少重复性代码, 很多业务类都需要进行原来没有的某一处理逻辑时, 可以使用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值