你好,這裡是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 常用的場景如下:
- 獲取上下文路徑和真實路徑:可以獲取當前 web 應用的上下文路徑和部署後的真實路徑。
- 執行一些 Servlet 上下文管理的操作:如獲取當前已經初始化的 Servlet、獲取 Servlet 的名稱等。
- 與會話管理相關的操作:可以透過 ServletContext 獲取應用的會話管理器,並進行相關的會話操作。
點選話題和專欄可以看更多往期文章。
關於作者
來自一線全棧程式設計師nine的探索與實踐,持續迭代中。
歡迎關注、評論、點贊。