此文章最先发布于我的个人博客,CSDN为同步发布,访问 腿短快跑的个人博客 可获取更多内容
什么是aop
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
我们知道,C语言 是一门面向过程的编程语言,面向过程是十分简单的,但是代码之间的耦合度很高,且无法对现实世界很好的建模,因此出现了 面向对象(OOP) 语言,例如:Java,面向对象可以让我们对现实世界更好的建模,但是对于一些场景仍然无法很好的实现,例如,需要在某一类方法执行之前或之后执行一系列的操作,因此,出现了 面向切面(AOP) 来弥补 面向对象(OOP) 的不足
实现
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
基础model
NoPackageResult.java 使用此注解标记的 class 或者 method,将不会封装返回结果
package cn.tdkpcw.aop.annotations;
import java.lang.annotation.*;
/**
* @author C.W
* @date 2022/6/7 7:45
* @desc 不需要结果封装
*/
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoPackageResult {
}
ErrorCodeEnum.class 用于定义业务编码
package cn.tdkpcw.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author C.W
* @date 2022/6/7 7:49
* @desc 错误码
*/
@Getter
@AllArgsConstructor
public enum ErrorCodeEnum {
NO_LOGIN(401, "您还未登录, 请登录后操作"),
SYSTEM_ERROR(500, "系统异常"),
SUCCESS(200, "success"),
PARAMS_ERROR(400, "%s"),
AUTH_FAIL(1000, "用户名或密码不正确");
private final Integer code;
private final String desc;
}
BizException.class 用于定义业务异常
package cn.tdkpcw.exception;
import cn.tdkpcw.enums.ErrorCodeEnum;
import lombok.Data;
/**
* @author C.W
* @date 2022/5/9 19:41
* @desc 异常
*/
@Data
public class BizException extends RuntimeException {
private Integer code;
public BizException(ErrorCodeEnum errorCodeEnum, String... params) {
super(String.format(errorCodeEnum.getDesc(), params));
this.code = errorCodeEnum.getCode();
}
}
RespModel.java 用于定义统一返回结果结构
package cn.tdkpcw.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author C.W
* @date 2022/5/9 15:34
* @desc 响应结果
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RespModel<T> {
public static final Integer SUCCESS_CODE = 200;
public static final Integer ERROR_CODE = 400;
private Integer code;
private String message;
private Long time;
private T data;
}
实现切面
package cn.tdkpcw.aop;
import cn.tdkpcw.aop.annotations.NoPackageResult;
import cn.tdkpcw.enums.ErrorCodeEnum;
import cn.tdkpcw.exception.BizException;
import cn.tdkpcw.model.RespModel;
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.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import java.lang.reflect.Method;
/**
* @author C.W
* @date 2022/6/6 17:08
* @desc 结果封装
*/
@Slf4j
@Aspect
@Component
public class ResultAspect {
@Pointcut("execution(* cn.tdkpcw.controller.rest.*..*(..))")
public void pointCut() {
}
@Around("pointCut()")
public Object aspect(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 转换为method
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
// 包装结果
return packageResult(proceedingJoinPoint, method);
}
/**
* 包装结果
*
* @param proceedingJoinPoint
* @param method
* @return
* @throws Throwable
*/
private Object packageResult(ProceedingJoinPoint proceedingJoinPoint, Method method) throws Throwable {
Class<?> returnType = method.getReturnType();
long startTime = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();
long time = System.currentTimeMillis() - startTime;
// void不需要包装
if (returnType.equals(void.class) || returnType.equals(Void.class)) {
return result;
}
// 设置了不需要包装的接口
NoPackageResult noPackageResult = method.getAnnotation(NoPackageResult.class);
if (noPackageResult == null) {
noPackageResult = method.getDeclaringClass().getAnnotation(NoPackageResult.class);
}
if (noPackageResult != null) {
return result;
}
// 非rest接口不需要包装
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
GetMapping getMapping = method.getAnnotation(GetMapping.class);
PostMapping postMapping = method.getAnnotation(PostMapping.class);
DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
PutMapping putMapping = method.getAnnotation(PutMapping.class);
PatchMapping patchMapping = method.getAnnotation(PatchMapping.class);
if (requestMapping != null
|| getMapping != null
|| postMapping != null
|| deleteMapping != null
|| putMapping != null
|| patchMapping != null
) {
// 包装结果
if (result == null) {
return RespModel.builder()
.code(ErrorCodeEnum.SYSTEM_ERROR.getCode())
.message(ErrorCodeEnum.SYSTEM_ERROR.getDesc())
.time(time)
.build();
} else {
if (result instanceof RespModel) {
RespModel resultVO = (RespModel) result;
resultVO.setTime(time);
return resultVO;
} else {
return RespModel.builder()
.code(ErrorCodeEnum.SUCCESS.getCode())
.message(ErrorCodeEnum.SUCCESS.getDesc())
.data(result)
.time(time)
.build();
}
}
} else {
return result;
}
}
}
**代码分析: **
- @Aspect 注解用于声明这是一个切面类、@Component 注解用于将切面交由 Spring 管理
- @Pointcut 注解用于定义一个切面,上述代码中的切面表示
cn.tdkpcw.controller.rest
包及其子包下的所有类和方法 - @Around(“pointCut()”) 表示此方法应用于
Pointcut
切面,Around 表示在切点的前后都执行此方法,还有以下类型- @Before
- @After
- @Around
- @AfterReturning
- @AfterThrowing
此处主要讲解统一结果封装,仅讲解 @Around 的使用,其余注解此处不做详细讲解,后续其他文章会详细讲解作用和执行顺序
Rest接口
package cn.tdkpcw.controller.rest;
import cn.tdkpcw.aop.annotations.NoPackageResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author C.W
* @date 2022/6/14 7:24
* @desc
*/
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("test1")
public Object test1() {
return "这是测试结果1";
}
@NoPackageResult
@GetMapping("test2")
public Object test2() {
return "这是测试结果2";
}
}
结果验证
/test/test1 接口,此接口会自动封装结果
/test/test2 接口,此接口不会自动封装结果
至此,我们就实现了统一的结果封装,大家写代码再也不需要自己手动封装指定的结构啦