SpringBoot學(xué)習(xí)筆記之AOP全局統(tǒng)一日志管理

前言

我們開發(fā)的 Web 系統(tǒng)都會有日志模塊,用來記錄對數(shù)據(jù)有進行變更的操作。一般都會記錄請求的 URL,請求的 IP,執(zhí)行的方法,操作人員等等。其目的可能是為了保留操作痕跡,防抵賴,或是記錄系統(tǒng)運行情況,再有就是審計要求。


一、AOP是什么?

360百科:在軟件業(yè),AOP 為 Aspect Oriented Programming 的縮寫,意為:面向切面編程,通過預(yù)編譯方式和運行期動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護的一種技術(shù)。AOP 是 OOP 的延續(xù),是軟件開發(fā)中的一個熱點,也是 Spring 框架中的一個重要內(nèi)容,是函數(shù)式編程的一種衍生范型。利用 AOP 可以對業(yè)務(wù)邏輯的各個部分進行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發(fā)的效率。

將一些功能從業(yè)務(wù)邏輯代碼中劃分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導(dǎo)業(yè)務(wù)邏輯的方法中,進而改變這些行為的時候不影響業(yè)務(wù)邏輯的代碼。比如最常見的日志記錄、性能統(tǒng)計、安全控制、事務(wù)處理、異常處理等等。

二、Spring AOP

這里主要為 Spring Framework 5.2.5 中 AOP 的介紹。

1.使用Spring進行切面的編程

Spring切面編程

2.AOP基本概念

AOP基本概念

譯文:

  • 切面(Aspect):切面是一個關(guān)注點的模塊化。事務(wù)管理是企業(yè) Java 應(yīng)用程序中橫切關(guān)注點的一個很好的例子。在 Spring AOP 中,切面是通過使用常規(guī)類(基于模式的方法)或使用 @Aspect 注解的常規(guī)類來實現(xiàn)的。

  • 連接點(Join Point):程序執(zhí)行過程中的一個點,如方法的執(zhí)行或異常的處理。在 Spring AOP 中,連接點總是表示方法執(zhí)行。

  • 通知(Advice):切面在特定連接點上采取的操作。不同類型的通知包括 aroundbefore 、after。許多 AOP 框架,包括 Spring,將通知建模為攔截器,并維護圍繞連接點的攔截器鏈。
    - 前置通知(Before advice):在連接點之前運行但不能阻止執(zhí)行流繼續(xù)到連接點的通知(除非它拋出異常)。
    - 后置通知(After returning advice):通知在連接點正常完成后運行(例如,如果一個方法沒有拋出異常而返回)。
    - 異常通知(After throwing advice):如果一個方法通過拋出異常退出,則要執(zhí)行的通知。
    - 最終通知(After (finally) advice):無論連接點以何種方式退出(正?;虍惓7祷?,都將執(zhí)行通知。
    - 環(huán)繞通知(Around advice):圍繞連接點(如方法調(diào)用)的通知。這是最有力的通知。環(huán)繞通知可以在方法調(diào)用之前和之后執(zhí)行自定義行為。它還負責(zé)選擇是繼續(xù)到連接點,還是通過返回它自己的返回值或拋出異常來簡化通知的方法執(zhí)行。

  • 切點(Pointcut):匹配連接點的術(shù)語。通知與切入點表達式相關(guān)聯(lián),并在與切入點匹配的任何連接點上運行(例如,執(zhí)行具有特定名稱的方法)。連接點由切入點表達式匹配的概念是 AOP 的核心,Spring 默認使用 AspectJ 切入點表達式語言。

  • 引入(Introduction):代表類型聲明其他方法或字段。Spring AOP 允許您將新的接口(和相應(yīng)的實現(xiàn))引入任何被建議的對象。例如,您可以使用一個引入來讓一個 bean 實現(xiàn)一個 IsModified 接口,以簡化緩存。(引入在 AspectJ 社區(qū)中稱為類型間聲明。)

  • 目標對象(Target object):被一個或多個切面告知的對象。也稱為“被通知對象”。因為 Spring AOP 是通過使用運行時代理來實現(xiàn)的,所以這個對象總是一個代理對象。

  • AOP代理(AOP proxy):為了實現(xiàn)切面契約(通知方法執(zhí)行等)而由 AOP 框架創(chuàng)建的對象。在 Spring 框架中,AOP 代理是 JDK 動態(tài)代理或 CGLIB 代理。

  • 織入(Weaving):將切面與其他應(yīng)用程序類型或?qū)ο箧溄右詣?chuàng)建通知的對象。這可以在編譯時(例如,使用 AspectJ 編譯器)、加載時或運行時完成。與其他純 Java AOP 框架一樣,Spring AOP 在運行時執(zhí)行編織。

