Spring AOP实现后台管理系统日志管理

Spring AOP实现后台管理系统日志管理

设计原则和思路:

  • 元注解方式结合AOP,灵活记录操作日志
  • 能够记录详细错误日志为运维提供支持
  • 日志记录尽可能减少性能影响

1.定义日志记录元注解


   
   
  1. package com.myron.ims.annotation;
  2. import java.lang.annotation.*;
  3. /**
  4. * 自定义注解 拦截Controller
  5. *
  6. * @author lin.r.x
  7. *
  8. */
  9. @Target({ ElementType.PARAMETER, ElementType.METHOD })
  10. @Retention(RetentionPolicy.RUNTIME)
  11. public @ interface SystemControllerLog {
  12. /**
  13. * 描述业务操作 例:Xxx管理-执行Xxx操作
  14. * @return
  15. */
  16. String description() default "";
  17. }

2.定义用于记录日志的实体类


   
   
  1. package com.myron.ims.bean;
  2. import java.io.Serializable;
  3. import com.myron.common.util.StringUtils;
  4. import com.myron.common.util.UuidUtils;
  5. import com.fasterxml.jackson.annotation.JsonFormat;
  6. import java.util.Date;
  7. import java.util.Map;
  8. /**
  9. * 日志类-记录用户操作行为
  10. * @author lin.r.x
  11. *
  12. */
  13. public class Log implements Serializable{
  14. private static final long serialVersionUID = 1 L;
  15. private String logId; //日志主键
  16. private String type; //日志类型
  17. private String title; //日志标题
  18. private String remoteAddr; //请求地址
  19. private String requestUri; //URI
  20. private String method; //请求方式
  21. private String params; //提交参数
  22. private String exception; //异常
  23. @JsonFormat(pattern= "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
  24. private Date operateDate; //开始时间
  25. private String timeout; //结束时间
  26. private String userId; //用户ID
  27. public String getLogId () {
  28. return StringUtils.isBlank(logId) ? logId : logId.trim();
  29. }
  30. public void setLogId (String logId) {
  31. this.logId = logId;
  32. }
  33. public String getType () {
  34. return StringUtils.isBlank(type) ? type : type.trim();
  35. }
  36. public void setType (String type) {
  37. this.type = type;
  38. }
  39. public String getTitle () {
  40. return StringUtils.isBlank(title) ? title : title.trim();
  41. }
  42. public void setTitle (String title) {
  43. this.title = title;
  44. }
  45. public String getRemoteAddr () {
  46. return StringUtils.isBlank(remoteAddr) ? remoteAddr : remoteAddr.trim();
  47. }
  48. public void setRemoteAddr (String remoteAddr) {
  49. this.remoteAddr = remoteAddr;
  50. }
  51. public String getRequestUri () {
  52. return StringUtils.isBlank(requestUri) ? requestUri : requestUri.trim();
  53. }
  54. public void setRequestUri (String requestUri) {
  55. this.requestUri = requestUri;
  56. }
  57. public String getMethod () {
  58. return StringUtils.isBlank(method) ? method : method.trim();
  59. }
  60. public void setMethod (String method) {
  61. this.method = method;
  62. }
  63. public String getParams () {
  64. return StringUtils.isBlank(params) ? params : params.trim();
  65. }
  66. public void setParams (String params) {
  67. this.params = params;
  68. }
  69. /**
  70. * 设置请求参数
  71. * @param paramMap
  72. */
  73. public void setMapToParams (Map<String, String[]> paramMap) {
  74. if (paramMap == null){
  75. return;
  76. }
  77. StringBuilder params = new StringBuilder();
  78. for (Map.Entry<String, String[]> param : ((Map<String, String[]>)paramMap).entrySet()){
  79. params.append(( "".equals(params.toString()) ? "" : "&") + param.getKey() + "=");
  80. String paramValue = (param.getValue() != null && param.getValue().length > 0 ? param.getValue()[ 0] : "");
  81. params.append(StringUtils.abbr(StringUtils.endsWithIgnoreCase(param.getKey(), "password") ? "" : paramValue, 100));
  82. }
  83. this.params = params.toString();
  84. }
  85. public String getException () {
  86. return StringUtils.isBlank(exception) ? exception : exception.trim();
  87. }
  88. public void setException (String exception) {
  89. this.exception = exception;
  90. }
  91. public Date getOperateDate () {
  92. return operateDate;
  93. }
  94. public void setOperateDate (Date operateDate) {
  95. this.operateDate = operateDate;
  96. }
  97. public String getTimeout () {
  98. return StringUtils.isBlank(timeout) ? timeout : timeout.trim();
  99. }
  100. public void setTimeout (String timeout) {
  101. this.timeout = timeout;
  102. }
  103. public String getUserId () {
  104. return StringUtils.isBlank(userId) ? userId : userId.trim();
  105. }
  106. public void setUserId (String userId) {
  107. this.userId = userId;
  108. }
  109. }

3.定义日志AOP切面类


   
   
  1. package com.myron.ims.aop;
  2. import java.lang.reflect.Method;
  3. import java.text.SimpleDateFormat;
  4. import java.util.Date;
  5. import java.util.Map;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpSession;
  8. import org.aspectj.lang.JoinPoint;
  9. import org.aspectj.lang.annotation.After;
  10. import org.aspectj.lang.annotation.AfterThrowing;
  11. import org.aspectj.lang.annotation.Aspect;
  12. import org.aspectj.lang.annotation.Before;
  13. import org.aspectj.lang.annotation.Pointcut;
  14. import org.aspectj.lang.reflect.MethodSignature;
  15. import org.slf4j.Logger;
  16. import org.slf4j.LoggerFactory;
  17. import org.springframework.beans.factory.annotation.Autowired;
  18. import org.springframework.core.NamedThreadLocal;
  19. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  20. import org.springframework.stereotype.Component;
  21. import com.myron.common.util.DateUtils;
  22. import com.myron.common.util.UuidUtils;
  23. import com.myron.ims.annotation.SystemControllerLog;
  24. import com.myron.ims.annotation.SystemServiceLog;
  25. import com.myron.ims.bean.Log;
  26. import com.myron.ims.bean.User;
  27. import com.myron.ims.service.LogService;
  28. /**
  29. * 系统日志切面类
  30. * @author lin.r.x
  31. *
  32. */
  33. @Aspect
  34. @Component
  35. public class SystemLogAspect {
  36. private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect. class);
  37. private static final ThreadLocal<Date> beginTimeThreadLocal =
  38. new NamedThreadLocal<Date>( "ThreadLocal beginTime");
  39. private static final ThreadLocal<Log> logThreadLocal =
  40. new NamedThreadLocal<Log>( "ThreadLocal log");
  41. private static final ThreadLocal<User> currentUser= new NamedThreadLocal<>( "ThreadLocal user");
  42. @Autowired(required= false)
  43. private HttpServletRequest request;
  44. @Autowired
  45. private ThreadPoolTaskExecutor threadPoolTaskExecutor;
  46. @Autowired
  47. private LogService logService;
  48. /**
  49. * Controller层切点 注解拦截
  50. */
  51. @Pointcut( "@annotation(com.myron.ims.annotation.SystemControllerLog)")
  52. public void controllerAspect (){}
  53. /**
  54. * 前置通知 用于拦截Controller层记录用户的操作的开始时间
  55. * @param joinPoint 切点
  56. * @throws InterruptedException
  57. */
  58. @Before( "controllerAspect()")
  59. public void doBefore (JoinPoint joinPoint) throws InterruptedException{
  60. Date beginTime= new Date();
  61. beginTimeThreadLocal.set(beginTime); //线程绑定变量(该数据只有当前请求的线程可见)
  62. if (logger.isDebugEnabled()){ //这里日志级别为debug
  63. logger.debug( "开始计时: {} URI: {}", new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss.SSS")
  64. .format(beginTime), request.getRequestURI());
  65. }
  66. //读取session中的用户
  67. HttpSession session = request.getSession();
  68. User user = (User) session.getAttribute( "ims_user");
  69. currentUser.set(user);
  70. }
  71. /**
  72. * 后置通知 用于拦截Controller层记录用户的操作
  73. * @param joinPoint 切点
  74. */
  75. @SuppressWarnings( "unchecked")
  76. @After( "controllerAspect()")
  77. public void doAfter (JoinPoint joinPoint) {
  78. User user = currentUser.get();
  79. if(user != null){
  80. String title= "";
  81. String type= "info"; //日志类型(info:入库,error:错误)
  82. String remoteAddr=request.getRemoteAddr(); //请求的IP
  83. String requestUri=request.getRequestURI(); //请求的Uri
  84. String method=request.getMethod(); //请求的方法类型(post/get)
  85. Map<String,String[]> params=request.getParameterMap(); //请求提交的参数
  86. try {
  87. title=getControllerMethodDescription2(joinPoint);
  88. } catch (Exception e) {
  89. e.printStackTrace();
  90. }
  91. // 打印JVM信息。
  92. long beginTime = beginTimeThreadLocal.get().getTime(); //得到线程绑定的局部变量(开始时间)
  93. long endTime = System.currentTimeMillis(); //2、结束时间
  94. if (logger.isDebugEnabled()){
  95. logger.debug( "计时结束:{} URI: {} 耗时: {} 最大内存: {}m 已分配内存: {}m 已分配内存中的剩余空间: {}m 最大可用内存: {}m",
  96. new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss.SSS").format(endTime),
  97. request.getRequestURI(),
  98. DateUtils.formatDateTime(endTime - beginTime),
  99. Runtime.getRuntime().maxMemory()/ 1024/ 1024,
  100. Runtime.getRuntime().totalMemory()/ 1024/ 1024,
  101. Runtime.getRuntime().freeMemory()/ 1024/ 1024,
  102. (Runtime.getRuntime().maxMemory()-Runtime.getRuntime().totalMemory()+Runtime.getRuntime().freeMemory())/ 1024/ 1024);
  103. }
  104. Log log= new Log();
  105. log.setLogId(UuidUtils.creatUUID());
  106. log.setTitle(title);
  107. log.setType(type);
  108. log.setRemoteAddr(remoteAddr);
  109. log.setRequestUri(requestUri);
  110. log.setMethod(method);
  111. log.setMapToParams(params);
  112. log.setUserId(user.getId());
  113. Date operateDate=beginTimeThreadLocal.get();
  114. log.setOperateDate(operateDate);
  115. log.setTimeout(DateUtils.formatDateTime(endTime - beginTime));
  116. //1.直接执行保存操作
  117. //this.logService.createSystemLog(log);
  118. //2.优化:异步保存日志
  119. //new SaveLogThread(log, logService).start();
  120. //3.再优化:通过线程池来执行日志保存
  121. threadPoolTaskExecutor.execute( new SaveLogThread(log, logService));
  122. logThreadLocal.set(log);
  123. }
  124. }
  125. /**
  126. * 异常通知 记录操作报错日志
  127. * @param joinPoint
  128. * @param e
  129. */
  130. @AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
  131. public void doAfterThrowing (JoinPoint joinPoint, Throwable e) {
  132. Log log = logThreadLocal.get();
  133. log.setType( "error");
  134. log.setException(e.toString());
  135. new UpdateLogThread(log, logService).start();
  136. }
  137. /**
  138. * 获取注解中对方法的描述信息 用于service层注解
  139. * @param joinPoint切点
  140. * @return discription
  141. */
  142. public static String getServiceMthodDescription2 (JoinPoint joinPoint) {
  143. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  144. Method method = signature.getMethod();
  145. SystemServiceLog serviceLog = method
  146. .getAnnotation(SystemServiceLog.class);
  147. String discription = serviceLog.description();
  148. return discription;
  149. }
  150. /**
  151. * 获取注解中对方法的描述信息 用于Controller层注解
  152. *
  153. * @param joinPoint 切点
  154. * @return discription
  155. */
  156. public static String getControllerMethodDescription2 (JoinPoint joinPoint) {
  157. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  158. Method method = signature.getMethod();
  159. SystemControllerLog controllerLog = method
  160. .getAnnotation(SystemControllerLog.class);
  161. String discription = controllerLog.description();
  162. return discription;
  163. }
  164. /**
  165. * 保存日志线程
  166. */
  167. private static class SaveLogThread implements Runnable {
  168. private Log log;
  169. private LogService logService;
  170. public SaveLogThread (Log log, LogService logService) {
  171. this.log = log;
  172. this.logService = logService;
  173. }
  174. @Override
  175. public void run () {
  176. logService.createLog(log);
  177. }
  178. }
  179. /**
  180. * 日志更新线程
  181. */
  182. private static class UpdateLogThread extends Thread {
  183. private Log log;
  184. private LogService logService;
  185. public UpdateLogThread (Log log, LogService logService) {
  186. super(UpdateLogThread.class.getSimpleName());
  187. this.log = log;
  188. this.logService = logService;
  189. }
  190. @Override
  191. public void run () {
  192. this.logService.updateLog(log);
  193. }
  194. }
  195. }

