把對象交給spring管理的3種方法及經典應用

背景

先說一說什麼叫把對象交給spring管理。它區別於把類交給spring管理。在spring里採用註解方式@Service、@Component這些,實際上管理的是類,把這些類交給spring來負責實例化。

 

而對象交給spring管理,舉個例子,最常見的在配置文件里定義一個bean,或者JavaConfig的方式就是在@Configure標籤標註的類里的@Bean對象。這些Bean已經new出來了。是以對象實例的方式交給spring管理的。這些對象往往是與業務無關的基礎組件。比如datasource的bean、redis連接池的bean。個數是有限的。

 

特別是面試的時候千萬要注意他們的區別。有時候有的人覺得自己回答沒問題,最後面試沒通過,很可能是因為沒有弄清楚面試的考察點。面試官的邏輯是如果你沒弄清楚問題就回答,面試官很可能會懷疑工作中接到任務也會沒有弄清楚就直接開干,做出些負產出來。建議面試的時候不但回答問題,同時也可以說對象交給spring管理和類交給spring管理有點類似,但是……這樣清楚的表達自己的思考。

 

方法一:XML配置Bean

這個方法非常常見,舉個例子:

<bean id="car" class="com.lm.spring.bean.Car">

這種定義方法相當於Car car = new Car() 然後 註冊car到spring容器。用的時候直接用。

既然bean是被實例化後的對象,就涉及到對象實例化的時候要傳參數的問題。

<bean id="car" class="com.lm.spring.bean.Car">     <constructor-arg value="Baoma"></constructor-arg>     <constructor-arg value="Red"></constructor-arg>     <constructor-arg value="400000"></constructor-arg></bean>

這是構造器傳參方式,相當於

Car car = new Car(“Baoma”,”Red”,”400000″)。當然構造器傳參還支援指定參數的index或者name啥的。其中value代表是基本數據類型,也可以傳對象,對象用ref=,代表傳的是另外一個bean.

經典的配置場景是資料庫配置:先配置dataSource,再配置需要引用dataSource的sessionFactory。

<!-- 數據源配置 --> <bean id="ds" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">      <!-- 基本屬性 url、user、password -->      <property name="url" value="#{props.url}" />      <property name="username" value="#{props.user}" />      <property name="password" value="#{props.password}" />      <!-- 配置初始化大小、最小、最大 -->      <property name="initialSize" value="#{props.initialPoolSize}" />      <property name="minIdle" value="#{props.minPoolSize}" />      <property name="maxActive" value="#{props.maxPoolSize}" />      <!-- 配置獲取連接等待超時的時間 -->      <property name="maxWait" value="#{props.maxIdleTime}" />      <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 -->      <property name="timeBetweenEvictionRunsMillis" value="#{props.timeBetweenEvictionRunsMillis}" />      <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->      <property name="minEvictableIdleTimeMillis" value="#{props.minEvictableIdleTimeMillis}" />      <!-- 打開PSCache,並且指定每個連接上PSCache的大小 -->      <property name="poolPreparedStatements" value="true" />      <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />  </bean>
 <!-- sessionFactory  --> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">      <property name="dataSource" ref="ds"></property>      <!-- <property name="hibernateProperties"></property> -->      <property name="configLocation" value="classpath:com/chinasofti/etc/conf/hibernate.cfg.xml"></property> </bean>

 

這些都是常規用法。前段時間見到了另外一種用法,雖然沒啥技術難度。確實之前沒有這麼用過,也覺得很有意思:

<bean id="car" class="java.util.ArrayList">    <property name="cars">      <list>        <ref bean="car1"></ref>        <ref bean="car2"></ref>        <ref bean="car3"></ref>      </list>    </property>    </bean>

這裡用property方法傳參相當於調用set方法傳參。當然,這裡除了可以定義ArrayList對象,還可以定義HashMap、HashSet、Array。

我看到這個恍然大悟,原來自己寫的一些方法都不地道。比如機房資訊,一個公司可能有七八個機房,什麼大興機房、永豐機房啥的。這個內容很少,放在資料庫里沒啥必要,之前項目就直接在程式里用枚舉定義出來,然後放到map里的。如果加了個機房,就改改程式碼上線唄。