說明:
環(huán)繞通知是最普遍的通知。因為 Spring AOP 和 AspectJ 一樣,提供了各種各樣的通知類型,所建議使用最不強大的通知類型來實現(xiàn)所需的行為。使用最特定的通知類型可以提供更簡單的編程模型,減少出錯的可能性。

所有的通知參數(shù)都是靜態(tài)類型的,這樣就可以使用適當類型的通知參數(shù)(例如:方法執(zhí)行返回值的類型),而不是對象數(shù)組。

由切入點匹配的連接點的概念是 AOP 的關(guān)鍵,它將 AOP 與只提供攔截的舊技術(shù)區(qū)分開來。切入點使通知能夠獨立于面向?qū)ο蟮膶哟谓Y(jié)構(gòu)。例如,可以將提供聲明性事務(wù)管理的環(huán)繞通知應(yīng)用于一組跨多個對象的方法(例如:服務(wù)層中的所有業(yè)務(wù)操作)。

3.AOP的功能和目標

Spring AOP 是在純 Java 中實現(xiàn)的。不需要特殊的編譯過程。Spring AOP 不需要控制類裝入器層次結(jié)構(gòu),因此適合在 servlet 容器或應(yīng)用程序服務(wù)器中使用。

Spring AOP 目前只支持方法執(zhí)行連接點(建議在 Spring bean 上執(zhí)行方法)。雖然可以在不破壞核心 Spring AOP api 的情況下添加對字段攔截的支持,但是沒有實現(xiàn)字段攔截。如果需要通知字段訪問和更新連接點,請考慮 AspectJ 之類的語言。

Spring 框架的 AOP 功能通常與 Spring IoC 容器一起使用。切面是通過使用普通的 bean 定義語法來配置的(盡管這允許強大的“自動代理”功能)。這是與其他 AOP 實現(xiàn)的一個重要區(qū)別。

4.AOP代理

Spirng 的 AOP 的動態(tài)代理實現(xiàn)機制有兩種,分別是:JDK 動態(tài)代理和 CGLib 動態(tài)代理。簡單介紹下兩種代理機制:

  • JDK 動態(tài)代理
    JDK 動態(tài)代理是面向接口的代理模式,如果被代理目標沒有接口那么 Spring 也無能為力,Spring 通過 Java 的反射機制生產(chǎn)被代理接口的新的匿名實現(xiàn)類,重寫了其中 AOP 的增強方法。
  • CGLib 動態(tài)代理
    CGLib 是一個強大、高性能的 Code 生產(chǎn)類庫,可以實現(xiàn)運行期動態(tài)擴展 Java 類,Spring 在運行期間通過 CGlib 繼承要被動態(tài)代理的類,重寫父類的方法,實現(xiàn) AOP 面向切面編程。

兩者對比:

  1. JDK 動態(tài)代理是面向接口,在創(chuàng)建代理實現(xiàn)類時比 CGLib 要快,創(chuàng)建代理速度快。而且 JDK 動態(tài)代理只能對實現(xiàn)了接口的類生成代理,而不能針對類。
  2. CGLib 動態(tài)代理是通過字節(jié)碼底層繼承要代理類來實現(xiàn)(如果被代理類被 final 關(guān)鍵字所修飾,那么抱歉會失?。趧?chuàng)建代理這一塊沒有 JDK 動態(tài)代理快,但是運行速度比 JDK 動態(tài)代理要快。

5.@AspectJ 支持

@AspectJ 指的是將切面聲明為用注解注釋的常規(guī) Java 類的樣式。@AspectJ 樣式是由 AspectJ 項目作為 AspectJ 5 發(fā)行版的一部分引入的。Spring 使用 AspectJ 提供的用于切入點解析和匹配的庫來解釋與 AspectJ 5 相同的注釋。但是 AOP 運行時仍然是純 Spring AOP,并且不依賴于 AspectJ 編譯器或編織器。

(1)聲明一個切面

  1. 第一個示例展示應(yīng)用程序上下文中的一個常規(guī) bean 定義,它指向一個具有@Aspect 注釋的 bean 類。
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of the aspect here -->
</bean>
  1. 第二個示例展示NotVeryUsefulAspect類定義,它是由org.aspectj.lang.annotation.Aspect 注解。
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}

