Springboot-cli 开发脚手架系列
简介
-
AOP面向切面编程
是通过预编译方式和运行期动态代理,实现在不修改源代码的情况下给程序动态统一添加功能的一种技术,同时是对OOP(面向对象编程)的补充和完善,常被用来在spring中实现日志记录、性能监控等功能。
面向对象实现日志记录,性能监控这些功能时,需要在每个对象中都添加相同的方法,这样就产生了较大的重复工作量和大量的重复代码,不利于维护,使用AOP,可以大大减少代码数量,方便维护。 -
AOP实现原理
Spring 实现AOP思想使⽤的是动态代理技术
默认状况下, Spring会根据被代理对象是否实现接⼝来选择使⽤JDK仍是CGLIB。当被代理对象没有实现
任何接⼝时, Spring会选择CGLIB。当被代理对象实现了接⼝, Spring会选择JDK官⽅的代理技术,不过能够经过配置的⽅式,让Spring强制使⽤CGLIB -
面向切面编程的步骤
接下我们以实战案例讲解AOP的实现步骤
1. 依赖
- pom.xml
<!-- aop依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- json 工具包 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
2. 定义注解
- 自定义一个注解
CustomLog.java
接口。
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Documented
public @interface CustomLog {
/**
* value 描述
*/
String value() default "";
}
- @Retention注解的生命周期
1、RetentionPolicy.SOURCE
:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
2、RetentionPolicy.CLASS
:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
3、RetentionPolicy.RUNTIME
:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在; - @Target:注解的作用目标
@Target(ElementType.TYPE)
——接口、类、枚举、注解
@Target(ElementType.FIELD)
——字段、枚举的常量
@Target(ElementType.METHOD)
——方法
@Target(ElementType.PARAMETER)
——方法参数
@Target(ElementType.CONSTRUCTOR)
——构造函数
@Target(ElementType.LOCAL_VARIABLE)
——局部变量
@Target(ElementType.ANNOTATION_TYPE)
——注解
@Target(ElementType.PACKAGE)
——包
3. 定义切面
- 日记实体封装
SysLog.java
@Data
@Accessors(chain = true)
public class SysLog implements Serializable {
private static final long serialVersionUID = -6309732882044872298L;
/**
* 日记id
*/
private Long logId;
/**
* 用户id
*/
private Long userId;
/**
* 包名
*/
private String packageName;
/**
* 执行时间
*/
private Long executionTime;
/**
* 方法名
*/
private String method;
/**
* 参数
*/
private String params;
/**
* 说明
*/
private String desc;
/**
* 创建日期
*/
private Date createTime;
}
- 切面
@Aspect
@Component
@Slf4j
public class LogAspect {
/**
* 植入触发条件
*/
@Pointcut("@annotation(com.springboot.cli.annotation.CustomLog)")
public void pointcut() {
}
/**
* 环绕增强,相当于MethodInterceptor
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
SysLog sysLog = this.getMethodInfo(joinPoint);
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long end = System.currentTimeMillis();
sysLog.setCreateTime(new Date())
.setExecutionTime(end - start);
log.info(JSON.toJSONString(sysLog));
return proceed;
}
/**
* 获取方法执行信息
*/
private SysLog getMethodInfo(ProceedingJoinPoint joinPoint) {
SysLog sysLog = new SysLog();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
CustomLog customLog = method.getAnnotation(CustomLog.class);
// 注解上的描述
Optional.ofNullable(customLog).ifPresent(c -> sysLog.setDesc(c.value()));
try {
sysLog
.setMethod(joinPoint.getSignature().getName())
.setPackageName(joinPoint.getTarget().getClass().getName())
.setParams(JSON.toJSONString(this.getParameters(joinPoint)));
} catch (Exception e) {
e.printStackTrace();
}
return sysLog;
}
/**
* 获取请求参数
*/
private Map<String, Object> getParameters(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
String[] parameterNames = methodSignature.getParameterNames();
Object[] parameterValues = joinPoint.getArgs();
Map<String, Object> paramsMap = new HashMap<>(2);
for (int i = 0; i < parameterValues.length; i++) {
try {
Object s = parameterValues[i];
paramsMap.put(parameterNames[i], s);
} catch (Exception e) {
e.printStackTrace();
}
}
return paramsMap;
}
/**
* 获取 Request
*/
private HttpServletRequest getRequest() {
return ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
}
}
-
@Aspect
:作用是把当前类标识为一个切面供容器读取 -
@Pointcut
:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。 -
@Around
:环绕增强,相当于MethodInterceptor -
@AfterReturning
:后置增强,相当于AfterReturningAdvice,方法正常退出时执行 -
@Before
:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有 -
@AfterThrowing
:异常抛出增强,相当于ThrowsAdvice -
@After
: final增强,不管是抛出异常或者正常退出都会执行
4. 效果演示
- 编写测试接口
IndexController.java
- 在需要生成日记的方法上添加
@CustomLog
注解
@RestController
public class IndexController {
/**
* 基础web
*/
@GetMapping("/test")
@CustomLog("基础web")
public String test(String msg) {
return "欢迎使用 springboot-cli !";
}
}
- 浏览器输入
http://localhost:9999/test?msg=hello
- 可以看到添加
@CustomLog
注解后,日记就自动生成了,这种规则的日记,在实际开发中对于日记的收集和分析是很有帮助的。
6. 源码分享
- Springboot-cli开发脚手架,集合各种常用框架使用案例,完善的文档,致力于让开发者快速搭建基础环境并让应用跑起来。
- 项目源码github地址
- 项目源码国内gitee地址