前言
spring源碼是一個困擾我很久的問題,這裏面水又深有渾,跟一會就蒙圈了,好不容易找到了一點思路,時間一久,概念又模糊了,然後再去跟源碼的時候又是一個噁心的循環。萬般無奈,高仿一個spring來幫助下次快速梳理springIOC源碼;
那就從第一次接觸spring來梳理吧:
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserInfo userInfo = (UserInfo) classPathXmlApplicationContext.getBean("userInfo");
System.out.println(userInfo.getName());
}
第一次接觸spring,就聽說,對象不用new,直接從容器中拿就可以了,我想大多數人心中都會有一個這樣的概念吧。就如上代碼,spring
加載完xml
文件之後,直接getBean
方法就可以獲取對象了,省去了new UserInfo()
。但是new ClassPathXmlApplicationContext("spring.xml")
又幫我們做了什麼呢?
至此,對於如上3行代碼進行高仿袖珍版springIOC容器,只是最簡單的springIOC容器,如有不對或者不嚴謹的地方,請大家即使指出,我會盡快修改,免得誤導別人。
1.創建核心接口
既然高仿就要像一點,第一步要創建心臟 BeanFactory
了。然後核心接口一定要有的。
/**
* Created by yangman on 2020/5/8.
* 精簡版本也要高仿的像一點 BeanFactory 不能少。
*/
public interface BeanFactory {
// 來兩個核心方法得了,其他的暫時先不搞了。
Object getBean(String name) throws IllegalAccessException, InstantiationException, NoSuchFieldException;
<T> T getBean(String name, @Nullable Class<T> requiredType) throws IllegalAccessException, NoSuchFieldException, InstantiationException, NoSuchMethodException, InvocationTargetException;
}
接着創建子接口,HierarchicalBeanFactory
,ListableBeanFactory
,ApplicationContext
但是前兩個用的不多,注重說ApplicationContext
吧。
網上對於ApplicationContext
接口有一個這樣的說法,就是說如果比喻BeanFactory
是心臟,則ApplicationContext
就是軀幹。由於這裏只是高仿,大部分功能是用不到,所以不寫了。
/**
* Created by yangman on 2020/5/8.
* beanFactory只有一些簡單的,核心的功能,
* ApplicationContext的接口就是一個全面化的接口,集結所有功能爲一身。
* 1.可以裝配管理bean
* 2.源碼中實現了ResourcePatternResolver->ResourceLoader 接口,可以加載資源文件
* 3.可以進行國際化處理等等
*/
public interface ApplicationContext extends HierarchicalBeanFactory,ListableBeanFactory {
}
接口有了,進行實現吧。創建ClassPathXmlApplicationContext
實現ApplicationContext
接口,注:spring源碼中ClassPathXmlApplicationContext
類不是直接實現的ApplicationContext
接口。
public class ClassPathXmlApplicationContext implements ApplicationContext {
public ClassPathXmlApplicationContext(String configLocation) {
this(new String[] {configLocation});
}
public ClassPathXmlApplicationContext(String[] configLocations) {
// 源碼中做了相應的處理, 這裏進行簡化
this.configLocations = configLocations;
synchronized (this.startupShutdownMonitor) {
try{
// 讀取配置文件
Document document = loadConfig();
//生成BeanDefinition
createBeanDefinition(document);
// IOC注入
IocAutoWrite();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
}
2.讀取配置文件
通過loadConfig
方法進行處理文件路徑,並且加載配置。
應該還記得classpath
吧,在源碼中PathMatchingResourcePatternResolver.getResources 可見真正的解析過程。
private Document loadConfig() throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
// 這裏其實是一個數組,只是爲了演示,這裏就不循環處理了。
InputStream inputStream = this.getFileInputStream(configLocations[0]);
// 獲取doc對象
Document document= docBuilder.parse(inputStream);
return document;
}
// 獲取並處理文件路徑
private InputStream getFileInputStream(String configLocation) {
// 真正的源碼中spring 有對classpath* 和 classpath 這兩個開頭做了處理 PathMatchingResourcePatternResolver.getResources 可見
int prefixEnd = (configLocation.startsWith("war:") ? configLocation.indexOf("*/") + 1 :
configLocation.indexOf(':') + 1);
// this.getClass().getClassLoader().getResourceAsStream 這個函數尋找文件的起點是JAVA項目編譯之後的根目錄 ,也就是/target/class目錄啦
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configLocation);
return inputStream;
}
3.生成BeanDefinition
可能有的小夥伴對與BeanDefinition
這個東西有點陌生,BeanDefinition
是用來存放bean的元數據的,比如bean的類名,屬性,scope等信息。但是就疑問了?這些東西配置文件中(xml)原本就有呀,何必在存一份呢?這裏可以打個比方,BeanDefinition
有如手機配件,bean
是手機,手機配件從工廠進行加工出來,需要進行檢測才能放到手機上,xml中的信息就是剛剛加工出來的手機配件,BeanDefinition
就是檢驗合格的手機配件。xml
中的信息加載到BeanDefinition
中,纔可以人爲xml
配置的信息被成功解析。
xml文件讀取之後,接下進行驗證xml文件,並且初始化到BeanDefinition
中了。
通過createBeanDefinition
初始化BeanDefinition
。
3.1創建BeanDefinition
BeanDefinition
是一個接口,這裏用到AbstractBeanDefinition
進行實現,現在存儲xml文件內容
/**
* Created by likuo on 2020/5/9.
* 高仿spring AbstractBeanDefinition.java
*/
public class AbstractBeanDefinition implements BeanDefinition {
// 類
private volatile Object beanClass;
// bean名稱,用來.getBean()中的值
private String beanName;
// bean的屬性值 主要用來存放 <property name="name" value="aa"></property> 這一段
private MutablePropertyValues propertyValues;
public Object getBeanClass() {
return beanClass;
}
public void setBeanClass(Object beanClass) {
this.beanClass = beanClass;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public MutablePropertyValues getPropertyValues() {
return propertyValues;
}
public void setPropertyValues(MutablePropertyValues propertyValues) {
this.propertyValues = propertyValues;
}
}
3.2讀取配置文件
主要完成從xml讀取,並且生成BeanDefinition
,BeanDefinition中存入類名,bean名稱,和屬性值,相當於把配置文件完全讀取到內存中。
// 源碼中就比較複雜, 這裏就從簡了哈。
private void createBeanDefinition(Document document) throws ClassNotFoundException {
// 獲取根節點
Element documentElement = document.getDocumentElement();
//這個方法名字也是高仿的,下載源碼搜索就能搜索到此方法
doRegisterBeanDefinitions(documentElement);
}
xml文件中有很多種標籤,,,等,這裏進行解析。
private void doRegisterBeanDefinitions(Element documentElement) throws ClassNotFoundException {
// 看到這兩個判斷就豁然開朗了吧,此內容高仿 DefaultBeanDefinitionDocumentReader.parseDefaultElement方法,有興趣的看一下哈。
// 如果是<beans> 則解析下子元素
if(NESTED_BEANS_ELEMENT.equals(documentElement.getNodeName())){
NodeList childNodes = documentElement.getChildNodes();
for(int i = 0 ;i<childNodes.getLength();i++){
Node item = childNodes.item(i);
if(item instanceof Element) {
// beans下面應該是bean,這裏進行循環解析
doRegisterBeanDefinitions((Element)item);
}
}
}else if(BEAN_ELEMENT.equals(documentElement.getNodeName())){
AbstractBeanDefinition beanDefinition = new AbstractBeanDefinition();
// 解析id屬性
String beanName = documentElement.getAttribute(BeanDefinitionParserDelegate.ID_ATTRIBUTE);
// 解析class屬性
String className = documentElement.getAttribute(BeanDefinitionParserDelegate.CLASS_ATTRIBUTE);
// 設置值
beanDefinition.setBeanClass(Class.forName(className));
beanDefinition.setBeanName(beanName);
// 如果有設置了值 property,則進行解析
NodeList childNodes = documentElement.getChildNodes();
if(childNodes!=null){
List<PropertyValue> propertyValueList = new ArrayList<>();
for(int i = 0 ;i<childNodes.getLength();i++){
Node item = childNodes.item(i);
if(item instanceof Element) {
Element property = (Element)item;
if(BeanDefinitionParserDelegate.PROPERTY_ELEMENT.equals(property.getNodeName())){
String propertyName = property.getAttribute(BeanDefinitionParserDelegate.NAME_ATTRIBUTE);
String propertyValue = property.getAttribute(BeanDefinitionParserDelegate.VALUE_ATTRIBUTE);
propertyValueList.add(new PropertyValue(propertyName,propertyValue));
}
}
}
beanDefinition.setPropertyValues(new MutablePropertyValues(propertyValueList));
}
// 保存beanDefinition
beanDefinitionMap.put(beanName,beanDefinition);
// 獲取的beanName放到集合中
beanDefinitionNames.add(beanName);
}
}
4.IOC注入
通過IocAutoWrite()
進行IOC注入,主要是通過BeanDefinition轉換成bean對象,並且放到map中。原本bean是一個代理對象,但是這裏就從簡,直接就放真實對象了。
// 這裏進行注入
private void IocAutoWrite() throws IllegalAccessException, NoSuchFieldException, InstantiationException {
System.out.println("進行注入");
for(String beanName:beanDefinitionNames){
this.getBean(beanName);
}
}
@Override
public Object getBean(String name) {
// 如果存在則直接返回
if(singletonObjects.containsKey(name)){
return singletonObjects.get(name);
}else{
Object o = null;
try {
o = doCreateBean(name);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return o;
}
}
在spring中,一般do開頭的方法都需要重點的看下, 裏面會實現很多邏輯。這裏也高仿真一下。
// 高仿 DefaultListableBeanFactory
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
//高仿DefaultSingletonBeanRegistry 用來存放單例的對象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
private Object doCreateBean(String name) throws IllegalAccessException, InstantiationException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
// 現在只處理單例情況
AbstractBeanDefinition beanDefinition = beanDefinitionMap.get(name);
// 這次不說代理的情況,原本是使用cglib進行代理,這裏簡化下
// 創建對象
Class clazz = (Class) beanDefinition.getBeanClass();
Object o = clazz.newInstance();
// 設置屬性 注意, 真實spring並不是這麼設置的。。
if(beanDefinition.getPropertyValues()!=null){
List<PropertyValue> propertyValueList = beanDefinition.getPropertyValues().getPropertyValueList();
for(PropertyValue propertyValue:propertyValueList){
// 獲取set方法的名稱
String setMethod = this.getSetMethod(propertyValue.getName());
// 通過調用set方法進行賦值
//
Method method = clazz.getMethod(setMethod,String.class);
method.invoke(o,propertyValue.getValue());
}
}
// 用來存放單例bean的map
singletonObjects.put(name,o);
return o;
}
至此IOC注入完成。
5.測試
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userInfo" class="com.base.entity.UserInfo">
<property name="name" value="張三"></property>
</bean>
</beans>
創建xml文件
public class UserInfo {
public Long id;
public String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserInfo userInfo = (UserInfo) classPathXmlApplicationContext.getBean("userInfo");
System.out.println(userInfo.getName());
}
}
輸出:
加載配置文件
初始化BeanDefinition
進行注入
張三