目录
1.什么是AOP切面
2.理解AOP
3.AOP实例
1.自定义注解
2.创建一个切面类
3.将自定义注解标注在测试接口上
1.什么是AOP切面
AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一。
在项目中经常会有些系统性的需求,例如权限校验,日志记录,统计等,这时我们就可以通过AOP切面去实现。
有多少业务代码就需要写多少重复校验和日志记录,这显然是不合理的,我们可以把这些重复操作抽离出来,写成公共的方法。
这样,代码冗余解决了,但是每个地方都要手动去调用还是很麻烦,有没有更好的方式呢?这就要用到我们的AOP(面向切面编程),AOP将权限校验,日志记录等非业务代码提取出来,和业务代码完全分离,并寻找节点切入业务代码中
2.理解AOP
简单理解AOP,AOP主要做三件事
- 在哪里切入,也就是日志记录等非业务代码在哪些业务代码中执行。
- 在什么时候切入,是在业务代码执行前还是后。
- 切入后做什么事情,比如权限校验,日志记录等
理解如下图
一些概念理解:
Pointcut:切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
Advice:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
Aspect:切面,即Pointcut和Advice。
Joint point:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。
3.AOP实例
- 自定义一个注解OperationLogAnnotation
- 创建一个切面类,切点设置为拦截标注OperationLogAnnotation的方法,截取传参,进行日志记录
- 将OperationLogAnnotation标注在测试接口上
具体的实现步骤如下:
1.自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLogAnnotion {//操作类型@NonNullOperationType type();//操作对象@NonNullOperationObjectType objType();}
public enum OperationType {CREATE("create"),UPDATE("update"),DELETE("delete"),ADD("add");private String type;OperationType(String type){this.type=type;}public String getType() {return type;}public void setType(String type) {this.type = type;}
}
public enum OperationObjectType {USER("user"),//项目PROJECT("project"),//页面管理PROJECT_PAGE("project page"),//人员管理ROLE_USER("role user"),//角色页面管理ROLE_PAGE("role page");private String objType;OperationObjectType(String objType){this.objType=objType;}public String getObjType() {return objType;}public void setObjType(String objType) {this.objType = objType;}
}
2.创建一个切面类
@Aspect
@Component
public class LogAdvice implements ApplicationListener<ContextRefreshedEvent> {private BlockingQueue<OperationLog> operationLogQ = new LinkedBlockingDeque<>();@Autowiredprivate IOperationLogService operationLogService;private static final Logger LOGGER = LogManager.getLogger();@Pointcut("@annotation(com.pwd.springdemo.aop.annotation.OperationLogAnnotion)")public void logAdvicePointcut(){}@AfterReturning(pointcut = "logAdvicePointcut() && @annotation(operationLogAnnotion)", returning = "returnResult")public void logAdvice(JoinPoint joinPoint, com.pwd.springdemo.aop.annotation.OperationLogAnnotion operationLogAnnotion, Object returnResult){try {if (!(returnResult instanceof ResultInfo)) {return;}//filter failed requestResultInfo result = (ResultInfo) returnResult;if (result.getCode() != ResultCode.SUCCESS.getCode()) {return;}HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();Object[] args = joinPoint.getArgs();String requestParams = JSONObject.toJSONString(args);String userIp = request.getHeader("UserIp");User user = (User) request.getSession().getAttribute("user");String userId = null;if (user != null) {userId = user.getUserId();}String type = operationLogAnnotion.type().getType();String objType = operationLogAnnotion.objType().getObjType();Signature signature = joinPoint.getSignature();String functionName=signature.getName();OperationLog log = new OperationLog(userId, userIp, functionName,type + " " + objType, requestParams);operationLogQ.add(log);} catch (Exception e) {LOGGER.error("operation log aspect error", e);}}/*** consume queue 2 save db*/private void saveLogs() {while (true) {try {OperationLog log = operationLogQ.poll(5, TimeUnit.MILLISECONDS);if (log != null) {operationLogService.addLog(log);}} catch (Exception e) {LOGGER.error("insert operation log to db error", e);}}}@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {ExecutorService pool = new ThreadPoolExecutor(1, 1,0L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(), new ThreadFactoryBuilder().setNameFormat("Save-Logs-%d").setDaemon(true).build());pool.submit(new Runnable() {@Overridepublic void run() {saveLogs();}});}
}
3.将自定义注解标注在测试接口上
@ResponseBody@OperationLogAnnotion(type = OperationType.DELETE,objType = OperationObjectType.USER)@RequestMapping(value = "deleteUserInfoById", method = RequestMethod.POST)public ResultInfo deleteUserInfoById(@RequestBody JSONObject jsonObject) {Integer id = jsonObject.getInteger("id");LOGGER.info("id:" + id);userService.deleteUserInfoById(id);return ResultInfo.success(null);}