4.spring 配置扫描切面,开启@AspectJ注解的支持


   
   
  1. <!-- 启动对@AspectJ注解的支持 -->
  2. <aop:aspectj-autoproxy/>
  3. <!-- 扫描切点类组件 -->
  4. <context:component-scan base-package="com.myron.ims.aop" />
  5. <context:component-scan base-package="com.myron.ims.service"/>
  6. <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
  7. <property name="corePoolSize" value="5" />
  8. <property name="maxPoolSize" value="10" />
  9. <property name="WaitForTasksToCompleteOnShutdown" value="true" />
  10. </bean>

5.使用范例LoginController方法中添加日志注解


   
   
  1. /**
  2. *系统登入
  3. */
  4. @RequestMapping( "/login.do")
  5. @SystemControllerLog(description= "登入系统")
  6. @ResponseBody
  7. public Map<String, Object> login (String username, String password, Boolean rememberMe, HttpServletRequest req){
  8. //业务代码省略...
  9. }
  10. /**
  11. * 安全退出登入
  12. * @return
  13. */
  14. @SystemControllerLog(description= "安全退出系统")
  15. @RequestMapping( "logout.do")
  16. public String logout (){
  17. Subject subject=SecurityUtils.getSubject();
  18. if(subject.isAuthenticated()){
  19. subject.logout(); // session 会销毁,在SessionListener监听session销毁,清理权限缓存
  20. }
  21. return "/login.jsp";
  22. }

6.运行效果 

这里写图片描述7.补充源码地址:https://github.com/MusicXi/demo-aop-log


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值