想減少程式碼量,快設置一個有感知的 Aware Spring Bean
摘要:正常情況下,Spring 中的 Bean 對 Spring 是無感知的,Spring 框架提供了這種擴展能力,能讓一個 bean 成為有感知的。
本文分享自華為雲社區《有感知的 Aware Spring Bean》,作者:陳皮的JavaLib。
有感知能力的 Spring Bean
正常情況下,Spring 中的 Bean 對 Spring 是無感知的,即它感知不到自己是由哪個 Bean 工廠(BeanFactory)創建的;感知不到自己在工廠中的 id,即 beanName;也感知不到 Spring 應用上下文對象(ApplicationContext)等等。
如果要想 Spring Bean 能感知到這些資訊,我們可以自己通過某些手段來獲取這些資訊,然後設置到 bean 實例中。這種方法缺點是需要我們自己獲取需要感知的資訊,然後主動設置到 bean 中,會寫許多冗餘程式碼。但是優點是可以和 Spring 解耦。
當然,Spring 框架提供了這種擴展能力,能讓一個 bean 成為有感知的。只要讓 bean 類實現特定的介面,重寫其中的方法即可。這樣 Spring 會在 bean 的生命周期的某個階段調用這些重寫的方法,注入需要感知的資訊。這方式的缺點是需要和 Spring 耦合,優點是能減少程式碼量,簡單。
Aware 介面
要讓 Spring 中的 bean 獲得某些感知能力,需要實現特定的介面,這些介面有個共同的父介面,即 Aware 介面。
package org.springframework.beans.factory; public interface Aware { }
Aware 是一個標記介面,表明一個 bean 可以通過回調方法得到 Spring 容器中特定的對象。具體的方法簽名由各個子介面定義,但通常應該只包含一個接受單個參數並返回 void 的方法。
注意,僅實現 Aware 介面不會提供默認功能。我們一般是實現 Aware 的子介面來獲得特定的感知能力。
Aware 介面的子介面有很多,例如 BeanNameAware,BeanFactoryAware,ApplicationContextAware,EnvironmentAware,ApplicationEventPublisherAware 等等,以下介紹幾個常用的用法。
BeanNameAware 介面
如果 bean 需要知道自己在 bean 工廠中的 beanName,即在 Spring 容器中的名字(標識)。可以實現此 BeanNameAware 介面。BeanNameAware 介面源碼如下,只有一個方法,beanName 會通過這個方法設置到 bean 中。
package org.springframework.beans.factory; public interface BeanNameAware extends Aware { void setBeanName(String name); }
其實,我們使用的 bean 一般不需要知道它在 beanFactory 中的名字,意義不大。一般是官方在 Spring 自身框架使用比較多,官方也不推薦我們使用,因為這樣會導致 bean 依賴 Spring API。
package com.chenpi; import org.springframework.beans.factory.BeanNameAware; import org.springframework.stereotype.Component; /** * @author 陳皮 * @version 1.0 * @description * @date 2022/4/3 */ @Component public class Person implements BeanNameAware { private String beanName; @Override public void setBeanName(String name) { this.beanName = name; } public String getBeanName() { return beanName; } }
編寫測試單元,驗證如下:
package com.chenpi; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class ApplicationTests { @Autowired private Person person; @Test public void testValue() { System.out.println("Person BeanName:" + person.getBeanName()); } } // 輸出結果如下 Person BeanName:person
BeanFactoryAware 介面
如果 Bean 想感知配置自己的 BeanFactory 對象,可以實現 BeanFactoryAware 介面。如果需要,bean 可以通過 BeanFactory 對象獲取其他 bean 對象,進行協作。當然也不推薦這種方式,推薦使用 DI 方式注入依賴的 bean 對象。BeanFactoryAware 介面源碼如下:
package org.springframework.beans.factory; import org.springframework.beans.BeansException; public interface BeanFactoryAware extends Aware { // 在bean屬性填充之後,但是在初始回調(例如afterPropertiesSet()方法)之前回調此方法 void setBeanFactory(BeanFactory beanFactory) throws BeansException; }
測試程式碼以及單元測試驗證結果如下:
package com.chenpi; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.stereotype.Component; /** * @author 陳皮 * @version 1.0 * @description * @date 2022/4/3 */ @Component public class Person implements BeanNameAware, BeanFactoryAware { private String beanName; private BeanFactory beanFactory; @Override public void setBeanName(String name) { this.beanName = name; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } public String getBeanName() { return beanName; } public BeanFactory getBeanFactory() { return beanFactory; } } package com.chenpi; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class ApplicationTests { @Autowired private Person person; @Test public void testValue() { System.out.println("Person BeanName:" + person.getBeanName()); System.out.println("Person Bean's BeanFactory:" + person.getBeanFactory().getClass()); System.out.println( person == person.getBeanFactory().getBean(person.getBeanName(), Person.class)); } } // 輸出結果如下 Person BeanName:person Person Bean's BeanFactory:class org.springframework.beans.factory.support.DefaultListableBeanFactory true
ApplicationContextAware 介面
如果 Spring Bean 需要感知 Spring 容器,即 ApplicationContext 對象,可以實現 ApplicationContextAware 介面。可以通過 Spring 容器獲取其他 Bean 對象,進行協作。
當然,如果一個 bean 對象需要訪問文件資源,例如調用applicationContext.getResource()方法,或想要發布一個應用程式事件applicationContext.publishEvent(ApplicationEvent event),或需要訪問MessageSource,此種方式可以實現。不過,官方對於這些的特定場景,最好實現更具體的ResourceLoaderAware、ApplicationEventPublisherAware或MessageSourceAware介面更合適。
package org.springframework.context; import org.springframework.beans.BeansException; import org.springframework.beans.factory.Aware; public interface ApplicationContextAware extends Aware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
測試程式碼以及單元測試驗證結果如下:
package com.chenpi; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * @author 陳皮 * @version 1.0 * @description * @date 2022/4/3 */ @Component public class Person implements BeanNameAware, BeanFactoryAware, ApplicationContextAware { private String beanName; private BeanFactory beanFactory; private ApplicationContext applicationContext; @Override public void setBeanName(String name) { this.beanName = name; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } public String getBeanName() { return beanName; } public BeanFactory getBeanFactory() { return beanFactory; } public ApplicationContext getApplicationContext() { return applicationContext; } } package com.chenpi; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; @SpringBootTest class ApplicationTests { @Autowired private Person person; @Test public void testValue() { System.out.println("Person BeanName:" + person.getBeanName()); System.out.println("Person Bean's BeanFactory:" + person.getBeanFactory().getClass()); System.out.println( person == person.getBeanFactory().getBean(person.getBeanName(), Person.class)); ApplicationContext applicationContext = person.getApplicationContext(); Person person1 = applicationContext.getBean(person.getBeanName(), Person.class); System.out.println(applicationContext.getClass()); System.out.println(person == person1); } } // 輸出結果如下 Person BeanName:person Person Bean's BeanFactory:class org.springframework.beans.factory.support.DefaultListableBeanFactory true class org.springframework.web.context.support.GenericWebApplicationContext true