AOP的业务应用
AOP+线程池实现日志异步入库功能
1.导入AOP依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.创建业务切面对象
2.1创建注解类型
示例:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredLog {
String operation();
}
2.2创建切面对象
示例:
@Aspect
@Component
public class SysLogAspect {
/**
* @Pointcut注解用于定义切入点
* @annotation(定义注解的全路径)为切入点表达式,后续由此注解描述的方法为切入
* 点方法
*/
@Pointcut("@annotation(com.cy.jt.common.annotation.RequiredLog)")
public void doLog(){}//此方法只负责承载切入点的定义
/**
* @Around注解描述的方法,可以在切入点执行之前和之后进行业务拓展,
* @param jp 连接点对象,此对象封装了要执行的目标方法信息.
* 可以通过连接点对象调用目标方法.
* @return 目标方法的执行结果
* @throws Throwable
*/
@Around("doLog()")
public Object doAround(ProceedingJoinPoint jp)throws Throwable{
}
2.3通过注解描述业务方法,此时这个方法为日志切入点方法
示例:
@RequiredLog(operation="公告查询")
@GetMapping
public JsonResult doSelectNotices(SysNotice sysNotice){....}
AOP的底层就是动态代理的实现,所以回顾一下动态代理的内容.
3.回顾动态代理知识点
动态代理方式 | 对比 |
---|---|
JDK动态代理 | 1.要求被代理者,必须实现接口. 2.代理对象根据接口的类型,通过反射机制创建出来一个实现类对象.(JDK源码底层实现的) 3.Spring容器面向接口注入时 ,默认条件下采用JDK动态代理的方式实例化对象. |
Cglib动态代理 | 1.CGLIB是JDK代理的一种补充. 2.不管被代理者是否实现接口,都可以为其创建代理对象 3.代理对象是被代理者的子类.(AOP默认采用Cglib代理) |
4.利用反射获取类中资源
//ProceedingJoinPoint 连接点
private void doLogInfo(ProceedingJoinPoint jp,
...)
throws Exception {
//1.3获取操作名(operation)-@RequiredLog注解中value属性的值
//1.3.1获取目标对象类
Class<?> targetCls=jp.getTarget().getClass();
//1.3.2获取目标方法
//先获取方法签名
MethodSignature ms=
(MethodSignature) jp.getSignature();
//利用目标对象类获取目标方法
Method targetMethod=targetCls.getMethod(
ms.getName(),ms.getParameterTypes());
//1.3.3 获取方法上RequiredLog注解
RequiredLog annotation =
targetMethod.getAnnotation(RequiredLog.class);
//1.3.4 获取注解中定义的操作名
String operation=annotation.operation();
//1.4获取方法声明(类全名+方法名)
String classMethodName=
targetCls.getName()+"."+targetMethod.getName();
//1.5获取方法实际参数信息
Object[]args=jp.getArgs();
//将参数对象序列化
String params=new ObjectMapper().writeValueAsString(args);
....
}
}
5.配置线程池
线程池的配置:(实例)
spring:
task:
#springboot中线程池的配置
execution:
pool:
//core-size :核心线程数
core-size: 8
//queue-capacity:队列容量
queue-capacity: 512
//max-size: 最大线程数
max-size: 128
//keep-alive:线程空闲时间
keep-alive: 60000
//thread-name-prefix:线程名的前缀
thread-name-prefix: aysnc-service-task-
//说明:
//core-size :核心线程数,当池中线程数没达到core-size的值时,每接收一个新的任务都会创建一个新线程,然后存储到池。假如池中线程数已经达到core-size设置的值,再接收新的任务时,要检测是否有空闲的核心线程,假如有,则使用空闲的核心线程执行新的任务。
//queue-capacity:队列容量,假如核心线程数已达到core-size设置的值,并且所有的核心线程都在忙,再来新的任务,会将任务存储到任务队列。
//max-size: 最大线程数,当任务队列已满,核心线程也都在忙,再来新的任务则会创建新的线程,但所有线程数不能超过max-size设置的值,否则可能会出现异常(拒绝执行)
//keep-alive:线程空闲时间,假如池中的线程数多余core-size设置的值,此时又没有新的任务,则一旦空闲线程空闲时间超过keep-alive设置的时间值,则会被释放。
//thread-name-prefix:线程名的前缀,项目中设置线程名的目的主要是为了对线程进行识别,一旦出现线程问题,可以更好的定位问题。
6.异步实现日志写入数据库:
6.1启动异步
@EnableAsync
@SpringBootApplication
public class SystemApplication {
public static void main(String[] args) {
SpringApplication.run(DbpmsApplication.class, args);
}
}
6.2定义切入点方法
@Async
public void saveLog(SysLog entity){
sysLogDao.insertLog(entity);
}