我們都知道瓮床,Spring Ioc和Aop是Spring的核心的功能,因此花一點(diǎn)時間去研究還是很有意義的,如果僅僅是知其所以然纤垂,也就體會不到大師設(shè)計Spring的精華矾策,還記得那句話,Spring為JavaEE開發(fā)帶來了春天峭沦。
IOC就是Inversion of control 也就是控制反轉(zhuǎn)的意思贾虽,另一種稱呼叫做依賴注入,這個可能更直觀一點(diǎn)吼鱼,拿個例子來說吧:
@Component
public class UserService {
@Autowired
private UserMapper mapper;
}
比如在UserService可能要調(diào)用一個Mapper蓬豁,這個Mapper去做DAO的操作,在這里我們直接通過@Autowired注解去注入這個Mapper菇肃,這個就叫做依賴注入地粪,你想要什么就注入什么,不過前提它是一個Bean琐谤。至于是怎么注入的蟆技,那是Spring容器做的事情,也是我們今天去探索的斗忌。
在進(jìn)行分析之前质礼,我先聲明一下,下面的這些代碼并不是從spring 源碼中直接拿過來织阳,而是通過一步步簡化眶蕉,抽取spring源碼的精華,如果直接貼源碼唧躲,我覺得可能很多人都會被嚇跑造挽,而且還不一定能夠?qū)W到真正的東西。
Spring要去管理Bean首先要把Bean放到容器里弄痹,那么Spring是如何獲得Bean的呢饭入?
- 首先,Spring有一個數(shù)據(jù)結(jié)構(gòu)界酒,BeanDefinition圣拄,這里存放的是Bean的內(nèi)容和元數(shù)據(jù)嘴秸,保存在BeanFactory當(dāng)中毁欣,包裝Bean的實體:
public class BeanDefinition {
//真正的Bean實例
private Object bean;
//Bean的類型信息
private Class beanClass;
//Bean類型信息的名字
private String beanClassName;
//用于bean的屬性注入 因為Bean可能有很多熟屬性
//所以這里用列表來進(jìn)行管理
private PropertyValues propertyValues = new PropertyValues();
}
- PerpertyValues存放Bean的所有屬性
public class PropertyValues {
private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
}
- PropertyValue存放的是每個屬性,可以看到兩個字段岳掐,name和valu凭疮。name存放的就是屬性名稱,value是object類型串述,可以是任何類型
public class PropertyValue {
private final String name;
private final Object value;
}
定義好這些數(shù)據(jù)結(jié)構(gòu)了执解,把Bean裝進(jìn)容器的過程,其實就是其BeanDefinition構(gòu)造的過程,那么怎么把一些類裝入的Spring容器呢衰腌?
- Spring有個接口就是獲取某個資源的輸入流新蟆,獲取這個輸入流后就可以進(jìn)一步處理了:
public interface Resource {
InputStream getInputStream() throws IOException;
}
- UrlResource 是對Resource功能的進(jìn)一步擴(kuò)展,通過拿到一個URL獲取輸入流右蕊。
public class UrlResource implements Resource {
private final URL url;
public UrlResource(URL url) {
this.url = url;
}
@Override
public InputStream getInputStream() throws IOException{
URLConnection urlConnection = url.openConnection();
urlConnection.connect();
return urlConnection.getInputStream();
}
- ResourceLoader是資源加載的主要方法琼稻,通過location定位Resource,
然后通過上面的UrlResource獲取輸入流:
public class ResourceLoader {
public Resource getResource(String location){
URL resource = this.getClass().getClassLoader().getResource(location);
return new UrlResource(resource);
}
}
- 大家可能會對上面的這段代碼產(chǎn)生疑問:
URL resource = this.getClass().getClassLoader().getResource(location);
為什么通過得到一個類的類類型饶囚,然后得到對應(yīng)的類加載器帕翻,然后調(diào)用類加載器的Reource怎么就得到了URL這種類型呢?
我們來看一下類加載器的這個方法:
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
從這個方法中我們可以看出萝风,類加載器去加載資源的時候嘀掸,先會去讓父類加載器去加載,如果父類加載器沒有的話规惰,會讓根加載器去加載睬塌,如果這兩個都沒有加載成功,那就自己嘗試去加載歇万,這個一方面為了java程序的安全性衫仑,不可能你用戶自己隨便寫一個加載器,就用你用戶的堕花。
- 接下來我們看一下重要角色文狱,這個是加載BeanDefinition用的。
public interface BeanDefinitionReader {
void loadBeanDefinitions(String location) throws Exception;
}
- 這個接口是用來從配置中讀取BeanDefinition:
其中registry key是bean的id缘挽,value存放資源中所有的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;
}
}
- 最后我們來看一個通過讀取Xml文件的BeanDefinitionReader:
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
/**
* 構(gòu)造函數(shù) 傳入我們之前分析過的ResourceLoader 這個通過
* location 可以 加載到Resource
*/
public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
super(resourceLoader);
}
/**
* 這個方法其實是BeanDefinitionReader這個接口中的方法
* 作用就是通過location來構(gòu)造BeanDefinition
*/
@Override
public void loadBeanDefinitions(String location) throws Exception {
//把location傳給ResourceLoader拿到Resource,然后獲取輸入流
InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
//接下來進(jìn)行輸入流的處理
doLoadBeanDefinitions(inputStream);
}
protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception {
//因為xml是文檔對象懈糯,所以下面進(jìn)行一些處理文檔工具的構(gòu)造
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
//把輸入流解析成一個文檔,java可以處理的文檔
Document doc = docBuilder.parse(inputStream);
// 處理這個文檔對象 也就是解析bean
registerBeanDefinitions(doc);
inputStream.close();
}
public void registerBeanDefinitions(Document doc) {
//得到文檔的根節(jié)點(diǎn),知道根節(jié)點(diǎn)后獲取子節(jié)點(diǎn)就是通過層級關(guān)系處理就行了
Element root = doc.getDocumentElement();
//解析根節(jié)點(diǎn) xml的根節(jié)點(diǎn)
parseBeanDefinitions(root);
}
protected void parseBeanDefinitions(Element root) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//element有屬性的包裝
if (node instanceof Element) {
Element ele = (Element) node;
processBeanDefinition(ele);
}
}
}
protected void processBeanDefinition(Element ele) {
/**
* <bean id="object***" class="com.***.***"/>
*/
//獲取element的id
String name = ele.getAttribute("id");
//獲取element的class
String className = ele.getAttribute("class");
BeanDefinition beanDefinition = new BeanDefinition();
//處理這個bean的屬性
processProperty(ele, beanDefinition);
//設(shè)置BeanDefinition的類名稱
beanDefinition.setBeanClassName(className);
//registry是一個map谤逼,存放所有的beanDefinition
getRegistry().put(name, beanDefinition);
}
private void processProperty(Element ele, BeanDefinition beanDefinition) {
/**
*類似這種:
<bean id="userServiceImpl" class="com.serviceImpl.UserServiceImpl">
<property name="userDao" ref="userDaoImpl"> </property>
</bean>
*/
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) {
//設(shè)置這個bean對應(yīng)definition里的屬性值
beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
} else {
//value是Reference的話 就會進(jìn)入到這里處理
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");
}
//構(gòu)造一個BeanReference 然后把這個引用方到屬性list里
BeanReference beanReference = new BeanReference(ref);
beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
}
}
}
}
}
- 上面用到了BeanReference(如下),其實這個和PropertyValue類似欲诺,用不同的類型是為了更好的區(qū)分:
public class BeanReference {
private String name;
private Object bean;
}
好了腮郊,到現(xiàn)在我們已經(jīng)分析完了Spring是如何找到Bean并加載進(jìn)入Spring容器的摹蘑,這里面最主要的數(shù)據(jù)結(jié)構(gòu)就是BeanDefinition,ReourceLoader來完成資源的定位轧飞,讀入衅鹿,然后獲取輸入流,進(jìn)一步的處理过咬,這個過程中有對xml文檔的解析和對屬性的填充大渤。
由于自己能力有限,有些地方表述的不準(zhǔn)確掸绞,還請大家諒解~~,