1. 程式人生 > >Spring日誌記錄+執行緒池

Spring日誌記錄+執行緒池

本文將介紹在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; 	// 操作的URI
	private 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());
	}

	@Override
	public 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);//設定本次請求的HttpServletRequest
		return 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;
		}

		@Override
		public void run() {
			if(log != null){
				//這裡進行持久化操作
				System.out.println("-----------請進行日誌持久化操作,下面將輸出日誌資訊--------------");
				System.out.println(log.toString());
			}
		}
	}
}

5 新建一個測試請求類
@Controller
@RequestMapping("test")
public class TestController {
	
	@Autowired
	private 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;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;		
	}

	@Override
	public 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

相關推薦

Spring日誌記錄+執行

本文將介紹在Spring框架下如何利用攔截器做日誌記錄,簡化我們的日誌處理, 1 首先我們需要在Spring-mvc.xml中註冊這個攔截器,程式碼如下: <mvc:interceptors> <mvc:interceptor> <

踩坑 Spring Cloud Hystrix 執行佇列配置

背景: 有一次在生產環境,突然出現了很多筆還款單被掛起,後來排查原因,發現是內部系統呼叫時出現了Hystrix呼叫異常。在開發過程中,因為核心執行緒數設定的比較大,沒有出現這種異常。放到了測試環境,偶爾有出現這種情況,後來在網上查詢解決方案,網上的方案是調整maxQueueSize屬性就好了,當時調整了一下

spring mvc tomcat 執行的坑

1 配置tomcat  執行緒池設定為20個執行緒處理請求 2 後臺框架是springmvc   3 模擬10個請求 4  發現tomcat執行緒池沒一個幹活的 5 幹活的是spring自己建立的執行緒 為什麼springmvc

使用Java佇列來處理日誌資訊(執行的使用)

阿里的規範是使用new ThreadPoolExecutor()來建立執行緒池,二不是使用Excutor的靜態工具類來建立執行緒池,具體可以檢視部落格(兩篇):   https://blog.csdn.net/angus_Lucky/article/details/798

2.1 Spring boot/cloud 執行

Step 1:ExecutePool配置,開啟@EnableAsync支援非同步任務 package com.springboot.begin.threadPool; import org.springframework.context.annotation.Bean; import org.

Spring中的執行

前言:   Java SE 5.0引入了ThreadPoolExecutor、ScheduledThreadPoolExecutor。Spring 2.x藉助ConcurrentTaskExecutor和ThreadPoolTaskExecutor能夠通過IoC配置形式自定義它

2、使用SPRING中的執行ThreadPoolTaskExecutor實現JAVA併發

new Thread的弊端如下:a. 每次new Thread新建物件效能差。b. 執行緒缺乏統一管理,可能無限制新建執行緒,相互之間競爭,及可能佔用過多系統資源導致宕機或oom。c. 缺乏更多功能,如定時執行、定期執行、執行緒中斷。相比new Thread,Java提供的四種執行緒池的好處在於:a

使用SPRING中的執行ThreadPoolTaskExecutor實現JAVA併發

使用SPRING中的執行緒池ThreadPoolTaskExecutor實現併發。 一:不需要返回值的情況  1,初始化執行緒池 Java程式碼   ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPool

Spring Boot系列二 Spring @Async非同步執行用法總結

Spring非同步執行緒池的介面類,其實質是java.util.concurrent.ExecutorSpring 已經實現的異常執行緒池: 1. SimpleAsyncTaskExecutor:不是真的執行緒池,這個類不重用執行緒,每次呼叫都會建立一個新的執行緒。 2. SyncTaskExecutor:這

Spring+TaskExecutor實現執行管理

目錄 目錄 寫在前面 執行緒池引數 程式碼實現 部落格參考 寫在前面   執行緒池可以很好的幫助我們管理執行緒,它會預先建立若干數量的執行緒,並且不能由開發者直接對執行緒的建立進行控制,這樣,消除了頻繁建立和消亡執行緒的系統資源開銷。

spring中使用執行

bean 配置applicationContext.xml <bean id="threadPoolTaskExecutor" class="org.springframework.sched

Spring--springmvc配執行Executor做多執行併發操作

載入xml檔案在ApplicationContext.xml檔案裡面新增[java] view plain copy print?xmlns:task="http://www.springframework.org/schema/task"xmlns:task="http

使用SPRING中的執行ThreadPoolTaskExecutor並且得到任務執行的結果

XML配置 <bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <!-- 核心執行

spring框架管理執行

今天來記錄一下,如何通過 spring框架 簡單配置並管理 執行緒池 1、首先需要搭建好一個簡單的spring框架(當然知道是屁話) 如果不知道怎麼搭建spring框架的同學可以看我的上一篇文章,也

spring中引入執行,設定執行優先順序。

在spring.xml 檔案中配置: <!-- 執行緒--> <bean id="taskExecutor" class="org.springframework.sc

Spring中配置執行ThreadPoolExecutor參考

<bean id="executorService" class="java.util.concurrent.ThreadPoolExecutor"> <constructor-

spring+ActiveMQ+JMS+執行實現簡單的分散式,多執行,多工的非同步任務處理系統

前言:隨著系統的業務功能不斷增強,傳統的單機、單任務,單執行緒的執行模式已經逐漸的被淘汰,取而代之的是分散式,多工,多執行緒,當然,現在開源的這方面的框架也非常的多,大概的思想也都類似,下面就結合我這一年多的工作心得,分享一個簡單易實現的分散式,多工,多執行緒的非同步任務處理系統的基本實現。 1.系統部署圖

Quartz應用——Spring和Quartz加執行實際應用

公司最近開發需要用到定時任務,引用了微服務的概念,但是開發還是spring相關的專案,沒有用到SpringCloud相關的微服務框架。定時任務我就沒選擇xxx-job,Elastic-Job。選擇了Quartz,簡單方便而且拓展行也高。 Spring是一個很優秀的框架,它無縫的集成了Quartz

1.1 Spring 執行 --- ThreadPoolTaskExecutor

Spring 擅長對元件的封裝和整合, Spring-context對JDK的併發包做了功能增強。  step 1 :Spring-context.xml 中增加如下程式碼 <bean id="poolTaskExecutor" class="org.springframe

Junit單元測試+aop+spring+執行,在進行Junit測試時切面中執行內呼叫的方法不執行

一、問題背景: 寫了一個切面,指向某service包下的所有類及方法,當該service包下方法被呼叫時切面執行,切面中用了執行緒池ExecutorService pool = Executors.newFixedThreadPool(5);執行緒池內呼叫了dao層的方法。 二、問題描述:單