实现日志管理的两种方式:aop、拦截器
一、Spring aop 實現
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 都是以攔截器作為通知的模型,并維護一個以連接點為中心的攔截器鏈??傊褪茿OP對連接點的處理通過通知來執行。個人理解: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)的類型:
- 前置通知(Before advice):在某個連接點(Join point)之前執行的通知,但這個通知不能阻止連接點的執行(除非它拋出一個異常)。
- 返回后通知(After returning advice):在某個連接點(Join point)正常完成后執行的通知。例如,一個方法沒有拋出任何異常正常返回。
- 拋出異常后通知(After throwing advice):在方法拋出異常后執行的通知。
- 后置通知(After(finally)advice):當某個連接點(Join point)退出的時候執行的通知(不論是正常返回還是發生異常退出)。
- 環繞通知(Around advice):包圍一個連接點(Join point)的通知,如方法調用。這是最強大的一種通知類型。環繞通知可以在方法前后完成自定義的行為。它也會選擇是否繼續執行連接點或直接返回它們自己的返回值或拋出異常來結束執行。
使用不同的通知,可以幫我們實現不同的日志記錄,正常的操作日志以及異常日志等。。。
aop 的實現方式需要我們自定義一個切面類,我們需要記錄的就是通過controller的請求操作, 具體代碼如下:
/*** 通過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();//1、開始時間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(); //2、結束時間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);}}復制代碼訪問個路徑,看下控制臺打印結果
二、攔截器 實現
實現HandlerInterceptor接口
HandlerInterceptor 接口中定義了三個方法,我們就是通過這三個方法來對用戶的請求進行攔截處理的。
-
preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法,顧名思義,該方法將在請求處理之前進行調用。SpringMVC 中的Interceptor 是鏈式的調用的,在一個應用中或者說是在一個請求中可以同時存在多個Interceptor 。每個Interceptor 的調用會依據它的聲明順序依次執行,而且最先執行的都是Interceptor 中的preHandle 方法,所以可以在這個方法中進行一些前置初始化操作或者是對當前請求的一個預處理,也可以在這個方法中進行一些判斷來決定請求是否要繼續進行下去。該方法的返回值是布爾值Boolean 類型的,當它返回為false 時,表示請求結束,后續的Interceptor 和Controller 都不會再執行;當返回值為true 時就會繼續調用下一個Interceptor 的preHandle 方法,如果已經是最后一個Interceptor 的時候就會是調用當前請求的Controller 方法。
-
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 渲染了對應的視圖之后執行。這個方法的主要作用是用于進行資源清理工作的。
1、自定義攔截器
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");@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {long beginTime = System.currentTimeMillis();//1、開始時間startTimeThreadLocal.set(beginTime); //線程綁定變量(該數據只有當前請求的線程可見)logger.debug("開始計時: {} URI: {}", new SimpleDateFormat("hh:mm:ss.SSS").format(beginTime), request.getRequestURI());return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {if (modelAndView != null){logger.info("ViewName: " + modelAndView.getViewName());}}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 打印JVM信息。long beginTime = startTimeThreadLocal.get();//得到線程綁定的局部變量(開始時間)long endTime = System.currentTimeMillis(); //2、結束時間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);}}復制代碼2、配置攔截器
@Configuration public class WebAppConfig extends WebMvcConfigurerAdapter {/*** addPathPatterns 配置需要攔截的請求路徑,excludePathPatterns配置不需要攔截的請求路徑。* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LogInterceptor()).addPathPatterns ("/**");super.addInterceptors(registry);} } 復制代碼查看打印結果
兩種方式都可以實現,具體還是要看自己的需求而定
總結
以上是生活随笔為你收集整理的实现日志管理的两种方式:aop、拦截器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Servlet底层原理、Servlet实
- 下一篇: SpringMVC_文件上传