欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring AOP注解實戰(zhàn)指南

 更新時間:2024年06月05日 09:54:02   作者:磚業(yè)洋__  
在現代軟件開發(fā)中,面向切面編程(AOP)是一種強大的編程范式,本文將介紹如何在Spring框架中通過AspectJ注解以及對應的XML配置來實現AOP,在不改變主業(yè)務邏輯的情況下增強應用程序的功能,需要的朋友可以參考下

1. 背景

在現代軟件開發(fā)中,面向切面編程(AOP)是一種強大的編程范式,允許開發(fā)者跨越應用程序的多個部分定義橫切關注點(如日志記錄、事務管理等)。本文將介紹如何在Spring框架中通過AspectJ注解以及對應的XML配置來實現AOP,在不改變主業(yè)務邏輯的情況下增強應用程序的功能。

2. 基于AspectJ注解來實現AOP

對于一個使用MavenSpring項目,需要在pom.xml中添加以下依賴:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.10</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.9.6</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.6</version>
    </dependency>
</dependencies>

確保版本號與使用的Spring版本相匹配,可以自行調整。

創(chuàng)建業(yè)務邏輯接口MyService

package com.example.demo.aop;


public interface MyService {
    void performAction();
}

創(chuàng)建業(yè)務邏輯類MyServiceImpl.java

package com.example.demo.aop;

import org.springframework.stereotype.Service;

@Service
public class MyServiceImpl implements MyService {
    @Override
    public void performAction() {
        System.out.println("Performing an action in MyService");
    }
}

定義切面

創(chuàng)建切面類MyAspect.java,并使用注解定義切面和通知:

package com.example.demo.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {

    @Before("execution(* com.example.demo.aop.MyServiceImpl.performAction(..))")
    public void logBeforeAction() {
        System.out.println("Before performing action");
    }
}

@Aspect注解是用來標識一個類作為AspectJ切面的一種方式,這在基于注解的AOP配置中是必需的。它相當于XML配置中定義切面的方式,但使用注解可以更加直觀和便捷地在類級別上聲明切面,而無需繁瑣的XML配置。

配置Spring以啟用注解和AOP

創(chuàng)建一個Java配置類來代替XML配置,使用@Configuration注解標記為配置類,并通過@ComponentScan注解來啟用組件掃描,通過@EnableAspectJAutoProxy啟用AspectJ自動代理:

package com.example.demo.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy
public class AppConfig {
}

@EnableAspectJAutoProxy注解在Spring中用于啟用對AspectJ風格的切面的支持。它告訴Spring框架去尋找?guī)в?code>@Aspect注解的類,并將它們注冊為Spring應用上下文中的切面,以便在運行時通過代理方式應用這些切面定義的通知(Advice)和切點(Pointcuts)。

如果不寫@EnableAspectJAutoProxy,Spring將不會自動處理@Aspect注解定義的切面,則定義的那些前置通知(@Before)、后置通知(@After、@AfterReturning@AfterThrowing)和環(huán)繞通知(@Around)將不會被自動應用到目標方法上。這意味著定義的AOP邏輯不會被執(zhí)行,失去了AOP帶來的功能增強。

@Before 注解定義了一個前置通知(Advice),它會在指定方法執(zhí)行之前運行。切點表達式execution(* com.example.demo.aop.MyServiceImpl.performAction(..))精確地定義了這些連接點的位置。在這個例子中,切點表達式指定了MyServiceImpl類中的performAction方法作為連接點,而@Before注解標識的方法(logBeforeAction)將在這個連接點之前執(zhí)行,即logBeforeAction方法(前置通知)會在performAction執(zhí)行之前被執(zhí)行。

創(chuàng)建主類和測試AOP功能

主程序如下:

package com.example.demo;

import com.example.demo.aop.MyService;
import com.example.demo.config.AppConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class DemoApplication {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        MyService service = context.getBean(MyService.class);
        service.performAction();
    }
}

運行結果如下:

在這里插入圖片描述

3. XML實現和注解實現AOP的代碼對比