切面(使用 @Aspect 注解的類)可以具有與任何其他類相同的方法和字段。它們還可以包含切入點、通知和引入(類型間)聲明。
通過組件掃描自動檢測切面:可以將切面類注冊為 Spring XML 配置中的常規(guī) bean,或者通過類路徑掃描自動檢測它們——與任何其他 Spring 管理的 bean 相同。但是,請注意 @Aspect 注解對于類路徑中的自動檢測是不夠的。為此,您需要添加一個單獨的 @Component 注解(或者,根據(jù) Spring 的組件掃描器的規(guī)則,一個定制的原型注釋)。

(2)聲明一個切入點

切入點確定感興趣的連接點,從而使我們能夠控制何時執(zhí)行通知。Spring AOP 只支持 Spring bean 的方法執(zhí)行連接點,因此可以將切入點看作是與 Spring bean 上的方法執(zhí)行相匹配的。切入點聲明有兩部分:一個簽名,它包含名稱和任何參數(shù);一個是切入點表達式,它確定我們對哪個方法執(zhí)行感興趣。在 AOP 的 @AspectJ 注解風(fēng)格中,切入點簽名由一個常規(guī)方法定義提供,切入點表達式通過使用 @Pointcut 注解來表示(作為切入點簽名的方法必須有一個 void 返回類型)。

@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature

切入點指示器
Spring AOP 支持以下用于切入點表達式的 AspectJ 切入點指示器(PCD):

  • execution:用于匹配方法執(zhí)行連接點。這是使用 Spring AOP 時要使用的主要切入點指示器。
  • within:限制對某些類型中的連接點的匹配(使用 Spring AOP 時在匹配類型中聲明的方法的執(zhí)行)。
  • this:限制連接點(使用 Spring AOP 時方法的執(zhí)行)的匹配,其中 bean 引用(Spring AOP 代理)是給定類型的實例。
  • target:限制對連接點(使用 Spring AOP 時方法的執(zhí)行)的匹配,其中目標對象(代理的應(yīng)用程序?qū)ο?是給定類型的實例。
  • args:限制連接點的匹配(使用 Spring AOP 時方法的執(zhí)行),其中的參數(shù)是給定類型的實例。
  • @target:限制連接點的匹配(使用 Spring AOP 時方法的執(zhí)行),其中執(zhí)行對象的類具有給定類型的注解。
  • @args:限制連接點的匹配(使用 Spring AOP 時方法的執(zhí)行),其中實際傳遞的參數(shù)的運行時類型具有給定類型的注解。
  • @within:限制對具有給定注解的類型中的連接點的匹配(在使用 Spring AOP 時,使用給定注解在類型中聲明的方法的執(zhí)行)。
  • @annotation:限制對連接點的匹配,連接點的主體(在 Spring AOP 中執(zhí)行的方法)具有給定的注解。

切入點表達式
可以使用 &&||! 組合切入點表達式。您還可以通過名稱引用切入點表達式。

// 如果方法執(zhí)行連接點表示任何公共方法的執(zhí)行,則匹配。
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} 

// 如果方法執(zhí)行在交易模塊中,則匹配。
@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {} 

// 如果方法執(zhí)行代表交易模塊中的任何公共方法,則匹配。
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} 

最佳實踐是從較小的命名組件構(gòu)建更復(fù)雜的切入點表達式,如前面所示。當通過名稱引用切入點時,應(yīng)用普通的Java可見性規(guī)則(您可以在相同的類型中看到私有切入點,層次結(jié)構(gòu)中受保護的切入點,任何地方的公共切入點,等等)??梢娦圆挥绊懬腥朦c匹配。

(3)聲明一個通知

通知與切入點表達式相關(guān)聯(lián),并在切入點匹配的方法執(zhí)行之前、之后或前后運行。切入點表達式可以是對指定切入點的簡單引用,也可以是在適當位置聲明的切入點表達式。

  • 前置通知:使用 @Before 注解在切面中聲明前置通知。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }

}
  • 后置通知:返回后,當匹配的方法執(zhí)行正常返回時,將運行通知。你可以使用 @AfterReturning 注解來聲明它。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}

需要在通知主體中訪問返回的實際值。您可以使用 @AfterReturning 的形式綁定返回值來獲得訪問權(quán)限。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }

}
  • 異常通知:拋出通知后,當匹配的方法執(zhí)行通過拋出異常退出時運行。您可以使用 @AfterThrowing 注解來聲明它。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }

}

