開局經驗之談:可能從這一篇文章開始,小夥伴們都會有點暈車的感受了,可是這個系列並非只是介紹下 spring 表面的一些膚淺的東西,本系列的目的是爲了讓你們從源碼層次深刻理解 Spring,這也是你們在將來的求職道路上的一個重要的漲薪手段,但願小夥伴都不要放棄,結合源碼多看幾遍,努力必定會有收穫。java
本文即開始帶領你們研讀Spring5源碼,若是尚未構建Spirng5源碼工程的話,能夠先去《Spring源碼解析(二)》構建 Spring5 源碼工程,開啓研讀Spring源碼之路 學習如何構建Spring5源碼工程。web
認識 IOC 與 DI
「IOC(Inversion of Control)控制反轉」:所謂控制反轉,就是把原先咱們代碼裏面須要實現的對象建立、依賴的代碼,反轉給容器來幫忙實現。那麼必然的咱們須要建立一個容器,同時須要一種描述來讓容器知道須要建立的對象與對象的關係。面試
這個描述最具體表現就是咱們所看到的配置文件。spring
「DI(Dependency Injection)依賴注入」:就是指對象是被動接受依賴類而不是本身主動去找,換句話說就是指對象不是從容器中查找它依賴的類,而是在容器實例化對象的時候主動將它依賴的類注入給它。安全
如何設計IOC和DI,咱們先來思考下面四個問題?網絡
一、對象和對象的關係怎麼表示?架構
能夠用 xml,properties 文件等語義化配置文件表示。app
二、描述對象關係的文件存放在哪裏?框架
多是 classpath,filesystem,或者是 URL 網絡資源,servletContext 等。ide
回到正題,有了配置文件,還須要對配置文件解析。
三、不一樣的配置文件對對象的描述不同,如標準的,自定義聲明式的,如何統一?
在內部須要有一個統一的關於對象的定義,全部外部的描述都必須轉化成統一的描述定義。
四、如何對不一樣的配置文件進行解析?
須要對不一樣的配置文件語法,採用不一樣的解析器。
Spring 核心容器類圖
一、BeanFactory
Spring Bean 的建立是典型的工廠模式,這一系列的 Bean 工廠,也即 IOC 容器爲開發者管理對象間的依賴關係提供了不少便利和基礎服務,在 Spring 中有許多的 IOC 容器的實現供用戶選擇和使用,其相互關係以下:
其中 BeanFactory 做爲最頂層的一個接口類,它定義了 IOC 容器的基本功能規範 BeanFactory 有三 個重要的子類:ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory。
可是從類圖中咱們能夠發現最終的默認實現類是 DefaultListableBeanFactory,它實現了全部的接口。
那爲什麼要定義這麼多層次的接口呢? 查閱這些接口的源碼和說明發現,每一個接口都有它使用的場合,它主要是爲了區分在 Spring 內部在操做過程當中對象的傳遞和轉化過程時,對對象的數據訪問所作的限制。
例如 ListableBeanFactory 接口表示這些 Bean 是可列表化的,而 HierarchicalBeanFactory 表示的是這些 Bean 是有繼承關係的,也就是每一個 Bean 有可能有父 Bean。
AutowireCapableBeanFactory 接口定義 Bean 的自動裝配規則。這三個接口共同定義了 Bean 的集合、Bean 之間的關係、以及 Bean 行爲。
最基本的 IOC 容器接口 BeanFactory,來看一下它的源碼:
public interface BeanFactory { //對FactoryBean的轉義定義,由於若是使用bean的名字檢索FactoryBean獲得的對象是工廠生成的對象, //若是須要獲得工廠自己,須要轉義 String FACTORY_BEAN_PREFIX = "&"; //根據bean的名字,獲取在IOC容器中獲得bean實例 Object getBean(String name) throws BeansException; //根據bean的名字和Class類型來獲得bean實例,增長了類型安全驗證機制。 <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException; Object getBean(String name, Object... args) throws BeansException; <T> T getBean(Class<T> requiredType) throws BeansException; <T> T getBean(Class<T> requiredType, Object... args) throws BeansException; //提供對bean的檢索,看看是否在IOC容器有這個名字的bean boolean containsBean(String name); //根據bean名字獲得bean實例,並同時判斷這個bean是否是單例 boolean isSingleton(String name) throws NoSuchBeanDefinitionException; boolean isPrototype(String name) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException; //獲得bean實例的Class類型 @Nullable Class<?> getType(String name) throws NoSuchBeanDefinitionException; //獲得bean的別名,若是根據別名檢索,那麼其原名也會被檢索出來 String[] getAliases(String name); }
在 BeanFactory 裏只對 IOC 容器的基本行爲做了定義,根本不關心你的 Bean 是如何定義怎樣加載的。
正如咱們只關心工廠裏獲得什麼的產品對象,至於工廠是怎麼生產這些對象的,這個基本的接口不關心。
而要知道工廠是如何產生對象的,咱們須要看具體的 IOC 容器實現,Spring 提供了許多 IOC 容器的 實現 。
比 如 GenericApplicationContext , ClasspathXmlApplicationContext 等 。
ApplicationContext 是 Spring 提供的一個高級的 IOC 容器,它除了可以提供 IOC 容器的基本功能外,還爲用戶提供瞭如下的附加服務。
從 ApplicationContext 接口的實現,咱們看出其特色:
一、支持信息源,能夠實現國際化。(實現 MessageSource 接口)
二、訪問資源。(實現 ResourcePatternResolver 接口,後面章節會講到)
三、支持應用事件。(實現 ApplicationEventPublisher 接口)
二、BeanDefinition
SpringIOC 容器管理了咱們定義的各類 Bean 對象及其相互的關係,Bean 對象在 Spring 實現中是以 BeanDefinition 來描述的,其繼承體系以下:
三、BeanDefinitionReader
Bean 的解析過程很是複雜,功能被分的很細,由於這裏須要被擴展的地方不少,必須保證有足夠的靈活性,以應對可能的變化。
Bean 的解析主要就是對 Spring 配置文件的解析。這個解析過程主要經過 BeanDefintionReader 來完成,最後看看 Spring 中 BeanDefintionReader 的類結構圖:
經過本章內容的分析,咱們對 Spring 框架體系有了一個基本的宏觀瞭解,但願小夥伴們好好理解,最好在腦海中造成畫面,爲之後的學習打下良好的鋪墊。
Web IOC 容器初體驗
咱們仍是從你們最熟悉的 DispatcherServlet 開始,咱們最早想到的仍是 DispatcherServlet 的 init()方法。咱們發如今 DispatherServlet 中並無找到 init()方法。
可是通過探索,往上追索在其父類 HttpServletBean 中找到了咱們想要的 init()方法,以下:
/** * Map config parameters onto bean properties of this servlet, and * invoke subclass initialization. * @throws ServletException if bean properties are invalid (or required * properties are missing), or if subclass initialization fails. */ @Override public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // Set bean properties from init parameters. PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { /**定位資源**/ BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); /**加載配置信息**/ ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); } throw ex; } } // Let subclasses do whatever initialization they like. initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } }
在 init()方法中,真正完成初始化容器動做的邏輯其實在 initServletBean()方法中,咱們繼續跟進 initServletBean()中的代碼在 FrameworkServlet 類中:
protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'"); if (this.logger.isInfoEnabled()) { this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started"); } long startTime = System.currentTimeMillis(); try { this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); }catch (ServletException ex) { this.logger.error("Context initialization failed", ex); throw ex; }catch (RuntimeException ex) { this.logger.error("Context initialization failed", ex); throw ex; } if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + elapsedTime + " ms"); } }
在上面的代碼中終於看到了咱們似曾相識的代碼 initWebAppplicationContext(),繼續跟進:
/** * Initialize and publish the WebApplicationContext for this servlet. * <p>Delegates to {@link #createWebApplicationContext} for actual creation * of the context. Can be overridden in subclasses. * @return the WebApplicationContext instance * @see #FrameworkServlet(WebApplicationContext) * @see #setContextClass * @see #setContextConfigLocation */ protected WebApplicationContext initWebApplicationContext() { /**先從ServletContext中得到父容器WebApplicationContext**/ WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); /**聲明子容器**/ WebApplicationContext wac = null; /**創建父,子容器之間的關聯關係**/ if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parent cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } /**先去ServletContext中查找Web容器的引用是否存在,並建立默認的空IOC容器**/ if (wac == null) { // No context instance was injected at construction time -> see if one // has been registered in the servlet context. If one exists, it is assumed // that the parent context (if any) has already been set and that the // user has performed any initialization such as setting the context id wac = findWebApplicationContext(); } /**給上一步建立好的IOC容器賦值**/ if (wac == null) { // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext); } /**觸發onRefresh方法**/ if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. onRefresh(wac); } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; } /** * Retrieve a {@code WebApplicationContext} from the {@code ServletContext} * attribute with the {@link #setContextAttribute configured name}. The * {@code WebApplicationContext} must have already been loaded and stored in the * {@code ServletContext} before this servlet gets initialized (or invoked). * </p><p>Subclasses may override this method to provide a different * {@code WebApplicationContext} retrieval strategy. * @return the WebApplicationContext for this servlet, or {@code null} if not found * @see #getContextAttribute() */ @Nullable protected WebApplicationContext findWebApplicationContext() { String attrName = getContextAttribute(); if (attrName == null) { return null; } WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); } return wac; } /** * Instantiate the WebApplicationContext for this servlet, either a default * {@link org.springframework.web.context.support.XmlWebApplicationContext} * or a {@link #setContextClass custom context class}, if set. * </p><p>This implementation expects custom contexts to implement the * {@link org.springframework.web.context.ConfigurableWebApplicationContext} * interface. Can be overridden in subclasses. * </p><p>Do not forget to register this servlet instance as application listener on the * created context (for triggering its {@link #onRefresh callback}, and to call * {@link org.springframework.context.ConfigurableApplicationContext#refresh()} * before returning the context instance. * @param parent the parent ApplicationContext to use, or {@code null} if none * @return the WebApplicationContext for this servlet * @see org.springframework.web.context.support.XmlWebApplicationContext */ protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { Class<!--?--> contextClass = getContextClass(); if (this.logger.isDebugEnabled()) { this.logger.debug("Servlet with name '" + getServletName() + "' will try to create custom WebApplicationContext context of class '" + contextClass.getName() + "'" + ", using parent context [" + parent + "]"); } if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); wac.setParent(parent); String configLocation = getContextConfigLocation(); if (configLocation != null) { wac.setConfigLocation(configLocation); } configureAndRefreshWebApplicationContext(wac); return wac; } protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value // -> assign a more useful id based on available information if (this.contextId != null) { wac.setId(this.contextId); } else { // Generate default id... wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName()); } } wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); // The wac environment's #initPropertySources will be called in any case when the context // is refreshed; do it eagerly here to ensure servlet property sources are in place for // use in any post-processing or initialization that occurs below prior to #refresh ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } postProcessWebApplicationContext(wac); applyInitializers(wac); wac.refresh(); }
從上面的代碼中能夠看出,在 configAndRefreshWebApplicationContext()方法中,調用 refresh()方法,這個是真正啓動 IOC 容器的入口,後面會詳細介紹。
IOC 容器初始化之後,最後調用了 DispatcherServlet 的 onRefresh()方法,在 onRefresh()方法中又是直接調用 initStrategies()方法初始化 SpringMVC 的九大組件:
/** * This implementation calls {@link #initStrategies}. */ @Override protected void onRefresh(ApplicationContext context) { initStrategies(context); } /** * Initialize the strategy objects that this servlet uses. * </p><p>May be overridden in subclasses in order to initialize further strategy objects. */ /**初始化策略**/ protected void initStrategies(ApplicationContext context) { /**多文件上傳的組件**/ initMultipartResolver(context); /**初始化本地語言環境**/ initLocaleResolver(context); /**初始化模板處理器**/ initThemeResolver(context); /**handlerMapping**/ initHandlerMappings(context); /**初始化參數適配器**/ initHandlerAdapters(context); /**初始化異常攔截器**/ initHandlerExceptionResolvers(context); /**初始化視圖預處理器**/ initRequestToViewNameTranslator(context); /**初始化視圖轉化器**/ initViewResolvers(context); initFlashMapManager(context); }
本文帶領你們把IOC的源碼流程大體走了一遍,後續會有詳細介紹,IOC是Spring源碼的重中之重,咱們將分多個篇幅來進行講解。
Spring Cloud 微服務精彩系列
- 阿里面試官問我:到底知不知道什麼是Eureka,此次,我沒沉默
- 萬字詳解Ribbon架構,針對面試高頻題多角度細說Ribbon
- 什麼是Hystrix,阿里技術最終面,遺憾的倒在Hystrix面前!
- 2萬字好文全方位深刻學習SpringCloud Fegin,面試不在彷徨
- Zuul,據說SpringCloud不許備要我了,但是爲何面試還要每天問我?
- 全網最全講解 Spring Cloud Gateway,認真看完這一篇就夠了!