上一篇:設計模式實戰:自定義SpringIOC(理論分析)
自定義SpringIOC(親手實踐)
上一篇文章,我們介紹了SpringIOC容器的核心組件及其作用,下面我們來動手仿寫一個SpringIOC容器,讓我們對SpringIOC容器理解地更加透徹!Start Go Go Go!
自定義SpringIOC
對下面的配置文件進行解析,并自定義SpringIOC,對涉及到的對象進行管理。
<?xml version="1.0" encoding="UTF-8"?>
<beans><bean id="courseService" class="com.hopeful.service.impl.CourseServiceImpl"><property name="courseDao" ref="courseDao"></property></bean><bean id="courseDao" class="com.hopeful.dao.impl.CourseDaoImpl"></bean>
</beans>
1) 創建與Bean相關的pojo類
- PropertyValue類: 用于封裝 bean 的屬性,體現到上面的配置文件就是封裝 bean 標簽的子標簽 property 標簽數據。
/*** 該類用來封裝bean標簽下的property子標簽的屬性* 1.name屬性* 2.ref屬性* 3.value屬性: 給基本數據類型及string類型數據賦的值**/
public class PropertyValue {private String name;private String ref;private String value;public PropertyValue() {}public PropertyValue(String name, String ref, String value) {this.name = name;this.ref = ref;this.value = value;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getRef() {return ref;}public void setRef(String ref) {this.ref = ref;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}
}
- MutablePropertyValues類: 一個bean標簽可以有多個property子標簽,所以再定義一個MutablePropertyValues類,用來存儲并管理多個PropertyValue對象。
package com.mashibing.framework.beans;import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;/*** 該類用來存儲和遍歷多個PropertyValue對象**/
public class MutablePropertyValues implements Iterable<PropertyValue>{//定義List集合,存儲PropertyValue的容器private final List<PropertyValue> propertyValueList;//空參構造中 初始化一個listpublic MutablePropertyValues() {this.propertyValueList = new ArrayList<PropertyValue>();}//有參構造 接收一個外部傳入的list,賦值propertyValueList屬性public MutablePropertyValues(List<PropertyValue> propertyValueList) {if(propertyValueList == null){this.propertyValueList = new ArrayList<PropertyValue>();}else{this.propertyValueList = propertyValueList;}}//獲取當前容器對應的迭代器對象@Overridepublic Iterator<PropertyValue> iterator() {//直接獲取List集合中的迭代器return propertyValueList.iterator();}//獲取所有的PropertyValuepublic PropertyValue[] getPropertyValues(){//將集合轉換為數組并返回return propertyValueList.toArray(new PropertyValue[0]); //new PropertyValue[0]聲明返回的數組類型}//根據name屬性值獲取PropertyValuepublic PropertyValue getPropertyValue(String propertyName){//遍歷集合對象for (PropertyValue propertyValue : propertyValueList) {if(propertyValue.getName().equals(propertyName)){return propertyValue;}}return null;}//判斷集合是否為空,是否存儲PropertyValuepublic boolean isEmpty(){return propertyValueList.isEmpty();}//向集合中添加public MutablePropertyValues addPropertyValue(PropertyValue value){//判斷集合中存儲的propertyvalue對象.是否重復,重復就進行覆蓋for (int i = 0; i < propertyValueList.size(); i++) {//獲取集合中每一個 PropertyValuePropertyValue currentPv = propertyValueList.get(i);//判斷當前的pv的name屬性 是否與傳入的相同,如果相同就覆蓋if(currentPv.getName().equals(value.getName())){propertyValueList.set(i,value);return this;}}//沒有重復this.propertyValueList.add(value);return this; //目的是實現鏈式編程}//判斷是否有指定name屬性值的對象public boolean contains(String propertyName){return getPropertyValue(propertyName) != null;}
}
- BeanDefinition類: 用來封裝 bean 信息的,主要包含id(即 bean 對象的名稱)、class(需要交由spring管理的類的全類名)及子標簽property數據。
/*** 封裝Bean標簽數據的類,包括id與class以及子標簽的數據**/
public class BeanDefinition {private String id;private String className;private MutablePropertyValues propertyValues;public BeanDefinition() {propertyValues = new MutablePropertyValues();}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getClassName() {return className;}public void setClassName(String className) {this.className = className;}public MutablePropertyValues getPropertyValues() {return propertyValues;}public void setPropertyValues(MutablePropertyValues propertyValues) {this.propertyValues = propertyValues;}
}
2) 創建注冊表相關的類
BeanDefinition 對象存取的操作, 其實是在BeanDefinitionRegistry接口中定義的,它被稱為是BeanDefinition的注冊中心。
//源碼
public interface BeanDefinitionRegistry extends AliasRegistry {void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException;void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;boolean containsBeanDefinition(String beanName);String[] getBeanDefinitionNames();int getBeanDefinitionCount();boolean isBeanNameInUse(String beanName);
}
BeanDefinitionRegistry繼承結構圖如下:
BeanDefinitionRegistry接口的子實現類主要有以下兩個:
-
DefaultListableBeanFactory:在該類中定義了如下代碼,就是用來注冊bean
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
-
SimpleBeanDefinitionRegistry:在該類中定義了如下代碼,就是用來注冊bean
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);
- 自定義BeanDefinitionRegistry接口定義了注冊表的相關操作,定義如下功能:
public interface BeanDefinitionRegistry {//注冊BeanDefinition對象到注冊表中void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);//從注冊表中刪除指定名稱的BeanDefinition對象void removeBeanDefinition(String beanName) throws Exception;//根據名稱從注冊表中獲取BeanDefinition對象BeanDefinition getBeanDefinition(String beanName) throws Exception;//判斷注冊表中是否包含指定名稱的BeanDefinition對象boolean containsBeanDefinition(String beanName);//獲取注冊表中BeanDefinition對象的個數int getBeanDefinitionCount();//獲取注冊表中所有的BeanDefinition的名稱String[] getBeanDefinitionNames();
}
- SimpleBeanDefinitionRegistry類, 該類實現了BeanDefinitionRegistry接口,定義了Map集合作為注冊表容器。
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>();@Overridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {beanDefinitionMap.put(beanName,beanDefinition);}@Overridepublic void removeBeanDefinition(String beanName) throws Exception {beanDefinitionMap.remove(beanName);}@Overridepublic BeanDefinition getBeanDefinition(String beanName) throws Exception {return beanDefinitionMap.get(beanName);}@Overridepublic boolean containsBeanDefinition(String beanName) {return beanDefinitionMap.containsKey(beanName);}@Overridepublic int getBeanDefinitionCount() {return beanDefinitionMap.size();}@Overridepublic String[] getBeanDefinitionNames() {return beanDefinitionMap.keySet().toArray(new String[1]);}
}
3) 創建解析器相關的類
BeanDefinitionReader 接口
- BeanDefinitionReader 用來解析配置文件并在注冊表中注冊 bean 的信息。定義了兩個規范:
- 獲取注冊表的功能,讓外界可以通過該對象獲取注冊表對象;
- 加載配置文件,并注冊bean數據
/*** 該類定義解析配置文件規則的接口**/
public interface BeanDefinitionReader {//獲取注冊表對象BeanDefinitionRegistry getRegistry();//加載配置文件并在注冊表中進行注冊void loadBeanDefinitions(String configLocation) throws Exception;
}
XmlBeanDefinitionReader類
- XmlBeanDefinitionReader 是專門用來解析 xml 配置文件的。該類實現 BeanDefinitionReader 接口并實現接口中的兩個功能。
/*** 該類是對XML文件進行解析的類**/
public class XmlBeanDefinitionReader implements BeanDefinitionReader {//聲明注冊表對象(將配置文件與注冊表解耦,通過Reader降低耦合性)private BeanDefinitionRegistry registry;public XmlBeanDefinitionReader() {registry = new SimpleBeanDefinitionRegistry();}@Overridepublic BeanDefinitionRegistry getRegistry() {return registry;}//加載配置文件@Overridepublic void loadBeanDefinitions(String configLocation) throws Exception {//使用dom4j解析xmlSAXReader reader = new SAXReader();//獲取配置文件,類路徑下InputStream is = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configLocation);//獲取document文檔對象Document document = reader.read(is);Element rootElement = document.getRootElement();//解析bean標簽parseBean(rootElement);}private void parseBean(Element rootElement) {//獲取所有的bean標簽List<Element> elements = rootElement.elements();//遍歷獲取每個bean標簽的屬性值和子標簽propertyfor (Element element : elements) {String id = element.attributeValue("id");String className = element.attributeValue("class");//封裝到beanDefinitionBeanDefinition beanDefinition = new BeanDefinition();beanDefinition.setId(id);beanDefinition.setClassName(className);//獲取propertyList<Element> list = element.elements("property");MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();//遍歷,封裝propertyValue,并保存到mutablePropertyValuesfor (Element element1 : list) {String name = element1.attributeValue("name");String ref = element1.attributeValue("ref");String value = element1.attributeValue("value");PropertyValue propertyValue = new PropertyValue(name,ref,value);mutablePropertyValues.addPropertyValue(propertyValue);}//將mutablePropertyValues封裝到beanDefinitionbeanDefinition.setPropertyValues(mutablePropertyValues);System.out.println(beanDefinition);//將beanDefinition注冊到注冊表registry.registerBeanDefinition(id,beanDefinition);}}
}
4) 創建IOC容器相關的類
1) BeanFactory接口
在該接口中定義 IOC 容器的統一規范和獲取bean對象的方法。
/*** IOC容器父接口**/
public interface BeanFactory {Object getBean(String name)throws Exception;//泛型方法,傳入當前類或者其子類<T> T getBean(String name ,Class<? extends T> clazz)throws Exception;
}
2) ApplicationContext 接口
該接口的所有的子實現類對 bean 對象的創建都是非延時的,所以在該接口中定義 refresh()
方法,該方法主要完成以下兩個功能:
- 加載配置文件。
- 根據注冊表中的 BeanDefinition 對象封裝的數據進行 bean 對象的創建。
/*** 定義非延時加載功能**/
public interface ApplicationContext extends BeanFactory {//進行配置文件加載,并進行對象創建void refresh();
}
3) AbstractApplicationContext類
- 作為 ApplicationContext 接口的子類,所以該類也是非延時加載,所以需要在該類中定義一個Map集合,作為bean對象存儲的容器。
- 聲明 BeanDefinitionReader 類型的變量,用來進行 xml 配置文件的解析,符合單一職責原則。
- BeanDefinitionReader 類型的對象創建交由子類實現,因為只有子類明確到底創建BeanDefinitionReader 哪兒個子實現類對象。
/*** ApplicationContext接口的子實現類* 創建容器對象時,加載配置文件,對bean進行初始化**/
public abstract class AbstractApplicationContext implements ApplicationContext {//聲明解析器變量protected BeanDefinitionReader beanDefinitionReader;//定義存儲bean對象的Map集合protected Map<String,Object> singletonObjects = new HashMap<>();//聲明配置文件類路徑的變量protected String configLocation;@Overridepublic void refresh() {//加載beanDefinition對象try {beanDefinitionReader.loadBeanDefinitions(configLocation);//初始化beanfinishBeanInitialization();} catch (Exception e) {e.printStackTrace();}}//bean初始化protected void finishBeanInitialization() throws Exception {//獲取對應的注冊表對象BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();//獲取beanDefinition對象String[] beanNames = registry.getBeanDefinitionNames();for (String beanName : beanNames) {//進行bean的初始化getBean(beanName);}};
}
4) ClassPathXmlApplicationContext類
該類主要是加載類路徑下的配置文件,并進行 bean 對象的創建,主要完成以下功能:
- 在構造方法中,創建 BeanDefinitionReader 對象。
- 在構造方法中,調用 refresh() 方法,用于進行配置文件加載、創建 bean 對象并存儲到容器中。
- 重寫父接口中的 getBean() 方法,并實現依賴注入操作。
/*** IOC容器具體的子實現類,加載XML格式配置文件**/
public class ClassPathXmlApplicationContext extends AbstractApplicationContext{public ClassPathXmlApplicationContext(String configLocation) {this.configLocation = configLocation;//構建解析器對象this.beanDefinitionReader = new XmlBeanDefinitionReader();this.refresh();}//跟據bean的對象名稱獲取bean對象@Overridepublic Object getBean(String name) throws Exception {//判斷對象容器中是否包含指定名稱的bean對象,如果包含就返回,否則自行創建Object obj = singletonObjects.get(name);if(obj != null){return obj;}//自行創建,獲取beanDefinition對象BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();BeanDefinition beanDefinition = registry.getBeanDefinition(name);//通過反射創建對象String className = beanDefinition.getClassName();Class<?> clazz = Class.forName(className);Object beanObj = clazz.newInstance();//CourseService與UserDao存依賴,所以要將UserDao一同初始化,進行依賴注入MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();for (PropertyValue propertyValue : propertyValues) {//獲取name屬性值String propertyName = propertyValue.getName();//獲取Value屬性String value = propertyValue.getValue();//獲取ref屬性String ref = propertyValue.getRef();//ref與value只能存在一個if(ref != null && !"".equals(ref)){//獲取依賴的bean對象,拼接set set+CourseObject bean = getBean(ref);String methodName = StringUtils.getSetterMethodFieldName(propertyName);//獲取所有方法對象Method[] methods = clazz.getMethods();for (Method method : methods) {if(methodName.equals(method.getName())){//執行該set方法method.invoke(beanObj,bean);}}}if(value != null && !"".equals(value)){String methodName = StringUtils.getSetterMethodFieldName(propertyName);//獲取methodMethod method = clazz.getMethod(methodName, String.class);method.invoke(beanObj,value);}}//在返回beanObj之前 ,需要將對象存儲到Map容器中this.singletonObjects.put(name,beanObj);return beanObj;}@Overridepublic <T> T getBean(String name, Class<? extends T> clazz) throws Exception {Object bean = getBean(name);if(bean == null){return null;}return clazz.cast(bean);}
}
5) 自定義IOC容器測試
第一步: 將我們寫好的自定義IOC容器項目,安裝到maven倉庫中,使其他項目可以引入其依賴
//依賴信息
<dependencies><dependency><groupId>com.hopeful</groupId><artifactId>user_defined_springioc</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>
第二步: 創建一個新的maven項目,引入上面的依賴
第三步: 完成代碼編寫
- dao
public interface CourseDao {public void add();
}public class CourseDaoImpl implements CourseDao {//value注入private String courseName;public String getCourseName() {return courseName;}public void setCourseName(String courseName) {this.courseName = courseName;}public CourseDaoImpl() {System.out.println("CourseDaoImpl創建了......");}@Overridepublic void add() {System.out.println("CourseDaoImpl的add方法執行了......" + courseName);}
}
- service
public interface CourseService {public void add();
}public class CourseServiceImpl implements CourseService {public CourseServiceImpl() {System.out.println("CourseServiceImpl創建了......");}private CourseDao courseDao;public void setCourseDao(CourseDao courseDao) {this.courseDao = courseDao;}@Overridepublic void add() {System.out.println("CourseServiceImpl的add方法執行了......");courseDao.add();}
}
- applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans><bean id="courseService" class="com.mashibing.test_springioc.service.impl.CourseServiceImpl"><property name="courseDao" ref="courseDao"></property></bean><bean id="courseDao" class="com.mashibing.test_springioc.dao.impl.CourseDaoImpl"><property name="courseName" value="java"></property></bean>
</beans>
- Controller
public class CourseController{public static void main(String[] args) {//1.創建Spring的容器對象ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//2.從容器對象中獲取CourseService對象CourseService courseService = context.getBean("courseService", CourseService.class);//3.調用UserService的add方法courseService.add();}
}
在此,我們就已經實現了專屬自己的IOC容器,是不是突然發現平時感覺很高深的Sping IoC容器也不是那么復雜!離大佬又近了一步,哈哈!
6) 案例中使用到的設計模式
- 工廠模式:這個使用工廠模式 + 配置文件的方式。
- 單例模式:Spring IOC管理的bean對象都是單例的,此處的單例不是通過構造器進行單例的控制的,而是spring框架對每一個bean只創建了一個對象。
- 模板方法模式:AbstractApplicationContext 類中的 finishBeanInitialization() 方法調用了子類的 getBean() 方法,因為 getBean() 的實現和環境息息相關。
- 迭代器模式。對于 MutablePropertyValues 類定義使用到了迭代器模式,因為此類存儲并管理PropertyValue 對象,也屬于一個容器,所以給該容器提供一個遍歷方式。