對于上面的代碼,我們將原有基于注解的AOP配置改寫為完全基于XML的形式,方便大家對比。首先需要移除切面類和業(yè)務邏輯類上的所有Spring相關注解,然后在XML文件中配置相應的beanAOP邏輯。

移除注解

首先,我們移除業(yè)務邏輯類和切面類上的所有注解。

MyService.java (無變化,接口保持原樣):

package com.example.demo.aop;

public interface MyService {
    void performAction();
}

MyServiceImpl.java (移除@Service注解):

package com.example.demo.aop;

public class MyServiceImpl implements MyService {
    @Override
    public void performAction() {
        System.out.println("Performing an action in MyService");
    }
}

MyAspect.java (移除@Aspect和@Component注解,同時去掉方法上的@Before注解):

package com.example.demo.aop;

public class MyAspect {
    public void logBeforeAction() {
        System.out.println("Before performing action");
    }
}

XML配置

接下來,刪除AppConfig配置類,在SpringXML配置文件中定義beansAOP配置。

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 定義業(yè)務邏輯bean -->
    <bean id="myService" class="com.example.demo.aop.MyServiceImpl"/>

    <!-- 定義切面 -->
    <bean id="myAspect" class="com.example.demo.aop.MyAspect"/>

    <!-- AOP配置 -->
    <aop:config>
        <aop:aspect id="aspect" ref="myAspect">
            <aop:pointcut id="serviceOperation" expression="execution(* com.example.demo.aop.MyService.performAction(..))"/>
            <aop:before pointcut-ref="serviceOperation" method="logBeforeAction"/>
        </aop:aspect>
    </aop:config>
</beans>

在這個XML配置中,我們手動注冊了MyServiceImplMyAspect作為beans,并通過<aop:config>元素定義了AOP邏輯。我們創(chuàng)建了一個切點serviceOperation,用于匹配MyService.performAction(..)方法的執(zhí)行,并定義了一個前置通知,當匹配的方法被調用時,MyAspectlogBeforeAction方法將被執(zhí)行。

主類和測試AOP功能

主類DemoApplication的代碼不需要改變,只是在創(chuàng)建ApplicationContext時使用XML配置文件而不是Java配置類:

package com.example.demo;

import com.example.demo.aop.MyService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class DemoApplication {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyService service = context.getBean(MyService.class);
        service.performAction();
    }
}

運行結果是一樣的

在這里插入圖片描述

4. AOP通知講解

Spring AOP中,通知(Advice)定義了切面(Aspect)在目標方法調用過程中的具體行為。Spring AOP支持五種類型的通知,它們分別是:前置通知(Before)、后置通知(After)、返回通知(After Returning)、異常通知(After Throwing)和環(huán)繞通知(Around)。通過使用這些通知,開發(fā)者可以在目標方法的不同執(zhí)行點插入自定義的邏輯。

@Before(前置通知)

前置通知是在目標方法執(zhí)行之前執(zhí)行的通知,通常用于執(zhí)行一些預處理任務,如日志記錄、安全檢查等。

@Before("execution(* com.example.demo.aop.MyServiceImpl.performAction(..))")
public void logBeforeAction() {
    System.out.println("Before performing action");
}

@AfterReturning(返回通知)

返回通知在目標方法成功執(zhí)行之后執(zhí)行,可以訪問方法的返回值。

@AfterReturning(pointcut = "execution(* com.example.demo.aop.MyServiceImpl.performAction(..))", returning = "result")
public void logAfterReturning(Object result) {
    System.out.println("Method returned value is : " + result);
}

這里在@AfterReturning注解中指定returning = "result"時,Spring AOP框架將目標方法的返回值傳遞給切面方法的名為result的參數,因此,切面方法需要有一個與之匹配的參數,類型兼容目標方法的返回類型。如果兩者不匹配,Spring在啟動時會拋出異常,因為它無法將返回值綁定到切面方法的參數。

@AfterThrowing(異常通知)

異常通知在目標方法拋出異常時執(zhí)行,允許訪問拋出的異常。

