超实用的SpringAOP实战之日记记录

打印 上一主题 下一主题

主题 828|帖子 828|积分 2499

本文主要以日记记录作为切入点,来讲解Spring AOP在实际项目开发中怎样更好的使项目业务代码更加简洁、开发更加高效。
日记处理惩罚只是AOP其中一种应用场景,当你掌握了这一种场景,对于其它应用场景也可以游刃有余的应对。
AOP常见的应用场景有:日记记录、异常处理惩罚、性能监控、事务管理、安全控制、自界说验证、缓存处理惩罚等等。文末会简单列举一下。
在看完本文(日记记录场景)后,可以练习其它场景如何实现。应用场景万般种,万变不离其中,掌握其本质最为紧张。
使用场景的本质是:在一个方法的执行前、执行后、执行异常和执行完成状态下,都可以做一些同一的操纵。AOP 的核心优势在于将这些横切功能从核心业务逻辑中提取出来,从而实当代码的解耦和复用,提升体系的可维护性和扩展性。
案例一:简单日记记录

本案例主要目的是为了理解整个AOP代码是怎样编写的。
引入依赖
  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-aop</artifactId>
  4. </dependency>
复制代码
自界说一个注解类
  1. import java.lang.annotation.ElementType;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.RetentionPolicy;
  4. import java.lang.annotation.Target;
  5. // 日志记录注解:作用于方法
  6. @Target(ElementType.METHOD)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface RecordLog {
  9.     String value() default "";
  10. }
复制代码
编写一个切面类Aspect

AOP切面有多种通知方式:@Before、@AfterReturning、@AfterThrowing、@After、@Around。因为@Around包含前面四种情况,本文案例都只使用@Around,其它可自行了解。
  1. import org.aspectj.lang.ProceedingJoinPoint;
  2. import org.aspectj.lang.annotation.Around;
  3. import org.aspectj.lang.annotation.Aspect;
  4. import org.springframework.stereotype.Component;
  5. @Aspect
  6. @Component
  7. public class RecordLogAspect {
  8.     // 指定自定义注解为切入点
  9.     @Around("@annotation(org.example.annotations.RecordLog)")
  10.     public void around(ProceedingJoinPoint proceedingJoinPoint){
  11.         try {
  12.             System.out.println("日志记录--执行前");
  13.             proceedingJoinPoint.proceed();
  14.             System.out.println("日志记录--执行后");
  15.         } catch (Throwable e) {
  16. //            e.printStackTrace();
  17.             System.out.println("日志记录--执行异常");
  18.         }
  19.         System.out.println("日志记录--执行完成");
  20.     }
  21. }
复制代码
编写一个Demo方法
  1. import org.example.annotations.RecordLog;
  2. import org.springframework.stereotype.Component;
  3. @Component
  4. public class RecordLogDemo {
  5.     @RecordLog
  6.     public void simpleRecordLog(){
  7.         System.out.println("执行当前方法:"+Thread.currentThread().getStackTrace()[1].getMethodName());
  8.         // 测试异常情况
  9. //        int a  = 1/0;
  10.     }
  11. }
复制代码
举行单元测试
  1. import org.example.demo.RecordLogDemo;
  2. import org.junit.jupiter.api.Test;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.boot.test.context.SpringBootTest;
  5. @SpringBootTest
  6. class SpringDemoAOPApplicationTests {
  7.     @Autowired
  8.     private RecordLogDemo recordLogDemo;
  9.     @Test
  10.     void contextLoads() {
  11.         System.out.println("Test...");
  12.         recordLogDemo.simpleRecordLog();
  13.     }
  14. }
复制代码
测试结果:

这是最简单的日记记录,主要目的是理解整个代码是怎样的。
案例二:交易日记记录

本案例完成切面类根据外部传进来的参数实现动态日记的记录。
切面获取外部信息的一些方法:

  • 获取目标方法(连接点)的参数:JoinPoint类下的getArgs()方法,或ProceedingJoinPoint类下的getArgs()方法
  • 获取自界说注解中的参数:自界说注解可以界说多个参数、必填参数、默认参数等
  • 通过抛出异常来传递业务处理惩罚情况,切面通过捕获异常来记录异常信息
也可以根据方法参数的名称去校验是否是你想要的参数 String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
切面获取内部信息:好比获取执行前后的时间。
调整后的代码如下
新增了罗列类
  1. public enum TransType {
  2.     // 转账交易类型
  3.     TRANSFER,
  4.     // 查询交易类型
  5.     QUERYING;
  6. }
复制代码
自界说注解类

