1. 程式人生 > >Spring AOP 的實現方式(以日誌管理為例)

Spring AOP 的實現方式(以日誌管理為例)

一、為什麼需要AOP

假如我們應用中有n個業務邏輯元件,每個業務邏輯元件又有m個方法,那現在我們的應用就一共包含了n*m個方法,我會抱怨方法太多。。。現在,我有這樣一個需求,每個方法都增加一個通用的功能,常見的如:事務處理,日誌,許可權控制。。。最容易想到的方法,先定義一個額外的方法,實現該功能,然後再每個需要實現這個功能的地方去呼叫這個額外的方法。這種做法的好處和壞處分別是——> 好處:可以動態地新增和刪除在切面上的邏輯而不影響原來的執行程式碼。 壞處:一旦要修改,就要開啟所有呼叫到的地方去修改。 好,現在我們用AOP的方式可以實現在不修改源方法程式碼的前提下,可以統一為原多個方法增加橫切性質的“通用處理”。

二、什麼是AOP

都說AOP好用,那現在我們來談談什麼是AOP。

AOP(Aspect-OrientedProgramming,面向方面程式設計),可以說是OOP(Object-Oriented Programing,面向物件程式設計)的補充和完善。本文提供Spring官方文件出處:Aspect Oriented Programming with Spring

從官方文件上摘抄的解釋就是:面向方面程式設計(AOP)是面向物件程式設計(OOP)補充的另一種提供思考程式結構補充。在OOP中模組化的關鍵單元是類,而在AOP模組的單位是一個方面。面對關注點,如事務管理跨越多個型別和物件切模組化。(這些關注經常被稱為在AOP文學橫切關注點。)

相關概念(只需做個大概的瞭解就好)----來自於官方文件直譯(本人英文水平有限。。。):

Aspect:這橫切多個物件關心的模組化。事務管理是企業Java應用程式的橫切關注點的一個很好的例子。在Spring AOP中,切面可以使用類(基於模式)或@Aspect註解(@AspectJ風格)註解普通班實施。 Join point:程式在執行過程中的一個點,如方法的執行或異常的處理。在Spring AOP中,一個連線點總是代表一個方法的執行。 Advice:在切面的某個特定的動作連線點。不同型別的意見,包括 "around," "before" and "after"的advice。 (通知的型別將在下面討論)。許多AOP框架,包括Spring都是以攔截器作為通知模型,去維護一條圍繞著一個連線點的攔截器鏈。 Pointcut:匹配連線點的斷言。通知是跟一個切入點表示式,並在執行在切入點匹配的連線點相關聯(例如,一個方法的執行要有一個確定的名字)。切入點表示式作為匹配的連線點的概念是重要的對AOP和Spring預設使用AspectJ切入點表示式語言。 Introduction:宣告代表的型別的額外的方法或欄位。 Spring允許引入新的介面(以及一個對應的實現)到任何被代理的物件。例如,你可以使用引入來使一個bean實現IsModified介面,以便簡化快取。 (介紹被譽為AspectJ的社會型別間的宣告。) Target object:物件由一個或多個方面被建議。也被稱作被通知物件。既然Spring AOP是通過執行時代理實現的,這個物件永遠是一個被代理物件。 AOP proxy:AOP框架,以實現切面契約(例如通知方法執行等等)建立的物件。在Spring中,AOP代理可以是JDK動態代理或者CGLIB代理。 Weaving:與連線其他應用程式型別或物件方面來建立一個被通知的物件。這是可以做到在編譯時(使用AspectJ編譯器,例如),載入時間,或在執行時。 Spring AOP中,像其他純Java AOP框架,在執行時進行編織。

以上概念個人感覺在做實驗的時候就差不多理解了,不需要咬文嚼字地去啃。

那問題來了,AOP是在什麼時候去改我們的程式碼的?即給我們加上額外的橫切性質的"通用處理"的?

兩個時機:

1.在編譯java原始碼的時候 ----編譯時增強 2.在執行時動態地修改類 ----執行時增強(動態代理)

我們的Spring的AOP的實現原理就是基於動態代理。(之後我會嘗試一下跟隨原始碼看看Spring在AOP方面做了哪些事情)

三、Spring AOP的3種實現方式(重要)

對於框架的學習,我覺得得先會用,然後再深入原理。關於Spring AOP的實現我在這裡劃分成3個方式(以日誌管理為例)廢話不多說,直接上程式碼了。

