咱們都知道,Spring Ioc和Aop是Spring的核心的功能,所以花一點時間去研究仍是頗有意義的,若是僅僅是知其因此然,也就體會不到大師設計Spring的精華,還記得那句話,Spring爲JavaEE開發帶來了春天。
IOC就是Inversion of control 也就是控制反轉的意思,另外一種稱呼叫作依賴注入,這個可能更直觀一點,拿個例子來講吧:java
@Component public class UserService { @Autowired private UserMapper mapper; }
好比在UserService可能要調用一個Mapper,這個Mapper去作DAO的操做,在這裏咱們直接經過@Autowired註解去注入這個Mapper,這個就叫作依賴注入,你想要什麼就注入什麼,不過前提它是一個Bean。至因而怎麼注入的,那是Spring容器作的事情,也是咱們今天去探索的。
在進行分析以前,我先聲明一下,下面的這些代碼並非從spring 源碼中直接拿過來,而是經過一步步簡化,抽取spring源碼的精華,若是直接貼源碼,我以爲可能不少人都會被嚇跑,並且還不必定可以學到真正的東西。
Spring要去管理Bean首先要把Bean放到容器裏,那麼Spring是如何得到Bean的呢?node
首先,Spring有一個數據結構,BeanDefinition,這裏存放的是Bean的內容和元數據,保存在BeanFactory當中,包裝Bean的實體:面試
public class BeanDefinition { //真正的Bean實例 private Object bean; //Bean的類型信息 private Class beanClass; //Bean類型信息的名字 private String beanClassName; //用於bean的屬性注入 由於Bean可能有不少熟屬性 //因此這裏用列表來進行管理 private PropertyValues propertyValues = new PropertyValues(); }
PerpertyValues存放Bean的全部屬性spring
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; }
定義好這些數據結構了,把Bean裝進容器的過程,其實就是其BeanDefinition構造的過程,那麼怎麼把一些類裝入的Spring容器呢?數據結構
Spring有個接口就是獲取某個資源的輸入流,獲取這個輸入流後就能夠進一步處理了:架構
public interface Resource { InputStream getInputStream() throws IOException; }
UrlResource 是對Resource功能的進一步擴展,經過拿到一個URL獲取輸入流。app
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); } }
你們可能會對上面的這段代碼產生疑問:分佈式
URL resource = this.getClass().getClassLoader().getResource(location);
爲何經過獲得一個類的類類型,而後獲得對應的類加載器,而後調用類加載器的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 { /** * 構造函數 傳入咱們以前分析過的ResourceLoader 這個經過 * location 能夠 加載到Resource */ public XmlBeanDefinitionReader(ResourceLoader resourceLoader) { super(resourceLoader); } /** * 這個方法實際上是BeanDefinitionReader這個接口中的方法 * 做用就是經過location來構造BeanDefinition */ @Override public void loadBeanDefinitions(String location) throws Exception { //把location傳給ResourceLoader拿到Resource,而後獲取輸入流 InputStream inputStream = getResourceLoader().getResource(location).getInputStream(); //接下來進行輸入流的處理 doLoadBeanDefinitions(inputStream); } protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception { //由於xml是文檔對象,因此下面進行一些處理文檔工具的構造 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = factory.newDocumentBuilder(); //把輸入流解析成一個文檔,java能夠處理的文檔 Document doc = docBuilder.parse(inputStream); // 處理這個文檔對象 也就是解析bean registerBeanDefinitions(doc); inputStream.close(); } public void registerBeanDefinitions(Document doc) { //獲得文檔的根節點,知道根節點後獲取子節點就是經過層級關係處理就好了 Element root = doc.getDocumentElement(); //解析根節點 xml的根節點 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); //設置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) { //設置這個bean對應definition裏的屬性值 beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value)); } else { //value是Reference的話 就會進入到這裏處理 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 而後把這個引用方到屬性list裏 BeanReference beanReference = new BeanReference(ref); beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference)); } } } } }
上面用到了BeanReference(以下),其實這個和PropertyValue相似,用不一樣的類型是爲了更好的區分:
public class BeanReference { private String name; private Object bean; }
好了,到如今咱們已經分析完了Spring是如何找到Bean並加載進入Spring容器的,這裏面最主要的數據結構就是BeanDefinition,ReourceLoader來完成資源的定位,讀入,而後獲取輸入流,進一步的處理,這個過程當中有對xml文檔的解析和對屬性的填充。
感興趣能夠加Java架構師羣獲取Java工程化、高性能及分佈式、高性能、深刻淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大數據等多個知識點高級進階乾貨的直播免費學習權限 都是大牛帶飛 讓你少走不少的彎路的 羣..號是:855801563 對了 小白勿進 最好是有開發經驗
注:加羣要求
一、具備工做經驗的,面對目前流行的技術不知從何下手,須要突破技術瓶頸的能夠加。
二、在公司待久了,過得很安逸,但跳槽時面試碰壁。須要在短期內進修、跳槽拿高薪的能夠加。
三、若是沒有工做經驗,但基礎很是紮實,對java工做機制,經常使用設計思想,經常使用java開發框架掌握熟練的,能夠加。
四、以爲本身很牛B,通常需求都能搞定。可是所學的知識點沒有系統化,很難在技術領域繼續突破的能夠加。
5.阿里Java高級大牛直播講解知識點,分享知識,多年工做經驗的梳理和總結,帶着你們全面、科學地創建本身的技術體系和技術認知!