自定義注解妙用,一行代碼搞定用戶操作日志記錄
1.簡(jiǎn)介
在使用spring完成項(xiàng)目的時(shí)候需要完成記錄日志,開(kāi)始以為Spring 的AOP功能,就可以輕松解決,半個(gè)小時(shí)都不用,可是經(jīng)過(guò)一番了解過(guò)后,發(fā)現(xiàn)一般的日志記錄,只能記錄一些簡(jiǎn)單的操作,例如表名、表名稱等記錄不到。
這個(gè)時(shí)侯就用到了自定義注解,把想要記錄的內(nèi)容放在注解中,通過(guò)切入點(diǎn)來(lái)獲取到注解參數(shù),然后將參數(shù)插入數(shù)據(jù)庫(kù)記錄
2.Spring AOP
2.1.關(guān)于Spring AOP的一些術(shù)語(yǔ)
- 切面(Aspect):在Spring AOP中,切面可以使用通用類或者在普通類中以@Aspect 注解(@AspectJ風(fēng)格)來(lái)實(shí)現(xiàn)
- 連接點(diǎn)(Joinpoint):在Spring AOP中一個(gè)連接點(diǎn)代表一個(gè)方法的執(zhí)行
- 通知(Advice):在切面的某個(gè)特定的連接點(diǎn)(Joinpoint)上執(zhí)行的動(dòng)作。通知有各種類型,其中包括"around"、"before”和"after"等通知。許多AOP框架,包括Spring,都是以攔截器做通知模型, 并維護(hù)一個(gè)以連接點(diǎn)為中心的攔截器鏈
- 切入點(diǎn)(Pointcut):定義出一個(gè)或一組方法,當(dāng)執(zhí)行這些方法時(shí)可產(chǎn)生通知,Spring缺省使用AspectJ切入點(diǎn)語(yǔ)法。
通知類型
- 前置通知(@Before):在某連接點(diǎn)(join point)之前執(zhí)行的通知,但這個(gè)通知不能阻止連接點(diǎn)前的執(zhí)行(除非它拋出一個(gè)異常)
- 返回后通知(@AfterReturning):在某連接點(diǎn)(join point)正常完成后執(zhí)行的通知:例如,一個(gè)方法沒(méi)有拋出任何異常,正常返回
- 拋出異常后通知(@AfterThrowing):方法拋出異常退出時(shí)執(zhí)行的通知
- 后通知(@After):當(dāng)某連接點(diǎn)退出的時(shí)候執(zhí)行的通知(不論是正常返回還是異常退出)
- 環(huán)繞通知(@Around):包圍一個(gè)連接點(diǎn)(join point)的通知,如方法調(diào)用。這是最強(qiáng)大的一種通知類型,環(huán)繞通知可以在方法調(diào)用前后完成自定義的行為,它也會(huì)選擇是否繼續(xù)執(zhí)行連接點(diǎn)或直接返回它們自己的返回值或拋出異常來(lái)結(jié)束執(zhí)行。更多Java項(xiàng)目分享
2.2.Spring AOP配置有兩種風(fēng)格:
- XML風(fēng)格 = 采用聲明形式實(shí)現(xiàn)Spring AOP
- AspectJ風(fēng)格 = 采用注解形式實(shí)現(xiàn)Spring AOP
3.首先自定義注解
定義一個(gè)日志描述和一個(gè)表名這里根據(jù)需要自定義注解
package?com.ywj.log;
?
import?java.lang.annotation.*;
?
/**
?*?ClassName?Crmlog
?*?AOP日志記錄?自定義注解類
?*/
@Target({ElementType.PARAMETER,?ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public?@interface?SystemCrmlog?{
????/**
?????*?日志描述
?????*?對(duì)于什么表格進(jìn)行了什么操作
?????*/
????String?description()??default?"";
?
????/**
?????*?操作了的表名
?????*?@return
?????*/
????String??tableName()?default?"";
}
3.1.定義切面類,從切入點(diǎn)獲取注解信息保存到數(shù)據(jù)庫(kù)
對(duì)于一些可能碰到的問(wèn)題我在方法的注釋里都有解決辦法,大家注意一下,這里我對(duì)于方法報(bào)錯(cuò)也有處理方法
這里是對(duì)于切面類里使用到的兩個(gè)類解釋:
AspectJ使用org.aspectj.lang.JoinPoint接口表示目標(biāo)類連接點(diǎn)對(duì)象,如果是環(huán)繞增強(qiáng)時(shí),使用org.aspectj.lang.ProceedingJoinPoint表示連接點(diǎn)對(duì)象,該類是JoinPoint的子接口。任何一個(gè)增強(qiáng)方法都可以通過(guò)將第一個(gè)入?yún)⒙暶鳛镴oinPoint訪問(wèn)到連接點(diǎn)上下文的信息。我們先來(lái)了解一下這兩個(gè)接口的主要方法:
1)JoinPoint
java.lang.Object[] getArgs():獲取連接點(diǎn)方法運(yùn)行時(shí)的入?yún)⒘斜恚?/li>Signature getSignature()?:獲取連接點(diǎn)的方法簽名對(duì)象;java.lang.Object getTarget()?:獲取連接點(diǎn)所在的目標(biāo)對(duì)象;java.lang.Object getThis()?:獲取代理對(duì)象本身;
2)ProceedingJoinPoint
ProceedingJoinPoint繼承JoinPoint子接口,它新增了兩個(gè)用于執(zhí)行連接點(diǎn)方法的方法:
java.lang.Object proceed() throws java.lang.Throwable:通過(guò)反射執(zhí)行目標(biāo)對(duì)象的連接點(diǎn)處的方法;java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通過(guò)反射執(zhí)行目標(biāo)對(duì)象連接點(diǎn)處的方法,不過(guò)使用新的入?yún)⑻鎿Q原來(lái)的入?yún)ⅰ?a style="font-family:'Optima-Regular', Optima, 'PingFangSC-light', 'PingFangTC-light', 'PingFang SC', Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;font-size:15px;text-align:left;background-color:rgb(255,255,255);">更多Java項(xiàng)目分享
package?com.ywj.log;
?
?
import?com.fasterxml.jackson.databind.ObjectMapper;
import?com.ywj.log.biz.Sys_logBiz;
import?com.ywj.log.dao.Sys_logDao;
import?com.ywj.login.biz.Sys_UserBiz;
import?com.ywj.login.dao.Sys_UserDao;
import?com.ywj.login.dao.Sys_righDao;
import?org.aspectj.lang.JoinPoint;
import?org.aspectj.lang.annotation.*;
import?org.springframework.stereotype.Component;
import?org.springframework.web.context.request.RequestAttributes;
import?org.springframework.web.context.request.RequestContextHolder;
import?org.springframework.web.context.request.ServletRequestAttributes;
?
import?javax.servlet.http.HttpServletRequest;
import?java.lang.reflect.Method;
import?java.text.SimpleDateFormat;
import?java.util.Arrays;
import?java.util.Date;
import?java.util.List;
import?java.util.Map;
?
?
/**
?*?@ClassName?SystemLogAspect
?*?@Author?Administrator
?*?@Describe??定義切入面類
?*/
@Aspect
@Component
public?class?SystemLogAspect?{
?
?
????/**
?????*?注解Pointcut切入點(diǎn)
?????*?定義出一個(gè)或一組方法,當(dāng)執(zhí)行這些方法時(shí)可產(chǎn)生通知
?????*?指向你的切面類方法
?????*?由于這里使用了自定義注解所以指向你的自定義注解
?????*/
????@Pointcut("@annotation(com.ywj.log.SystemCrmlog)")
????public?void?crmAspect()?{
????}
?
?
????/**
?????*拋出異常后通知(@AfterThrowing):方法拋出異常退出時(shí)執(zhí)行的通知
?????*?注意在這里不能使用ProceedingJoinPoint
?????*?不然會(huì)報(bào)錯(cuò)ProceedingJoinPoint?is?only?supported?for?around?advice
?????*?throwing注解為錯(cuò)誤信息
?????*?@param?joinPoint
?????*?@param?ex
?????*/
????@AfterThrowing(value="crmAspect()",?throwing="ex")
????public?void?afterThrowingMethod(JoinPoint?joinPoint,?Exception?ex)?throws?Exception?{
????????HttpServletRequest?httpServletRequest?=?getHttpServletRequest();
????????//獲取管理員用戶信息\
????????WebUtil?webUtil?=?new?WebUtil();
????????Map?user?=?webUtil.getUser(httpServletRequest);
????????CrmLogMessage?log=new?CrmLogMessage();
????????//獲取需要的信息
????????String?context=getServiceMthodDescription(joinPoint);
????????String?usr_name="";
????????String?rolename="";
????????if(user!=null){
????????????usr_name?=?user.get("usr_name").toString();
????????????rolename=user.get("rolename").toString();
????????}
????????//管理員姓名
????????log.setUserName(usr_name);
????????//角色名
????????log.setUserRole(rolename);
????????//日志信息
????????log.setContent(usr_name+context);
????????//設(shè)置參數(shù)集合
????????log.setRemarks(getServiceMthodParams(joinPoint));
????????//設(shè)置表名
????????log.setTableName(getServiceMthodTableName(joinPoint));
????????//操作時(shí)間
????????SimpleDateFormat?sif=new?SimpleDateFormat("yyyy-MM-dd?HH:mm:ss");
????????log.setDateTime(sif.format(new?Date()));
????????//設(shè)置ip地址
????????log.setIp(httpServletRequest.getRemoteAddr());
????????//設(shè)置請(qǐng)求地址
????????log.setRequestUrl(httpServletRequest.getRequestURI());
????????//執(zhí)行結(jié)果
????????log.setResult("執(zhí)行失敗");
????????//錯(cuò)誤信息
????????log.setExString(ex.getMessage());
????????//將數(shù)據(jù)保存到數(shù)據(jù)庫(kù)
????????Sys_logDao?sysLogDao=new?Sys_logDao();
????????sysLogDao.addSys_log(log);
????}
?
?
????/**
?????*?返回后通知(@AfterReturning):在某連接點(diǎn)(joinpoint)
?????*?正常完成后執(zhí)行的通知:例如,一個(gè)方法沒(méi)有拋出任何異常,正常返回
?????*?方法執(zhí)行完畢之后
?????*?注意在這里不能使用ProceedingJoinPoint
?????*?不然會(huì)報(bào)錯(cuò)ProceedingJoinPoint?is?only?supported?for?around?advice
?????*?crmAspect()指向需要控制的方法
?????*??returning??注解返回值
?????*?@param?joinPoint
?????*?@param?returnValue??返回值
?????*?@throws?Exception
?????*/
????@AfterReturning(value?=?"crmAspect()",returning?=?"returnValue")
????public??void?doCrmLog(JoinPoint?joinPoint,Object?returnValue)?throws?Exception?{
????????HttpServletRequest?httpServletRequest?=?getHttpServletRequest();
????????//獲取管理員用戶信息
????????WebUtil?webUtil?=?new?WebUtil();
????????Map?user?=?webUtil.getUser(httpServletRequest);
????????CrmLogMessage?log=new?CrmLogMessage();
????????String?context=getServiceMthodDescription(joinPoint);
?
????????String?usr_name="";
????????String?rolename="";
????????if(user!=null){
?????????usr_name?=?user.get("usr_name").toString();
?????????rolename=user.get("rolename").toString();
????????}
????????//管理員姓名
????????log.setUserName(usr_name);
????????//角色名
????????log.setUserRole(rolename);
????????//日志信息
????????log.setContent(usr_name+context);
????????//設(shè)置參數(shù)集合
????????log.setRemarks(getServiceMthodParams(joinPoint));
????????//設(shè)置表名
????????log.setTableName(getServiceMthodTableName(joinPoint));
????????//操作時(shí)間
????????SimpleDateFormat?sif=new?SimpleDateFormat("yyyy-MM-dd?HH:mm:ss");
????????log.setDateTime(sif.format(new?Date()));
????????//設(shè)置ip地址
????????log.setIp(httpServletRequest.getRemoteAddr());
????????//設(shè)置請(qǐng)求地址
????????log.setRequestUrl(httpServletRequest.getRequestURI());
????????if(returnValue!=null){
???????????if(returnValue?instanceof?List){
???????????????List?ls=?(List)?returnValue;
???????????????if(ls.size()>0){
???????????????????log.setResult("執(zhí)行成功");
???????????????}else{
???????????????????log.setResult("執(zhí)行成功");
???????????????}
???????????}else?if(returnValue?instanceof?Boolean){
???????????????Boolean?falg=?(Boolean)?returnValue;
???????????????if(falg){
???????????????????log.setResult("執(zhí)行成功");
???????????????}else{
???????????????????log.setResult("執(zhí)行失敗");
???????????????}
???????????}else?if(returnValue?instanceof?Integer){
???????????????Integer?i=?(Integer)?returnValue;
???????????????if(i>0){
???????????????????log.setResult("執(zhí)行成功");
???????????????}else{
???????????????????log.setResult("執(zhí)行失敗");
???????????????}
???????????}else{
???????????????log.setResult("執(zhí)行成功");
???????????}
?
????????}
????????//將數(shù)據(jù)保存到數(shù)據(jù)庫(kù)
????????Sys_logDao?sysLogDao=new?Sys_logDao();
????????sysLogDao.addSys_log(log);
????}
?
?
????/**
?????*獲取自定義注解里的日志描述
?????*?@param?joinPoint
?????*?@return?返回注解里面的日志描述
?????*?@throws?Exception
?????*/
????private?String?getServiceMthodDescription(JoinPoint?joinPoint)
????????????throws?Exception?{
????????//類名
????????String?targetName?=?joinPoint.getTarget().getClass().getName();
????????//方法名
????????String?methodName?=?joinPoint.getSignature().getName();
????????//參數(shù)
????????Object[]?arguments?=?joinPoint.getArgs();
????????//通過(guò)反射獲取示例對(duì)象
????????Class?targetClass?=?Class.forName(targetName);
????????//通過(guò)實(shí)例對(duì)象方法數(shù)組
????????Method[]?methods?=?targetClass.getMethods();
????????String?description?=?"";
????????for(Method?method?:?methods)?{
????????????//判斷方法名是不是一樣
????????????if(method.getName().equals(methodName))?{
????????????????//對(duì)比參數(shù)數(shù)組的長(zhǎng)度
????????????????Class[]?clazzs?=?method.getParameterTypes();
????????????????if(clazzs.length?==?arguments.length)?{
????????????????????//獲取注解里的日志信息
????????????????????description?=?method.getAnnotation(SystemCrmlog.class).description();
????????????????????break;
????????????????}
????????????}
????????}
????????return?description;
????}
?
????/**
?????*獲取自定義注解里的表名
?????*?@param?joinPoint
?????*?@return?返回注解里的表名字
?????*?@throws?Exception
?????*/
????private?String?getServiceMthodTableName(JoinPoint?joinPoint)
????????????throws?Exception?{
????????//類名
????????String?targetName?=?joinPoint.getTarget().getClass().getName();
????????//方法名
????????String?methodName?=?joinPoint.getSignature().getName();
????????//參數(shù)
????????Object[]?arguments?=?joinPoint.getArgs();
????????//通過(guò)反射獲取示例對(duì)象
????????Class?targetClass?=?Class.forName(targetName);
????????//通過(guò)實(shí)例對(duì)象方法數(shù)組
????????Method[]?methods?=?targetClass.getMethods();
????????//表名
????????String?tableName?=?"";
????????for?(Method?method?:?methods)?{
????????????//判斷方法名是不是一樣
????????????if?(method.getName().equals(methodName))?{
????????????????//對(duì)比參數(shù)數(shù)組的長(zhǎng)度
????????????????Class[]?clazzs?=?method.getParameterTypes();
????????????????if?(clazzs.length?==?arguments.length)?{
????????????????????//獲取注解里的表名
????????????????????tableName?=?method.getAnnotation(SystemCrmlog.class).tableName();
????????????????????break;
????????????????}
????????????}
????????}
????????return?tableName;
????}
?
????/**
?????*?獲取json格式的參數(shù)用于存儲(chǔ)到數(shù)據(jù)庫(kù)中
?????*?@param?joinPoint
?????*?@return
?????*?@throws?Exception
?????*/
????private?String?getServiceMthodParams(JoinPoint?joinPoint)
????????????throws?Exception?{
????????Object[]?arguments?=?joinPoint.getArgs();
????????ObjectMapper?om=new?ObjectMapper();
????????return?om.writeValueAsString(arguments);
????}
?
????/**
?????*?獲取當(dāng)前的request
?????*?這里如果報(bào)空指針異常是因?yàn)閱为?dú)使用spring獲取request
?????*?需要在配置文件里添加監(jiān)聽(tīng)
?????*?
?????*?
?????*?org.springframework.web.context.request.RequestContextListener
?????*?
?????*?
?????*?@return
?????*/
????public?HttpServletRequest?getHttpServletRequest(){
????????RequestAttributes?ra?=?RequestContextHolder.getRequestAttributes();
????????ServletRequestAttributes?sra?=?(ServletRequestAttributes)ra;
????????HttpServletRequest?request?=?sra.getRequest();
????????return?request;
????}
?
}
每個(gè)切面?zhèn)鬟f的數(shù)據(jù)的都不一樣,最終決定,獲取切面的所有參數(shù),轉(zhuǎn)成json字符串,保存到數(shù)據(jù)庫(kù)中。更多Java項(xiàng)目分享
相關(guān)類:
日志信息類
package?com.ywj.log;
?
/**
?*?@ClassName?CrmLogMessage
?*?@Author?Administrator
?*?@Describe?數(shù)據(jù)庫(kù)日志類
?*/
public?class?CrmLogMessage?{
????private?Integer?logid;//日志id
????private?String?UserName;//管理員姓名
????private?String?UserRole;//管理員角色
????private?String?Content;//日志描述
????private?String?Remarks;//參數(shù)集合
????private?String?TableName;//表格名稱
????private?String?DateTime;//操作時(shí)間
????private?String?resultValue;//返回值
????private?String?ip;//ip地址
????private?String??requestUrl;//請(qǐng)求地址
????private?String?result;//操作結(jié)果
????private??String?ExString;//錯(cuò)誤信息
?
????public?CrmLogMessage()?{
????}
?
????@Override
????public?String?toString()?{
????????return?"CrmLogMessage{"?+
????????????????"logid="?+?logid?+
????????????????",?UserName='"?+?UserName?+?'\''?+
????????????????",?UserRole='"?+?UserRole?+?'\''?+
????????????????",?Content='"?+?Content?+?'\''?+
????????????????",?Remarks='"?+?Remarks?+?'\''?+
????????????????",?TableName='"?+?TableName?+?'\''?+
????????????????",?DateTime='"?+?DateTime?+?'\''?+
????????????????",?resultValue='"?+?resultValue?+?'\''?+
????????????????",?ip='"?+?ip?+?'\''?+
????????????????",?requestUrl='"?+?requestUrl?+?'\''?+
????????????????",?result='"?+?result?+?'\''?+
????????????????",?ExString='"?+?ExString?+?'\''?+
????????????????'}';
????}
?
????public?CrmLogMessage(Integer?logid,?String?userName,?String?userRole,?String?content,?String?remarks,?String?tableName,?String?dateTime,?String?resultValue,?String?ip,?String?requestUrl,?String?result,?String?exString)?{
????????this.logid?=?logid;
????????UserName?=?userName;
????????UserRole?=?userRole;
????????Content?=?content;
????????Remarks?=?remarks;
????????TableName?=?tableName;
????????DateTime?=?dateTime;
????????this.resultValue?=?resultValue;
????????this.ip?=?ip;
????????this.requestUrl?=?requestUrl;
????????this.result?=?result;
????????ExString?=?exString;
????}
?
????public?String?getExString()?{
????????return?ExString;
????}
?
????public?void?setExString(String?exString)?{
????????ExString?=?exString;
????}
?
????public?Integer?getLogid()?{
????????return?logid;
????}
?
????public?void?setLogid(Integer?logid)?{
????????this.logid?=?logid;
????}
?
????public?String?getUserName()?{
????????return?UserName;
????}
?
????public?void?setUserName(String?userName)?{
????????UserName?=?userName;
????}
?
????public?String?getUserRole()?{
????????return?UserRole;
????}
?
????public?void?setUserRole(String?userRole)?{
????????UserRole?=?userRole;
????}
?
????public?String?getContent()?{
????????return?Content;
????}
?
????public?void?setContent(String?content)?{
????????Content?=?content;
????}
?
????public?String?getRemarks()?{
????????return?Remarks;
????}
?
????public?void?setRemarks(String?remarks)?{
????????Remarks?=?remarks;
????}
?
????public?String?getTableName()?{
????????return?TableName;
????}
?
????public?void?setTableName(String?tableName)?{
????????TableName?=?tableName;
????}
?
????public?String?getDateTime()?{
????????return?DateTime;
????}
?
????public?void?setDateTime(String?dateTime)?{
????????DateTime?=?dateTime;
????}
?
????public?String?getResultValue()?{
????????return?resultValue;
????}
?
????public?void?setResultValue(String?resultValue)?{
????????this.resultValue?=?resultValue;
????}
?
????public?String?getIp()?{
????????return?ip;
????}
?
????public?void?setIp(String?ip)?{
????????this.ip?=?ip;
????}
?
????public?String?getRequestUrl()?{
????????return?requestUrl;
????}
?
????public?void?setRequestUrl(String?requestUrl)?{
????????this.requestUrl?=?requestUrl;
????}
?
????public?String?getResult()?{
????????return?result;
????}
?
????public?void?setResult(String?result)?{
????????this.result?=?result;
????}
}
用來(lái)獲取登錄用戶信息的幫助類:
package?com.ywj.log;
?
import?com.base.web.BaseAction;
?
import?javax.servlet.http.HttpServletRequest;
import?java.util.Map;
?
/**
?*?@ClassName?WebUtil
?*?@Author?Administrator
?*?@Describe??日志幫助類?用來(lái)獲取session中的用戶信息來(lái)存入數(shù)據(jù)庫(kù)
?*/
public?class?WebUtil??{
?
?
????/**
?????*?從session中獲取到用戶對(duì)象
?????*?@return
?????*/
????public?Map???getUser(HttpServletRequest?request) {
????????Map?attribute=null;
????????if(request!=null){
????????Object?user?=?request.getSession().getAttribute(Constans.USER_KEY);
??????attribute?=?(Map)?user;}
????return?attribute;
}
????
}
在你的spring-context.xml中配置
??
????<aop:aspectj-autoproxy?proxy-target-class="true"?/>
????
?
????<context:component-scan?base-package="com.*.*.biz.impl"?/>
然后在你需要記錄的方法上加上注解
@SystemCrmlog(description?=?"進(jìn)行了登錄操作",tableName?=Constans.USER_TABLENAME)
效果這里表名使用了常量類

對(duì)于一些表的信息可以寫一個(gè)常量類

然后執(zhí)行登錄操作數(shù)據(jù)庫(kù)記錄為:

來(lái)源:blog.csdn.net/yjt520557/article/details/85099115

程序汪資料鏈接
程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理
Java項(xiàng)目分享 最新整理全集,找項(xiàng)目不累啦 06版
堪稱神級(jí)的Spring Boot手冊(cè),從基礎(chǔ)入門到實(shí)戰(zhàn)進(jìn)階
臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開(kāi)放下載!
臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開(kāi)放下載!
字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開(kāi)放下載!
歡迎添加程序汪個(gè)人微信 itwang009? 進(jìn)粉絲群或圍觀朋友圈
