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