首页 > 编程学习 > 【springboot项目】利用AOP+注解实现代码解耦

文章目录

  • 1 一段臃肿的代码
  • 2 利用AOP+注解实现代码解耦
    • 2.1自定义注解
    • 2.2 日志切面类开发 --- AOP
    • 2.3 臃肿的代码瘦身以后
  • 3 简单测试

本文源码地址:https://github.com/nieandsun/NRSC-STUDY

1 一段臃肿的代码

线上运行的项目必然会出现这样或那样的问题,为了让维护者迅速且准确的对问题进行定位,在代码的关键位置打log肯定必不可少。于是很多人的代码都写成了下面的样子:

/**** 臃肿的代码* @param positionCode* @return*/public List<UserInfo> getUserInfoListByPositionCode111(Long positionCode) {//(0) 还可能先去缓存里拿数据,如果拿不到再走下面的逻辑//(1) 记录日志String loginUserName = "james"; //模拟获取当前登陆用户的方法log.info("【{}】调用getUserInfoListByPositionCode方法获取信息,参数为:【{}】",loginUserName, positionCode);//(2) 异常捕捉try {//(3)真正的业务代码List<UserInfo> userInfos = aopRepository.getUserInfoListByPositionCode(positionCode);//(3)-1  还可能有将拿到的数据存到缓存等其他操作return userInfos;} catch (Exception e) {//(4) 捕捉到异常后打个日志log.error("【{}】调用getUserInfoListByPositionCode方法查询数据库出错,参数为:【{}】",loginUserName, positionCode);//(5)将异常抛出交由统一异常处理类处理throw e;}}

这种代码的一些问题:

(1)可读性差 — 本来只是想根据positionCode查一下用户,一看这个方法却这么长,让人根本没有想多看的欲望;
(2) 可复用性差 — 肯定service层的很多方法都需要按照上面的方式进行打log,假如每一个需要的方法都这样写一遍,多累啊;
(3)可维护性差 — 万一有一天日志里,不让出现用户名,那就要对每一个方法进行检查,然后删掉日志中的用户名;
。。。。

2 利用AOP+注解实现代码解耦

2.1自定义注解

package com.nrsc.elegant.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD}) //指定注解在类上、方法上生效
@Retention(RetentionPolicy.RUNTIME) //指定本注解在运行期起作用
public @interface LogAnnotation {/****  用于获取方法中的某个参数的值* @return*/String key();/**** 是否记录日志 --- 默认不记录* @return*/boolean needLog() default false;
}

2.2 日志切面类开发 — AOP

  • 切面类
package com.nrsc.elegant.aop;
import com.nrsc.elegant.annotation.LogAnnotation;
import com.nrsc.elegant.util.SpelParserUtil;
import lombok.extern.slf4j.Slf4j;
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.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
@Aspect
@Component
@Slf4j
public class LogAspect {@Around("@annotation(logAnnotation)")public Object doAround(ProceedingJoinPoint pjp, LogAnnotation logAnnotation) throws Throwable {//(0) 获取到key的值可能会有其他用途,比如去缓存里查询数据String key = logAnnotation.key();//解析注解中el表达式对应的变量的值log.info("获取指定的形参的值" + this.getKey(key, pjp));boolean needLog = logAnnotation.needLog();//(1) 记录日志//模拟获取当前登陆用户的方法String loginUserName = "james";//获取方法名和参数String methodName = pjp.getSignature().getName();List<Object> args = Arrays.asList(pjp.getArgs());if (needLog) {log.info("【{}】调用【{}】方法获取信息,参数为:【{}】", loginUserName, methodName, args);}//(2) 异常捕捉try {//(3)真正的业务代码Object proceed = pjp.proceed();//(3)-1  还可能有将拿到的数据存到缓存等其他操作return proceed;} catch (Throwable throwable) {//(4) 捕捉到异常打印一句日志log.error("【{}】调用【{}】方法查询数据库失败,参数为:【{}】", loginUserName, methodName, args);//(5) 将异常抛出交由统一异常处理类处理throw throwable;}}/****  解析获得key中的真实值* @param key* @param pjp* @return*/private String getKey(String key, ProceedingJoinPoint pjp) {//从连接点里获取到当前方法Method method = ((MethodSignature) (pjp.getSignature())).getMethod();//获取方法形参的名字String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(method);return SpelParserUtil.getKey(key, parameterNames, pjp.getArgs());}
}
  • 解析注解中el表达式对应值的工具类
package com.nrsc.elegant.util;import org.springframework.expression.Expression;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;public class SpelParserUtil {private SpelParserUtil() {}private static ExpressionParser parser = new SpelExpressionParser();/**** @param key   el表达式字符串,占位符以#开头* @param paramsNames 形参名称,可以理解为占位符名称* @param args 参数值,可以理解为占位符真实值* @return 返回el表达式经过参数替换后的字符串*/public static String getKey(String key, String[] paramsNames, Object[] args) {//将key字符串解析为el表达式Expression exp = parser.parseExpression(key);//初始化赋值上下文EvaluationContext context = new StandardEvaluationContext();if (args.length <= 0) {return null;}//将 形参 和 形参值 以配对的方式配置到赋值上下文中for (int i = 0; i < args.length; i++) {context.setVariable(paramsNames[i], args[i]);}//根据赋值上下文运算el表达式return exp.getValue(context, String.class);}
}

2.3 臃肿的代码瘦身以后

 @Override@LogAnnotation(key = "#positionCode", needLog = true)public List<UserInfo> getUserInfoListByPositionCode(Long positionCode) {return aopRepository.getUserInfoListByPositionCode(positionCode);}

可以发现此时代码已经被大大简化,变得不再臃肿。


3 简单测试

假设

  • AopDemoRepository中对应的getUserInfoListByPositionCode方法如下:
package com.nrsc.elegant.Repository;import com.nrsc.elegant.pojo.UserInfo;
import org.springframework.stereotype.Repository;import java.util.Arrays;
import java.util.List;
@Repository
public class AopDemoRepository {public List<UserInfo> getUserInfoListByPositionCode(Long positionCode) {Long i = 1 / positionCode;UserInfo u1 = new UserInfo("小明", 18, "male");UserInfo u2 = new UserInfo("小红", 18, "female");return Arrays.asList(u1, u2);}
}
  • AopDemoController 代码如下:
@RestController
@RequestMapping(value = "/users")
public class AopDemoController {@Autowiredprivate AopDemoService aopDemoService;@GetMapping("/list/{positionCode}")public List<UserInfo> getUserInfo(@PathVariable Long positionCode) {return aopDemoService.getUserInfoListByPositionCode(positionCode);}
}

(1)启动项目,直接在浏览器访问http://localhost:9100/users/list/111 ,获取到正确结果,且打印出相应日志:

在这里插入图片描述

(2) 直接在浏览器访问 http://localhost:9100/users/list/0,可以看到后端正确打印了日志,且打印出了相应的异常信息,可以让维护者快速定位到问题的正确位置,同时前端获取到统一异常处理的结果。

在这里插入图片描述


本文链接:https://www.ngui.cc/el/1113715.html
Copyright © 2010-2022 ngui.cc 版权所有 |关于我们| 联系方式| 豫B2-20100000