中国建设很行河北省分行合作网站,成都有啥好玩的地方,微网站费用,北京朝阳区建设工作办公网站** Spring有两大核心特性#xff1a;IOC(控制反转) 和 AOP(面向切面编程)#xff0c;但是 相比IOC在日常工作中的广泛应用#xff0c;AOP却常常做了冷板凳#xff0c;下面我从工作中的场景为大家打开AOP面向切面编程的大门。**
什么是AOP?
在软件业#xff0c;AOP为Asp…** Spring有两大核心特性IOC(控制反转) 和 AOP(面向切面编程)但是 相比IOC在日常工作中的广泛应用AOP却常常做了冷板凳下面我从工作中的场景为大家打开AOP面向切面编程的大门。**
什么是AOP?
在软件业AOP为Aspect Oriented Programming的缩写意为面向切面编程通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续是软件开发中的一个热点也是Spring框架中的一个重要内容是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离从而使得业务逻辑各部分之间的耦合度降低提高程序的可重用性同时提高了开发的效率。
下面用一张图理解一下AOP 可以看到通过AOP技术我们可以通过将业务中的通用部分抽取出来并对业务进行加强而我们只需要关心具体的业务逻辑即可。
下面是AOP相关的一些概念 Aspect切面 Aspect 声明类似于 Java 中的类声明在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。 Joint point连接点表示在程序中明确定义的点典型的包括方法调用对类成员的访问以及异常处理程序块的执行等等它自身还可以嵌套其它 joint point。 Pointcut切点表示一组 joint point这些 joint point 或是通过逻辑关系组合起来或是通过通配、正则表达式等方式集中起来它定义了相应的 Advice 将要发生的地方。 Advice增强Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。 Target目标对象织入 Advice 的目标对象.。 Weaving织入将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
那么在工作中AOP具体怎么用呢
场景1: 接口限流
背景假设我们需要对一些接口进行限流要求同一个用户指定时间内不可重复请求。
代码实现 首先在类上面添加Aspect标识这是一个切面类 并添加Component注解让其被Spring发现 Slf4j
Aspect
Component
public class RateLimitingAspect {创建切面方法并定义增强类型及切入点 Around(annotation(com.beansmile.common.config.RateLimiting))public Object rateLimiting(ProceedingJoinPoint pjp) throws Throwable {其中Around代表增强类型为环绕增强、除此之外还有before(执行前) after(执行后)注解的内容是设置的切入点这里设置的为添加RateLimiting注解的地方此处也可以通过表达式设置为 xxx包下的类、xxx类下方法等等 创建自定义注解 RateLimiting Retention(RetentionPolicy.RUNTIME)
Target(ElementType.METHOD)
public interface RateLimiting {/*** 间隔时间单位秒*/int limitInSeconds() default 2;/*** 返回信息*/String returnMsg() default 您的请求太快了请稍后提交;
}具体逻辑代码 Around(annotation(com.beansmile.common.config.RateLimiting))public Object rateLimiting(ProceedingJoinPoint pjp) throws Throwable {String method pjp.getSignature().toShortString();// lockKey userId:xxxController.xxxMethod()String lockKey AuthUtil.getUserId() : method;MethodSignature methodSignature (MethodSignature) pjp.getSignature();Method signatureMethod methodSignature.getMethod();// 拿到注解内容RateLimiting rateLimitingAnnotation signatureMethod.getAnnotation(RateLimiting.class);String returnMsg rateLimitingAnnotation.returnMsg();int limitInSeconds rateLimitingAnnotation.limitInSeconds();Boolean lock redisTemplate.opsForValue().setIfAbsent(method, 1, limitInSeconds, TimeUnit.SECONDS);if (BooleanUtils.isTrue(lock)) {// 放行return pjp.proceed();} else {log.info(请求限流lockKey{}间隔时间{}, lockKey, limitInSeconds);throw new ServiceException(returnMsg);}}上述代码中我们以 userId 类名称 方法名称 作为唯一标识通过redis锁的方式进行限流。
测试代码: 这里我们设置了请求间隔为1秒RequestMapping(value /test, method RequestMethod.GET)RateLimiting(limitInSeconds 1)public Result? test() {// 具体业务逻辑// xxxxxxxxxxxxxreturn Result.OK(操作成功);}正常请求 快速请求 可以看到我们通过使用AOP的方式实现了 接口限流的逻辑并且没有修改任何业务逻辑。
场景2: 全局异常捕获
背景在程序运行时不可避免的会出现一些异常, 如NullPointerException空指针、ArrayIndexOutOfBoundsException数组下标越界等, 这些异常直接抛到前端页面会导致用户体验不好过多的try/catch捕捉又会导致代码冗余。
代码实现
定义切入点 Pointcut(execution(public * com.beansmile.modules..*.*Controller.*(..))))public void exceptionPointcut() {}这里定义的切入点为 com.beansmile.modules包下 所有控制器下的方法。
创建异常枚举类
Getter
AllArgsConstructor
public enum ExceptionEnum {/*** NullPointerException*/NULL_POINTER(10001, NullPointerException.class, 空指针异常),/*** ArrayIndexOutOfBoundsException*/ARRAY_INDEX_OUT_OF_BOUND(10002, ArrayIndexOutOfBoundsException.class, 数组下标越界),/*** IOException*/IO(10003, IOException.class, 输入输出异常);final int code;final Class? clazz;final String name;public static ExceptionEnum getEnumByClazz(Class? clazz) {for (ExceptionEnum exceptionEnum : values()) {if (clazz.isAssignableFrom(exceptionEnum.clazz)) {return exceptionEnum;}}return null;}
}具体逻辑代码 Around(exceptionPointcut())public Object exceptionHandling(ProceedingJoinPoint pjp) throws Throwable {try {return pjp.proceed();} catch (Exception e) {// 通过枚举类获取异常信息ExceptionEnum exceptionEnum ExceptionEnum.getEnumByClazz(e.getClass());if (null exceptionEnum) throw e;// 打印异常信息printExceptionInfo(pjp, e);// 抛出自定义异常并携带错误码用于快速排查。throw new JeecgBootException(网络开小差了请稍后重试或联系客服[ exceptionEnum.getCode() ]);}}private void printExceptionInfo(ProceedingJoinPoint pjp, Exception e) {String methodName pjp.getSignature().getName();String className pjp.getTarget().getClass().getSimpleName();log.error(printExceptionInfo_类名{}方法名{}异常信息{}, className, methodName, e.toString());e.printStackTrace();}上述代码中通过对异常时进行切入对异常做了统一抛出和打印日志。
测试代码 RequestMapping(value /test, method RequestMethod.GET)public Result? test() {// 模拟空指针异常String str null;str.length();return Result.OK(操作成功);}请求接口进行测试符合预期统一错误返回并携带错误码 查看后端日志: 保留了原始错误信息并快速记录了错误的发生源头。
printExceptionInfo_类名ApprovalFlowController方法名test异常信息java.lang.NullPointerException
java.lang.NullPointerException