Spring中的切面Aspect,这是Spring的一大优势。面向切面编程往往让我们的开发更加低耦合,也大大减少了代码量,同时呢让我们更专注于业务模块的开发,把那些与业务无关的东西提取出去,便于后期的维护和迭代。
相关的学习教程是看的动力节点的视频,然后整理了这篇博客作为笔记,方便以后复习时候看看,也分享给有需要的小伙伴。
首先要理解‘切’字,需要把对象想象成一个立方体,传统的面向对象编程思维,类定义完成之后(封装)。每次实例化一个对象,对类定义中的成员变量赋值,就相当于对这个立方体进行了一个定义,定义完成之后,就等着被使用,等着被回收。
面向切面编程则是指,对于一个我们已经封装好的类,我们可以在编译期间或在运行期间,对其进行切割,把立方体切开,在原有的方法里面添加(织入)一些新的代码,对原有的方法代码进行一次增强处理。而那些增强部分的代码,就被称之为切面,如下面代码实例中的通用日志处理代码,常见的还有事务处理、权限认证等等。
要对哪些类中的哪些方法进行增强,进行切割,指的是被增强的方法。即要切哪些东西。
知道了要切哪些方法后,剩下的就是什么时候切,在原方法的哪一个执行阶段加入增加代码,这个就是连接点。如方法调用前,方法调用后,发生异常时等等。
通知被织入方法,改如何被增强。定义切面的具体实现。那么这里面就涉及到一个问题,空间(切哪里)和时间(什么时候切,在何时加入增加代码),空间我们已经知道了就是切入点中定义的方法,而什么时候切,则是连接点的概念。
被一个或多个切面所通知的对象,即为目标对象。
AOP代理是AOP框架所生成的对象,该对象是目标对象的代理对象。代理对象能够在目标对象的基础上,在相应的连接点上调用通知。
将切面切入到目标方法之中,使目标方法得到增强的过程被称之为织入。
AOP 必须的依赖
|
1 2 3 4 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> |
日志实体类
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.space.aspect.bo; import lombok.Data; /** * 系统日志bo */ @Data public class SysLogBO { private String className; private String methodName; private String params; private Long exeuTime; private String remark; private String createDate; } |
日志service
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package com.space.aspect.service; import com.space.aspect.bo.SysLogBO; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Slf4j @Service public class SysLogService { public boolean save(SysLogBO sysLogBO){ // 这里就不做具体实现了 log.info(sysLogBO.getParams()); return true; } } |
定义日志注解
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.space.aspect.anno; import java.lang.annotation.*; /** * 定义系统日志注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SysLog { String value() default ""; } |
声明切面,完成日志记录
以上4点我们的准备工作已经完成。接下来就是重点了
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
package com.space.aspect.aspect; import com.google.gson.Gson; import com.space.aspect.anno.SysLog; import com.space.aspect.bo.SysLogBO; import com.space.aspect.service.SysLogService; 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.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * 系统日志切面 */ @Aspect // 使用@Aspect注解声明一个切面 @Component public class SysLogAspect { @Autowired private SysLogService sysLogService; /** * 这里我们使用注解的形式 * 当然,我们也可以通过切点表达式直接指定需要拦截的package,需要拦截的class 以及 method * 切点表达式: execution(...) * * execution(public * *(..)) 任意的公共方法 * execution(* set*(..)) 以set开头的所有的方法 * execution(* com.LoggerApply.*(..))com.LoggerApply这个类里的所有的方法 * execution(* com.annotation.*.*(..))com.annotation包下的所有的类的所有的方法 * execution(* com.annotation..*.*(..))com.annotation包及子包下所有的类的所有的方法 * execution(* com.annotation..*.*(String,?,Long)) com.annotation包及子包下所有的类的有三个参数,第一个参数为String类型,第二个参数为任意类型,第三个参数为Long类型的方法 * execution(@annotation(com.lingyejun.annotation.Lingyejun)) */ @Pointcut("@annotation(com.space.aspect.anno.SysLog)") public void logPointCut() {} /** * 环绕通知 @Around , 当然也可以使用 @Before (前置通知) @After (后置通知) * @param point * @return * @throws Throwable */ @Around("logPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { long beginTime = System.currentTimeMillis(); Object result = point.proceed(); long time = System.currentTimeMillis() - beginTime; try { saveLog(point, time); } catch (Exception e) { } return result; } /** * 保存日志 */ private void saveLog(ProceedingJoinPoint joinPoint, long time) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); SysLogBO sysLogBO = new SysLogBO(); sysLogBO.setExeuTime(time); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); sysLogBO.setCreateDate(dateFormat.format(new Date())); SysLog sysLog = method.getAnnotation(SysLog.class); if(sysLog != null){ //注解上的描述 sysLogBO.setRemark(sysLog.value()); } //请求的 类名、方法名 String className = joinPoint.getTarget().getClass().getName(); String methodName = signature.getName(); sysLogBO.setClassName(className); sysLogBO.setMethodName(methodName); //请求的参数 Object[] args = joinPoint.getArgs(); try{ List<String> list = new ArrayList<String>(); for (Object o : args) { list.add(new Gson().toJson(o)); } sysLogBO.setParams(list.toString()); }catch (Exception e){ } sysLogService.save(sysLogBO); } } |
测试
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.space.aspect.controller; import com.space.aspect.anno.SysLog; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @SysLog("测试") @GetMapping("/test") public String test(@RequestParam("name") String name){ return name; } } |
这样,我们就成功实现了使用Aspect实现切面记录日志。
from:https://blog.csdn.net/DDDYSz/article/details/123378554