1. 程式人生 > >解決spring中不同配置檔案中存在name或者id相同的bean可能引起的問題

解決spring中不同配置檔案中存在name或者id相同的bean可能引起的問題

spring對同一配置檔案中相同id或者name的兩個或以上的bean時,做直接拋異常的處理,而對不同配置檔案中相同id或者名稱的bean,只會在列印日誌級別為info的資訊,資訊內容大概為"Overriding bean definition for bean xxx : replacing xxx with beanDefinition ".
可能引發的問題:
當不同檔案中配置了相同id或者name的同一型別的兩個bean時,如果這兩個bean的型別雖然相同,但配置時又有差別時,如:

<bean name="a" class="com.zyr.A">
 <property name="age" value="20" />
</bean>
<bean name="a" class="com.zyr.A">
 <property name="age" value="20" />
</bean>



那麼最終spring容器只會例項化後面的這個bean,後者將前者覆蓋了。這種情況下,要排查問題很困難。


那麼如何解決這個問題呢?靠程式設計師自律?絕對不定義重複名稱的bean?我覺得這個是非常不靠譜的,因為專案依賴可能比較複雜,開發人員不盡相同.所以我認為只有通過在程式中引入一種報錯機制才能解決這個問題。


上次在除錯spring原始碼時,無意中發現DefaultListableBeanFactory類有一個allowBeanDefinitionOverriding屬性,其預設值為true.如:
/** Whether to allow re-registration of a different definition with the same name */
private boolean allowBeanDefinitionOverriding = true;


//allowBeanDefinitionOverriding屬性在下面程式碼中起作用:
synchronized (this.beanDefinitionMap) {
 Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
 if (oldBeanDefinition != null) {
  if (!this.allowBeanDefinitionOverriding) {
   throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
     "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
     "': There is already [" + oldBeanDefinition + "] bound.");
  }
  else {
   if (this.logger.isInfoEnabled()) {
    this.logger.info("Overriding bean definition for bean '" + beanName +
      "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
   }
  }
 }
 else {
  this.beanDefinitionNames.add(beanName);
  this.frozenBeanDefinitionNames = null;
 }
 this.beanDefinitionMap.put(beanName, beanDefinition);
}




想到只要將其值更改為false時就可能可以解決上面的問題,即存在id或者name相同的bean時,不是打印出相關資訊,而是直接拋異常,這樣就可以迫使開發人員必須解決id或者name重複的問題後才能成功啟動容器。然後就嘗試了下,
最終找到了兩個解決方案:
方案1:
1.自己寫一個繼承ContextLoaderListener的listener,比如SpringContextLoaderListener,然後重寫方法customizeContext,如:
public class SpringContextLoaderListener extends ContextLoaderListener {
 
 @Override
 protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
  super.customizeContext(servletContext, applicationContext);
  
  XmlWebApplicationContext context = (XmlWebApplicationContext) applicationContext;
  context.setAllowBeanDefinitionOverriding(false); //在這裡將XmlWebApplicationContext屬性allowBeanDefinitionOverriding設定為false,這個屬性的值最終
  //會傳遞給DefaultListableBeanFactory類的allowBeanDefinitionOverriding屬性
 }
}


2.在web.xml使用自定義的listener,配置如下:
<listener>
      <listener-class>com.zyr.web.spring.SpringContextLoaderListener</listener-class>
</listener>


這樣,在專案啟動時,不同配置檔案中如果有同名id或者name的bean,直接拋異常,容器停止啟動.


但後來一想,這個方案不夠好,感覺有點投機取巧。然後再想了下,想到spring既然提供了allowBeanDefinitionOverriding這個屬性,理論上應該會提供方法讓使用者用配置來更改其預設值的,確實如此,最終就有了方案2 .
方案2:
在org.springframework.web.context.ContextLoader類中找到了CONTEXT_INITIALIZER_CLASSES_PARAM常量,該常量可用於配置spring上下文相關全域性特性,該常量在如下程式碼中起作用:
protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
  determineContextInitializerClasses(ServletContext servletContext) {


 List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
   new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();


   .........


 String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
 if (localClassNames != null) {
  for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
   classes.add(loadInitializerClass(className));
  }
 }


 return classes;
}