注解多了必填的交易类型和选填的交易说明
  1. import org.example.enums.TransType;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. // 日志记录注解:作用于方法
  7. @Target(ElementType.METHOD)
  8. @Retention(RetentionPolicy.RUNTIME)
  9. public @interface RecordLog {
  10.     // 必填的交易类型
  11.     TransType transType() ;
  12.     // 选填的交易说明
  13.     String description() default "";
  14. }
复制代码
切面类

新增了开头所说的三种获取外部信息的代码
  1. import org.aspectj.lang.ProceedingJoinPoint;
  2. import org.aspectj.lang.annotation.*;
  3. import org.aspectj.lang.reflect.MethodSignature;
  4. import org.example.annotations.RecordLog;
  5. import org.example.enums.TransType;
  6. import org.springframework.stereotype.Component;
  7. import java.lang.reflect.Method;
  8. @Aspect
  9. @Component
  10. public class RecordLogAspect {
  11.     // 指定自定义注解为切入点
  12.     @Around("@annotation(org.example.annotations.RecordLog)")
  13.     public Object around(ProceedingJoinPoint proceedingJoinPoint){
  14.         // 1.获取目标方法(连接点)的参数信息
  15.         Object[] args = proceedingJoinPoint.getArgs();
  16.         // 获取特定类型的参数:根据具体情况而定
  17.         for (Object arg : args) {
  18.             if (arg instanceof String) {
  19.                 System.out.println("日志记录--执行前:方法请求参数信息记录: " + arg);
  20.             }
  21.         }
  22.         // 2.获取自定义注解中的参数
  23.         MethodSignature methodSignature = (MethodSignature)proceedingJoinPoint.getSignature();
  24.         Method method = methodSignature.getMethod();
  25.         RecordLog annotation = method.getAnnotation(RecordLog.class);
  26.         // 交易类型
  27.         TransType transType = annotation.transType();
  28.         // 交易描述信息
  29.         String description = annotation.description();
  30.         try {
  31.             System.out.println("日志记录--执行前:注解参数信息记录:"+transType+"|"+description+"|");
  32.             Object proceed = proceedingJoinPoint.proceed();
  33.             System.out.println("日志记录--执行后:"+proceed.toString());
  34.             // 只要没异常,那就是执行成功
  35.             System.out.println("日志记录--执行成功:200");
  36.             return proceed;
  37.         } catch (Throwable e) {
  38. //            e.printStackTrace();
  39.             // 3.捕获异常来记录异常信息
  40.             String errorMessage = e.getMessage();
  41.             System.out.println("日志记录--执行异常: "+errorMessage);
  42.             throw new Exception("日志记录--执行异常: ").initCause(e);
  43.         } finally {
  44.             System.out.println("日志记录--执行完成");
  45.         };
  46.     }
  47. }
复制代码
新增了业务相关类
  1. public class TransInfoBean {
  2.     private String transStatusCode;
  3.     private String transResultInfo;
  4.     private String account;
  5.     private BigDecimal transAmt;
  6.     public String getTransStatusCode() {
  7.         return transStatusCode;
  8.     }
  9.     public void setTransStatusCode(String transStatusCode) {
  10.         this.transStatusCode = transStatusCode;
  11.     }
  12.     public String getTransResultInfo() {
  13.         return transResultInfo;
  14.     }
  15.     public void setTransResultInfo(String transResultInfo) {
  16.         this.transResultInfo = transResultInfo;
  17.     }
  18.     public String getAccount() {
  19.         return account;
  20.     }
  21.     public void setAccount(String account) {
  22.         this.account = account;
  23.     }
  24.     public BigDecimal getTransAmt() {
  25.         return transAmt;
  26.     }
  27.     public void setTransAmt(BigDecimal transAmt) {
  28.         this.transAmt = transAmt;
  29.     }
  30. }
复制代码
业务Demo类

