Spring AOP实现切入增强的两种方式(execution+annotation)-Demo

pom文件依赖

<!-- AOP切面编程启动环境依赖组 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

1、通过execution表达式实现切入增强

package com.xch.aop_execution.advicetype;

import org.springframework.stereotype.Component;

/**
 * 计算业务实现类
 *
 * @author XuChenghe
 * @date 2023/8/19 17:43
 */
@Component
public class Count {
    
    /**
     * 正常计算方法
     *
     * @param num1
     * @param num2
     * @return
     */
    public Integer normal(Integer num1, Integer num2) {
        System.out.println("-----执行正常计算方法的逻辑(开始)-----");
        int x = 1 / 1;
        System.out.println("-----执行正常计算方法的逻辑(结束)-----");
        return num1 + num2;
    }
    
    /**
     * 异常计算方法
     *
     * @param num1
     * @param num2
     * @return
     */
    public Integer abnormal(Integer num1, Integer num2) {
        System.out.println("-----执行异常计算方法的逻辑(开始)-----");
        int x = 1 / 0;
        System.out.println("-----执行异常计算方法的逻辑(结束)-----");
        return num1 - num2;
    }
    
}
package com.xch.aop_execution.advicetype;

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

import java.util.Arrays;

/**
 * 计算业务的切面类:测试通知类型
 * -其中,后置通知/返回通知 和 最终通知的执行顺序,会根据Spring的版本而异
 *
 * @author XuChenghe
 * @date 2023/8/19 17:41
 */
@Aspect // 声明该类的作用:切面类
@Component // 等价于@Configuration:交由Spring的IOC容器管理
public class CountAspect {
    
    /**
     * 定义公共切入点
     * execution表达式配置规则:
     * -execution([修饰符] 返回类型 方法全限定名称(参数) [异常])
     * -其中修饰符和异常可选
     * -参数..代表任意入参
     * -*代表任意返回类型或任意方法名
     * -*xxx/xxx*代表以xxx结尾或开头的任意方法名
     * -xxx.*代表xxx类下的所有方法或xxx包下所有类的所有方法
     * -xxx..*代表xxx包下所有包或类的所有方法
     */
    @Pointcut("execution(* com.xch.aop_execution.advicetype.Count.*(..))")
    public void myPointcut() {
        // 公共切入点
    }
    
    /**
     * 前置通知
     *
     * @param joinPoint 连接点:当前切入方法的信息
     */
    @Before("myPointcut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("=====前置通知(开始):执行安全检查逻辑=====");
        
        // 前置通知,可以拿到方法名称+方法入参
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("name = " + name);
        System.out.println("args = " + Arrays.toString(args));
        
        System.out.println("=====前置通知(结束):执行安全检查逻辑=====");
    }
    
    /**
     * 后置通知/返回通知
     * 只有目标方法中没有抛出异常,即正常执行返回结果后,才进入该通知
     *
     * @param joinPoint 连接点:当前切入方法的信息
     * @param result    方法执行的返回结果
     */
    @AfterReturning(pointcut = "myPointcut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("=====后置通知/返回通知(开始):记录日志=====");
        
        // 后置通知/返回通知,可以拿到方法名称+方法入参+方法返回值
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("name = " + name);
        System.out.println("args = " + Arrays.toString(args));
        System.out.println("result = " + result);
        
        System.out.println("=====后置通知/返回通知(结束):记录日志=====");
    }
    
    /**
     * 最终通知
     * 不管目标方法中有没有抛出异常,都会进入该通知
     *
     * @param joinPoint 连接点:当前切入方法的信息
     */
    @After("myPointcut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("=====最终通知(开始):释放资源=====");
        
        // 最终通知,可以拿到方法名称+方法入参
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("name = " + name);
        System.out.println("args = " + Arrays.toString(args));
        
        System.out.println("=====最终通知(结束):释放资源=====");
    }
    
    /**
     * 环绕通知
     * 该通知等价于其余四种通知的大集合
     * 执行了被切入的方法代码,改变了原执行路径,需返回执行结果
     *
     * @param pjp 连接点:当前切入方法的信息
     * @return 方法执行的返回结果
     * @throws Throwable 方法执行时抛出的异常
     */
    @Around("myPointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("=====环绕通知(开始):手动开启事务=====");
        
        // 环绕通知,可以拿到方法名称+方法入参+方法返回值
        String name = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        Object proceed = pjp.proceed();
        System.out.println("name = " + name);
        System.out.println("args = " + Arrays.toString(args));
        System.out.println("proceed = " + proceed);
        
        System.out.println("=====环绕通知(结束):手动提交事务=====");
        return proceed;
    }
    
    /**
     * 抛出异常后通知
     * 不能在Around环绕通知中捕获异常,否则不会给Spring AOP处理进入该通知
     *
     * @param joinPoint 连接点:当前切入方法的信息
     * @param exception 方法执行时抛出的异常
     */
    @AfterThrowing(pointcut = "myPointcut()", throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint, Exception exception) {
        System.out.println("=====抛出异常后通知(开始):手动回滚事务=====");
        
        // 抛出异常后通知,可以拿到方法名称+方法入参+抛出的异常
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("name = " + name);
        System.out.println("args = " + Arrays.toString(args));
        exception.printStackTrace();
        
        System.out.println("=====抛出异常后通知(结束):手动回滚事务=====");
    }
    
}
package com.xch.aop_execution.advicetype;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * 计算业务测试类
 *
 * @author XuChenghe
 * @date 2023/8/19 18:17
 */
