實現日誌管理的兩種方式:aop、攔截器

1、Spring aop 實現bash

AOP概念:框架

  • 切面(Aspect):一個關注點的模塊化,這個關注點可能會橫切多個對象。事務管理是Java應用程序中一個關於橫切關注點的很好的例子。在Spring AOP中,切面可使用經過類(基於模式(XML)的風格)或者在普通類中以@Aspect註解(AspectJ風格)來實現。
  • 鏈接點(Join point):程序執行過程當中某個特定的點,好比某方法調用的時候或者處理異常的時候。在Spring AOP中一個鏈接點老是表明一個方法的執行。說人話就是AOP攔截到的方法就是一個鏈接點。經過聲明一個org.aspectj.lang.JoinPoint類型參數咱們能夠在通知(Advice)中得到鏈接點的信息。這個在稍後會給出案例。
  • 通知(Advice):在切面(Aspect)的某個特定鏈接點上(Join point)執行的動做。通知的類型包括"around","before","after"等等。通知的類型將在後面進行討論。許多AOP框架,包括Spring 都是以攔截器做爲通知的模型,並維護一個以鏈接點爲中心的攔截器鏈。總之就是AOP對鏈接點的處理經過通知來執行。我的理解:Advice指當一個方法被AOP攔截到的時候要執行的代碼。
  • 切入點(Pointcut):匹配鏈接點(Join point)的斷言。通知(Advice)跟切入點表達式關聯,並在與切入點匹配的任何鏈接點上面運行。切入點表達式如何跟鏈接點匹配是AOP的核心,Spring默認使用AspectJ做爲切入點語法。我的理解:經過切入點的表達式來肯定哪些方法要被AOP攔截,以後這些被攔截的方法會執行相對應的Advice代碼。
  • 引入(Introduction):聲明額外的方法或字段。Spring AOP容許你向任何被通知(Advice)對象引入一個新的接口(及其實現類)。我的理解:AOP容許在運行時動態的向代理對象實現新的接口來完成一些額外的功能而且不影響現有對象的功能。
  • 目標對象(Target object):被一個或多個切面(Aspect)所通知(Advice)的對象,也稱做被通知對象。因爲Spring AOP是經過運行時代理實現的,因此這個對象永遠是被代理對象。我的理解:全部的對象在AOP中都會生成一個代理類,AOP整個過程都是針對代理類在進行處理。
  • AOP代理(AOP proxy):AOP框架建立的對象,用來實現切面契約(aspect contract)(包括通知方法執行等功能),在Spring中AOP能夠是JDK動態代理或者是CGLIB代理。
  • 織入(Weaving):把切面(aspect)鏈接到其餘的應用程序類型或者對象上,並建立一個被通知對象。這些能夠在編譯時(例如使用AspectJ編譯器),類加載時和運行時完成。Spring和其餘純AOP框架同樣,在運行時完成織入。我的理解:把切面跟對象關聯並建立該對象的代理對象的過程。

通知(Advice)的類型:ide

  • 前置通知(Before advice):在某個鏈接點(Join point)以前執行的通知,但這個通知不能阻止鏈接點的執行(除非它拋出一個異常)。
  • 返回後通知(After returning advice):在某個鏈接點(Join point)正常完成後執行的通知。例如,一個方法沒有拋出任何異常正常返回。
  • 拋出異常後通知(After throwing advice):在方法拋出異常後執行的通知。
  • 後置通知(After(finally)advice):當某個鏈接點(Join point)退出的時候執行的通知(不管是正常返回仍是發生異常退出)。
  • 環繞通知(Around advice):包圍一個鏈接點(Join point)的通知,如方法調用。這是最強大的一種通知類型。環繞通知能夠在方法先後完成自定義的行爲。它也會選擇是否繼續執行鏈接點或直接返回它們本身的返回值或拋出異常來結束執行。

使用不一樣的通知,能夠幫咱們實現不一樣的日誌記錄,正常的操做日誌以及異常日誌等。。。模塊化

aop 的實現方式須要咱們自定義一個切面類,咱們須要記錄的就是經過controller的請求操做, 具體代碼以下:post

/**
 * 經過aop 實現簡單的日誌功能
 */
@Aspect
@Component
public class LogAspect {
    private final static Logger logger = LoggerFactory.getLogger(LogAspect.class);
    private static final ThreadLocal<Long> startTimeThreadLocal =
            new NamedThreadLocal<Long>("ThreadLocal StartTime");

    // 聲明一個切入點
    @Pointcut("execution(public * com.test.controller..*.*(..))")
    public void aspect() {
    }


    @Before("aspect()")
    public void LogRequestInfo(JoinPoint joinPoint) throws Exception {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        long beginTime = System.currentTimeMillis();//一、開始時間
        startTimeThreadLocal.set(beginTime);		//線程綁定變量(該數據只有當前請求的線程可見)

        logger.debug("開始計時: {} URI: {} 請求方式: {} 參數: {}" , new SimpleDateFormat("hh:mm:ss.SSS")
                .format(beginTime), request.getRequestURI(),request.getMethod(),JSONObject.toJSONString(joinPoint.getArgs()));


    }