配置之前注意配置檔案要加上名稱空間:xmlns:aop="http://www.springframework.org/schema/aop"

1.基於xml配置的實現

spring-mvc.xml

     <!-- 使用xml配置aop -->     <!-- 強制使用cglib代理,如果不設定,將預設使用jdk的代理,但是jdk的代理是基於介面的 -->     <aop:config proxy-target-class="true" />       <aop:config>     <!--定義切面-->      <aop:aspect id="logAspect" ref="logInterceptor">      <!-- 定義切入點 (配置在com.gray.user.controller下所有的類在呼叫之前都會被攔截)-->       <aop:pointcut expression="execution(* com.gray.user.controller.*.*(..))" id="logPointCut"/>       <!--方法執行之前被呼叫執行的-->       <aop:before method="before" pointcut-ref="logPointCut"/><!--一個切入點的引用-->      <aop:after method="after" pointcut-ref="logPointCut"/><!--一個切入點的引用-->      </aop:aspect>     </aop:config> LogInterceptor.java package com.gray.interceptor;   import org.springframework.stereotype.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory;   @Component public class LogInterceptor {     private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);     public void before(){         logger.info("login start!");     }          public void after(){         logger.info("login end!");     } } 在這裡我沒有配bean是因為在之前的配置檔案裡面寫了自動掃描元件的配置了 要加入日誌管理邏輯的地方 @RequestMapping("/dologin.do") //url public String dologin(User user, Model model){     logger.info("login ....");     String info = loginUser(user);     if (!"SUCC".equals(info)) {         model.addAttribute("failMsg", "使用者不存在或密碼錯誤!");         return "/jsp/fail";     }else{         model.addAttribute("successMsg", "登陸成功!");//返回到頁面說夾帶的引數         model.addAttribute("name", user.getUsername());         return "/jsp/success";//返回的頁面     }   } 結果截圖:

2.基於註解的實現

spring-mvc.xml

    <aop:aspectj-autoproxy proxy-target-class="true">     </aop:aspectj-autoproxy> LogInterceptor.java

@Aspect @Component public class LogInterceptor {     private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);     @Before(value = "execution(* com.gray.user.controller.*.*(..))")     public void before(){         logger.info("login start!");     }     @After(value = "execution(* com.gray.user.controller.*.*(..))")     public void after(){         logger.info("login end!");     } } 要加入邏輯的地方同上。 結果截圖:

3.基於自定義註解的實現

基於註解,所以spring-mvc.xml也是和上面的一樣的。

LogInterceptor.java(這裡我只加入前置日誌)

package com.gray.interceptor;   import java.lang.reflect.Method;   import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component;   import com.gray.annotation.Log;   @Aspect @Component public class LogInterceptor {     private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);       @Pointcut("@annotation(com.gray.annotation.Log)")         public void controllerAspect() {              }     @Before("controllerAspect()")     public void before(JoinPoint joinPoint){         logger.info(getOper(joinPoint));     }     private String getOper(JoinPoint joinPoint) {         MethodSignature methodName = (MethodSignature)joinPoint.getSignature();         Method method = methodName.getMethod();         return method.getAnnotation(Log.class).oper();     } } 同時,加入邏輯的地方需要加入Log註解 @RequestMapping("/dologin.do") //url @Log(oper="user login") public String dologin(User user, Model model){     logger.info("login ....");     String info = loginUser(user);     if (!"SUCC".equals(info)) {         model.addAttribute("failMsg", "使用者不存在或密碼錯誤!");         return "/jsp/fail";     }else{         model.addAttribute("successMsg", "登陸成功!");//返回到頁面說夾帶的引數         model.addAttribute("name", user.getUsername());         return "/jsp/success";//返回的頁面     }   } 結果截圖:

以上就是我總結的SpringAOP的3種實現方式,對於這我還有點話要說,常見的基於註解的方式除了before和after,還有around和AfterThrowing等,在做第三種方式實驗的時候還遇到這種錯誤:error at ::0 can't find referenced pointcut。

的解決辦法是:因為我的jdk是1.7,與原來在pom.xml中配的那個aspectjweaver的jar包版本不匹配,所以我需要升級,由1.5.4改成1.7.4。

此外多說一句,對於日誌,許可權等業務邏輯很多人其實也喜歡用攔截器來實現的。 ---------------------  作者:GrayHJX  來源:CSDN  原文:https://blog.csdn.net/donggua3694857/article/details/52752503