把對象交給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配置文件的方法都可以用這個方法改寫。
public class ServiceConfig {
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牛的地方是它的理念,它沒有做什麼事情,只是把能替程式設計師做的事情都做了,給程式設計師開發帶來了春天。把開發變成了藝術。