    @AfterReturning("aspect()")
    public void logResultVOInfo(JoinPoint joinPoint) throws Exception {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        long beginTime = startTimeThreadLocal.get();//獲得線程綁定的局部變量(開始時間)
        long endTime = System.currentTimeMillis(); 	//二、結束時間

        logger.debug("結束時間: {} 耗時: {} 最大內存: {}m 已分配內存: {}m 已分配內存中的剩餘空間: {}m 最大可用內存: {}m",
                new SimpleDateFormat("hh:mm:ss.SSS").format(endTime), DateUtils.formatDateTime(endTime - beginTime),
                Runtime.getRuntime().maxMemory()/1024/1024, Runtime.getRuntime().totalMemory()/1024/1024, Runtime.getRuntime().freeMemory()/1024/1024,
                (Runtime.getRuntime().maxMemory()-Runtime.getRuntime().totalMemory()+Runtime.getRuntime().freeMemory())/1024/1024);

    }

}

複製代碼

訪問個路徑,看下控制檯打印結果ui

2、攔截器 實現spa

實現HandlerInterceptor接口線程

HandlerInterceptor 接口中定義了三個方法,咱們就是經過這三個方法來對用戶的請求進行攔截處理的。debug

  • preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法,顧名思義,該方法將在請求處理以前進行調用。SpringMVC 中的Interceptor 是鏈式的調用的,在一個應用中或者說是在一個請求中能夠同時存在多個Interceptor 。每一個Interceptor 的調用會依據它的聲明順序依次執行,並且最早執行的都是Interceptor 中的preHandle 方法,因此能夠在這個方法中進行一些前置初始化操做或者是對當前請求的一個預處理,也能夠在這個方法中進行一些判斷來決定請求是否要繼續進行下去。該方法的返回值是布爾值Boolean 類型的,當它返回爲false 時,表示請求結束,後續的Interceptor 和Controller 都不會再執行;當返回值爲true 時就會繼續調用下一個Interceptor 的preHandle 方法,若是已是最後一個Interceptor 的時候就會是調用當前請求的Controller 方法。3d

  • postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView) 方法,由preHandle 方法的解釋咱們知道這個方法包括後面要說到的afterCompletion 方法都只能是在當前所屬的Interceptor 的preHandle 方法的返回值爲true 時才能被調用。postHandle 方法,顧名思義就是在當前請求進行處理以後,也就是Controller 方法調用以後執行,可是它會在DispatcherServlet 進行視圖返回渲染以前被調用,因此咱們能夠在這個方法中對Controller 處理以後的ModelAndView 對象進行操做。postHandle 方法被調用的方向跟preHandle 是相反的,也就是說先聲明的Interceptor 的postHandle 方法反而會後執行。

  • afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法,該方法也是須要當前對應的Interceptor 的preHandle 方法的返回值爲true 時纔會執行。顧名思義,該方法將在整個請求結束以後,也就是在DispatcherServlet 渲染了對應的視圖以後執行。這個方法的主要做用是用於進行資源清理工做的。

一、自定義攔截器

public class LogInterceptor implements HandlerInterceptor {
	private final static Logger logger = LoggerFactory.getLogger(LogInterceptor.class);

	private static final ThreadLocal<Long> startTimeThreadLocal =
			new NamedThreadLocal<Long>("ThreadLocal StartTime");
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
							 Object handler) throws Exception {

		long beginTime = System.currentTimeMillis();//一、開始時間
		startTimeThreadLocal.set(beginTime);        //線程綁定變量(該數據只有當前請求的線程可見)
		logger.debug("開始計時: {} URI: {}", new SimpleDateFormat("hh:mm:ss.SSS")
				.format(beginTime), request.getRequestURI());

		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, 
			ModelAndView modelAndView) throws Exception {
		if (modelAndView != null){
			logger.info("ViewName: " + modelAndView.getViewName());
		}
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
			Object handler, Exception ex) throws Exception {


		// 打印JVM信息。
		long beginTime = startTimeThreadLocal.get();//獲得線程綁定的局部變量(開始時間)
		long endTime = System.currentTimeMillis();    //二、結束時間
		logger.debug("計時結束:{} 耗時:{} URI: {} 最大內存: {}m 已分配內存: {}m 已分配內存中的剩餘空間: {}m 最大可用內存: {}m",
				new SimpleDateFormat("hh:mm:ss.SSS").format(endTime), DateUtils.formatDateTime(endTime - beginTime),
				request.getRequestURI(), Runtime.getRuntime().maxMemory() / 1024 / 1024, Runtime.getRuntime().totalMemory() / 1024 / 1024, Runtime.getRuntime().freeMemory() / 1024 / 1024,
				(Runtime.getRuntime().maxMemory()-Runtime.getRuntime().totalMemory()+Runtime.getRuntime().freeMemory())/1024/1024);

	}

}

複製代碼

二、配置攔截器

@Configuration
public class WebAppConfig extends WebMvcConfigurerAdapter {

    /**
     * addPathPatterns 配置須要攔截的請求路徑,excludePathPatterns配置不須要攔截的請求路徑。
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor()).addPathPatterns ("/**");
        super.addInterceptors(registry);
    }
}
複製代碼

查看打印結果

兩種方式均可以實現,具體仍是要看本身的需求而定

相關文章
相關標籤/搜索