@AfterThrowing(pointcut = "execution(* com.example.demo.aop.MyServiceImpl.performAction(..))", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Throwable ex) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("@AfterThrowing: Exception in method: " + methodName + "; Exception: " + ex.toString());
}

@AfterThrowing注解的方法中包含JoinPoint參數是可選的,當想知道哪個連接點(即方法)引發(fā)了異常的詳細信息時非常有用,假設有多個方法可能拋出相同類型的異常,而我們想在日志中明確指出是哪個方法引發(fā)了異常。通過訪問JoinPoint提供的信息,可以記錄下引發(fā)異常的方法名稱和其他上下文信息,從而使得日志更加清晰和有用。

@After(后置通知)

后置通知在目標方法執(zhí)行之后執(zhí)行,無論方法執(zhí)行是否成功,即便發(fā)生異常,仍然會執(zhí)行。它類似于finally塊。

@After("execution(* com.example.demo.aop.MyServiceImpl.performAction(..))")
public void logAfter() {
    System.out.println("After performing action");
}

@Around(環(huán)繞通知)

環(huán)繞通知圍繞目標方法執(zhí)行,可以在方法調用前后執(zhí)行自定義邏輯,同時決定是否繼續(xù)執(zhí)行目標方法。環(huán)繞通知提供了最大的靈活性和控制力。

@Around("execution(* com.example.demo.aop.MyServiceImpl.performAction(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("Before method execution");
    Object result = joinPoint.proceed(); // 繼續(xù)執(zhí)行目標方法
    System.out.println("After method execution");
    return result;
}

接下來,我們來演示一下,全部代碼如下:

服務接口(MyService.java):

package com.example.demo.aop;

public interface MyService {
    String performAction(String input);
}

服務實現(MyServiceImpl.java):

修改performAction方法,使其在接收到特定輸入時拋出異常

package com.example.demo.aop;

import org.springframework.stereotype.Service;

@Service
public class MyServiceImpl implements MyService {
    @Override
    public String performAction(String input) {
        System.out.println("Performing action with: " + input);
        if ("error".equals(input)) {
            throw new RuntimeException("Simulated error");
        }
        return "Processed " + input;
    }
}

完整的切面類(包含所有通知類型)

切面類(MyAspect.java) - 保持不變,確保包含所有類型的通知:

package com.example.demo.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {

    @Before("execution(* com.example.demo.aop.MyService.performAction(..))")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("@Before: Before calling performAction");
    }

    @After("execution(* com.example.demo.aop.MyService.performAction(..))")
    public void afterAdvice(JoinPoint joinPoint) {
        System.out.println("@After: After calling performAction");
    }

    @AfterReturning(pointcut = "execution(* com.example.demo.aop.MyService.performAction(..))", returning = "result")
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        System.out.println("@AfterReturning: Method returned value is : " + result);
    }

    @AfterThrowing(pointcut = "execution(* com.example.demo.aop.MyService.performAction(..))", throwing = "ex")
    public void afterThrowingAdvice(JoinPoint joinPoint, Throwable ex) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("@AfterThrowing: Exception in method: " + methodName + "; Exception: " + ex.toString());
    }

    @Around("execution(* com.example.demo.aop.MyService.performAction(..))")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("@Around: Before method execution");
        Object result = null;
        try {
            result = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
        	// 如果執(zhí)行方法出現異常,打印這里
            System.out.println("@Around: Exception in method execution");
            throw throwable;
        }
        // 如果執(zhí)行方法正常,打印這里
        System.out.println("@Around: After method execution");
        return result;
    }
}

這里要強調幾點:

@Around環(huán)繞通知常見用例是異常捕獲和重新拋出。在這個例子中,我們通過ProceedingJoinPointproceed()方法調用目標方法。如果目標方法執(zhí)行成功,記錄執(zhí)行后的消息并返回結果。如果在執(zhí)行過程中發(fā)生異常,在控制臺上打印出異常信息,然后重新拋出這個異常。這樣做可以確保異常不會被吞沒,而是可以被上層調用者捕獲或由其他異常通知處理。

