自定义注解妙用,一行代码搞定用户操作日志记录,你学会了吗?
1。简介
我在使用spring完成项目的时候需要完成记录日志,我开始以为Spring的AOP功能,就可以轻松解决,半个小时都不用,可是经过一番了解过后,发现一般的日志记录,只能记录一些简单的操作,例如表名、表名称等记录不到。
这个时侯就用到了自定义注解,把想要记录的内容放在注解中,通过切入点来获取到注解参数,然后将参数插入数据库记录2。SpringAOP
对于SpringAop的基本介绍大家可以看看:
https:blog。csdn。netyjt520557articledetails84833508
这里是为了方便大家理解如何实现给大家解释一下2。1。关于SpringAOP的一些术语切面(Aspect):在SpringAOP中,切面可以使用通用类或者在普通类中以Aspect注解(AspectJ风格)来实现连接点(Joinpoint):在SpringAOP中一个连接点代表一个方法的执行通知(Advice):在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括around、before和after等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链切入点(Pointcut):定义出一个或一组方法,当执行这些方法时可产生通知,Spring缺省使用AspectJ切入点语法。通知类型前置通知(Before):在某连接点(joinpoint)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)返回后通知(AfterReturning):在某连接点(joinpoint)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回抛出异常后通知(AfterThrowing):方法抛出异常退出时执行的通知后通知(After):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)环绕通知(Around):包围一个连接点(joinpoint)的通知,如方法调用。这是最强大的一种通知类型,环绕通知可以在方法调用前后完成自定义的行为,它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行2。2。SpringAOP配置有两种风格:XML风格采用声明形式实现SpringAOPAspectJ风格采用注解形式实现SpringAOP3。首先自定义注解
定义一个日志描述和一个表名这里根据需要自定义注解packagecom。ywj。log;importjava。lang。annotation。;ClassNameCrmlogAOP日志记录自定义注解类Target({ElementType。PARAMETER,ElementType。METHOD})Retention(RetentionPolicy。RUNTIME)DocumentedpublicinterfaceSystemCrmlog{日志描述对于什么表格进行了什么操作Stringdescription()default;操作了的表名returnStringtableName()default;}3。1。定义切面类,从切入点获取注解信息保存到数据库
对于一些可能碰到的问题我在方法的注释里都有解决办法,大家注意一下,这里我对于方法报错也有处理方法
这里是对于切面类里使用到的两个类解释:
AspectJ使用org。aspectj。lang。JoinPoint接口表示目标类连接点对象,如果是环绕增强时,使用org。aspectj。lang。ProceedingJoinPoint表示连接点对象,该类是JoinPoint的子接口。任何一个增强方法都可以通过将第一个入参声明为JoinPoint访问到连接点上下文的信息。我们先来了解一下这两个接口的主要方法:1)JoinPointjava。lang。Object〔〕getArgs():获取连接点方法运行时的入参列表;SignaturegetSignature():获取连接点的方法签名对象;java。lang。ObjectgetTarget():获取连接点所在的目标对象;java。lang。ObjectgetThis():获取代理对象本身;2)ProceedingJoinPoint
ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法:java。lang。Objectproceed()throwsjava。lang。Throwable:通过反射执行目标对象的连接点处的方法;java。lang。Objectproceed(java。lang。Object〔〕args)throwsjava。lang。Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。packagecom。ywj。log;importcom。fasterxml。jackson。databind。ObjectMapper;importcom。ywj。log。biz。SyslogBiz;importcom。ywj。log。dao。SyslogDao;importcom。ywj。login。biz。SysUserBiz;importcom。ywj。login。dao。SysUserDao;importcom。ywj。login。dao。SysrighDao;importorg。aspectj。lang。JoinPoint;importorg。aspectj。lang。annotation。;importorg。springframework。stereotype。Component;importorg。springframework。web。context。request。RequestAttributes;importorg。springframework。web。context。request。RequestContextHolder;importorg。springframework。web。context。request。ServletRequestAttributes;importjavax。servlet。http。HttpServletRequest;importjava。lang。reflect。Method;importjava。text。SimpleDateFormat;importjava。util。Arrays;importjava。util。Date;importjava。util。List;importjava。util。Map;ClassNameSystemLogAspectAuthorAdministratorDescribe定义切入面类AspectComponentpublicclassSystemLogAspect{注解Pointcut切入点定义出一个或一组方法,当执行这些方法时可产生通知指向你的切面类方法由于这里使用了自定义注解所以指向你的自定义注解Pointcut(annotation(com。ywj。log。SystemCrmlog))publicvoidcrmAspect(){}抛出异常后通知(AfterThrowing):方法抛出异常退出时执行的通知注意在这里不能使用ProceedingJoinPoint不然会报错ProceedingJoinPointisonlysupportedforaroundadvicethrowing注解为错误信息paramjoinPointparamexAfterThrowing(valuecrmAspect(),throwingex)publicvoidafterThrowingMethod(JoinPointjoinPoint,Exceptionex)throwsException{HttpServletRequesthttpServletRequestgetHttpServletRequest();获取管理员用户信息WebUtilwebUtilnewWebUtil();MapString,ObjectuserwebUtil。getUser(httpServletRequest);CrmLogMessagelognewCrmLogMessage();获取需要的信息StringcontextgetServiceMthodDescription(joinPoint);Stringusrname;Stringrolename;if(user!null){usrnameuser。get(usrname)。toString();rolenameuser。get(rolename)。toString();}管理员姓名log。setUserName(usrname);角色名log。setUserRole(rolename);日志信息log。setContent(usrnamecontext);设置参数集合log。setRemarks(getServiceMthodParams(joinPoint));设置表名log。setTableName(getServiceMthodTableName(joinPoint));操作时间SimpleDateFormatsifnewSimpleDateFormat(yyyyMMddHH:mm:ss);log。setDateTime(sif。format(newDate()));设置ip地址log。setIp(httpServletRequest。getRemoteAddr());设置请求地址log。setRequestUrl(httpServletRequest。getRequestURI());执行结果log。setResult(执行失败);错误信息log。setExString(ex。getMessage());将数据保存到数据库SyslogDaosysLogDaonewSyslogDao();sysLogDao。addSyslog(log);}返回后通知(AfterReturning):在某连接点(joinpoint)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回方法执行完毕之后注意在这里不能使用ProceedingJoinPoint不然会报错ProceedingJoinPointisonlysupportedforaroundadvicecrmAspect()指向需要控制的方法returning注解返回值paramjoinPointparamreturnValue返回值throwsExceptionAfterReturning(valuecrmAspect(),returningreturnValue)publicvoiddoCrmLog(JoinPointjoinPoint,ObjectreturnValue)throwsException{HttpServletRequesthttpServletRequestgetHttpServletRequest();获取管理员用户信息WebUtilwebUtilnewWebUtil();MapString,ObjectuserwebUtil。getUser(httpServletRequest);CrmLogMessagelognewCrmLogMessage();StringcontextgetServiceMthodDescription(joinPoint);Stringusrname;Stringrolename;if(user!null){usrnameuser。get(usrname)。toString();rolenameuser。get(rolename)。toString();}管理员姓名log。setUserName(usrname);角色名log。setUserRole(rolename);日志信息log。setContent(usrnamecontext);设置参数集合log。setRemarks(getServiceMthodParams(joinPoint));设置表名log。setTableName(getServiceMthodTableName(joinPoint));操作时间SimpleDateFormatsifnewSimpleDateFormat(yyyyMMddHH:mm:ss);log。setDateTime(sif。format(newDate()));设置ip地址log。setIp(httpServletRequest。getRemoteAddr());设置请求地址log。setRequestUrl(httpServletRequest。getRequestURI());if(returnValue!null){if(returnValueinstanceofList){Listls(List)returnValue;if(ls。size()0){log。setResult(执行成功);}else{log。setResult(执行成功);}}elseif(returnValueinstanceofBoolean){Booleanfalg(Boolean)returnValue;if(falg){log。setResult(执行成功);}else{log。setResult(执行失败);}}elseif(returnValueinstanceofInteger){Integeri(Integer)returnValue;if(i0){log。setResult(执行成功);}else{log。setResult(执行失败);}}else{log。setResult(执行成功);}}将数据保存到数据库SyslogDaosysLogDaonewSyslogDao();sysLogDao。addSyslog(log);}获取自定义注解里的日志描述paramjoinPointreturn返回注解里面的日志描述throwsExceptionprivateStringgetServiceMthodDescription(JoinPointjoinPoint)throwsException{类名StringtargetNamejoinPoint。getTarget()。getClass()。getName();方法名StringmethodNamejoinPoint。getSignature()。getName();参数Object〔〕argumentsjoinPoint。getArgs();通过反射获取示例对象ClasstargetClassClass。forName(targetName);通过实例对象方法数组Method〔〕methodstargetClass。getMethods();Stringdescription;for(Methodmethod:methods){判断方法名是不是一样if(method。getName()。equals(methodName)){对比参数数组的长度Class〔〕clazzsmethod。getParameterTypes();if(clazzs。lengtharguments。length){获取注解里的日志信息descriptionmethod。getAnnotation(SystemCrmlog。class)。description();break;}}}returndescription;}获取自定义注解里的表名paramjoinPointreturn返回注解里的表名字throwsExceptionprivateStringgetServiceMthodTableName(JoinPointjoinPoint)throwsException{类名StringtargetNamejoinPoint。getTarget()。getClass()。getName();方法名StringmethodNamejoinPoint。getSignature()。getName();参数Object〔〕argumentsjoinPoint。getArgs();通过反射获取示例对象ClasstargetClassClass。forName(targetName);通过实例对象方法数组Method〔〕methodstargetClass。getMethods();表名StringtableName;for(Methodmethod:methods){判断方法名是不是一样if(method。getName()。equals(methodName)){对比参数数组的长度Class〔〕clazzsmethod。getParameterTypes();if(clazzs。lengtharguments。length){获取注解里的表名tableNamemethod。getAnnotation(SystemCrmlog。class)。tableName();break;}}}returntableName;}获取json格式的参数用于存储到数据库中paramjoinPointreturnthrowsExceptionprivateStringgetServiceMthodParams(JoinPointjoinPoint)throwsException{Object〔〕argumentsjoinPoint。getArgs();ObjectMapperomnewObjectMapper();returnom。writeValueAsString(arguments);}获取当前的request这里如果报空指针异常是因为单独使用spring获取request需要在配置文件里添加监听listenerlistenerclassorg。springframework。web。context。request。RequestContextListenerlistenerclasslistenerreturnpublicHttpServletRequestgetHttpServletRequest(){RequestAttributesraRequestContextHolder。getRequestAttributes();ServletRequestAttributessra(ServletRequestAttributes)ra;HttpServletRequestrequestsra。getRequest();returnrequest;}}
每个切面传递的数据的都不一样,最终决定,获取切面的所有参数,转成json字符串,保存到数据库中。相关类:
日志信息类packagecom。ywj。log;ClassNameCrmLogMessageAuthorAdministratorDescribe数据库日志类publicclassCrmLogMessage{privateIntegerlogid;日志idprivateStringUserName;管理员姓名privateStringUserRole;管理员角色privateStringContent;日志描述privateStringRemarks;参数集合privateStringTableName;表格名称privateStringDateTime;操作时间privateStringresultValue;返回值privateStringip;ip地址privateStringrequestUrl;请求地址privateStringresult;操作结果privateStringExString;错误信息publicCrmLogMessage(){}OverridepublicStringtoString(){returnCrmLogMessage{logidlogid,UserNameUserName,UserRoleUserRole,ContentContent,RemarksRemarks,TableNameTableName,DateTimeDateTime,resultValueresultValue,ipip,requestUrlrequestUrl,resultresult,ExStringExString};}publicCrmLogMessage(Integerlogid,StringuserName,StringuserRole,Stringcontent,Stringremarks,StringtableName,StringdateTime,StringresultValue,Stringip,StringrequestUrl,Stringresult,StringexString){this。logidlogid;UserNameuserName;UserRoleuserRole;Contentcontent;Remarksremarks;TableNametableName;DateTimedateTime;this。resultValueresultValue;this。ipip;this。requestUrlrequestUrl;this。resultresult;ExStringexString;}publicStringgetExString(){returnExString;}publicvoidsetExString(StringexString){ExStringexString;}publicIntegergetLogid(){returnlogid;}publicvoidsetLogid(Integerlogid){this。logidlogid;}publicStringgetUserName(){returnUserName;}publicvoidsetUserName(StringuserName){UserNameuserName;}publicStringgetUserRole(){returnUserRole;}publicvoidsetUserRole(StringuserRole){UserRoleuserRole;}publicStringgetContent(){returnContent;}publicvoidsetContent(Stringcontent){Contentcontent;}publicStringgetRemarks(){returnRemarks;}publicvoidsetRemarks(Stringremarks){Remarksremarks;}publicStringgetTableName(){returnTableName;}publicvoidsetTableName(StringtableName){TableNametableName;}publicStringgetDateTime(){returnDateTime;}publicvoidsetDateTime(StringdateTime){DateTimedateTime;}publicStringgetResultValue(){returnresultValue;}publicvoidsetResultValue(StringresultValue){this。resultValueresultValue;}publicStringgetIp(){returnip;}publicvoidsetIp(Stringip){this。ipip;}publicStringgetRequestUrl(){returnrequestUrl;}publicvoidsetRequestUrl(StringrequestUrl){this。requestUrlrequestUrl;}publicStringgetResult(){returnresult;}publicvoidsetResult(Stringresult){this。resultresult;}}
用来获取登录用户信息的帮助类:packagecom。ywj。log;importcom。base。web。BaseAction;importjavax。servlet。http。HttpServletRequest;importjava。util。Map;ClassNameWebUtilAuthorAdministratorDescribe日志帮助类用来获取session中的用户信息来存入数据库publicclassWebUtil{从session中获取到用户对象returnpublicMapString,ObjectgetUser(HttpServletRequestrequest){MapString,Objectattributenull;if(request!null){Objectuserrequest。getSession()。getAttribute(Constans。USERKEY);attribute(MapString,Object)user;}returnattribute;}}
在你的springcontext。xml中配置!启动对AspectJ注解的支持!自动扫描包路径!你需要刚才的切面类的包路径context:componentscanbasepackagecom。ywj。log!你需要注解方法的包路径context:componentscanbasepackagecom。。。biz。impl
然后在你需要记录的方法上加上注解SystemCrmlog(description进行了登录操作,tableNameConstans。USERTABLENAME)
效果这里表名使用了常量类
对于一些表的信息可以写一个常量类
然后执行登录操作数据库记录为: