簡單瞭解下Spring中的各種Aware介面實現依賴注入

落叶微风發表於2024-08-03

你好,這裡是codetrend專欄“Spring6全攻略”。

在Spring框架中,Aware介面是一組用於提供特定資源或環境資訊的回撥介面。這些介面被設計用來允許Bean獲取對Spring容器或其他相關資源的引用,並在需要時進行適當的處理。

Aware介面的設計是為了讓Bean能夠感知到其所處的環境並與之互動。透過實現這些介面,Bean可以獲取對Spring容器或其他相關資源的引用,從而能夠更好地適應和利用所處的環境。

使用場景

  • 獲取Spring容器的引用:ApplicationContextAware介面可以讓Bean獲取對Spring容器的引用,從而能夠訪問容器中的其他Bean或執行一些特定的操作。
  • 資源載入和處理:BeanClassLoaderAware和ResourceLoaderAware介面可以讓Bean獲取類載入器和資源載入器的引用,用於載入資原始檔或進行類載入操作。
  • 事件釋出和訊息處理:ApplicationEventPublisherAware和MessageSourceAware介面可以讓Bean獲取事件釋出器和訊息源的引用,用於釋出事件或處理國際化訊息。
  • Web環境處理:ServletConfigAware和ServletContextAware介面可以讓Bean獲取對Servlet配置和上下文的引用,在Web應用中進行特定的處理。

Aware介面

名稱 注入的依賴
ApplicationContextAware(常用) 宣告的 ApplicationContext。
ApplicationEventPublisherAware 包含的 ApplicationContext 的事件釋出器。
BeanClassLoaderAware 用於載入 bean 類的類載入器。
BeanFactoryAware(常用) 宣告的 BeanFactory。
BeanNameAware 宣告 bean 的名稱。
LoadTimeWeaverAware 在載入時處理類定義的已定義織入器。
MessageSourceAware 配置用於解析訊息的策略(支援引數化和國際化)。
NotificationPublisherAware Spring JMX 通知釋出器。
ResourceLoaderAware 配置的載入器,用於低階別訪問資源。
ServletConfigAware 容器執行的當前 ServletConfig。僅在 web 感知的 Spring ApplicationContext 中有效。
ServletContextAware 容器執行的當前 ServletContext。僅在 web 感知的 Spring ApplicationContext 中有效。

ApplicationContextAware 介面

實現這個介面的Bean可以在其初始化時獲取到ApplicationContext,從而能夠訪問Spring容器中的所有Bean及其配置。具體來說,這個介面主要用於需要與Spring上下文進行互動的場景。

例子程式碼如下:

class ContextAwareBean implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public void showBeanDetails() {
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        System.out.println("Bean names in ApplicationContext:");
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
    }
}

注意事項:

  • 避免過度依賴ApplicationContext:雖然ApplicationContextAware提供了強大的功能,但應謹慎使用,以避免過度依賴Spring上下文,導致程式碼難以測試和維護。
  • Bean生命週期:確保在Bean初始化完成後再呼叫依賴ApplicationContext的方法,否則可能會遇到空指標異常(NullPointerException)。
  • 測試困難:由於ApplicationContextAware直接依賴於Spring容器,在單元測試中模擬這些依賴可能會比較複雜。

ApplicationEventPublisherAware 介面

ApplicationEventPublisherAware介面允許Bean獲取到ApplicationEventPublisher例項,以便能夠釋出事件。透過實現該介面,Bean可以在執行時嚮應用程式上下文釋出自定義事件或標準Spring事件。

例子程式碼如下:

@Getter
class CustomEvent extends ApplicationEvent {

    private final String message;
    public CustomEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

}

/**
 * 事件釋出
 */
@Component
class EventPublisherBean implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void publishCustomEvent(final String message) {
        CustomEvent customEvent = new CustomEvent(this, message);
        applicationEventPublisher.publishEvent(customEvent);
    }
}

/**
 * 事件監聽
 */
@Component
class CustomEventListener {
    @EventListener
    public void handleCustomEvent(CustomEvent event) {
        System.out.println("Received custom event - " + event.getMessage());
    }
}

測試程式碼輸出如下:

Received custom event - Hello, Spring Events!

注意事項:

  • 避免過度依賴:儘量將事件釋出邏輯與業務邏輯分離,遵循單一職責原則,避免過度依賴Spring的事件機制,從而保持程式碼的可維護性和可測試性。
  • 非同步事件處理:在需要非同步處理事件的場景下,可以結合Spring的非同步支援(如@Async註解)來提高效能。
  • 事件傳播:注意事件的傳播範圍和生命週期,確保事件釋出和處理的順序和時機符合業務需求。

BeanClassLoaderAware 介面

透過實現這個介面,Bean可以在其生命週期內訪問載入它的類載入器,從而進行一些需要類載入器操作的任務。

實現demo如下:

/**
 * 說明:BeanClassLoaderAware demo
 * @since 2024/6/13
 * @author sunz
 */
public class ClassLoaderAwareDemo {
    public static void main(String[] args) {
        // 建立一個基於 Java Config 的應用上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppScanConfig.class);
        ClassLoaderAwareBean classLoaderAwareBean = context.getBean(ClassLoaderAwareBean.class);
        classLoaderAwareBean.performClassLoadingTask("org.springframework.beans.factory.BeanClassLoaderAware");
        // 銷燬容器
        context.close();
    }
}


@Component
class ClassLoaderAwareBean implements BeanClassLoaderAware {

    private ClassLoader classLoader;

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
        System.out.println("ClassLoader has been set.");
    }

    public void performClassLoadingTask(String clsName) {
        try {
            // 例如動態載入一個類
            Class<?> loadedClass = classLoader.loadClass(clsName);
            System.out.println("Class loaded: " + loadedClass.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

輸出結果如下:

ClassLoader has been set.
Class loaded: org.springframework.beans.factory.BeanClassLoaderAware

注意事項:

  • 避免過度使用:雖然BeanClassLoaderAware介面提供了類載入器的訪問能力,但應謹慎使用,避免在業務邏輯中過度依賴類載入器,保持程式碼的簡潔和可維護性。
  • 類載入器隔離:在複雜的應用場景中,特別是涉及模組化或外掛化的系統中,不同模組可能會使用不同的類載入器。確保正確理解和管理類載入器的隔離和作用範圍。
  • 錯誤處理:在動態載入類或資源時,應注意處理可能的異常情況,例如類未找到(ClassNotFoundException)或資源不存在等。

BeanFactoryAware 介面

透過實現這個介面,Bean 可以在自身的生命週期中訪問 Spring 容器,從而動態地獲取其他 Bean 或者進行一些容器級別的操作。

比如寫個策略模式,透過策略動態獲取bean就可能用到。

一般場景如下:

  • 動態獲取 Bean: 雖然依賴注入已經非常強大,但在某些情況下,可能需要動態獲取 Bean。例如,根據執行時條件選擇性地建立或獲取不同型別的 Bean。
  • 容器級別操作: 有時需要執行一些高階操作,如檢查 Bean 的存在性、獲取 Bean 的定義資訊等。

Demo程式碼如下:

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;
/**
 * 說明:BeanFactoryAware 例子
 * @since 2024/6/13
 * @author sunz
 */
public class BeanFactoryAwareDemo {
    public static void main(String[] args) {
        // 建立一個基於 Java Config 的應用上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppScanConfig.class);
        MyBeanFactoryAware bean = context.getBean(MyBeanFactoryAware.class);
        bean.doSomething();
        // 銷燬容器
        context.close();
    }
}


@Component
class MyBeanFactoryAware implements BeanFactoryAware {

    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public void doSomething() {
        // 現在可以使用 beanFactory 來獲取其他的 Bean
        MyService myService = beanFactory.getBean(MyService.class);
        myService.performAction();
    }
}

@Component
class MyService {
    public void performAction() {
        System.out.println("hello world");
    }
}

注意事項:

  • 避免濫用: 過度依賴 BeanFactoryAware 可能導致程式碼與 Spring 框架緊密耦合,降低程式碼的可測試性和靈活性。應該儘量使用依賴注入來代替直接訪問 BeanFactory。
  • 單例模式: 如果 Bean 是單例的,那麼它所持有的 BeanFactory 也是單例的。這意味著同一個 BeanFactory 例項會被多個單例 Bean 共享。

BeanNameAware 介面

實現這個介面的 Bean 物件在被 Spring 容器例項化後,能夠獲取到自己在容器中的名稱。

這在某些情況下可能會非常有用,例如在除錯、日誌記錄或需要根據 Bean 名稱執行特定邏輯時。

一般應用場景:

  • 除錯和日誌記錄: 在開發和維護過程中,知道 Bean 的名稱可以幫助除錯和記錄日誌。
  • 動態處理邏輯: 某些情況下,業務邏輯可能需要根據 Bean 的名稱來進行不同的處理。

demo程式碼如下:

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

/**
 * 說明: BeanNameAware 例子
 *
 * @author sunz
 * @since 2024/6/13
 */
public class BeanNameAwareDemo {
    public static void main(String[] args) {
        // 建立一個基於 Java Config 的應用上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppScanConfig.class);
        MyBeanNameAware bean = context.getBean(MyBeanNameAware.class);
        bean.setBeanName("hello,jack");
        bean.printBeanName();
        // 銷燬容器
        context.close();
    }
}

@Component
class MyBeanNameAware implements BeanNameAware {

    private String beanName;

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }

    public void printBeanName() {
        System.out.println("Bean name is: " + beanName);
    }
}

注意事項:

  • 避免過度依賴: 類似於其他 Aware 介面,過度依賴 BeanNameAware 可能導致程式碼與 Spring 框架緊密耦合,降低程式碼的可測試性和靈活性。應儘可能使用依賴注入和其他更解耦的設計模式。

LoadTimeWeaverAware 介面

實現這個介面的 Bean 在被 Spring 容器例項化後,能夠獲取到一個 LoadTimeWeaver 例項。LoadTimeWeaver 是用於在類載入時進行位元組碼增強的重要機制之一,比如在 AOP(面向切面程式設計)中動態地織入橫切關注點。

程式碼沒什麼實際使用場景,開源專案搜尋也沒發現使用例子,實際使用還是比較少。

不推薦使用,使用AOP還是直接用spring aop即可。

實際使用程式碼和之前的Aware是一致的,只要實現介面即可。

class MyLoadTimeWeaverAware implements LoadTimeWeaverAware {
    private LoadTimeWeaver loadTimeWeaver;
    @Override
    public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
        this.loadTimeWeaver = loadTimeWeaver;
    }
    public void doSomething() {
        // 使用 loadTimeWeaver 進行位元組碼增強等操作
        // 這裡僅作為示例,不進行實際操作
        System.out.println("LoadTimeWeaver is set and can be used now.");
    }
}

MessageSourceAware 介面

MessageSourceAware 是 Spring 框架中的一個介面,用於實現國際化(i18n)的功能。它允許一個 bean 接收 MessageSource 物件,從而能夠在應用程式中訪問國際化的訊息資源。

實現 MessageSourceAware 介面的類可以直接使用 MessageSource 來獲取國際化的訊息,而不必顯式地在其配置中注入 MessageSource bean。

這對於需要頻繁訪問國際化訊息的 bean 非常有用。

例子程式碼如下:

/**
 * 說明:MessageSourceAware demo
 * @since 2024/6/13
 * @author sunz
 */
public class MessageSourceAwareDemo {
    public static void main(String[] args) {
        // 建立一個基於 Java Config 的應用上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMessageConfig.class);
        GreetingService bean = context.getBean(GreetingService.class);
        String greetingEn = bean.getGreeting("John", Locale.ENGLISH);
        System.out.println(greetingEn); // 輸出: Hello, John!
        String greetingZh = bean.getGreeting("李雷", Locale.CHINESE);
        System.out.println(greetingZh); // 輸出: 你好, 李雷!
        // 銷燬容器
        context.close();
    }
}

/**
 * 配置類
 */
@Configuration
@ComponentScan(basePackages = "io.yulin.learn.spring.s108")
class AppMessageConfig {
    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
}

/**
 * 服務bean
 */
@Component
class GreetingService implements MessageSourceAware {

    private MessageSource messageSource;

    @Override
    public void setMessageSource(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    public String getGreeting(String name, Locale locale) {
        return messageSource.getMessage("greeting", new Object[]{name}, locale);
    }
}

其中資原始檔如下:

<!-- messages_en.properties -->
greeting=Hello, {0}!
<!-- messages_zh.properties -->
greeting=你好, {0}!

一般情況下使用多語言工具可以進行二次封裝,使得使用起來更簡潔方便,而不是每次進行注入。

以下是一個封裝的例子:

import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;

import java.util.Locale;

@Component
public class MessageUtils {
    private static MessageSource messageSource;

    // Spring 會自動注入該方法中的引數 messageSource
    public MessageUtils(MessageSource messageSource) {
        MessageUtils.messageSource = messageSource;
    }

    /**
     * 獲取國際化訊息
     *
     * @param code 訊息程式碼
     * @param args 訊息引數
     * @return 國際化訊息
     */
    public static String getMessage(String code, Object... args) {
        Locale locale = LocaleContextHolder.getLocale();
        return messageSource.getMessage(code, args, locale);
    }
}

NotificationPublisherAware 介面

NotificationPublisherAware 是 Spring 框架中的一個介面,用於與 JMX(Java Management Extensions)相關的通知機制進行整合。實現該介面的 bean 可以透過 Spring 容器獲得一個 NotificationPublisher 例項,從而能夠釋出 JMX 通知。

在需要釋出 JMX 通知的 Spring 管理的 bean 中,實現 NotificationPublisherAware 介面可以方便地獲取 NotificationPublisher 併發布通知。這通常用於監控和管理應用程式狀態變化,如資源使用情況、效能指標等。

例子程式碼如下:


/**
 * 說明:NotificationPublisherAware  demo
 * @since 2024/6/13
 * @author sunz
 */
public class NotificationPublisherAwareDemo {
    public static void main(String[] args) {
        // 建立一個基於 Java Config 的應用上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppNotifyConfig.class);
        MyManagedBean bean = context.getBean(MyManagedBean.class);
        bean.doSomething();
        // 銷燬容器
        context.close();
    }
}
@Configuration
@ComponentScan(basePackages = "io.yulin.learn.spring.s108")
class AppNotifyConfig{
    @Bean
    public MBeanExporter mBeanExporter() {
        MBeanExporter exporter = new MBeanExporter();
        exporter.setRegistrationPolicy(RegistrationPolicy.REPLACE_EXISTING);
        Map<String, Object> beans = new HashMap<>();
        beans.put("bean:name=myManagedBean", myManagedBean());
        exporter.setBeans(beans);
        return exporter;
    }
    @Bean
    public MyManagedBean myManagedBean() {
        return new MyManagedBean();
    }
}

/**
 * 服務bean
 */
@Component
class MyManagedBean extends NotificationBroadcasterSupport implements MyManagedBeanMBean,NotificationPublisherAware {

    private NotificationPublisher notificationPublisher;

    @Override
    public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
        this.notificationPublisher = notificationPublisher;
    }

    @Override
    public void doSomething() {
        // 執行某些操作
        System.out.println("Doing something...");
        // 釋出通知
        Notification notification = new Notification(
                "myNotificationType",
                this,
                System.currentTimeMillis(),
                "Something happened!"
        );
        if (this.notificationPublisher != null) {
            notificationPublisher.sendNotification(notification);
        } else {
            // 使用 NotificationBroadcasterSupport 自帶的方法
            sendNotification(notification);
        }
    }
}

JMX接收和監聽比較複雜,這裡只是使用了NotificationPublisher進行推送通知訊息。

ResourceLoaderAware 介面

實現 ResourceLoaderAware 介面的類會在其被 Spring 容器管理時自動獲得一個 ResourceLoader 例項。透過這個例項,類可以方便地載入各種型別的資源(如檔案系統、類路徑、URL 等)。

通常在需要訪問外部資源(例如檔案、配置檔案、圖片等)的類中,可以實現 ResourceLoaderAware 介面。這比直接依賴 File 或其他資源載入機制更靈活,因為 ResourceLoader 可以處理多種型別的資源路徑(如類路徑、檔案系統路徑、URL 等)。

以下是一個簡單的示例,展示瞭如何實現 ResourceLoaderAware 並使用 ResourceLoader 載入文字檔案。

/**
 * 說明:ResourceLoaderAware   demo
 * @since 2024/6/14
 * @author sunz
 */
public class ResourceLoaderAwareDemo {
    public static void main(String[] args) {
        // 建立一個基於 Java Config 的應用上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppScanConfig.class);
        MyResourceLoaderAwareBean bean = context.getBean(MyResourceLoaderAwareBean.class);
        // 假設 "test.txt" 位於 classpath 路徑下
        bean.loadResource("classpath:application.yml");
        // 銷燬容器
        context.close();
    }
}
/**
 * 服務bean
 */
@Component
class MyResourceLoaderAwareBean implements ResourceLoaderAware {

    private ResourceLoader resourceLoader;

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    public void loadResource(String location) {
        try {
            Resource resource = resourceLoader.getResource(location);
            BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

ServletConfigAware 介面

實現 ServletConfigAware 介面的類會在其被 Spring 容器管理時自動獲得一個 ServletConfig 例項。透過這個例項,類可以方便地獲取關於當前 Servlet 的配置資訊。

使用demo如下:

/**
 * 服務bean
 */
@RestController
@RequestMapping("/test")
public class MyServletConfigAwareBean implements ServletConfigAware {

    private ServletConfig servletConfig;

    @Override
    public void setServletConfig(ServletConfig servletConfig) {
        this.servletConfig = servletConfig;
    }

    @GetMapping("/demo")
    public void printServletInfo() {
        String servletName = servletConfig.getServletName();
        String initParamValue = servletConfig.getInitParameter("myInitParam");
        String contextPath = servletConfig.getServletContext().getContextPath();

        System.out.println("Servlet Name: " + servletName);
        System.out.println("Init Param Value: " + initParamValue);
        System.out.println("Context Path: " + contextPath);
    }
}

實際驗證會發現servletConfig報空指標異常,這塊是因為沒初始化相關的配置導致的。

ServletContextAware 介面

ServletContextAware 介面是 Spring 框架中的一個介面,用於讓實現它的類獲取當前 Servlet 上下文(ServletContext)的引用。透過實現這個介面,類可以在 Spring 容器初始化時自動獲取 Servlet 上下文物件,從而進行一些與 Servlet 相關的操作。

使用demo如下:

@RestController
@RequestMapping("/test2")
public class MyServletContextAwareBean implements ServletContextAware {

    private ServletContext servletContext;

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    // 可以新增其他方法來使用 servletContext 物件
    @GetMapping("/demo")
    public void printServletInfo() {
        System.out.println("ServletContext: " + servletContext);
        System.out.println("ServletContext name: " + servletContext.getServletContextName());
        System.out.println("ServletContext context path: " + servletContext.getContextPath());
        System.out.println("ServletContext server info: " + servletContext.getServerInfo());
        System.out.println("ServletContext major version: " + servletContext.getMajorVersion());
        System.out.println("ServletContext minor version: " + servletContext.getMinorVersion());
    }
}

ServletContext 常用的場景如下:

  1. 獲取上下文路徑和真實路徑:可以獲取當前 web 應用的上下文路徑和部署後的真實路徑。
  2. 執行一些 Servlet 上下文管理的操作:如獲取當前已經初始化的 Servlet、獲取 Servlet 的名稱等。
  3. 與會話管理相關的操作:可以透過 ServletContext 獲取應用的會話管理器,並進行相關的會話操作。

點選話題和專欄可以看更多往期文章。

關於作者

來自一線全棧程式設計師nine的探索與實踐,持續迭代中。

歡迎關注、評論、點贊。

相關文章