通常,您希望僅在拋出給定類型的異常時才運行通知,并且常常需要在通知正文中訪問拋出的異常。您可以使用 throwing 屬性來限制匹配(如果需要,可以使用 Throwable 作為異常類型),并將拋出的異常綁定到一個通知參數(shù)。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }

}
  • 最終通知:當匹配的方法執(zhí)行退出時,將運行最終通知。它是使用 @After 注解聲明的。最終通知必須準備好處理正常和異常返回條件。它通常用于釋放資源和類似的目的。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }

}
  • 環(huán)繞通知:環(huán)繞 通知是通過使用 @Around 注解來聲明的。通知方法的第一個參數(shù)必須是類型為 ProceedingJoinPoint。在通知的主體中,對過程ProceedingJoinPoint 調(diào)用 proceed() 會導(dǎo)致底層方法執(zhí)行。proceed 方法也可以傳遞一個 Object[]。當方法執(zhí)行時,數(shù)組中的值用作方法執(zhí)行的參數(shù)。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    @Around("com.xyz.myapp.SystemArchitecture.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }

}

三、AOP全局統(tǒng)一日志管理

1.環(huán)境說明

開發(fā)工具:IDEA 2019.3.1
框架版本:SpringBoot 2.2.6

2.具體實現(xiàn)

  1. pom.xml 中加入 Spring AOP 依賴
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 啟用 @AspectJ 支持,這里默認已經(jīng)開啟

    @AspectJ支持

  2. 自定義日志注解類

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER, ElementType.METHOD })
public @interface WebLog {

   /**
    * 渠道
    * @return 渠道標識
    */
   String channel() default "web";

   /**
    * 功能名稱
    * @return 功能名稱
    */
   String name() default "";

   /**
    * 方法名稱
    * @return 方法名稱
    */
   String action() default "";

   /**
    * 是否保存(默認不保存)
    * @return 是否保存
    */
   boolean saveFlag() default false;

}

@Retention 注解保留策略
RetentionPolicy 策略枚舉類(RUNTIME 注解將由編譯器記錄在類文件中,并在運行時由VM保留,因此可以反射性地讀取它們。)
@Target 注解目標位置(也就是該注解要用在什么地方)
ElementType 目標元素類型枚舉類(PARAMETER:參數(shù),METHOD:方法)

  1. 日志切面類
@Component
@Aspect
public class WebLogAspect {

   private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

   private final SysUserLogService sysUserLogService;

   public WebLogAspect(SysUserLogService sysUserLogService) {
      this.sysUserLogService = sysUserLogService;
   }

   /**
    * 連接點(切入點)
    * 切入點表達式:匹配 web 包及子包 Controller 類的任何公共方法
    */
   @Pointcut("execution(public * com.skillset.web..*Controller.*(..))")
   public void webLog() {
   }

   /**
    * 通知:前置通知(Before advice),在連接點之前運行但不能阻止執(zhí)行流繼續(xù)到連接點的通知(除非它拋出異常)。
    * 在日志文件或控制臺輸出請求信息
    * 
    * @param joinPoint
    */
   @Before("webLog()")
   public void doBefore(JoinPoint joinPoint) {

      // 利用RequestContextHolder獲取HttpServletRequest對象
      ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
      HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();

      // 重組請求信息
      StringBuffer sb = new StringBuffer();
      sb.append("收到請求:");
      sb.append("\r\n訪問URI   :" + httpServletRequest.getRequestURI().toString());
      sb.append("\r\nSession :" + httpServletRequest.getSession().getId());
      sb.append("\r\n訪問IP    :" + RequestUtil.getIP(httpServletRequest));
      sb.append("\r\n響應(yīng)類 :" + joinPoint.getSignature().getDeclaringTypeName());
      sb.append("\r\n方法     :" + joinPoint.getSignature().getName());

      Object[] objects = joinPoint.getArgs();
      for (Object arg : objects) {
         if (arg != null) {
            sb.append("\r\n參數(shù)     :" + arg.toString());
         }
      }

      // 打印請求信息
      logger.info(sb.toString());

   }

   /**
    * 通知:后置通知(After returning advice),通知在連接點正常完成后運行
    * 處理請求日志信息
    * 
    * @param joinPoint
    */
   @AfterReturning(pointcut = "webLog()", returning = "rvt")
   public void doAfterReturning(JoinPoint joinPoint, Object rvt) {
      // 處理日志信息
      handleLog(joinPoint, null);
   }