其實更合適的方法,應該是在配置文件里定義一個class=”java.util.map”的bean。然後把內容都放到配置文件里。這些每次添加機房,只改配置文件,不改程式碼,更合理一些。

雖然實際來講哈,現在都走devops工具發布了,成本工作量是一樣的。但是這樣寫,能明確修改的是配置,不是程式碼邏輯。影響範圍會更明確些。

 

方法二:@Bean

這個方法是spring boot流行後的常用方法。本質和XML配置方法相同。所有用XML配置文件的方法都可以用這個方法改寫。

@Configuration //此處為配置項public class ServiceConfig {    @Bean //此處返回的是一個Spring的配置Bean,與xml的<bean>等價    public IMessageService getMessageService() {//方法名稱隨便寫        return new MessgeServiceImpl();    }}

很多基礎組件的使用文檔提供的是XML配置形式的,看到過一些剛畢業的同學在抓耳撓腮。因為項目用的spring boot。如果了解他們的本質就會知道可以直接自己這樣寫。

 

方法三:BeanFacoty registerSingleton

先上程式碼,定義一個普通Bean。

@Data
public class User {
private Integer id;
private String name;
private String password;
private Integer age;
}

定義一個被spring可以掃描的類,這個類要實現

BeanFactoryPostProcessor。裡面調用registerSingleton註冊一個對象。


@Component
public class MyBeanFacoty implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
User user1 = new User();
user1.setId(1);
user1.setName("賈元春");
user1.setAge(27);
configurableListableBeanFactory.registerSingleton("user", user1);
}

}

之後就可以隨時進行依賴了。

@RestController
public class JacksonController {
@Resource
private User user;
@GetMapping("/writeStringAsString")
public String writeStringAsString(String toWrite) throws Exception {
System.out.println(user.getAge());
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(toWrite);
}

這時候大家是否會有個疑問,XML配置Bean是傳統的spring mvc里常用的將對象交給spring來管理的方法,@Bean是spring boot里將對象交給spring來管理的方法。那為什麼還要有這個先實現BeanFactoryPostProcessor的方法呢?

因為這種方法可以用來做這件事情,但是不僅僅可以做這件事情。它神通廣大,不僅可以將一個對象交給spring管理,還可以將已經交給spring管理的對象拿出來進行修改,還有其他各種的spring初始化的干預都可以做。所以用它來僅僅註冊一個Bean有點殺雞用牛刀的味道。

 

總結思考

之前也寫過一些spring的文章,如:

SpringBoot啟動原理

學習Spring的思考框架

專治不會看源碼的毛病–spring源碼解析AOP篇(2017版)

SpringBoot優雅退出

你看不懂的spring原理是因為不知道這幾個概念

Spring參數的自解析–還在自己轉換?你out了!

這些文章主要圍繞的核心就是spring framework的原理和spring看似基礎的應用技巧。

這是我的一個理念。學spring和學廚師很像。基礎就是刀工、材料和火候。掌握了基礎,通過自己的悟性就可以千變萬化了。悟性不是靠教的,有用的還是基礎。

spring有很多的擴展內容,spring boot、spring cloud、spring batch。之所以有這些就是因為它做了兩件事:第一是控制反轉,使用Bean的時候隨時取用;第二是aop,把那些可以重用的程式碼寫一份想用的地方都可以生效。

剛畢業的時候,有大師教我們說控制反轉和依賴注入是一回事,後來我想想其實不是。控制反轉是把對象和類交給spring來管理,由spring來控制器生命周期的過程。依賴注入是在使用對象的時候,可以通過set、構造器和註解方式獲取對象的過程。一個是存錢的過程,一個是取錢的過程。聯繫是存錢是為取錢服務的。

而spring的一些高級框架就是將更多的東西交給spring來管理的過程,比如spring-jdbc,就是通過spring容器就可以調用jdbc了。我個人認為原理了解了,學的價值不大。

 

spring作者是學音樂出身的。spring牛的地方是它的理念,它沒有做什麼事情,只是把能替程式設計師做的事情都做了,給程式設計師開發帶來了春天。把開發變成了藝術。