背景

先說一說什麼叫把物件交給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牛的地方是它的理念,它沒有做什麼事情,只是把能替程式設計師做的事情都做了,給程式設計師開發帶來了春天。把開發變成了藝術。