加入了外部参数注解、方法参数和异常,只要交易失败都要抛出异常。
  1. import org.example.annotations.RecordLog;
  2. import org.example.enums.TransType;
  3. import org.springframework.stereotype.Component;
  4. import java.math.BigDecimal;
  5. @Component
  6. public class RecordLogDemo {
  7.     @RecordLog(transType = TransType.QUERYING,description = "查询账户剩余多少金额")
  8.     public BigDecimal queryRecordLog(String account) throws Exception {
  9.         System.out.println("--执行当前方法:"+Thread.currentThread().getStackTrace()[1].getMethodName());
  10.         try{
  11.             // 执行查询操作:这里只是模拟
  12.             TransInfoBean transInfoBean = this.queryAccountAmt(account);
  13.             BigDecimal accountAmt = transInfoBean.getTransAmt();
  14.             System.out.println("--查询到的账户余额为:"+accountAmt);
  15.             return accountAmt;
  16.         }catch (Exception e){
  17.             throw new Exception("查询账户余额异常:"+e.getMessage());
  18.         }
  19.     }
  20.     /**
  21.      * 调用查询交易
  22.      * @param account
  23.      * @return TransInfoBean
  24.      */
  25.     private TransInfoBean queryAccountAmt(String account) throws Exception {
  26.         TransInfoBean transInfoBean = new TransInfoBean();
  27.         transInfoBean.setAccount(account);
  28.         try{
  29.             // 调用查询交易
  30. //            int n = 1/0;
  31.             transInfoBean.setTransAmt(new BigDecimal("1.25"));
  32.             //交易成功:模拟交易接口返回来的状态
  33.             transInfoBean.setTransStatusCode("200");
  34.             transInfoBean.setTransResultInfo("成功");
  35.         }catch (Exception e){
  36.             //交易成功:模拟交易接口返回来的状态
  37.             transInfoBean.setTransStatusCode("500");
  38.             transInfoBean.setTransResultInfo("失败");
  39.             throw new Exception(transInfoBean.getTransStatusCode()+"|"+transInfoBean.getTransResultInfo());
  40.         }
  41.         return transInfoBean;
  42.     }
  43. }
复制代码
单元测试
  1. @SpringBootTest
  2. class SpringDemoAOPApplicationTests {
  3.     @Autowired
  4.     private RecordLogDemo recordLogDemo;
  5.     @Test
  6.     void contextLoads() throws Exception {
  7.         System.out.println("Test...");
  8.         recordLogDemo.queryRecordLog("123567890");
  9.     }
  10. }
复制代码
测试结果

成功的情况

失败的情况

总结

使用AOP后,交易处理惩罚的日记就不需要和业务代码交织在一起,起到解耦作用,提高代码可读性和可维护性。
其次是代码的扩展性问题,好比后面要开发转账交易,后面只管写业务代码,打上日记记录注解即可完成日记相关代码@RecordLog(transType = TransType.TRANSFER,description = "转账交易")。假如一个项目几十个交易接口需要编写,那这日记记录就少写了几十次,大大的提高了开发服从。
这个案例只是讲解了日记记录,假如将输出的日记信息存到一个对象,并生存到数据库,那就可以记录所有交易的处理惩罚情况了。把日记记录处理惩罚换成你所需要做的操纵即可。
常用场景简述

事务管理

Spring AOP 提供了 @Transactional 注解来简化事务管理,底层是通过 AOP 实现的。通过声明式事务管理,可以根据方法的执行情况主动提交或回滚事务。
例如:在方法执行之前开启事务,执行后提交事务,出现异常时回滚事务
  1. @Transactional
  2. public void transferMoney(String fromAccount, String toAccount, double amount) {
  3.     accountService.debit(fromAccount, amount);
  4.     accountService.credit(toAccount, amount);
  5. }
复制代码
可以自界说事务管理切面,也可以同时兼容@Transactional事务管理注解。执行目标方法前开启事务,执行异常时回滚事务,执行正常时可以不用处理惩罚。
  1. @Aspect
  2. @Component
  3. public class TransactionAspect {
  4.     @Before("execution(* com.example.service.*.*(..)) && @annotation(org.springframework.transaction.annotation.Transactional)")
  5.     public void beforeTransaction(JoinPoint joinPoint) {
  6.         System.out.println("Starting transaction for method: " + joinPoint.getSignature().getName());
  7.     }
  8.     @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..)) && @annotation(org.springframework.transaction.annotation.Transactional)", throwing = "exception")
  9.     public void transactionFailure(Exception exception) {
  10.         System.out.println("Transaction failed: " + exception.getMessage());
  11.     }
  12. }
复制代码
性能监控

AOP 可以用来监控方法的执行性能(如执行时间、频率等),特别得当用于体系的性能分析和优化。
示例:

  • 计算方法执行时间,并记录日记。
  • 监控方法的调用频率。
  1. @Aspect
  2. @Component
  3. public class PerformanceMonitorAspect {
  4.     @Around("execution(* com.example.service.*.*(..))")
  5.     public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
  6.         long start = System.currentTimeMillis();
  7.         
  8.         // 执行目标方法
  9.         Object result = joinPoint.proceed();
  10.         long elapsedTime = System.currentTimeMillis() - start;
  11.         System.out.println("Method " + joinPoint.getSignature().getName() + " executed in " + elapsedTime + " ms");
  12.         return result;
  13.     }
  14. }
复制代码
安全控制

