SpringBoot+AOP(@Around)
虽然SpringBoot很方便,可以使我们不太懂原理的情况下都可以轻松的写出一个CRUD的项目,但是SpringBoot的控制反转和依赖注入我们时时刻刻在用,可面向切面编程我们却不常用,接下来我们来一次简单的面向切面编程实现日志增强的例子(AOP的主要功能就是将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来)
pom文件导入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
其实有更新的版本,但是不知道为什么,1.9.6版本我就是导入不成功,所以就退而求其次了。
二、新建一个@Annootation
目录右键新建JavaClass,选择@Annotation
package com.crud.aop;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Documented //这个其实个人感觉没啥用,主要是在javadoc上生成注解用的
@Target(ElementType.METHOD)//注解放置的目标位置,METHOD是可注解在方法级别上
/**
* 1.CONSTRUCTOR:用于描述构造器
* 2.FIELD:用于描述域
* 3.LOCAL_VARIABLE:用于描述局部变量
* 4.METHOD:用于描述方法
* 5.PACKAGE:用于描述包
* 6.PARAMETER:用于描述参数
* 7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
* **/
@Retention(RetentionPolicy.RUNTIME)
/**
* 1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
* 2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
* 3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
* 首先要明确生命周期长度SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,
* 那只能用RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如ButterKnife),就用CLASS注解;如果只是做一些检查性的操作,
* 比如@Override 和@SuppressWarnings,则可选用SOURCE 注解。
* **/
public @interface LogAnnotation {
String moudle() default ""; //模块
String operate() default ""; //操作
}
三、创建切面类
package com.crud.aop;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonAlias;
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 java.lang.reflect.Method;
@Aspect
@Component
@Slf4j
public class LogAspect {
/**
* 设置操作日志切入点 记录操作日志 在注解的位置切入代码
* @Pointcut("execution(* com.crud.controller.BlogController.getBlogById())")
* 上面的这种是excution表达式,比如说我并不想开发一个专门的注解,我就想在某一个或者某一类的方法上添加这个aop,那就可以通过excution表达式来做
* @Pointcut("execution(public * com.crud.controller.*.*(..))")
* ..两个点表明多个,*代表一个, 上面表达式代表切入com.xhx.springboot.controller包下的所有类的所有方法,
* 方法参数不限,返回类型不限。 其中访问修饰符可以不写,不能用*,,第一个*代表返回类型不限,第二个*表示所有类,第三个*表示所有方法,..两个点表示方法里的参数不限
* 有一点非常重要,Spring的AOP只能支持到方法级别的切入。换句话说,切入点只能是某个方法
*/
@Pointcut("@annotation(com.crud.aop.LogAnnotation)")
public void logAspectPoint(){}
@Around("logAspectPoint()")
public Object log(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//前置
long startTime= System.currentTimeMillis();
/**
* 环绕通知=前置+目标方法执行+后置,proceed方法就是用于启动目标方法执行的
* Proceedingjoinpoint 继承了 JoinPoint。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。
* 暴露出这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,,这也是环绕通知和前置、后置通知方法的一个最大区别。这跟切面类型有关)
* */
Object result=proceedingJoinPoint.proceed();
//后置
long time=System.currentTimeMillis()-startTime;
recordLog(proceedingJoinPoint,time);
return result;
}
public void recordLog(ProceedingJoinPoint proceedingJoinPoint,long time){
//getSignature());是获取到这样的信息 :修饰符+ 包名+组件名(类名) +方法名
MethodSignature methodSignature= (MethodSignature) proceedingJoinPoint.getSignature();
Method method=methodSignature.getMethod();
//getAnnotation:方法如果存在这样的注释,则返回指定类型的元素的注释,否则为null
LogAnnotation logAnnotation=method.getAnnotation(LogAnnotation.class);
log.info("==============================开始记录日志===============================");
log.info("moudle:{}",logAnnotation.moudle());
log.info("operato:{}",logAnnotation.operate());
//proceedingJoinPoint.getTarget():获取切入点所在目标对象
String className=proceedingJoinPoint.getTarget().getClass().getName();
String methodName=methodSignature.getName();
log.info("请求的方法是:{}",className+"."+methodName+"()");
//这里返回的是切入点方法的参数列表
Object[] args=proceedingJoinPoint.getArgs();
String params= JSON.toJSONString(args[0]);
log.info("请求的参数是:{}",params);
log.info("执行时间总共为:{}",time);
log.info("=================================end===================================");
}
}
四、方法上添加注解
package com.crud.controller;
import com.crud.aop.LogAnnotation;
import com.crud.entity.Blog;
import com.crud.service.impl.BlogServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/crud")
@Slf4j
public class BlogController {
@Autowired
private BlogServiceImpl blogServiceImpl;
@LogAnnotation(moudle = "博客",operate = "根据博客id获取博客详情")
@RequestMapping(value = "/getById",method = RequestMethod.POST)
public List<Blog> getBlogById(@RequestParam(value = "id") Integer id){
return blogServiceImpl.getBlogByUserId(id);
}
}
五、查看执行结果
15:19:00.279 [http-nio-8082-exec-1] INFO com.crud.aop.LogAspect - ==============================开始记录日志===============================
15:19:00.280 [http-nio-8082-exec-1] INFO com.crud.aop.LogAspect - moudle:博客
15:19:00.280 [http-nio-8082-exec-1] INFO com.crud.aop.LogAspect - operato:根据博客id获取博客详情
15:19:00.280 [http-nio-8082-exec-1] INFO com.crud.aop.LogAspect - 请求的方法是:com.crud.controller.BlogController.getBlogById()
15:19:00.325 [http-nio-8082-exec-1] INFO com.crud.aop.LogAspect - 请求的参数是:1
15:19:00.325 [http-nio-8082-exec-1] INFO com.crud.aop.LogAspect - 执行时间总共为:875
15:19:00.325 [http-nio-8082-exec-1] INFO com.crud.aop.LogAspect - =================================end===================================