@SpringBootTest
public class CountTest {
    
    @Autowired
    private Count count;
    
    @Test
    public void test() {
        count.normal(3, 8);
        System.out.println();
        count.abnormal(8, 3);
    }
    
}

2、通过annotation注解实现切入增强

package com.xch.aop_annotation.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 用户信息实体类
 *
 * @author XuChenghe
 * @date 2023/8/19 12:37
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    
    /**
     * 用户ID
     */
    private Integer id;
    
    /**
     * 用户名称
     */
    private String name;
    
}
package com.xch.aop_annotation.controller;

import com.xch.aop_annotation.annotation.LogAnnotation;
import com.xch.aop_annotation.entity.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;

/**
 * 用户信息管理接口层实现类
 *
 * @author XuChenghe
 * @date 2023/8/19 12:35
 */
@RestController
@RequestMapping("/user")
public class UserController {
    
    /**
     * 获取所有用户信息
     *
     * @return
     */
    @GetMapping("/getUserList")
    public List<User> getUserList() {
        // 模拟业务的数据库操作
        return Arrays.asList(
                new User(1, "Alice"),
                new User(2, "Bob"),
                new User(3, "Lucy")
        );
    }
    
    /**
     * 通过用户ID获取用户信息
     *
     * @param id
     * @return
     */
    @GetMapping("/getUserById/{id}")
    public User getUserById(@PathVariable("id") Integer id) {
        // 模拟业务的数据库操作
        return new User(1, "Alice");
    }
    
    /**
     * 通过用户名称获取用户信息
     *
     * @param name
     * @return
     */
    @GetMapping("/getUserByName/{name}")
    public User getUserByName(@PathVariable("name") String name) {
        // 模拟业务的数据库操作
        return new User(2, "Bob");
    }
    
    /**
     * 保存用户信息
     *
     * @return
     */
    @GetMapping("/saveUser")
    @LogAnnotation("保存用户信息")
    public String saveUser() {
        // 模拟业务的数据库操作
        return "添加用户信息成功!";
    }
    
    /**
     * 编辑用户信息
     *
     * @return
     */
    @GetMapping("/editUser")
    @LogAnnotation("编辑用户信息")
    public String editUser() {
        // 模拟业务的数据库操作
        return "编辑用户信息成功!";
    }
    
    /**
     * 通过用户ID删除用户信息
     *
     * @param id
     * @return
     */
    @GetMapping("/deleteUserById/{id}")
    @LogAnnotation("通过用户ID删除用户信息")
    public String deleteUserById(@PathVariable("id") Integer id) {
        // 模拟业务的数据库操作
        return "通过用户ID删除用户信息成功!";
    }
    
    /**
     * 通过用户名称删除用户信息
     *
     * @param name
     * @return
     */
    @GetMapping("/deleteUserByName/{name}")
    @LogAnnotation("通过用户名称删除用户信息")
    public String deleteUserByName(@PathVariable("name") String name) {
        // 模拟业务的数据库操作
        return "通过用户名称删除用户信息成功!";
    }
    
}
package com.xch.aop_annotation.annotation;

import java.lang.annotation.*;

/**
 * 记录写操作方法日志的注解
 *
 * @author XuChenghe
 * @date 2023/8/19 12:57
 */
@Target(ElementType.METHOD) // 声明该注解作用的地方:方法
@Retention(RetentionPolicy.RUNTIME) // 声明该注解作用的时间:运行时
@Documented // 以上三个都是元注解:描述注解的注解
public @interface LogAnnotation {
    
    /**
     * 方法的作用描述
     */
    String value() default "暂无作用描述(默认)";
    
}
package com.xch.aop_annotation.aspect;

import com.xch.aop_annotation.annotation.LogAnnotation;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 记录写操作方法日志的的切面类
 *
 * @author XuChenghe
 * @date 2023/8/19 13:06
 */
@Aspect // 声明该类的作用:切面类
@Component // 等价于@Configuration:交由Spring的IOC容器管理
public class LogAspect {
    
    /**
     * 定义公共切入点
     */
    @Pointcut("@annotation(com.xch.aop_annotation.annotation.LogAnnotation)")
    public void logPointCut() {
        // 公共切入点
    }
    
    /**
     * 切面的执行方法(环绕通知):记录日志操作的代码
     *
     * @param pjp 连接点:当前切入方法的信息
     * @return 方法执行的返回结果
     */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        // 模拟业务的记录日志到数据库操作
        
        // 当前切入的方法名称
        String name = pjp.getSignature().getName();
        // 当前切入的方法返回值(执行了被切入的方法代码,改变了原执行路径,需返回执行结果)
        Object proceed = pjp.proceed();
        // 当前切入的方法入参
        Object[] args = pjp.getArgs();
        
        // 获取注解的属性内容
        // 1.通过反射反向获取方法
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        // 2.通过反射反向获取注解
        LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
        if (logAnnotation != null) {
            String value = logAnnotation.value();
            
            System.out.println("========================================");
            System.out.println("name = " + name);
            System.out.println("proceed = " + proceed);
            System.out.println("args = " + Arrays.toString(args));
            System.out.println("value = " + value);
            System.out.println("========================================");
        }
        
        return proceed;
    }
    
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BB-X

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值