AOP 适用于实现方法级别的安全控制。例如,你可以在方法调用之前查抄用户的权限,决定是否答应访问。
示例:

  • 校验用户是否具有某个操纵的权限。
  • 使用注解 @Secured 或自界说注解,基于角色或权限举行安全验证。
  1. @Aspect
  2. @Component
  3. public class SecurityAspect {
  4.    
  5.     @Before("@annotation(com.example.security.Secured)")
  6.     public void checkSecurity(Secured secured) {
  7.         // 获取当前用户的权限
  8.         String currentUserRole = getCurrentUserRole();
  9.         if (!Arrays.asList(secured.roles()).contains(currentUserRole)) {
  10.             throw new SecurityException("Insufficient permissions");
  11.         }
  12.     }
  13. }
复制代码
缓存管理

AOP 可以用于方法结果的缓存。对于一些耗时较长的方法,可以使用 AOP 来在第一次调用时执行计算,后续的调用则直接从缓存中获取结果,从而提高性能。
示例:使用 AOP 实现方法结果缓存,避免重复计算。
  1. @Aspect
  2. @Component
  3. public class CachingAspect {
  4.     private Map<String, Object> cache = new HashMap<>();
  5.     @Around("execution(* com.example.service.*.get*(..))")
  6.     public Object cacheResult(ProceedingJoinPoint joinPoint) throws Throwable {
  7.         String key = joinPoint.getSignature().toShortString();
  8.         if (cache.containsKey(key)) {
  9.             return cache.get(key); // 从缓存中获取
  10.         }
  11.         // 执行目标方法并缓存结果
  12.         Object result = joinPoint.proceed();
  13.         cache.put(key, result);
  14.         return result;
  15.     }
  16. }
复制代码
异常处理惩罚

AOP 可以同一处理惩罚方法中的异常,好比记录日记、发送警报或执行其他处理惩罚。可以通过 @AfterThrowing 或 @Around 注解来实现异常的捕获和处理惩罚。
示例:同一捕获异常并记录日记或发送通知。
  1. @Aspect
  2. @Component
  3. public class ExceptionHandlingAspect {
  4.     @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "exception")
  5.     public void handleException(Exception exception) {
  6.         System.out.println("Exception caught: " + exception.getMessage());
  7.         // 发送邮件或日志记录
  8.     }
  9. }
复制代码
自界说验证

AOP 可以用于方法参数的验证,尤其在输入数据的校验上。在方法调用之前举行参数验证,避免无效数据的传入。
示例:校验方法参数是否为空或符合特定规则,好比密码格式校验
  1. @Aspect
  2. @Component
  3. public class ValidationAspect {
  4.     // 正则表达式
  5.     private static final String PASSWORD_PATTERN =
  6.             "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[!@#$%^&*(),.?:{}|<>_]).{8,16}$";
  7.     @Before("execution(* org.example.demo.UserService.createUser(..))")
  8.     public void validateUserInput(JoinPoint joinPoint) {
  9.         Object[] args = joinPoint.getArgs();
  10.         for(Object arg : args){
  11.             // 类型检查
  12.             if(arg instanceof UserService){
  13.                 UserService userService = (UserService) arg;
  14.                 // 然后再校验对象属性的值:是否为空、是否不符合格式要求等等,
  15.                 // 比如密码校验
  16.                 if(!validatePassword(userService.getPassword())){
  17.                     // 不符合就抛出异常即可
  18.                     throw new IllegalArgumentException("Invalid user input");
  19.                 }
  20.             }
  21.         }
  22.     }
  23.     /**
  24.      * 密码校验:8~16为,要有大小学字母+数字+特殊字符
  25.      * @param password String
  26.      * @return boolean
  27.      */
  28.     public static boolean validatePassword(String password) {
  29.         if(password == null)
  30.             return false;
  31.         return Pattern.matches(PASSWORD_PATTERN, password);
  32.     }
  33. }
复制代码
总结

应用场景万般种,万变不离其中,掌握其本质最为紧张。
使用场景的本质是:在一个方法的执行前、执行后、执行异常和执行完成状态下,都可以做一些同一的操纵。AOP 的核心优势在于将这些横切功能从核心业务逻辑中提取出来,从而实当代码的解耦和复用,提升体系的可维护性和扩展性。

软考中级--软件计划师毫无保留的备考分享
单例模式及其思想
2023年下半年软考考试重磅消息
通过软考后却领取不到实体证书?
计算机算法计划与分析(第5版)
Java全栈学习门路、学习资源和面试题一条龙
软考据书=职称证书?
什么是计划模式?

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大连全瓷种植牙齿制作中心

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表