本文的主要講解是關於spring裏面的ioc發展以及對於ioc的理解
學習任何知識點都不應該是直接去扣它的細節,而是應該先去從脈絡上去熟悉相關知識點。
什麼是ioc?
從spring容器的創建到銷燬,每個bean都會有自己獨立的生命週期。而ioc容器主要是用於配置,定位,實力化這些個bean的相關信息。個人的認識裏面,ioc更多的是一種思想,原先我們創建一個bean的時候,都需要手動的去對這個bean進行初始化,然後各種內部依賴的bean都需要額外去注入,這就會導致每個對象內部引入的對象信息都需要手動去完善和維護,代碼耦合性也較高。而spring的ioc容器裏面,將這部分的工作全部都封裝在了其框架的內部,從而使得原先的對象引入從主動引入變爲了被動引入,更加透明化,降低耦合程度。
ioc改變了什麼?
主動式注入對象,引入某些對象字段的時候我們並不需要去關心這個對象是如何實現的,有些類似於響應式編程的味道。
主動地完善了依賴信息的注入,更加地降低了代碼的耦合程度等。
在理解ioc容器的基本知識之後,推薦還可以去閱讀下國外大牛對於ioc容器的理解博文,下邊是對應的鏈接地址:
https://martinfowler.com/articles/injection.html
當然martinfowler在提出ioc和di的理解之前,已經有了spring容器的出現。
除了spring之外,還有哪些依賴注入的容器?
在martin大神的文章中還有介紹來同類型的這些容器注入管理框架,其中舉例說明了spring容器,picocontainer框架等。
相關連的github地址如下所示:
https://github.com/picocontainer/picocontainer
Ioc和DI怎麼理解?
下邊我列舉了一段維基百科上關於ioc的知識介紹:
In object-oriented programming, there are several basic techniques to implement inversion of control. These are:
Using a service locator pattern
Using dependency injection, for example
Constructor injection
Parameter injection
Setter injection
Interface injection
Using a contextualized lookup
Using template method design pattern
Using strategy design pattern
In an original article by Martin Fowler,[9] the first three different techniques are discussed. In a description about inversion of control types,[10] the last one is mentioned. Often the contextualized lookup will be accomplished using a service locator
ioc的實現策略有哪些?
在這段描述裏面,我簡單介紹下這幾種實現ioc的策略分別是哪些:
第一種是服務定位模式
這種方式主要是javaee裏定位具體bean的一種技術手段,例如說java內部的jndi技術,關於jndi方面的技術可以去自行閱讀下以下鏈接內容:
https://www.cnblogs.com/zengda/p/4767036.html
第二類是依賴注入 DI
這類型方面的主要實現可以參考以下內容:
setter注入
構造器注入
靜態工廠
動態工廠
這部分我整理了相應的代碼來進行總結
1.什麼是setter注入?
首先是bean的xml模塊內容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 這裏面對於user對象需要設置無參數構造函數 -->
<bean class="com.think.spring.bean.User" id="user" />
<bean class="com.think.spring.dao.UserDaoImpl" id="userDao"></bean>
<bean class="com.think.spring.service.UserServiceImpl" id="userService">
<property name="userDao" ref="userDao"></property>
</bean>
</beans>
然後是對應的容器調用部分:
package com.think.spring;
import com.think.spring.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @Author idea
* @Date created in 2:36 下午 2020/4/19
*/
public class ApplicationTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
System.out.println(userService.findOne());
}
}
接下來是對應服務的實現具體細節方面,它們分別是:
package com.think.spring.service;
import com.think.spring.bean.User;
import com.think.spring.dao.UserDao;
/**
* @Author idea
* @Date created in 3:35 下午 2020/4/19
*/
public class UserServiceImpl implements UserService{
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public User findOne() {
return userDao.findOne();
}
}
package com.think.spring.service;
import com.think.spring.bean.User;
/**
* @Author idea
* @Date created in 3:35 下午 2020/4/19
*/
public interface UserService {
/**
* find one obj
* @return
*/
User findOne();
}
package com.think.spring.dao;
import com.think.spring.bean.User;
/**
* @Author idea
* @Date created in 3:36 下午 2020/4/19
*/
public interface UserDao {
/**
* findOne
*
* @return
*/
User findOne();
}
package com.think.spring.dao;
import com.think.spring.bean.User;
/**
* @Author idea
* @Date created in 3:37 下午 2020/4/19
*/
public class UserDaoImpl implements UserDao {
@Override
public User findOne() {
System.out.println("this is findOne");
return new User(1,"idea");
}
}
package com.think.spring.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 創建用於測試的user對象
*
* @Author idea
* @Date created in 2:38 下午 2020/4/19
*/
@Data
public class User implements Serializable {
private static final long serialVersionUID = -6841693635661644101L;
private Integer id;
private String username;
public User(Integer id, String username) {
this.id = id;
this.username = username;
System.out.println("this is init...");
}
public User() {
System.out.println("this is no args init...");
}
}
通過這個簡單的案例我們可以很好的理解來什麼是構造器注入,那麼基於setter注入的方式又是如何的呢?
2.構造器注入
其實所謂的構造器注入主要是差別是在xml的配置上邊:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 這裏面對於user對象需要設置無參數構造函數 -->
<bean class="com.think.spring.bean.User" id="user" />
<bean class="com.think.spring.dao.UserDaoImpl" id="userDao"></bean>
<bean class="com.think.spring.service.UserServiceImpl" id="userService">
<!-- <property name="userDao" ref="userDao"></property>-->
<constructor-arg index="0" type="com.think.spring.dao.UserDao" ref="userDao"> </constructor-arg>
</bean>
</beans>
然後對應對實現類對構造函數也有部分細節變化
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
當然在我們對dao做屬性注入對時候,可以設置對應對type或者index來區分構造函數裏對參數信息。
3.參數注入–靜態工廠
關於參數注入這塊我對於其的理解更多是偏向於靜態工廠的方式來初始化bean。
舉個例子來說明:
package com.think.spring.dao;
import com.think.spring.bean.User;
import java.util.HashMap;
import java.util.Map;
/**
* @Author idea
* @Date created in 4:01 下午 2020/4/19
*/
public class UserFactory {
static Map<String, User> userMap = new HashMap<>(3);
static {
userMap.put("1",new User(1,"user_01"));
userMap.put("2",new User(2,"user_02"));
userMap.put("3",new User(3,"user_03"));
}
public static User getUserFromMap(String id){
return userMap.get(id);
}
public static User getDynamicUser(int id){
return new User(id,"user_0"+id);
}
}
這裏有一個工廠專門用於生成user這個bean,然後我們希望通過spring容器能夠指定生成對應的bean對象,這個時候可以考慮使用factory-method這個屬性,並且在xml裏面做些修改調整:(注意 靜態工廠在spring容器裏可以不做初始化操作,因此如果xml裏面沒有配置靜態工廠的bean相關屬性也是可以的)
<bean id="staticFactoryUser01" class="com.think.spring.dao.UserFactory" factory-method="getUserFromMap">
<constructor-arg value="1"></constructor-arg>
</bean>
<bean id="staticFactoryUser02" class="com.think.spring.dao.UserFactory" factory-method="getUserFromMap">
<constructor-arg value="2"></constructor-arg>
</bean>
<bean id="staticFactoryUser03" class="com.think.spring.dao.UserFactory" factory-method="getUserFromMap">
<constructor-arg value="3"></constructor-arg>
</bean>
對應的測試類如下:
public class ApplicationTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
for (int i=1;i<=3;i++){
User user = (User) applicationContext.getBean("staticFactoryUser0"+i);
System.out.println(user.toString());
}
}
}
然後我們在容器裏面取bean的時候就可以根據指定的標示來加載不同的bean對象了。
4.動態工廠
瞭解了靜態工廠之後,對於動態工廠也就比較好理解了,動態工廠在調用具體對象的時候,需要先創建對象本身,然後再進行進一步的調用,代碼案例如下所示:
public User getDynamicUser(int id){
System.out.println("需要先創建對象本身,然後再來調用對象信息");
return new User(id,"user_0"+id);
}
xml案例如下:
<bean id="userFactory" class="com.think.spring.dao.UserFactory" ></bean>
<bean id="dyFactoryUser" class="com.think.spring.dao.UserFactory" factory-bean="userFactory" factory-method="getDynamicUser">
<constructor-arg value="1"></constructor-arg>
</bean>
動態工廠和靜態工廠是兩個長相相似的工廠類,但是其存在的目的卻有所不同,靜態工廠在調用對應方法的時候不需要實例化對應的對象,在高併發場景中比較能節省內存消耗,而對於動態工廠而言,調用對應的函數時候需要先對該對象作出具體的實現,比較消耗內存空間。
上邊說到的這四類是目前主流的di實現的策略方式,但是並不是說ioc就只有這些實現策略類。下邊我們繼續來深入挖掘ioc還有哪些實現策略。
java內部有個叫做beancontext的上下文,專門用於管理javabean,不過個人功力有限,目前對這塊領域的知識還不是太熟悉。
模版模式的實現,例如說傳統的jdbctemplate這塊的實現,內部使用了callback的機制,也是有涉及部分關於ioc的設計靈感。