|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "spring aop记录访问日志 " |
| 4 | +date: 2021-05-15 20:45:59 +0800 |
| 5 | +tags: java |
| 6 | +description: |
| 7 | +--- |
| 8 | + |
| 9 | +spring框架最大的特点之一就是AOP(Aspect Oriented Programming)。也就是面向切面编程。 |
| 10 | + |
| 11 | +可以在对原本的逻辑不产生修改的情况下,对原来的函数进行增强。大大降低代码的耦合度,提高程序的可重用性。可以说是面向对象思想下的一个衍生,但是在某些情况下,要优于面向对象。比如日志、鉴权等情况下,使用AOP,可以很轻松的进行实现。本文对系统访问日志采用AOP的方式进行开发。 |
| 12 | + |
| 13 | +## 引入依赖 |
| 14 | +不论是spring boot 还是 springMVC都需要引用下面的两个包 |
| 15 | +{% highlight java %} |
| 16 | +<dependency> |
| 17 | + <groupId>org.aspectj</groupId> |
| 18 | + <artifactId>aspectjweaver</artifactId> |
| 19 | + <version>1.9.6</version> |
| 20 | +</dependency> |
| 21 | +<dependency> |
| 22 | + <groupId>org.aspectj</groupId> |
| 23 | + <artifactId>aspectjrt</artifactId> |
| 24 | + <version>1.9.6</version> |
| 25 | +</dependency> |
| 26 | +{% endhighlight %} |
| 27 | + |
| 28 | +在spring boot中还需要引入 |
| 29 | +{% highlight java %} |
| 30 | +<dependency> |
| 31 | + <groupId>org.springframework.boot</groupId> |
| 32 | + <artifactId>spring-boot-starter-aop</artifactId> |
| 33 | +</dependency> |
| 34 | +{% endhighlight %} |
| 35 | + |
| 36 | +在springMVC中,需要引入 |
| 37 | +{% highlight java %} |
| 38 | +<dependency> |
| 39 | + <groupId>org.springframework</groupId> |
| 40 | + <artifactId>spring-aop</artifactId> |
| 41 | + <version>5.2.3.RELEASE</version> |
| 42 | +</dependency> |
| 43 | +{% endhighlight %} |
| 44 | + |
| 45 | +## 创建切面类 |
| 46 | + |
| 47 | +一个切面类需要定义一个切点(一般情况下是某个函数,也可能是某个属性等)。再根据切点进行增强处理。对于系统访问日志来说,我做了如下两个后置增强 |
| 48 | +{% highlight java %} |
| 49 | +@Aspect |
| 50 | +@Component |
| 51 | +public class LogAspect { |
| 52 | + |
| 53 | + private HttpServletRequest request; |
| 54 | + |
| 55 | + //创建切点。所有的控制器 |
| 56 | + @Pointcut("execution(* com.bc.center.controller.*.*Controller.*(..))") |
| 57 | + public void cutPoint(){ |
| 58 | + } |
| 59 | + |
| 60 | + /** |
| 61 | + * 前置赋值 |
| 62 | + */ |
| 63 | + @Before("cutPoint()") |
| 64 | + public void beforeController(){ |
| 65 | + request = getRequest(); |
| 66 | + } |
| 67 | + |
| 68 | + /** |
| 69 | + * 请求成功记录 |
| 70 | + */ |
| 71 | + @AfterReturning("cutPoint()") |
| 72 | + public void requestSuccess(){ |
| 73 | + SystemLogDTO systemLogDTO = new SystemLogDTO(); |
| 74 | + systemLogDTO.setIsError(0); |
| 75 | + saveLog(systemLogDTO); |
| 76 | + } |
| 77 | + |
| 78 | + /** |
| 79 | + * 请求记录失败 |
| 80 | + */ |
| 81 | + @AfterThrowing(value = "cutPoint()",throwing = "e") |
| 82 | + public void requestFail(Throwable e){ |
| 83 | + SystemLogDTO systemLogDTO = new SystemLogDTO(); |
| 84 | + systemLogDTO.setIsError(1); |
| 85 | + //获取异常位置 |
| 86 | + Map errorDetail = new HashMap(2); |
| 87 | + StackTraceElement[] stack = e.getStackTrace(); |
| 88 | + for (StackTraceElement stackTraceElement : stack){ |
| 89 | + String className = stackTraceElement.getClassName(); |
| 90 | + int lineNumber = stackTraceElement.getLineNumber(); |
| 91 | + if(className.contains("com.bc") && lineNumber > 0){ |
| 92 | + //存在当前项目目录异常,记录异常类和行数 |
| 93 | + errorDetail.put("className",className); |
| 94 | + errorDetail.put("lineNumber",lineNumber); |
| 95 | + } |
| 96 | + } |
| 97 | + if(errorDetail.size() > 0){ |
| 98 | + errorDetail.put("message",e.getMessage()); |
| 99 | + //记录成功,保存到对象中 |
| 100 | + systemLogDTO.setErrorDetail(JsonUtil.toJSONString(errorDetail)); |
| 101 | + } |
| 102 | + |
| 103 | + saveLog(systemLogDTO); |
| 104 | + } |
| 105 | +} |
| 106 | +{% endhighlight %} |
| 107 | + |
| 108 | +DTO类的属性如下: |
| 109 | +{% highlight java %} |
| 110 | +public class SystemLogDTO { |
| 111 | + |
| 112 | + /** |
| 113 | + * 请求url |
| 114 | + */ |
| 115 | + private String uri; |
| 116 | + |
| 117 | + /** |
| 118 | + * 请求方式 |
| 119 | + */ |
| 120 | + private String requestType; |
| 121 | + |
| 122 | + /** |
| 123 | + * 请求参数 |
| 124 | + */ |
| 125 | + private String params; |
| 126 | + |
| 127 | + /** |
| 128 | + * 是否是抛出异常时的请求 |
| 129 | + */ |
| 130 | + private Integer isError; |
| 131 | + |
| 132 | + /** |
| 133 | + * 存在异常时,记录应用内的 |
| 134 | + */ |
| 135 | + private String errorDetail; |
| 136 | + |
| 137 | +} |
| 138 | +{% endhighlight %} |
| 139 | + |
| 140 | +@Aspect:声明当前类是切面类。 |
| 141 | + |
| 142 | +@Pointcut:设置切点。通过execution关键字进行设置 |
| 143 | +> * com.bc.center.controller.*.*Controller.*(..)) |
| 144 | +> 第一个 * 参数是返回值 *表示任何返回值 |
| 145 | +> 第二个 com.bc.center.controller.*.*Controller.* 是匹配得切点路径 |
| 146 | +> 第三个 (..) 是请求参数。 ..表示任意参数 |
| 147 | +
|
| 148 | +@Before:在切点之前执行 |
| 149 | +@After:在切点之后执行 |
| 150 | +@AfterReturn:切点正常返回时执行 |
| 151 | +@AfterThrowing:切点抛出异常时执行 |
| 152 | + |
| 153 | +这里我使用了**@AfterReturn**和**@AfterThrowing**,为了区分记录正常返回和异常返回的日志。 |
| 154 | + |
| 155 | +## 小结 |
| 156 | + |
| 157 | +以往采用的方案是以拦截器的方式进行记录。需要配置拦截器以及放行的url,相对要繁琐一些。使用切面的话,可以以所有的控制器为切点,只需要一个切面类,就可以实现日志的访问记录,最重要的是对原来的逻辑没有任何入侵。 |
0 commit comments