1. 程式人生 > 程式設計 >如何寫好一個Spring元件的實現步驟

如何寫好一個Spring元件的實現步驟

本文詳細的介紹了Spring元件的實現步驟,分享給大家,具體如下:

如何寫好一個Spring元件的實現步驟

背景

Spring 框架提供了許多介面,可以使用這些介面來定製化 bean ,而非簡單的 getter/setter 或者構造器注入。細翻 Spring Cloud Netflix、Spring Cloud Alibaba 等這些構建在 Spring Framework 的成熟框架原始碼,你會發現大量的擴充套件 bean 例如

Eureka 健康檢查

package org.springframework.cloud.netflix.eureka;

public class EurekaHealthCheckHandler implements InitializingBean {}

Seata Feign 配置

package com.alibaba.cloud.seata.feign;

public class SeataContextBeanPostProcessor implements BeanPostProcessor {}

程式碼示例

如何寫好一個Spring元件的實現步驟

DemoBean

@Slf4j
public class DemoBean implements InitializingBean {

 public DemoBean() {
  log.info("--> instantiate ");
 }

 @PostConstruct
 public void postConstruct() {
  log.info("--> @PostConstruct ");
 }

 @Override
 public void afterPropertiesSet() throws Exception {
  log.info("--> InitializingBean.afterPropertiesSet ");
 }

 public void initMethod() {
  log.info("--> custom initMehotd");
 }
}

DemoBeanPostProcessor

@Configuration
public class DemoBeanPostProcessor implements BeanPostProcessor {
 @Override
 public Object postProcessBeforeInitialization(Object bean,String beanName) throws BeansException {
  if ("demoBean".equals(beanName)){
   log.info("--> BeanPostProcessor.postProcessBeforeInitialization ");
  }
  return bean;
 }

 @Override
 public Object postProcessAfterInitialization(Object bean,String beanName) throws BeansException {
  if ("demoBean".equals(beanName)){
   log.info("--> BeanPostProcessor.postProcessAfterInitialization ");
  }
  return bean;
 }
}

DemoConfig

@Configuration
public class DemoConfig {

 @Bean(initMethod = "initMethod")
 public DemoBean demoBean() {
  return new DemoBean();
 }
}

執行輸出日誌

整個 bean 的建立過程日誌輸出如下和文首圖片橫線以上 bean 建立週期一致

DemoBean : --> instantiate
DemoBeanPostProcessor: --> BeanPostProcessor.postProcessBeforeInitialization
DemoBean : --> @PostConstruct
DemoBean : --> InitializingBean.afterPropertiesSet
DemoBean : --> custom initMehotd
DemoBeanPostProcessor: --> BeanPostProcessor.postProcessAfterInitialization

執行過程核心原始碼

AbstractAutowireCapableBeanFactory.initializeBean

protected Object initializeBean(final String beanName,final Object bean,@Nullable RootBeanDefinition mbd) {

 // 執行BeanPostProcessor.postProcessBeforeInitialization
 Object wrappedBean = wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean,beanName);
  ...
  // 執行使用者自定義初始化and JSR 250 定義的方法
 invokeInitMethods(beanName,wrappedBean,mbd);
  ...
 // 執行執行BeanPostProcessor.postProcessAfterInitialization
 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean,beanName);

 return wrappedBean;
}

下文就詳細說明一下每個 bean 的擴充套件週期的最佳使用場景BeanPostProcessor

如何寫好一個Spring元件的實現步驟

BeanPostProcessor 是一個可以自定義實現回撥方法介面,來實現自己的例項化邏輯、依賴解決邏輯等,如果想要在 Spring 完成物件例項化、配置、初始化之後實現自己的業務邏輯,可以通過擴充套件實現一個或多個 BeanPostProcessor 處理。

多用於介面卡模式,可以在實現同一介面 bean 建立前後進行包裝轉換

