Spring日志记录+线程池

来源:互联网 发布:nba2kol球员数据更新 编辑:程序博客网 时间:2024/06/13 05:34

本文将介绍在Spring框架下如何利用拦截器做日志记录,简化我们的日志处理,

1 首先我们需要在Spring-mvc.xml中注册这个拦截器,代码如下:

<mvc:interceptors><mvc:interceptor><mvc:mapping path="/*/*" /><!-- 需拦截的地址 --><mvc:exclude-mapping path="/login/loginIn" /><!-- 登录不拦截 --><bean class="com.zsq.cn.common.sys.interceptor.LogInterceptor" /></mvc:interceptor></mvc:interceptors>

2 新建一个日志类 (setter、getter方法就没有拷贝了,但是重写了setParams方法)

public class Log {private static final long serialVersionUID = 1L;private String type; // 日志类型(1:操作日志;2:错误日志)private String title;// 日志标题private String remoteAddr; // 操作用户的IP地址private String requestUri; // 操作的URIprivate String method; // 操作的方式private String params; // 操作提交的数据private String userAgent;// 操作用户代理信息private String exception; // 异常信息// 日志类型(1:接入日志;2:错误日志)public static final String TYPE_ACCESS = "1";public static final String TYPE_EXCEPTION = "2";public void setParams(String params) {this.params = params;}/** * 设置请求参数 * @param paramMap */@SuppressWarnings({ "unchecked", "deprecation" })public void setParams(Map paramMap){if(paramMap == null){return;}StringBuilder sb = new StringBuilder();for(Map.Entry<String, String[]> para : (((Map<String, String[]>) paramMap).entrySet())){sb.append(para.getKey().endsWith("password") ?para.getKey()+"="+"...":para.getKey()+"="+Arrays.toString(para.getValue())+"::");}setParams(sb.toString());}@Overridepublic String toString(){return ReflectionToStringBuilder.toString(this);}}
3 新建一个拦截器类,即 1中bean的class类

这里需要说明下,这个类的最后一个方法的程序段

String title = (String)request.getAttribute(Constant.LOG_TITLE);
if(title != null){
LogUtils.saveNormalLog(request, handler, ex);
}

因为有很多请求我们是不需要做日志记录的,所以我判断了下 如果有日志信息,则进行日志的持久化操作。这里的Constant.LOG_TITLE一般写在Controller的方法里,我将会在第5个程序段中给出实例。

public class LogInterceptor implements HandlerInterceptor{private Logger logger = LoggerFactory.getLogger(LogInterceptor.class);//每次请求都会保留一个HttpServletRequest副本public static ThreadLocal<HttpServletRequest> curentHttpRequest = new ThreadLocal<>();public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {curentHttpRequest.set(request);//设置本次请求的HttpServletRequestreturn true;}public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {//System.out.println(LocalDate.now()+"访问视图"+modelAndView.getViewName());if(modelAndView != null){logger.info(LocalDate.now()+"访问视图"+modelAndView.getViewName());}}public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {String title = (String)request.getAttribute(Constant.LOG_TITLE);if(title != null){LogUtils.saveNormalLog(request, handler, ex);}}}

4 新建一个日志工具类来处理日志
public class LogUtils {/** * 保存日志 * @param request * @param handler * @param ex * @param title */public static void saveLog(HttpServletRequest request, Object handler, Exception ex,String title) {User user = SessionUtils.getSession(request, SessionNames.SESSION_USER);if (user != null){Log log = new Log();log.setTitle(title);log.setType(ex == null ? Log.TYPE_ACCESS : Log.TYPE_EXCEPTION);log.setRemoteAddr(request.getRemoteAddr());log.setUserAgent(request.getHeader("user-agent"));log.setRequestUri(request.getRequestURI());log.setParams(request.getParameterMap());log.setMethod(request.getMethod());// 异步保存日志new Thread(new SaveLogThread(log)).start();}}/**保存异常日志 * @param exceptionMessage */public static void  saveExceptionLog(String exceptionMessage,Exception ex){HttpServletRequest request = LogInterceptor.curentHttpRequest.get();String title = (String) request.getAttribute(Constant.LOG_TITLE);saveLog(request,null,ex,title+"--"+exceptionMessage);}/** * 正常保存日志 * @param request * @param handler * @param ex */public static void saveNormalLog(HttpServletRequest request, Object handler, Exception ex) {saveLog(request,handler,ex,(String)request.getAttribute(Constant.LOG_TITLE));}/** * 单独开启一个线程持久化日志 * */static class SaveLogThread implements Runnable{private Log log;public SaveLogThread(Log log){this.log = log;}@Overridepublic void run() {if(log != null){//这里进行持久化操作System.out.println("-----------请进行日志持久化操作,下面将输出日志信息--------------");System.out.println(log.toString());}}}}

5 新建一个测试请求类

@Controller@RequestMapping("test")public class TestController {@Autowiredprivate TestService testService;@RequestMapping("test")public String test(HttpServletRequest request,User user,Model model){//注意只有这里request设置日志信息后,该请求才会持久化操作,因为我在3中的最后一个方法判断了如果没有日志信息则不持久化request.setAttribute(Constant.LOG_TITLE, "测试");User user2 = testService.getUser();model.addAttribute("user", user2);return "index/test";}}
但是需要说明的是,只要异常的地方,并且你想记录异常日志,调用saveExceptionLog(String exceptionMessage,Exception ex)方法即可,即使该请求的Controller没有设置

request.setAttribute(Constant.LOG_TITLE, "测试");异常日志也会持久化。


当然,如果每一次日志记录系统都开启一个线程的话,那将会很耗费系统资源的,因为单个线程的开启和销毁都是需要时间的,在这里我们需要对上面的实现方法进行改进,使用线程去做日志持久化记录。

2.1 首先在spring-context.xml中注册一个线程池的bean 

<!-- 使用线程池管理线程 --><bean id="taskExecutor"class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><property name="corePoolSize" value="5" /> <!-- 线程池维护线程的最少数量 --><property name="keepAliveSeconds" value="300" /> <!-- 线程池维护线程所允许的空闲时间 --><property name="maxPoolSize" value="10" /> <!-- 线程池维护线程的最大数量 --><property name="queueCapacity" value="25" /> <!-- 线程池所使用的缓冲队列大小,不设置的话 默认为Integer.MAX_VALUE --></bean>
说明:你可以看到,注册bean的class 是spring提供的ThreadPoolTaskExecutor,我看了下这个类的源码如下



源码中,默认为某些属性赋了默认值,请注意上图中划红线的一个属性,ThreadPoolExecutor。这个类是jdk提供的java.util.concurrent.ThreadPoolExecutor,其实,spring的ThreadPoolTaskExecutor只是对ThreadPoolExecutor完成了一次封装,其最终的实现仍然是ThreadPoolExecutor这个类。到底ThreadPoolTaskExecutor是怎样封装ThreadPoolExecutor的呢?,我们就拿corePoolSize为例,在xml文件设置ThreadPoolTaskExecutor类的属性 :

<property name="corePoolSize" value="5" /> <!-- 线程池维护线程的最少数量 -->

在spring注册这个bean的时候,将会执行下面代码。这段代码也是ThreadPoolTaskExecutor的源码,ThreadPoolTaskExecutor中的每个属性都有这样的一段设置属性值得代码

public void setCorePoolSize(int corePoolSize) {synchronized (this.poolSizeMonitor) {this.corePoolSize = corePoolSize;if (this.threadPoolExecutor != null) {this.threadPoolExecutor.setCorePoolSize(corePoolSize);}}}


好了,回归主题。继续讲一下如何用线程池来做日志服务


2.3 新建一个上下文工具,用于运行时获取spring注册的bean,这个类需要在spring-context.xml文件中配置如下:

<bean id="ApplicationContextUtils" class="com.zsq.cn.common.sys.utils.ApplicationContextUtils" />

public class ApplicationContextUtils implements ApplicationContextAware,DisposableBean{private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}@Overridepublic void destroy() throws Exception {this.applicationContext = null;}/** * 根据类获取bean * @param t  * @return */public static <T> T getBean(Class<T> t){if(null != applicationContext){T object = null;object = applicationContext.getBean(t);return object;}return null;}/** * 根据bean的名字获取bean * @param beanName * @return */public static Object getBean(String beanName){if(null != applicationContext){return  applicationContext.getBean(beanName);}return null;}}



2.2 新建一个线程池工具类,所有实现Runable接口的线程都可以用这个线程池

public class ThreadPoolUtils {/** * 获取线程池对象,其已经在 spring-context.xml中注册。 */private static ThreadPoolTaskExecutor tpte = (ThreadPoolTaskExecutor) ApplicationContextUtils.getBean(ThreadPoolTaskExecutor.class);public static void execute(Runnable task){tpte.execute(task);}}

2.3 将 代码段 4 中的 

//异步报错日志

new Thread(new SaveLog(log)).start()

这行代码改为

ThreadPoolUtils.execute(new LogTask(log));


这样,所有的日志服务都可以交给线程池去处理了。关于线程池这篇文章没有太深入研究原理,还有一些特性并没有提到。比如。任务缓存队列及排队策略、任务拒绝策略、线程池的关闭、线程池容量的动态调整。感兴趣的朋友可以参考下这篇博文,很详细地阐述了线程池的原理。

http://www.importnew.com/19011.html