利用SpringMVC的AOP來實現後臺系統的操作日誌記錄
阿新 • • 發佈:2019-01-30
最近在專案中要求把後臺的一些關鍵操作記錄下來,想了好半天能想到的也就那兩三種方式,要麼就是寫一個攔截器,然後再web.xml裡面進行配置,要麼就是就是在每個需要記錄操作日誌的程式碼裡面進行攔截,最後我選擇了第三種,也就是基於AOP的攔截,用這種方式,只需要在需記錄操作的介面方法上,新增上自定義註解就好了。其實在專案開發裡面真正用到AOP感覺不是很多,我也一樣很少就沒有怎麼用到。剛好這次碰見了,就記錄下來,也算是自己學習一次。
我在做的專案是基於SSH(Spring,Spring MVC,Hibernate)框架。
- 首先我們先想想我們做的功能到底是什麼,我們要記錄操作,我們首先就應該有一張記錄操作記錄的表
`@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Entity
@Table(name = "表名")
public class LogModel extends IdEntity {
//使用者
private User user;
//IP
private String remoteAddr;
//異常
private String exception;
//日誌標題
private String title;
//請求地址
private String requestUri;
//日誌型別
private String type;
//日誌記錄描述
private String description;
以上是我所用到的實體類
2. 關於實體類的Service層,Dao層,Impl等等我就不再這裡過多敘述,無非就是一些與資料庫操作掛鉤的一些程式碼。
3. 自定義一個切面註解
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodLog {
/**
* 該註解作用於方法上時需要備註資訊
*/
String remarks() default "";
}
4. 接下來就是重點了,切面類的具體實現。
@Component
@Aspect
public class SystemLogAspect {
//這段程式碼呼叫了org.slf4j.LoggerFactory line:280
private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);
private static final ThreadLocal<Date> beginTimeThreadLocal =
new NamedThreadLocal("ThreadLocal beginTime");
private static final ThreadLocal<LogModel> logThreadLocal =
new NamedThreadLocal("ThreadLocal log");
private static final ThreadLocal<User> currentUser = new NamedThreadLocal("ThreadLocal user");
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
/**
* Controller層切點 註解攔截
*/
@Pointcut("@annotation(com.shopping.logrecord.MethodLog)")
public void controllerAspect() {
}
/**
* 用於攔截Controller層記錄使用者的操作的開始時間
*
* @param joinPoint 切點
* @throws InterruptedException
*/
@Before("controllerAspect()")
public void doBefore(JoinPoint joinPoint) throws InterruptedException {
Date beginTime = new Date();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
beginTimeThreadLocal.set(beginTime);//執行緒繫結變數(該資料只有當前請求的執行緒可見)
if (logger.isDebugEnabled()) {//這裡日誌級別為debug
logger.debug("開始計時: {} URI: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
.format(beginTime), request.getRequestURI());
}
//讀取session中的使用者
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
System.out.println(user);
currentUser.set(user);
}
/**
* 用於攔截Controller層記錄使用者的操作
*
* @param joinPoint 切點
*/
@SuppressWarnings("unchecked")
@After("controllerAspect()")
public void doAfter(JoinPoint joinPoint)throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
User user = currentUser.get();
if (user != null) {
String title = "";
String type = "info"; //日誌型別(info:入庫,error:錯誤)
String remoteAddr = SystemLogAspect.getIp();//請求的IP
String requestUri = request.getRequestURI();//請求的Uri
try {
title = getControllerMethodDescription2(joinPoint);
} catch (Exception e) {
e.printStackTrace();
}
LogModel logModel = new LogModel();
logModel.setTitle(title);
logModel.setType(type);
logModel.setRemoteAddr(remoteAddr);
logModel.setRequestUri(requestUri);
logModel.setException("無異常");
logModel.setUserId(user.getId());
User users = userService.getObjById(user.getId());
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = simpleDateFormat.format(date);
logModel.setAddTime(date);
logModel.setUserName(users.getUserName());
//通過執行緒池來執行日誌儲存
threadPoolTaskExecutor.execute(new SaveLogThread(logModel, logModelService));
logThreadLocal.set(logModel);
}
}
/**
* 異常通知 記錄操作報錯日誌
* @param joinPoint
* @param e
*/
@AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
LogModel logModel = logThreadLocal.get();
logModel.setType("error");
logModel.setException(e.toString());
new UpdateLogThread(logModel, logModelService).start();
}
/**
* 獲取註解中對方法的描述資訊 用於Controller層註解
* @param joinPoint 切點
* @return 方法描述
*/
public static String getControllerMethodDescription2(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
MethodLog controllerLog = method
.getAnnotation(MethodLog.class);
String discription = controllerLog.remarks();
return discription;
}
/**
* 獲取請求ip
*/
public static String getIp()throws Exception {
InetAddress ia=null;
ia=ia.getLocalHost();
String localip=ia.getHostAddress();
return localip;
}
/**
* 儲存日誌執行緒
*
* @author lin.r.x
*
*/
private static class SaveLogThread implements Runnable {
private LogModel logModel;
private LogModelService logModelService;
public SaveLogThread(LogModel logModel, LogModelService logModelService) {
this.logModel = logModel;
this.logModelService = logModelService;
}
@Override
public void run() {
logModelService.save(logModel);
}
}
/**
* 日誌更新執行緒
*
* @author lin.r.x
*
*/
private static class UpdateLogThread extends Thread {
private LogModel logModel;
private LogModelService logModelService;
public UpdateLogThread(LogModel logModel, LogModelService logModelService) {
super(UpdateLogThread.class.getSimpleName());
this.logModel = logModel;
this.logModelService = logModelService;
}
@Override
public void run() {
this.logModelService.update(logModel);
}
}
}
接下來我們需要做一步操作,那就是在XML中開啟對AOP的支援
<!-- 啟動對@AspectJ註解的支援 -->
<aop:aspectj-autoproxy/>
<!-- 掃描切點類元件 -->
//放置切面實現類的包路徑
<context:component-scan base-package="com.shopping.logrecord.logss"/>
//日誌表的service的包路徑
<context:component-scan base-package="com.shopping.logrecord.logservicess"/>
<bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5" />
<property name="maxPoolSize" value="10" />
</bean>
OKAY,到這我們就基本算是配置完成了。
接下來,加入我們需要執行編輯操作
在編輯介面方法上新增自定義的註解
okay,完成,**注意**remarks的值。
看一下表資料
大功告成!