概述
IoC(Inversion of Controller),即控制反轉。它是一種設計思想,簡單說就是建立Java物件的過程從之前new出來,變成了由Spring工廠建立出來(由Spring來負責控制物件的生命週期和物件之間的關係)。控制反轉,轉移的就是建立物件的主動權。
tiny-spring實現了基本的 IoC 容器,支援singleton型別的bean,包括初始化、屬性注入、以及依賴 Bean 注入,可從 XML 中讀取配置。
第一步-最基本的容器
IoC最基本的角色有兩個:容器(BeanFactory
)和Bean
本身。這裡使用BeanDefinition
來封裝了bean物件,這樣可以儲存一些額外的元資訊
BeanDefinition類
定義BeanDefinition類來封裝一個spring bean物件
/**
* 核心bean的定義,其物件被註冊到BeanFactory中
*/
public class BeanDefinition {
private Object bean;
public BeanDefinition(Object bean) {
this.bean = bean;
}
public Object getBean() {
return bean;
}
}
複製程式碼
BeanFactory類
定義一個Bean工廠類,即容器,用一個Map實現Bean的註冊和獲取。序號產生器制很簡單,key為bean名稱,value為BeanDefinition類物件。
/**
* bean的工廠類,用於儲存註冊的bean
*/
public class BeanFactory {
/**
* 存放Factory裡的所有Bean的詳細資訊,可以看做是Bean的登錄檔
*/
private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
public Object getBean(String name) {
return beanDefinitionMap.get(name).getBean();
}
/**
* 將新加的BeanDefinition物件加到登錄檔中
* @param name 登錄檔Map的key
* @param beanDefinition BeanDefinition物件,儲存了Bean的詳細資訊
*/
public void registerBeanDefinition(String name, BeanDefinition beanDefinition) {
beanDefinitionMap.put(name, beanDefinition);
}
}
複製程式碼
測試
用於測試的bean物件,其只擁有一個方法,沒有成員變數
/**
* 用於測試的bean
*/
public class HelloWorldService {
public void helloWorld(){
System.out.println("Hello World!");
}
}
複製程式碼
bean註冊與獲取實現的測試類
public class BeanFactoryTest {
@Test
public void test() {
// 1.初始化beanfactory
BeanFactory beanFactory = new BeanFactory();
// 2.注入bean
BeanDefinition beanDefinition = new BeanDefinition(new HelloWorldService());
beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);
// 3.獲取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();
}
}
------------output------------
Hello world!
複製程式碼
第二步-將bean建立放入工廠
step1中的bean是初始化好之後再作為BeanDefinition
的入參。實際使用中,我們希望容器來管理bean的建立。於是我們將bean的初始化放入BeanFactory
中。為了保證擴充套件性,我們使用介面提取的方法,將BeanFactory
替換成介面,而使用AbstractBeanFactory
和AutowireCapableBeanFactory
作為其實現。"AutowireCapable"的意思是“可自動裝配的”,為我們後面注入屬性做準備。
BeanDefinition類
為BeanDefinition
類增加一個Class類成員變數用來記錄當前bean的Class型別,以及記錄bean的類全限定名稱。
public class BeanDefinition {
/**
* bean物件
*/
private Object bean;
/**
* 記錄bean物件的Class資訊
*/
private Class beanClass;
/**
* 記錄bean物件的ClassName
*/
private String beanClassName;
/**
* 通過bean物件的className載入該物件,並初始化
* @param beanClassName 類名
*/
public void setBeanClassName(String beanClassName) {
this.beanClassName = beanClassName;
try {
this.beanClass = Class.forName(beanClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// 省略其他的getter和setter方法
}
複製程式碼
BeanFactory容器介面
public interface BeanFactory {
Object getBean(String name);
void registerBeanDefinition(String name, BeanDefinition beanDefinition);
}
複製程式碼
實現容器的虛擬類
public abstract class AbstractBeanFactory implements BeanFactory {
/**
* 實際儲存bean的容器
*/
private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
@Override
public Object getBean(String name) {
return beanDefinitionMap.get(name).getBean();
}
@Override
public void registerBeanDefinition(String name, BeanDefinition beanDefinition) {
Object bean = doCreateBean(beanDefinition);
beanDefinition.setBean(bean);
beanDefinitionMap.put(name, beanDefinition);
}
/**
* 初始化bean,實際建立方式交給子類
* @param beanDefinition
* @return
*/
protected abstract Object doCreateBean(BeanDefinition beanDefinition);
}
複製程式碼
可自動裝配的實現類
public class AutowireCapableBeanFactory extends AbstractBeanFactory {
/**
* 根據類限定名稱建立類例項
* @param beanDefinition
* @return
*/
@Override
protected Object doCreateBean(BeanDefinition beanDefinition) {
try {
Object bean = beanDefinition.getBeanClass().newInstance();
return bean;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
複製程式碼
測試
public class BeanFactoryTest {
@Test
public void test() {
// 1.初始化beanfactory
BeanFactory beanFactory = new AutowireCapableBeanFactory();
// 2.注入bean
BeanDefinition beanDefinition = new BeanDefinition();
String className = new HelloWorldService().getClass().getName();
beanDefinition.setBeanClassName(className);
beanFactory.registerBeanDefinition(className, beanDefinition);
// 3.獲取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean(className);
helloWorldService.helloWorld();
}
}
------------------output-----------------
Hello World!
複製程式碼
第三步-為bean注入屬性
這一步,我們想要為bean注入屬性。我們選擇將屬性注入資訊儲存成PropertyValue
物件,並且儲存到BeanDefinition
中。這樣在初始化bean的時候,我們就可以根據ropertyValue
來進行bean屬性的注入。Spring本身使用了setter來進行注入,這裡為了程式碼簡潔,我們使用Field的形式來注入。
PropertyValue類,用於單個屬性注入
public class PropertyValue {
// 屬性
private final String name;
// 屬性值
private final Object value;
public PropertyValue(String name, Object value) {
this.name = name;
this.value = value;
}
// 省略getter和setter方法
}
複製程式碼
PropertyValues類,包裝一個物件所有的PropertyValue
public class PropertyValues {
private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
public PropertyValues() {
}
public void addPropertyValue(PropertyValue pv) {
//TODO:這裡可以對於重複propertyName進行判斷,直接用list沒法做到
this.propertyValueList.add(pv);
}
public List<PropertyValue> getPropertyValues() {
return this.propertyValueList;
}
}
複製程式碼
為BeanDefinition新增一個屬性成員
public class BeanDefinition {
private Object bean;
private Class beanClass;
private String beanClassName;
/**
* 儲存bean的屬性
*/
private PropertyValues propertyValues;
// 省略其他的getter和setter方法
}
複製程式碼
AutowireCapableBeanFactory虛擬類
增加對注入屬性的繫結
public class AutowireCapableBeanFactory extends AbstractBeanFactory {
@Override
protected Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
Object bean = createBeanInstance(beanDefinition);
applyPropertyValues(bean, beanDefinition);
return bean;
}
protected Object createBeanInstance(BeanDefinition beanDefinition) throws Exception {
return beanDefinition.getBeanClass().newInstance();
}
// 使用反射繫結屬性列表
protected void applyPropertyValues(Object bean, BeanDefinition mbd) throws Exception {
for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
declaredField.setAccessible(true);
declaredField.set(bean, propertyValue.getValue());
}
}
}
複製程式碼
測試
用於測試的HelloWorldService
不再是隻含有一個方法了,新增了一個String型別的成員變數。
public class HelloWorldService {
private String text;
public void helloWorld(){
System.out.println(text);
}
public void setText(String text) {
this.text = text;
}
}
複製程式碼
相應的測試方法實現了屬性繫結結果的驗證。
public class BeanFactoryTest {
@Test
public void test() throws Exception {
// 1.初始化beanfactory
BeanFactory beanFactory = new AutowireCapableBeanFactory();
// 2.bean定義
BeanDefinition beanDefinition = new BeanDefinition();
String className = new HelloWorldService().getClass().getName();
beanDefinition.setBeanClassName(className);
// 3.設定屬性
PropertyValues propertyValues = new PropertyValues();
propertyValues.addPropertyValue(new PropertyValue("text", "Hello World!"));
beanDefinition.setPropertyValues(propertyValues);
// 4.生成bean
beanFactory.registerBeanDefinition(className, beanDefinition);
// 5.獲取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean(className);
helloWorldService.helloWorld();
}
}
-----------output---------
Hello World!
複製程式碼
第四步-讀取xml配置來初始化bean
這麼大一坨初始化程式碼讓人心煩。這裡的BeanDefinition
只是一些配置,我們還是用xml來初始化吧。我們定義了BeanDefinitionReader
初始化bean,它有一個實現是XmlBeanDefinitionReader
。這一步其實就是從xml中讀取數然後再用反射實現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" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<bean name="helloWorldService" class="us.codecraft.tinyioc.HelloWorldService">
<property name="text" value="Hello World!"></property>
</bean>
</beans>
複製程式碼
xml檔案中包含一個名為helloWorldService
的bean以及其一個text屬性。
BeanDefinitionReader介面
這個記錄用來從配置中讀取BeanDefinition。
public interface BeanDefinitionReader {
void loadBeanDefinitions(String location) throws Exception;
}
複製程式碼
AbstractBeanDefinitionReader虛類
其實現了BeanDefinitionReader介面,但沒有真正去定義載入bean的實現方式。主要功能是配置一個BeanDefinition的容器,並定義一個載入器。
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
private Map<String,BeanDefinition> registry;
private ResourceLoader resourceLoader;
protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
this.registry = new HashMap<String, BeanDefinition>();
this.resourceLoader = resourceLoader;
}
public Map<String, BeanDefinition> getRegistry() {
return registry;
}
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
}
複製程式碼
XmlBeanDefinitionReader類
XmlBeanDefinitionReader
類繼承AbstractBeanDefinitionReader
,實現了從xml檔案中解析並繫結spring bean及其屬性。
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
super(resourceLoader);
}
@Override
public void loadBeanDefinitions(String location) throws Exception {
InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
doLoadBeanDefinitions(inputStream);
}
protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
Document doc = docBuilder.parse(inputStream);
// 解析bean
registerBeanDefinitions(doc);
inputStream.close();
}
public void registerBeanDefinitions(Document doc) {
Element root = doc.getDocumentElement();
parseBeanDefinitions(root);
}
protected void parseBeanDefinitions(Element root) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
processBeanDefinition(ele);
}
}
}
// 解析
protected void processBeanDefinition(Element ele) {
String name = ele.getAttribute("name");
String className = ele.getAttribute("class");
BeanDefinition beanDefinition = new BeanDefinition();
processProperty(ele,beanDefinition);
// 繫結bean
beanDefinition.setBeanClassName(className);
getRegistry().put(name, beanDefinition);
}
// 繫結屬性
private void processProperty(Element ele,BeanDefinition beanDefinition) {
NodeList propertyNode = ele.getElementsByTagName("property");
for (int i = 0; i < propertyNode.getLength(); i++) {
Node node = propertyNode.item(i);
if (node instanceof Element) {
Element propertyEle = (Element) node;
String name = propertyEle.getAttribute("name");
String value = propertyEle.getAttribute("value");
beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name,value));
}
}
}
}
複製程式碼
測試
public class BeanFactoryTest {
@Test
public void test() throws Exception {
// 1.讀取配置
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");
// 2.初始化BeanFactory並註冊bean
BeanFactory beanFactory = new AutowireCapableBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}
// 3.獲取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();
}
}
複製程式碼
第五步-為bean注入bean
使用xml配置之後,似乎裡我們熟知的Spring更近了一步!但是現在有一個大問題沒有解決:我們無法處理bean之間的依賴,無法將bean注入到bean中,所以它無法稱之為完整的IoC容器!如何實現呢?我們定義一個BeanReference,來表示這個屬性是對另一個bean的引用。這個在讀取xml的時候初始化,並在初始化bean的時候,進行解析和真實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" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<bean name="outputService" class="us.codecraft.tinyioc.OutputService">
<property name="helloWorldService" ref="helloWorldService"></property>
</bean>
<bean name="helloWorldService" class="us.codecraft.tinyioc.HelloWorldService">
<property name="text" value="Hello World!"></property>
<property name="outputService" ref="outputService"></property>
</bean>
</beans>
複製程式碼
可以發現相比上一步,bean對了一個引用型別的成員變數。
BeanReference類
該類表示這個屬性是對另一個spring bean的引用。
public class BeanReference {
private String name;
private Object bean;
public BeanReference(String name) {
this.name = name;
}
// 省略getter/setter方法
}
複製程式碼
BeanDefinition類
public class BeanDefinition {
private Object bean;
private Class beanClass;
private String beanClassName;
private PropertyValues propertyValues = new PropertyValues();
// 省略getter/setter方法
複製程式碼
AbstractBeanFactory類
同時為了解決迴圈依賴的問題,我們使用lazy-init的方式,將createBean的事情放到getBean的時候才執行,是不是一下子方便很多?這樣在注入bean的時候,如果該屬性對應的bean找不到,那麼就先建立!因為總是先建立後注入,所以不會存在兩個迴圈依賴的bean建立死鎖的問題。
public abstract class AbstractBeanFactory implements BeanFactory {
private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
private final List<String> beanDefinitionNames = new ArrayList<String>();
// 實現懶載入
@Override
public Object getBean(String name) throws Exception {
BeanDefinition beanDefinition = beanDefinitionMap.get(name);
if (beanDefinition == null) {
throw new IllegalArgumentException("No bean named " + name + " is defined");
}
Object bean = beanDefinition.getBean();
if (bean == null) {
bean = doCreateBean(beanDefinition);
}
return bean;
}
@Override
public void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception {
beanDefinitionMap.put(name, beanDefinition);
beanDefinitionNames.add(name);
}
public void preInstantiateSingletons() throws Exception {
for (Iterator it = this.beanDefinitionNames.iterator(); it.hasNext();) {
String beanName = (String) it.next();
getBean(beanName);
}
}
/**
* 初始化bean
*
* @param beanDefinition
* @return
*/
protected abstract Object doCreateBean(BeanDefinition beanDefinition) throws Exception;
}
複製程式碼
AutowireCapableBeanFactory類
由於引入了bean之間的依賴,所以需要當前spring bean的成員變數不是基本資料型別時,需要新增而外的處理方法。
public class AutowireCapableBeanFactory extends AbstractBeanFactory {
@Override
protected Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
Object bean = createBeanInstance(beanDefinition);
beanDefinition.setBean(bean);
applyPropertyValues(bean, beanDefinition);
return bean;
}
protected Object createBeanInstance(BeanDefinition beanDefinition) throws Exception {
return beanDefinition.getBeanClass().newInstance();
}
protected void applyPropertyValues(Object bean, BeanDefinition mbd) throws Exception {
for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
declaredField.setAccessible(true);
Object value = propertyValue.getValue();
// 處理成員變數是引用型別的情況
if (value instanceof BeanReference) {
BeanReference beanReference = (BeanReference) value;
value = getBean(beanReference.getName());
}
declaredField.set(bean, value);
}
}
複製程式碼
XmlBeanDefinitionReader類
同樣的,需要單獨處理xml檔案中property元素為引用型別的情況。
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
...
private void processProperty(Element ele, BeanDefinition beanDefinition) {
NodeList propertyNode = ele.getElementsByTagName("property");
for (int i = 0; i < propertyNode.getLength(); i++) {
Node node = propertyNode.item(i);
if (node instanceof Element) {
Element propertyEle = (Element) node;
String name = propertyEle.getAttribute("name");
String value = propertyEle.getAttribute("value");
if (value != null && value.length() > 0) {
beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
} else {
// 處理引用型別
String ref = propertyEle.getAttribute("ref");
if (ref == null || ref.length() == 0) {
throw new IllegalArgumentException("Configuration problem: <property> element for property '"
+ name + "' must specify a ref or value");
}
BeanReference beanReference = new BeanReference(ref);
beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
}
}
}
}
...
}
複製程式碼
測試
相應的需要更改測試所用的模型類
public class HelloWorldService {
private String text;
// 增加一個類成員變數
private OutputService outputService;
public void helloWorld(){
outputService.output(text);
}
// 省略setter方法
}
public class OutputService {
private HelloWorldService helloWorldService;
public void output(String text){
Assert.assertNotNull(helloWorldService);
System.out.println(text);
}
public void setHelloWorldService(HelloWorldService helloWorldService) {
this.helloWorldService = helloWorldService;
}
}
複製程式碼
測試方法:
@Test
public void testPreInstantiate() throws Exception {
// 1.讀取配置
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");
// 2.初始化BeanFactory並註冊bean
AbstractBeanFactory beanFactory = new AutowireCapableBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}
// 3.初始化bean
beanFactory.preInstantiateSingletons();
// 4.獲取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();
}
複製程式碼
第六步-ApplicationContext登場
現在BeanFactory
的功能齊全了,但是使用起來有點麻煩。於是我們引入熟悉的ApplicationContext
介面,並在AbstractApplicationContext
的refresh()
方法中進行bean的初始化工作。
ApplicationContext介面
ApplicationContext
介面繼承BeanFactory
。BeanFactory
是對內的容器,而ApplicationContext
即對外提供的介面。
public interface ApplicationContext extends BeanFactory {
}
複製程式碼
AbstractApplicationContext類
public abstract class AbstractApplicationContext implements ApplicationContext {
protected AbstractBeanFactory beanFactory;
public AbstractApplicationContext(AbstractBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void refresh() throws Exception{
}
// 實際提供bean的還是BeanFactory
@Override
public Object getBean(String name) throws Exception {
return beanFactory.getBean(name);
}
}
複製程式碼
ClassPathXmlApplicationContext類
ClassPathXmlApplicationContext
類定義refresh
方法,實現類的自動載入和初始化工作。
public class ClassPathXmlApplicationContext extends AbstractApplicationContext {
private String configLocation;
public ClassPathXmlApplicationContext(String configLocation) throws Exception {
this(configLocation, new AutowireCapableBeanFactory());
}
public ClassPathXmlApplicationContext(String configLocation, AbstractBeanFactory beanFactory) throws Exception {
super(beanFactory);
this.configLocation = configLocation;
refresh();
}
@Override
public void refresh() throws Exception {
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions(configLocation);
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}
}
}
複製程式碼
測試
public class ApplicationContextTest {
@Test
public void test() throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
helloWorldService.helloWorld();
}
}
複製程式碼
頓時感覺清爽了很多。