【Java EE 学习 76 下】【数据采集系统第八天】【通过AOP实现日志管理】【日志管理功能分析和初步实现】

时间:2020-12-07 14:39:58

一、日志管理相关分析

  1.日志管理是一种典型的系统级别的应用,非常适合使用spring AOP实现。

  2.使用日志管理的目的:对系统修改的动作进行记录,比如对权限、角色、用户的写操作、修改操作、删除操作等

  3.确定使用的通知方式:使用环绕通知。复习一下环绕通知,所谓环绕通知实际上就是AOP代理对接口中声明方法的执行进行拦截,在执行方法之前或者之后进行一些操作,在日志管理功能模块中,我们对Service接口中声明的方法进行拦截,如果是对系统进行的修改操作的方法,则将方法执行之后就需要将相关信息保存下来方便以后查看,比如是谁登陆的系统进行的修改(最重要),执行的是什么方法,方法的参数是什么,执行成功额还是失败了,方法的返回值是什么,执行方法的时间是什么等等。

  4.日志管理功能在系统中的体现:单击导航菜单中的"日志管理"超链接,查看所有的系统的变更历史记录。

二、日志实体和相关类的书写

  1.根据一种的分析,得到了以下的日志实体

 public class Log {
private String logId; //日志消息标识ID
private String operator=""; //操作人
private Date operatorDate=new Date(); //操作日期
private String operatorName; //操作的名称(方法名)
private String operateParams; //操作参数
private String operateResult=""; //操作结果(success|failure)
private String resultMessage=""; //结果消息
}

  2.DAO类的书写和Service的书写略。

  3.创建日志切面

  日志切面提供了保存保存日志的详细方法(通知),该方法将会拦截目标方法的执行并且记录该方法执行过程中的详细信息。

 package com.kdyzm.aspect;

 import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import org.apache.struts2.ServletActionContext;
import org.aspectj.lang.ProceedingJoinPoint; import com.kdyzm.domain.Log;
import com.kdyzm.domain.User;
import com.kdyzm.service.LogService;
import com.kdyzm.utils.StringUtils; public class Logger {
private LogService logService;
public LogService getLogService() {
return logService;
}
public void setLogService(LogService logService) {
this.logService = logService;
}
//通知方法
public Object record(ProceedingJoinPoint joinPoint) throws Throwable{
Log log=new Log();
try{
//获取操作人
HttpServletRequest request=ServletActionContext.getRequest();
if(request!=null){
HttpSession session=request.getSession();
User user=(User) session.getAttribute("user");
if(user!=null){
log.setOperator(user.getUserId()+"-"+user.getEmail());//设置操作人
}
} //设置方法名
String methodName=joinPoint.getSignature().getName();
log.setOperatorName(methodName); //获取参数列表
Object[] params=joinPoint.getArgs();
log.setOperateParams(StringUtils.arr2String(params)); //操作结果和结果消息的获取
Object obj=joinPoint.proceed();
log.setOperateResult("SUCCESS"); if(obj!=null){
log.setResultMessage(obj.toString());
}
return obj; //返回执行结果
}catch(Exception e){
log.setOperateResult("FAILURE");
log.setResultMessage(e.getMessage());
}finally{
logService.saveLog(log);
}
return null;
}
}

  4.配置spring配置文件applicationConext.xml

    (1)首先将切面注入到spring容器

<bean id="logger" class="com.kdyzm.aspect.Logger">
<property name="logService" ref="logService"></property>
</bean>  

    (2)在aop配置中声明切入点表达式,表示对那些方法进行日志记录

      注意这些方法都在事务通知上有定义,但是不能直接使用事务通知中的声明,因为还有不同之处:必须刨除掉logService中的所有方法,否则最后肯定会抛出堆栈溢出的异常。

      分析原因:logService中的记录方法本身也是写操作(默认加上了事务),如果是写操作按照规则是需要写入日志表的,但是写入的时候又被AOP日志代理拦截,每次想写的时候都被日志AOP代理拦截,最终这种无限递归方式就会导致堆栈溢出,注意以下的切入点表达式的写法已经将logService刨除掉了。

 <aop:pointcut
expression="(execution(* *..*Service.save*(..))
or execution(* *..*Service.update*(..))
or execution(* *..*Service.delete*(..))
or execution(* *..*Service.batch*(..))
or execution(* *..*Service.create*(..))
or execution(* *..*Service.new*(..))) and !bean(logService)"
id="loggerPointcut" />

    (3)切面配置,声明使用哪个切面中的哪些方法使用何种通知方式在指定的切入点上织入到目标对象

<aop:aspect id="loggerAspect" ref="logger" order="1">
  <aop:around method="record" pointcut-ref="loggerPointcut" />
</aop:aspect>

      这里的意思是使用logger切面,使用切面中的record方法以环绕通知的方式在loggerPointcut指定的切入点上织入到目标对象(Service对象)。

  5.显示所有日志列表

    基本显示是没有什么问题的,但是这里使用了静态调用的方法,可以直接在jsp页面调用某个类的静态方法,这里由于可能会出现参数名称过长的问题,必须在在这里对显示的长度进行限制。

    (1)com.kdyzm.utils.StringUtils类中定义静态方法setTagContentLimitLength,默认长度为15,也就是说最多只是显示15个字符。

 //通过jsp页面的静态调用可以直接调用某个类的某个方法
public static String setTagContentLimitLength(String string){
int length=15;
System.out.println("访问了setTagContentLimitLength方法!"+string);
if(string !=null){
if(string.length()>length){
return string.substring(0,length)+"......";
}else{
return string;
}
}
return "";
}

    (2)在jsp页面中调用,调用新式:<s:property value="@类的全名@方法名(参数列表)"/>

<s:iterator value="%{#logList}" status="st">
  <tr>
    <td><s:property value="operator" /></td>
    <td><s:property value="operatorName" /></td>
    <td><s:property value="@com.kdyzm.utils.StringUtils@setTagContentLimitLength(operateParams)" /></td>
    <td><s:property value="operateResult" /></td>
    <td><s:property value="resultMessage" /></td>
    <td><s:date name="operatorDate" format="yyyy/MM/dd HH:mm:ss" /></td>
  </tr>
</s:iterator>

    (3)如果只是以上两步肯定还会报错,因为strutrs2默认禁用静态调用,需要在配置文件中开启,这里为了方便起见,直接使用了struts.properties进行了配置

 struts.i18n.encoding=UTF-8
struts.action.extension=action,,
struts.serve.static.browserCache=false
struts.i18n.reload=true
struts.configuration.xml.reload=true
struts.devMode=true
struts.ui.theme=simple
struts.enable.DynamicMethodInvocation=true
struts.multipart.maxSize=2097152
struts.ognl.allowStaticMethodAccess=true

三、测试日志

  1.新建调查之后查看日志:

【Java EE 学习 76 下】【数据采集系统第八天】【通过AOP实现日志管理】【日志管理功能分析和初步实现】

  2.将新建的调查删除掉之后:

【Java EE 学习 76 下】【数据采集系统第八天】【通过AOP实现日志管理】【日志管理功能分析和初步实现】