所以我們要做的有2步:
1.建立一個實現介面ApplicationContextInitializer的類,如SpringApplicationContextInitializer,程式碼如下:
public class SpringApplicationContextInitializer implements ApplicationContextInitializer<XmlWebApplicationContext> {


 public void initialize(XmlWebApplicationContext applicationContext) {
  applicationContext.setAllowBeanDefinitionOverriding(false);//在這裡將XmlWebApplicationContext屬性allowBeanDefinitionOverriding設定為false,這個屬
  //性的值最終會傳遞給DefaultListableBeanFactory類的allowBeanDefinitionOverriding屬性
 }
}


2.web.xml中的增加的配置如下:
 
<context-param>
        <param-name>contextInitializerClasses</param-name>
        <param-value>com.zyr.web.spring.SpringApplicationContextInitializer</param-value>
 </context-param>


但到這裡,如果專案在前端使用的是spring mvc時,問題還只解決了一半,即spring根容器(相對使用spring mvc來說)如果存在同名id或者name時,容器報錯停止啟動.而spring mvc 的容器是作為子容器來初始化的,所以上述方案2只解決了spring根容器同名id或者name的問題,並沒有解決spring mvc子容器的同名id或者name問題。
經檢視原始碼,最終有了方案3.
方案3:
在web.xml檔案中的dispatcherServlet配置中增加如下部分配置
<servlet>
 <servlet-name>dispatcherServlet</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 <init-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:spring/spring-mvc.xml</param-value>
 </init-param>
 <!-- 增加如下配置 -->
 <init-param> 
  <param-name>contextInitializerClasses</param-name>
  <param-value>com.zyr.web.spring.SpringApplicationContextInitializer</param-value> <!--這個類與方案2中的是一個類 -->
 </init-param>
 <load-on-startup>1</load-on-startup>
</servlet>


寫了個簡單測試專案,放在github上:https://github.com/zgmzyr/sameIdOrNameForBeanInSpringTest.git 


引申開來:上述解決方案雖然說是為了解決重名id或者name問題而得出的,但完全可用於以後我們如果要改變spring容器的其它全域性行為時,也可以用方案2解決,只不過最終改變屬性值不同而已。

相關推薦

解決spring不同配置檔案存在name或者id相同bean可能引起的問題

spring對同一配置檔案中相同id或者name的兩個或以上的bean時,做直接拋異常的處理,而對不同配置檔案中相同id或者名稱的bean,只會在列印日誌級別為info的資訊,資訊內容大概為"Overriding bean definition for bean xxx :

Spring Boot YML配置檔案使用MAVEN變數@[email protected]

在application.properties字尾的配置檔案中我們如下使用MAVEN變數: [email protected]@ @執行時符號會自替換成MAVEN變數配置好的值,但是換成YML配置檔案不行了,會報錯,如下配置即可解決: eureka:   cl

log4j 日誌檔案儲存資料庫的解決方案一(配置檔案寫sql語句)

log4jmysql.properties: log4j.rootLogger=ERROR,console,db log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.

spring提供讀取配置檔案的屬性註解@Value

有時,當我們需要把專案中的properties檔案的屬性可以在專案中得到,spring就提供了一個註解@Value來讀取(前提是屬性檔案properties需要首先讓spring管理,即spring配置檔案中包含一下) @Value("${屬性名}")          /

spring boot在配置檔案設定虛擬路徑

由於spring Boot是內建tomcat的,所以無法用之前的設定虛擬路徑的方式進行設定,spring Boot提供了方便的配置方案,只需要在配置檔案中配置就可以. 具體程式碼: w

記一次大坑:SpringBoot+Mybatis專案配置檔案的修改了SQL語句後不生效

問題:原是SSM框架專案,轉移到SpringBoot+Mybatis,使用的是C3P0連線資料庫。轉移到SpringBoot後的專案,我修改了xml配置檔案中的查詢sql語句,也就是增加了一個查詢欄位,無論是在前端頁面測試,還是使用單元測試時候,我修改後的SQL就是不生效,查

專案關於配置檔案密碼的加密處理