// seata 上下文轉換,將其他型別 wrap 成 SeataFeignContext
public class SeataContextBeanPostProcessor implements BeanPostProcessor {

 @Override
 public Object postProcessBeforeInitialization(Object bean,String beanName){
  if (bean instanceof FeignContext && !(bean instanceof SeataFeignContext)) {
   return new SeataFeignContext(getSeataFeignObjectWrapper(),(FeignContext) bean);
  }
  return bean;
 }
}

自定義 註解查詢擴充套件

net.dreamlu.mica.redisson.stream.RStreamListenerDetector 查詢自定義 @RStreamListener 實現 基於 Redisson 的 pub/sub

public class RStreamListenerDetector implements BeanPostProcessor {

 @Override
 public Object postProcessAfterInitialization(Object bean,String beanName) throws BeansException {
  Class<?> userClass = ClassUtils.getUserClass(bean);
  ReflectionUtils.doWithMethods(userClass,method -> {
   RStreamListener listener = AnnotationUtils.findAnnotation(method,RStreamListener.class);
   .... do something
  },ReflectionUtils.USER_DECLARED_METHODS);
  return bean;
 }
}

PostConstruct

JavaEE5 引入了@PostConstruct 作用於 Servlet 生命週期的註解,實現 Bean 初始化之前的自定義操作。

  • 只能有一個非靜態方法使用此註解
  • 被註解的方法不能有返回值和方法引數
  • 被註解的方法不得丟擲異常

這裡需要注意的 這個註解不是 Spring 定義的,而是屬於 JavaEE JSR-250 規範定義的註解,當你在使用 Java11 的時候要手動引入相關 jar(因為 Java11 移除了)

<dependency>
 <groupId>javax.annotation</groupId>
 <artifactId>javax.annotation-api</artifactId>
</dependency>

使用場景: 在之前的版本,我們可以在啟動後通過 @PostConstruct 註解的方法執行初始化資料。但由於 Java 高版本已經移除相關 API ,我們不推薦使用此 註解,可以通過 Spring 相關 Event 回撥事件處理

@PostConstruct 註解的方法在專案啟動的時候執行這個方法,也可以理解為在 spring 容器啟動的時候執行,可作為一些資料的常規化載入,比如資料字典之類的。

InitializingBean

如何寫好一個Spring元件的實現步驟

InitializingBean 介面方法會在 容器初始化(getter/setter/構造器)完成 bean 的屬性注入後執行。

應用場景: 動態修改容器注入的 Bean 引數

正常使用者配置引數注入到 bean

security:
 oauth2:
  ignore-urls:
  - '/ws/**'

@ConfigurationProperties(prefix = "security.oauth2")
public class PermitAllUrlProperties {
 @Getter
 @Setter
 private List<String> ignoreUrls = new ArrayList<>();
}

我們發現此時使用者配置並不完整,還有一些通用不需要使用者維護,可通過實現 InitializingBean 介面回撥擴充套件

@ConfigurationProperties(prefix = "security.oauth2.ignore")
public class PermitAllUrlProperties implements InitializingBean {

 @Getter
 @Setter
 private List<String> urls = new ArrayList<>();

 @Override
 public void afterPropertiesSet() {
  urls.add("/common/*");
 }
}

initMethod

上文 @PostConstruct 已經不推薦大家使用,可以使用 Bean(initMethod = 'initMehotd') 替代,相關的限制如上。

@Bean(initMethod = "initMethod")
public DemoBean demoBean() {
 return new DemoBean();
}

public void initMethod() {
 log.info("--> custom initMehotd");
}

總結

參考

https://docs.spring.io/spring/docs/5.2.6.RELEASE/spring-framework-reference/core.html#beans-factory-nature
mica : https://github.com/lets-mica/mica

pig: https://github.com/lltx/pig

到此這篇關於如何寫好一個Spring元件的實現步驟的文章就介紹到這了,更多相關Spring 元件內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!