1.简单回顾AOP
- @Before 前置通知(Before advice) :在某连接点(JoinPoint)——核心代码(类或者方法)之前执行的通知,但这个通知不能阻止连接点前的执行。为啥不能阻止线程进入核心代码呢?因为@Before注解的方法入参不能传ProceedingJoinPoint,而只能传入JoinPoint。要知道从aop走到核心代码就是通过调用ProceedingJionPoint的proceed()方法。而JoinPoint没有这个方法。
- 这里牵扯区别这两个类:Proceedingjoinpoint 继承了 JoinPoint 。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。暴露出这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,这跟切面类型有关), 能决定是否走代理链还是走自己拦截的其他逻辑。建议看一下 JdkDynamicAopProxy的invoke方法,了解一下代理链的执行原理。这样你就能明白 proceed方法的重要性。
- @After 后通知(After advice) :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- @AfterReturning 返回后通知(After return advice) :在某连接点正常完成后执行的通知,不包括抛出异常的情况。
- @Around 环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。这时aop的最重要的,最常用的注解。用这个注解的方法入参传的是ProceedingJionPoint pjp,可以决定当前线程能否进入核心方法中——通过调用pjp.proceed();
- @AfterThrowing 抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。
2.spring boot AOP使用流程
- 首先将一个类声明成切面类
@Aspect
@Component
public class LogAop {}
- 接着定义切点,在此使用注解切点
@Pointcut(value = "@annotation(cn.stylefeng.guns.core.common.annotion.BussinessLog)")
public void cutService() { }
- 接着定义建言
@Around("cutService()")
public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
//先执行业务
Object result = point.proceed();
try {
handle(point);
} catch (Exception e) {
log.error("日志记录出错!", e);
}
return result;
}
- 最后在需要织入建言的方法上加入注解即可
@RequestMapping(value = "/add")
@BussinessLog(value = "添加角色", key = "name", dict = RoleDict.class)
public ResponseData add(@Valid Role role, BindingResult result) {
if (result.hasErrors()) {
throw new ServiceException(BizExceptionEnum.REQUEST_NULL);
}
role.setId(null);
this.roleService.insert(role);
return SUCCESS_TIP;
}
3.日志框架的基本功能
- 对用户的登陆状态进行记录(成功、失败、退出)------直接在相应登陆控制器上记录日志
- 对异常操作进行记录(自定义异常和运行时异常)------直接在全局异常拦截控制器上记录日志
- 对业务操作进行记录(增删改查)------使用AOP方式记录日志
4.日志框架实现
注意: 在实际记录日志时候,需要开启线程记录,因为记录操作会非常多防止堵塞主线程
- 第一种方法如guns一样自定义线程池
//异步操作记录日志的线程池
private ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(10);
- 第二种方法使用spring的线程池,需要配置相关的配置类,之后在需要执行的方法打上@Async上打上注解即可异步调用
@Async
public void async(){}
5.分析AOP的主要逻辑
- 可以看到主要方法在handle(),并且是在执行具体业务逻辑之后才开始执行aop的逻辑
@Around("cutService()")
public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
//先执行业务
Object result = point.proceed();
try {
handle(point);
} catch (Exception e) {
log.error("日志记录出错!", e);
}
return result;
}
- 首先获取的是拦截方法名
private void handle(ProceedingJoinPoint point) throws Exception {
//获取拦截的方法名
Signature sig = point.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
msig = (MethodSignature) sig;
Object target = point.getTarget();
Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
String methodName = currentMethod.getName();
- 下方是一些常用操作
//拦截的实体类
Object target = point.getTarget();
//拦截的方法名称
String methodName = point.getSignature().getName();
//拦截的方法参数
Object[] args = point.getArgs();
//拦截的放参数类型
Class[] parameterTypes = ((MethodSignature)point.getSignature()).getMethod().getParameterTypes();
Method m = null;
try {
//通过反射获得拦截的method
m = target.getClass().getMethod(methodName, parameterTypes);
//如果是桥则要获得实际拦截的method
if(m.isBridge()){
for(int i = 0; i < args.length; i++){
//获得泛型类型
Class genClazz = GenericsUtils.getSuperClassGenricType(target.getClass());
//根据实际参数类型替换parameterType中的类型
if(args[i].getClass().isAssignableFrom(genClazz)){
parameterTypes[i] = genClazz;
}
}
//获得parameterType参数类型的方法
m = target.getClass().getMethod(methodName, parameterTypes);
- 获取注解上的值写入日志 ,包括业务内容value、主键key、字典类dict
//获取操作名称
BussinessLog annotation = currentMethod.getAnnotation(BussinessLog.class);
String bussinessName = annotation.value();
String key = annotation.key();
Class dictClass = annotation.dict();
@RequestMapping(value = "/add")
@BussinessLog(value = "添加角色", key = "name", dict = RoleDict.class)
@Permission(Const.ADMIN_NAME)
@ResponseBody
public ResponseData add(@Valid Role role, BindingResult result) {
if (result.hasErrors()) {
throw new ServiceException(BizExceptionEnum.REQUEST_NULL);
}
role.setId(null);
this.roleService.insert(role);
return SUCCESS_TIP;
}
---------------------
参考原文:https://blog.csdn.net/zhanglf02/article/details/78132304