專案中關於配置檔案中密碼的加密處理 轉載地址:http://supanccy2013.iteye.com/blog/2101964     在專案中,為了專案的配置靈活,長把一些常量寫在配置檔案中,這時涉及一個問題,就是如果配置欄位是密碼,就不夠安全。這個時候需要在配置檔案中

Web專案配置檔案的密碼進行加密(資料庫連線密碼)

Web專案的如配置檔案applicationContext.xml檔案中,如資料庫連線的使用者密碼資訊的加密。 我們平時直接寫的專案配置是這樣的: <bean id="dataSource" class="org.apache.commons.dbcp.B

java Web專案properties配置檔案的密碼加密

get到一項新技能(properties檔案中value替換),之前也想過properties中配置項暴露但沒有落實,今天看到部落格分享一下: 我們使用的專案經常是這個樣子的: 1 <bean id="dataSourc

tp在控制器中和在模板呼叫配置檔案的常量

config.php中定義 'FIRST_WEIGHT'=> "2", 'TITLE'=> 'XXXX', 控制器中 $first_weight = C('FIRST_WEIGHT'); 模板中 {$Think.config.TITLE}

Java讀取配置檔案的內容,並將其賦值給靜態變數的方法

應用場景 專案開發中某個功能需要抽取成方法寫成一個工具類,提供給別人使用。寫過工具類的人都知道,工具類中的方法一般都是靜態方法,可以直接使用類名點方法名呼叫, 使用很方便,比如判斷某個物件是否為空的方式Objects.equals().由於我寫的這個工具類中需要讀取配置檔案中的內容,但是常規方法注入成員變數時

springboot專案logback.xml或者logback-spring.xml讀取不到application.yml或application.properties配置檔案配置解決辦法

在springboot專案中我們可能想要實現不同環境的日誌專案配置不同,比如我想讓不同環境的日誌路徑不同。 這時候我們很容易想: 1、到將日誌路徑配置在springboot的:application-dev.yml,application-prod.yml,applica

Spring配置檔案屬性值不能提示的解決辦法(eclipse新增xsd檔案)

原因:eclipse中沒有配置xsd檔案解決辦法:步驟一:把標頭檔案拷貝到你的spring配置檔案中。<?xml version="1.0" encoding="UTF-8"?> <b

Spring配置檔案如何使用外部配置檔案配置資料庫連線

版權宣告:本文為博主原創文章,歡迎指正或者轉載。 https://blog.csdn.net/qq_38663729/article/details/78821258 直接在spring的配置檔案中applicationContext.xml檔案中配置資料庫連線也可以,但是有個問題,需要在url

Spring配置檔案配置資料庫連線(mysql,sqlserver,oracle)

xml配置檔案中配置如下: <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName"

讓Eclipsespring的xml配置檔案出現屬性和類提示

在spring配置檔案中可以讓配置bean的時候出現提示,這裡需要做一些設定。設定包括安裝springide外掛,spring-beans-version.xsd檔案引入,增加xml編輯提示的字元,預設只有=>:。最後是讓配置檔案可以通過Spring Config Editor的方式開

Spring 配置檔案 Bean 的 property 屬性使用示例

在 Spring 配置檔案中,beans 元素是 spring-beans 內容模型的根結點,bean 元素是 beans 元素的子節點,通常 bean 元素用於定義 JavaBean。而 bean 元素包含以下幾種子元素,它們分別是: constructor-arg 元素property 元素

Spring配置檔案配置property標籤的name和ref的區別:

在看李剛的《Java EE企業實戰》,裡面有一個關於Spring的配置的 <bean id=“person” class=“service.Person”> <!-- 控制器呼叫setAxe方法,將容器中的axe bean作為傳入的引數 --> <!–此處的na

JAVA spring 把全域性變數寫到配置檔案

把一些全域性的引數配置到配置檔案裡面,把全域性屬性注入到類裡面,由程式程式碼直接引用. 普通引入properties方法(只介紹) 在spring的配置檔案applicationContext.xml配置 <bean id= "propertyConfigure

Spring Boot注入配置檔案application.properties的list 物件引數

例如要注入下列引數: dyn.spring.datasources[0].name=branchtadyn.spring.datasources[0].driverClassName=oracle.jdbc.OracleDriverdyn.spring.datasources[0].url=jdbc:ora