@AfterThrowing注解標明這個通知只有在目標方法因為異常而終止時才會執(zhí)行。throwing屬性指定了綁定到通知方法參數上的異常對象的名稱。這樣當異常發(fā)生時,異常對象會被傳遞到afterThrowingAdvice方法中,方法中可以對異常進行記錄或處理。

@AfterThrowing@AfterReturning通知不會在同一個方法調用中同時執(zhí)行。這兩個通知的觸發(fā)條件是互斥的。@AfterReturning 通知只有在目標方法成功執(zhí)行并正常返回后才會被觸發(fā),這個通知可以訪問方法的返回值。@AfterThrowing 通知只有在目標方法拋出異常時才會被觸發(fā),這個通知可以訪問拋出的異常對象。

假設想要某個邏輯總是在方法返回時執(zhí)行,不管是拋出異常還是正常返回,則考慮放在@After或者@Around通知里執(zhí)行。

配置類

package com.example.demo.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy
public class AppConfig {
}

測試不同情況

為了測試所有通知類型的觸發(fā),在主類中執(zhí)行performAction方法兩次:一次傳入正常參數,一次傳入會導致異常的參數。

主程序如下:

package com.example.demo;

import com.example.demo.aop.MyService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class DemoApplication {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        MyService service = context.getBean(MyService.class);

        try {
            // 正常情況
            System.out.println("Calling performAction with 'test'");
            service.performAction("test");

            // 異常情況
            System.out.println("\nCalling performAction with 'error'");
            service.performAction("error");
        } catch (Exception e) {
            System.out.println("Exception caught in DemoApplication: " + e.getMessage());
        }
    }
}

在這個例子中,當performAction方法被第二次調用并傳入"error"作為參數時,將會拋出異常,從而觸發(fā)@AfterThrowing通知。

運行結果如下:

在這里插入圖片描述

5. AOP時序圖

這里展示在Spring AOP框架中一個方法調用的典型處理流程,包括不同類型的通知(Advice)的執(zhí)行時機。

在這里插入圖片描述

  1. 客戶端調用方法:
  • 客戶端(Client)發(fā)起對某個方法的調用。這個調用首先被AOP代理(AOP Proxy)接收,這是因為在Spring AOP中,代理負責在真實對象(Target)和外界之間進行中介。
  1. 環(huán)繞通知開始 (@Around):
  • AOP代理首先調用切面(Aspect)中定義的環(huán)繞通知的開始部分。環(huán)繞通知可以在方法執(zhí)行前后執(zhí)行代碼,并且能決定是否繼續(xù)執(zhí)行方法或直接返回自定義結果。這里的“開始部分”通常包括方法執(zhí)行前的邏輯。
  1. 前置通知 (@Before):
  • 在目標方法執(zhí)行之前,執(zhí)行前置通知。這用于在方法執(zhí)行前執(zhí)行如日志記錄、安全檢查等操作。
  1. 執(zhí)行目標方法:
  • 如果環(huán)繞通知和前置通知沒有中斷執(zhí)行流程,代理會調用目標對象(Target)的實際方法。
  1. 方法完成:
  • 方法執(zhí)行完成后,控制權返回到AOP代理。這里的“完成”可以是成功結束,也可以是拋出異常。
  1. 返回通知或異常通知:
  • 返回通知 (@AfterReturning):如果方法成功完成,即沒有拋出異常,執(zhí)行返回通知。這可以用來處理方法的返回值或進行某些后續(xù)操作。
  • 異常通知 (@AfterThrowing):如果方法執(zhí)行過程中拋出異常,執(zhí)行異常通知。這通常用于異常記錄或進行異常處理。
  1. 后置通知 (@After):
  • 獨立于方法執(zhí)行結果,后置通知總是會執(zhí)行。這類似于在編程中的finally塊,常用于資源清理。
  1. 環(huán)繞通知結束 (@Around):
  • 在所有其他通知執(zhí)行完畢后,環(huán)繞通知的結束部分被執(zhí)行。這可以用于執(zhí)行清理工作,或者在方法執(zhí)行后修改返回值。
  1. 返回結果:
  • 最終,AOP代理將處理的結果返回給客戶端。這個結果可能是方法的返回值,或者通過環(huán)繞通知修改后的值,或者是異常通知中處理的結果。