   /**
    * 通知:異常通知(After throwing advice),方法通過拋出異常退出,則要執(zhí)行的通知
    * 處理請求異常日志信息
    * 
    * @param joinPoint
    * @param e
    */
   @AfterThrowing(pointcut = "webLog()", throwing = "e")
   public void afterThrowing(JoinPoint joinPoint, Exception e) {
      // 處理日志信息
       handleLog(joinPoint, e);
   }

   /**
    * 日志處理
    * 
    * @param joinPoint
    * @param e
    */
   private void handleLog(JoinPoint joinPoint, Exception e) {
      // 利用RequestContextHolder獲取HttpServletRequest對象
      ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
      HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();

      // 獲取執(zhí)行的方法
      Signature signature = joinPoint.getSignature();
      if(!(signature instanceof MethodSignature)) {
         throw new IllegalArgumentException("暫不支持非方法注解");
      }
      MethodSignature methodSignature = (MethodSignature) signature;
      Method method = methodSignature.getMethod();

      if (method != null) {
         // 獲取注解
         WebLog controllerLog = method.getAnnotation(WebLog.class);

         if (controllerLog != null) {

            // 保存日志到數(shù)據(jù)庫
            if (controllerLog.saveFlag()) {
               SysUserLog sysUserLog = new SysUserLog();
               // SessionId
               sysUserLog.setAccount(httpServletRequest.getRequestedSessionId());
               // 渠道
               sysUserLog.setChannel(controllerLog.channel());
               // 功能名稱
               sysUserLog.setName(controllerLog.name());
               // 響應(yīng)類.方法
               sysUserLog.setAction(signature.getDeclaringTypeName() + "." + method.getName());
               // URI
               sysUserLog.setUrl(httpServletRequest.getRequestURI());
               // 參數(shù)
               sysUserLog.setParams(JSONObject.toJSONString(httpServletRequest.getParameterMap()).replace("\"", ""));
               // 請求IP
               sysUserLog.setIp(RequestUtil.getIP(httpServletRequest));
               // 操作時間
               sysUserLog.setLogTime(new Date());

               // 異常信息
               if (e != null) {
                  sysUserLog.setErrMsg(e.getMessage());
               }

               sysUserLogService.insert(sysUserLog);

            }
         }
      }

      // 發(fā)生異常時打印錯誤信息
      if (e != null) {
         StringBuffer sb = new StringBuffer();
         sb.append("時間:");
         sb.append(DateFormat.getDateTimeInstance().format(new Date()));
         sb.append("方法:");
         sb.append(joinPoint.getSignature() + "\n");
         sb.append("異常信息:" + e.getMessage());

         logger.error(sb.toString());
      }

   }
}
  1. Controller請求處理層添加注解
/**
 * 系統(tǒng)用戶 列表頁
 *
 * @param sysUserCriteria
 * @param model
 * @return
 */
@WebLog(channel = "web", name = "系統(tǒng)用戶列表", action = "/sysUser", saveFlag = true)
@GetMapping("")
public String list(SysUserCriteria sysUserCriteria, Model model) {
   model.addAttribute("sysUserCriteria", sysUserCriteria);
   return "sysUser/list";
}
  1. 存庫日志記錄
日志記錄

四、總結(jié)說明

拋開雜念,靜下心來,只看當下;
充電片刻,日積月累,步步向前。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 概述 Spring是什么? Spring是一個開源框架,為了解決企業(yè)應(yīng)用開發(fā)的復(fù)雜性而創(chuàng)建的,但是現(xiàn)在已經(jīng)不止于企...
    瑯筑閱讀 1,297評論 2 8
  • 本章內(nèi)容: 面向切面編程的基本原理 通過POJO創(chuàng)建切面 使用@AspectJ注解 為AspectJ切面注入依賴 ...
    謝隨安閱讀 3,425評論 0 9
  • 前言 上一章節(jié),介紹了目前開發(fā)中常見的log4j2及l(fā)ogback日志框架的整合知識。在很多時候,我們在開發(fā)一個系...
    oKong閱讀 1,003評論 0 12
  • 內(nèi)容概覽 什么是面向切面的編程 通過切點來選擇連接點 使用注解創(chuàng)建切面 在xml中聲明切面 總結(jié) 1. 什么是面向...
    郭藝賓閱讀 808評論 0 0
  • 考研差了十幾分過線,真的是很可惜了,和自己預(yù)想的分數(shù)查的好遠,感覺一下子沒了目標,找工作還是繼續(xù)考研這個確實是值得...
    卷發(fā)人閱讀 96評論 0 1

友情鏈接更多精彩內(nèi)容