本篇文章的诞生离不开这篇文章的作者:http://blog.csdn.net/czmchen/article/details/42392985。

前言

操作日志在javaWeb的业务系统中是在是太常见的功能了,主要记录用户再什么时间,什么位置进行了什么操作。如果每新增一个功能都要写一个插入代码的话,是非常不容易维护的。加一个字段就要在每个插入语句上加入这个字段。所以AOP+注解的优势就显现了出来,不仅如此,当我们有了这套代码以后,可以通用在该系统的wap端或者其他的系统中,不必修改太多的代码。针对日志这种实时性不是很高的功能,这里用了异步的方式进行,这样日志系统独立出来,不会影响业务。下面是我整理的代码,欢迎留下宝贵意见。这里再次感谢原作者提供良好的思路。


代码一、核心类1.自定义注解 拦截Controller

/****自定义注解拦截Controller*@see[相关类/方法]*@since[产品/模块版本]*/@Target({ElementType.PARAMETER,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceSystemControllerLog{Stringdescription()default"";//描述StringmoduleType()default"";//模块代码StringoperateValue()default"";//操作类型booleanfirstParamName()defaultfalse;}2.自定义注解 拦截service

/****自定义注解拦截service*@see[相关类/方法]*@since[产品/模块版本]*/@Target({ElementType.PARAMETER,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceSystemServiceLog{Stringdescription()default"";//描述StringmoduleType()default"";//模块代码StringoperateValue()default"";//操作类型}3.AOP可以拦截到controller的配置

spring.xml中加入下面这句话

<aop:aspectj-autoproxyproxy-target-class="true"/>4.切点类

这里用的是返回通知,用来接收成功或失败。

/****AOP记录操作&异常日志-切点类*@see[相关类/方法]*@since[产品/模块版本]*/@Aspect@ComponentpublicclassSystemLogAspect{privatestaticfinalLoggerlogger=LoggerFactory.getLogger(SystemLogAspect.class);//队列privatestaticBlockingQueue<Log>queue=newLinkedBlockingQueue<Log>();//缓存线程池privatestaticExecutorServicethreadPool=Executors.newFixedThreadPool(3);//任务数privatestaticinttaskSize=6;//线程是否已启动booleanisStartThread=false;//用来启动或停止线程staticbooleanrun=true;@AutowiredprivateLogServicelogService;@AutowiredprivateUserServiceuserService;//Service层切点@Pointcut("@annotation(com.rzzl.wap.log.annotation.SystemServiceLog)")publicvoidserviceAspect(){}//Controller层切点@Pointcut("@annotation(com.rzzl.wap.log.annotation.SystemControllerLog)")publicvoidcontrollerAspect(){}publicstaticBlockingQueue<Log>getQueue(){returnqueue;}publicstaticvoidsetQueue(BlockingQueue<Log>queue){SystemLogAspect.queue=queue;}publicstaticbooleanisRun(){returnrun;}publicstaticvoidsetRun(booleanrun){SystemLogAspect.run=run;}/****返回通知用于拦截Controller层记录用户的操作*@paramjoinPoint切点*@paramresult返回值*@see[类、类#方法、类#成员]*/@AfterReturning(value="controllerAspect()",returning="result")publicvoidafterReturn(JoinPointjoinPoint,Objectresult){//请求的IPUseruser=WebUtils.getSessionValue(LoginContact.SESSION_USER);Stringparams="";WebResultwebResult=newWebResult();webResult.setCode(FlagContact.BACK_SUCCESS);try{if(WebResult.class.isInstance(result)){webResult=(WebResult)result;}StringloginName="";InnnerBeaninnnerBean=getControllerMethodDescription(joinPoint);Object[]arguments=innnerBean.getArguments();Stringremark=innnerBean.getDescription();Loglog=newLog.Builder().type(LogTypes.type.operate).moduleType(innnerBean.getModuleType()).operateCode(joinPoint.getSignature().getName()).operateValue(innnerBean.getOperateValue()).remark(remark).operateStatus(webResult.getCode().equals(FlagContact.BACK_SUCCESS)?LogTypes.operateStatus.Y:LogTypes.operateStatus.N)//返回值1操作成功,否则失败.method((joinPoint.getTarget().getClass().getName()+"."+joinPoint.getSignature().getName()+"()")).param(params).loginName(user.getAccountNo()).fullName(user.getUserName()).build();//放入队列queue.put(log);if(!isStartThread){for(inti=0;i<taskSize;i++){threadPool.execute(newsaveLogThread());}isStartThread=true;}}catch(Exceptione){logger.error("异常信息:{}",e.toString());}}/***异常通知用于拦截service层记录异常日志*@paramjoinPoint*@parame*@see[类、类#方法、类#成员]*/@AfterThrowing(pointcut="serviceAspect()",throwing="e")publicvoiddoAfterThrowing(JoinPointjoinPoint,Throwablee){//读取session中的用户Useruser=WebUtils.getSessionValue(LoginContact.SESSION_USER);Stringparams="";try{if(joinPoint.getArgs()!=null&&joinPoint.getArgs().length>0){for(inti=0;i<joinPoint.getArgs().length;i++){params+=JSONUtils.valueToString(joinPoint.getArgs()[i].toString())+";";}}InnnerBeaninnnerBean=getServiceMthodDescription(joinPoint);StringloginName="";Loglog=newLog.Builder().type(LogTypes.type.exception).moduleType(innnerBean.getModuleType()).operateCode(joinPoint.getSignature().getName()).operateValue(innnerBean.getOperateValue()).remark(innnerBean.getDescription()).operateStatus(LogTypes.operateStatus.N).method((joinPoint.getTarget().getClass().getName()+"."+joinPoint.getSignature().getName()+"()")).param(params).exceptionDetail(e.toString()).build();//放入队列queue.put(log);if(!isStartThread){newThread(newsaveLogThread()).start();isStartThread=true;}}catch(Exceptionex){logger.error("异常信息:{}",ex.toString());}finally{logger.error("异常方法:{"+joinPoint.getTarget().getClass().getName()+"}异常代码:{"+joinPoint.getSignature().getName()+"}异常信息:{"+e.toString()+"}参数:{"+params+"}");}}/***获取注解中对方法的描述信息用于service层注解*@paramjoinPoint切点*@return方法描述*@throwsException*@see[类、类#方法、类#成员]*/@SuppressWarnings("rawtypes")publicstaticInnnerBeangetServiceMthodDescription(JoinPointjoinPoint)throwsException{StringtargetName=joinPoint.getTarget().getClass().getName();StringmethodName=joinPoint.getSignature().getName();Object[]arguments=joinPoint.getArgs();ClasstargetClass=Class.forName(targetName);Method[]methods=targetClass.getMethods();StringmoduleType="";StringoperateValue="";Stringdescription="";InnnerBeaninnnerBean=newInnnerBean(moduleType,operateValue,description);for(Methodmethod:methods){if(method.getName().equals(methodName)){Class[]clazzs=method.getParameterTypes();if(clazzs.length==arguments.length){SystemServiceLogannotation=method.getAnnotation(SystemServiceLog.class);moduleType=annotation.moduleType();operateValue=annotation.operateValue();description=annotation.description();innnerBean=newInnnerBean(moduleType,operateValue,description);break;}}}innnerBean.setArguments(arguments);returninnnerBean;}/***获取注解中对方法的描述信息用于Controller层注解*@paramjoinPoint切点*@return方法描述*@throwsException*@see[类、类#方法、类#成员]*/@SuppressWarnings("rawtypes")publicstaticInnnerBeangetControllerMethodDescription(JoinPointjoinPoint)throwsException{StringtargetName=joinPoint.getTarget().getClass().getName();StringmethodName=joinPoint.getSignature().getName();Object[]arguments=joinPoint.getArgs();ClasstargetClass=Class.forName(targetName);Method[]methods=targetClass.getMethods();StringmoduleType="";StringoperateValue="";Stringdescription="";booleanfirstParamName=false;InnnerBeaninnnerBean=newInnnerBean(moduleType,operateValue,description);for(Methodmethod:methods){if(method.getName().equals(methodName)){Class[]clazzs=method.getParameterTypes();if(clazzs.length==arguments.length){SystemControllerLogannotation=method.getAnnotation(SystemControllerLog.class);moduleType=annotation.moduleType();operateValue=annotation.operateValue();description=annotation.description();firstParamName=annotation.firstParamName();innnerBean=newInnnerBean(moduleType,operateValue,description);innnerBean.setFirstParamName(firstParamName);break;}}}innnerBean.setArguments(arguments);returninnnerBean;}/****内部类封装注入信息*@see[相关类/方法]*@since[产品/模块版本]*/staticclassInnnerBean{privateStringmoduleType;//模块代码privateStringdescription;//描述privateStringoperateValue;//操作类型privatebooleanfirstParamName;privateObject[]arguments;publicInnnerBean(StringmoduleType,StringoperateValue,Stringdescription){super();this.moduleType=moduleType;this.description=description;this.operateValue=operateValue;}publicStringgetOperateValue(){returnoperateValue;}publicvoidsetOperateValue(StringoperateValue){this.operateValue=operateValue;}publicStringgetModuleType(){returnmoduleType;}publicvoidsetModuleType(StringmoduleType){this.moduleType=moduleType;}publicStringgetDescription(){returndescription;}publicvoidsetDescription(Stringdescription){this.description=description;}publicObject[]getArguments(){returnarguments;}publicvoidsetArguments(Object[]arguments){this.arguments=arguments;}publicbooleanisFirstParamName(){returnfirstParamName;}publicvoidsetFirstParamName(booleanfirstParamName){this.firstParamName=firstParamName;}}/****异步保存日志*@see[相关类/方法]*@since[产品/模块版本]*/classsaveLogThreadimplementsRunnable{Locklock=newReentrantLock();@Overridepublicvoidrun(){try{while(run){while(queue.size()!=0){//如果对插入顺序无要求,此处不需要同步可提升效率lock.lock();Loglog=queue.take();logService.insert(log);lock.unlock();}Thread.sleep(3000);}}catch(InterruptedExceptione){logger.error("saveLogThread被唤醒:"+e.toString());}catch(Exceptione){logger.error("saveLogThread异常:"+e.toString());}}}}二、定值类日志系统中的定值

/****日志系统中的定值**@see[相关类/方法]*@since[产品/模块版本]*/publicinterfaceLogTypes{/****操作状态(成功与否Y\\N)*@see[相关类/方法]*@since[产品/模块版本]*/staticinterfaceoperateStatus{//成功finalStringY="Y";//失败finalStringN="N";}/****日志类型*@see[相关类/方法]*@since[产品/模块版本]*/staticinterfacetype{//操作日志finalStringoperate="operate";//异常日志finalStringexception="exception";}/****模块类型*@see[相关类/方法]*@since[产品/模块版本]*/staticinterfacemoduleType{//登录模块finalStringLOGIN="LOGIN";//项目模块finalStringPROJECT="PROJECT";//客户模块finalStringCUSTOMER="CUSTOMER";//用户模块finalStringSYS_USER="SYS_USER";}/****操作类型*@see[相关类/方法]*@since[产品/模块版本]*/staticinterfaceoperateValue{//查询finalStringselect="select";//登录finalStringlogin="login";//保存finalStringsave="save";//新增finalStringadd="add";//修改finalStringedit="edit";//删除finalStringdelete="delete";//查看finalStringview="view";//修改密码finalStringeditPassword="editPassword";//上传finalStringupload="upload";//下载finalStringdown="down";//下载finalStringpackagedown="packagedown";}/****保存描述的前缀*方便于批量修改该备注*@see[相关类/方法]*@since[产品/模块版本]*/staticinterfacePrefix{//查询finalStringsavePrefix="新增/编辑";}}三、实体类1、日志实体


应用了Builder设计模式

/****操作日志&异常日志*@see[相关类/方法]*@since[产品/模块版本]*/publicclassLogimplementsSerializable{/***serialVersionUID*/privatestaticfinallongserialVersionUID=1L;privatestaticfinalStringreqSource;//请求来源,pc:pc端,wap:wap端默认来源为pcprivatestaticfinalStringlocalAddr;//服务器IPprivateStringip;//操作电脑ipprivateStringfullName;//操作人员名字privateStringloginName;//操作人员登录账号privateDateoperateDateTime;//操作时间privateDatecreateDateTime;//创建时间privateLongid;privateStringtype;//日志类型,‘operate’:操作日志,‘exception’:异常日志privateStringmoduleType;//模块代码privateStringoperateCode;//操作代码privateStringoperateValue;//操作类型privateStringremark;//操作备注(记录参数)privateStringoperateStatus;//操作状态(成功与否Y\\N)privateStringmethod;//调用方法privateStringparam;//方法的请求参数privateStringexceptionDetail;//异常信息static{reqSource="wap";localAddr=WebUtils.getLocalAddr();}publicvoidinit(){Useruser=WebUtils.getSessionValue(LoginContact.SESSION_USER);ip=WebUtils.getRemoteIP();loginName=user!=null?user.getAccountNo():getLoginName();fullName=user!=null?user.getUserName():getFullName();operateDateTime=newDate();createDateTime=newDate();}publicLog(){init();}publicLog(Logorigin){init();this.id=origin.id;this.type=origin.type;this.moduleType=origin.moduleType;this.operateCode=origin.operateCode;this.operateValue=origin.operateValue;this.remark=origin.remark;this.operateStatus=origin.operateStatus;this.method=origin.method;this.param=origin.param;this.exceptionDetail=origin.exceptionDetail;this.fullName=origin.fullName;this.loginName=origin.loginName;}publicStringgetIp(){returnip;}publicvoidsetIp(Stringip){this.ip=ip;}publicStringgetFullName(){returnfullName;}publicvoidsetFullName(StringfullName){this.fullName=fullName;}publicStringgetLoginName(){returnloginName;}publicvoidsetLoginName(StringloginName){this.loginName=loginName;}publicDategetOperateDateTime(){returnoperateDateTime;}publicvoidsetOperateDateTime(DateoperateDateTime){this.operateDateTime=operateDateTime;}publicDategetCreateDateTime(){returncreateDateTime;}publicvoidsetCreateDateTime(DatecreateDateTime){this.createDateTime=createDateTime;}publicLonggetId(){returnid;}publicvoidsetId(Longid){this.id=id;}publicStringgetType(){returntype;}publicvoidsetType(Stringtype){this.type=type;}publicStringgetModuleType(){returnmoduleType;}publicvoidsetModuleType(StringmoduleType){this.moduleType=moduleType;}publicStringgetOperateCode(){returnoperateCode;}publicvoidsetOperateCode(StringoperateCode){this.operateCode=operateCode;}publicStringgetOperateValue(){returnoperateValue;}publicvoidsetOperateValue(StringoperateValue){this.operateValue=operateValue;}publicStringgetRemark(){returnremark;}publicvoidsetRemark(Stringremark){this.remark=remark;}publicStringgetOperateStatus(){returnoperateStatus;}publicvoidsetOperateStatus(StringoperateStatus){this.operateStatus=operateStatus;}publicStringgetMethod(){returnmethod;}publicvoidsetMethod(Stringmethod){this.method=method;}publicStringgetParam(){returnparam;}publicvoidsetParam(Stringparam){this.param=param;}publicStringgetExceptionDetail(){returnexceptionDetail;}publicvoidsetExceptionDetail(StringexceptionDetail){this.exceptionDetail=exceptionDetail;}publicStringgetLocalAddr(){returnlocalAddr;}publicstaticclassBuilder{privateLogtarget;publicBuilder(){target=newLog();}publicBuilderid(Longid){target.id=id;returnthis;}publicBuildertype(Stringtype){target.type=type;returnthis;}publicBuildermoduleType(StringmoduleType){target.moduleType=moduleType;returnthis;}publicBuilderoperateCode(StringoperateCode){target.operateCode=operateCode;returnthis;}publicBuilderoperateValue(StringoperateValue){target.operateValue=operateValue;returnthis;}publicBuilderremark(Stringremark){target.remark=remark;returnthis;}publicBuilderoperateStatus(StringoperateStatus){target.operateStatus=operateStatus;returnthis;}publicBuildermethod(Stringmethod){target.method=method;returnthis;}publicBuilderparam(Stringparam){target.param=param;returnthis;}publicBuilderexceptionDetail(StringexceptionDetail){target.exceptionDetail=exceptionDetail;returnthis;}publicBuilderloginName(StringloginName){target.loginName=loginName;returnthis;}publicBuilderfullName(StringfullName){target.fullName=fullName;returnthis;}publicLogbuild(){returnnewLog(target);}}}2.返回结果实体

/***@ClassName:WebResult*@version1.0*@Desc:WEB返回JSON结果*@historyv1.0*/publicclassWebResultimplementsSerializable{privatestaticfinallongserialVersionUID=-4776437900752507269L;/***返回消息*/privateStringmsg;/***返回码*/privateStringcode;/***返回数据*/privateObjectdata;privateMap<?,?>map;privateList<Map<String,Object>>list;publicWebResult(){}publicWebResult(Stringmsg,Stringcode){super();this.msg=msg;this.code=code;}publicWebResult(Stringmsg,Stringcode,Objectdata){super();this.msg=msg;this.code=code;this.data=data;}publicStringgetMsg(){returnmsg;}publicvoidsetMsg(Stringmsg){this.msg=msg;}publicStringgetCode(){returncode;}publicvoidsetCode(Stringcode){this.code=code;}publicObjectgetData(){returndata;}publicvoidsetData(Objectdata){this.data=data;}publicMap<?,?>getMap(){returnmap;}publicvoidsetMap(Map<?,?>map){this.map=map;}publicList<Map<String,Object>>getList(){returnlist;}publicvoidsetList(List<Map<String,Object>>list){this.list=list;}@OverridepublicStringtoString(){return"WebResult[msg="+msg+",code="+code+",data="+data+"]";}/***初始失败方法**@authorccHSSD0473*@see[类、类#方法、类#成员]*/publicvoidinvokeFail(){this.data=null;this.code=FlagContact.BACK_FAIL;this.msg="操作失败";}publicvoidinvokeFail(Stringmsg){this.data=null;this.code=FlagContact.BACK_FAIL;if(msg!=null&&!msg.equals("")){this.msg=msg;}}publicvoidinvokeSuccess(){this.code=FlagContact.BACK_SUCCESS;this.msg="操作成功";}publicvoidinvokeSuccess(Stringmsg){if(msg!=null&&!msg.equals("")){this.msg=msg;}this.code=FlagContact.BACK_SUCCESS;}}三、工具类

获取登录用户,客户ip主机ip等方法

***@ClassName:WebUtils*@version1.0*@Desc:WebUtils*@historyv1.0*/publicclassWebUtils{/***描述:获取request对象*@return*/publicstaticHttpServletRequestgetRequest(){return((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();}/***描述:获取responce对象*@return*/publicstaticHttpServletResponsegetResponse(){return((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();}/***描述:获取session*@return*/publicstaticHttpSessiongetSession(){returngetRequest().getSession();}/***描述:设置session值*@paramkey*@paramval*/publicstatic<T>voidsetSessionValue(Stringkey,Tval){getSession().setAttribute(key,val);}/***描述:获取session值*@paramkey*@return*/@SuppressWarnings("unchecked")publicstatic<T>TgetSessionValue(Stringkey){return(T)getSession().getAttribute(key);}/***描述:移除session*@paramkey*@return*/@SuppressWarnings("unchecked")publicstatic<T>TremoveSessionValue(Stringkey){Objectobj=getSession().getAttribute(key);getSession().removeAttribute(key);return(T)obj;}/***描述:获取客户端ip*@paramrequest*@return*/publicstaticStringgetRemoteIP(HttpServletRequestrequest){Stringip=request.getHeader("x-forwarded-for");if(ip==null||ip.length()==0||"unknown".equalsIgnoreCase(ip)){ip=request.getHeader("Proxy-Client-IP");}if(ip==null||ip.length()==0||"unknown".equalsIgnoreCase(ip)){ip=request.getHeader("WL-Proxy-Client-IP");}if(ip==null||ip.length()==0||"unknown".equalsIgnoreCase(ip)){ip=request.getRemoteAddr();}returnip.equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip;}/****获得本机IP*@return*@throwsException*@see[类、类#方法、类#成员]*/publicfinalstaticStringgetLocalAddr(){StringhostAddress="";try{hostAddress=InetAddress.getLocalHost().getHostAddress();}catch(Exceptione){}returnhostAddress;}/***描述:获取客户端ip*@return*/publicstaticStringgetRemoteIP(){HttpServletRequestrequest=getRequest();returngetRemoteIP(request);}}四、清理工作tomcat停止前,异步日志的清理动作

/****tomcat停止前,异步日志的清理动作*@see[相关类/方法]*@since[产品/模块版本]*/@WebListener()publicclassBeforeDestoryListenerimplementsServletContextListener{@OverridepublicvoidcontextInitialized(ServletContextEventsce){}@OverridepublicvoidcontextDestroyed(ServletContextEventsce){while(SystemLogAspect.getQueue().size()==0){SystemLogAspect.setRun(false);break;}}}


数据库

用的是mysql,其他数据库请自行更改语句

CREATETABLE`t_log`(`id`bigint(20)NOTNULLAUTO_INCREMENT,`reqSource`varchar(10)DEFAULT'pc'COMMENT'请求来源,pc:pc端,wap:wap端默认来源为pc',`type`varchar(10)DEFAULTNULLCOMMENT'日志类型,‘operate’:操作日志,‘exception’:异常日志',`ip`varchar(20)NOTNULLCOMMENT'操作电脑ip',`fullName`varchar(50)NOTNULLCOMMENT'操作人员名字',`loginName`varchar(50)NOTNULLCOMMENT'操作人员登录账号',`moduleType`varchar(50)NOTNULLCOMMENT'模块代码',`operateCode`varchar(50)NOTNULLCOMMENT'操作代码',`operateValue`varchar(50)DEFAULTNULLCOMMENT'操作类型',`operateDateTime`datetimeNOTNULLCOMMENT'操作时间',`createDateTime`datetimeNOTNULLCOMMENT'创建时间',`remark`varchar(100)DEFAULTNULLCOMMENT'操作备注(记录参数)',`operateStatus`varchar(20)DEFAULTNULLCOMMENT'操作状态(成功与否Y\\N)',`localAddr`varchar(20)DEFAULTNULLCOMMENT'服务器IP',`method`varchar(100)DEFAULTNULLCOMMENT'调用方法',`param`varchar(2000)DEFAULTNULLCOMMENT'方法的请求参数',`exceptionDetail`varchar(1000)DEFAULTNULLCOMMENT'异常信息',PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=3430DEFAULTCHARSET=utf8ROW_FORMAT=DYNAMIC;


测试及结果控制层代码

/***登录请求**@paramuserName用户名*@parampassword密码*@returnWebResultmsg:系统反馈消息code:登录标识码*@see[类、类#方法、类#成员]*/@ResponseBody@RequestMapping("login")@SystemControllerLog(moduleType=LogTypes.moduleType.LOGIN,operateValue=LogTypes.operateValue.login,description="登录动作")publicWebResultuserLogin(StringaccoutnNo,Stringpassword){WebResultwt=newWebResult();try{//登录逻辑}catch(Exceptione){log.error("登录异常:"+e.toString());wt.invokeFail();}returnwt;}数据库结果