以上就是Spring AOP注解實戰(zhàn)指南的詳細內容,更多關于Spring AOP注解的資料請關注腳本之家其它相關文章!

相關文章

  • SpringBoot整合MongoDB的步驟詳解

    SpringBoot整合MongoDB的步驟詳解

    這篇文章主要介紹了SpringBoot整合MongoDB的步驟詳解,幫助大家更好的理解和學習使用SpringBoot框架,感興趣的朋友可以了解下
    2021-04-04
  • Java的關鍵字之transient詳解

    Java的關鍵字之transient詳解

    這篇文章主要介紹了Java的關鍵字之transient詳解,在Java編程中,transient是一個關鍵字,通常用于修飾變量,它的主要作用是用于指示JVM在對象序列化時忽略指定變量,從而避免數據泄露的安全問題,需要的朋友可以參考下
    2023-09-09
  • Java集合之LinkedHashSet類詳解

    Java集合之LinkedHashSet類詳解

    這篇文章主要介紹了Java集合之LinkedHashSet類詳解,LinkedHashSet 是 Java 中的一個集合類,它是 HashSet 的子類,并實現了 Set 接口,與 HashSet 不同的是,LinkedHashSet 保留了元素插入的順序,并且具有 HashSet 的快速查找特性,需要的朋友可以參考下
    2023-09-09
  • SpringBoot整合Swagger頁面禁止訪問swagger-ui.html方式

    SpringBoot整合Swagger頁面禁止訪問swagger-ui.html方式

    本文介紹了如何在SpringBoot項目中通過配置SpringSecurity和創(chuàng)建攔截器來禁止訪問SwaggerUI頁面,此外,還提供了禁用SwaggerUI和Swagger資源的配置方法,以確保這些端點和頁面對外部用戶不可見或無法訪問
    2025-02-02
  • 如何使用Spring boot的@Transactional進行事務管理

    如何使用Spring boot的@Transactional進行事務管理

    這篇文章介紹了SpringBoot中使用@Transactional注解進行聲明式事務管理的詳細信息,包括基本用法、核心配置參數、關鍵注意事項、調試技巧、最佳實踐以及完整示例,感興趣的朋友一起看看吧
    2025-02-02
  • Java根據實體生成SQL數據庫表的示例代碼

    Java根據實體生成SQL數據庫表的示例代碼

    這篇文章主要來和大家分享一個Java實現根據實體生成SQL數據庫表的代碼,文中的實現代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-07-07
  • Java JDK動態(tài)代理在攔截器和聲明式接口中的應用小結

    Java JDK動態(tài)代理在攔截器和聲明式接口中的應用小結

    Java動態(tài)代理技術通過反射機制在運行時動態(tài)生成代理類,實現對目標對象方法的攔截和增強,本文給大家介紹Java JDK動態(tài)代理在攔截器和聲明式接口中的應用小結,感興趣的朋友跟隨小編一起看看吧
    2025-01-01
  • MyBatis-Plus攔截器對敏感數據實現加密

    MyBatis-Plus攔截器對敏感數據實現加密

    做課程項目petstore時遇到需要加密屬性的問題,而MyBatis-Plus為開發(fā)者提供了攔截器的相關接口,本文主要介紹通過MyBatis-Plus的攔截器接口自定義一個攔截器類實現敏感數據如用戶密碼的加密功能,感興趣的可以了解一下
    2021-11-11
  • Java Reference源碼解析

    Java Reference源碼解析

    這篇文章主要為大家詳細解析了Java Reference源碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • Map之computeIfAbsent使用解讀

    Map之computeIfAbsent使用解讀

    `computeIfAbsent`是Java 8引入的一個Map接口方法,用于簡化在Map中獲取值的操作,如果指定的鍵不存在,它會調用提供的函數生成一個新的值,并將其與鍵關聯,這種方法減少了手動檢查和插入的樣板代碼,使代碼更加簡潔和易讀
    2025-02-02

最新評論