IOC(Inversion of Control)
控制反轉:所謂控制反轉,就是把原先咱們代碼裏面須要實現的對象創 建、依賴的代碼,反轉給容器來幫忙實現。那麼必然的咱們須要建立一個容器,同時須要一種描述來讓 容器知道須要建立的對象與對象的關係。這個描述最具體表現就是咱們所看到的配置文件。java
DI(Dependency Injection)
依賴注入:就是指對象是被動接受依賴類而不是本身主動去找,換句話說就 是指對象不是從容器中查找它依賴的類,而是在容器實例化對象的時候主動將它依賴的類注入給它。web
上面是 ioc
和 DI
的通俗理解,咱們也能夠用咱們現有的知識來思考這兩點的實現,其實二者主要仍是依賴與反射機制來實現這些功能,那麼咱們爲了提出一些關鍵問題,來跟着關鍵問題來看下具體的流程。spring
在 spring
中對象之間的關係如何來表現
在咱們配置文件中或者javaconfig中均又相應的方式來提現微信
描述對象之間關係的文件或者信息存在哪裏
可能存在於classpat、fileSystem、url或者context中,mvc
對於不一樣的存儲位置和文件格式,其實的描述是不相同的,如何作到統一解析和聲明
咱們能夠想一下,將這些外部的信息按照模型,進行轉換,在內部維護一個統一的模型對象app
如何對這些信息進行不一樣的解析
根據各自的特性指定相應的策略來進行解析ide
在 spring
中 BeanFactory
是頂層的容器接口,咱們能夠看出來其實 spring
中容器的本質就是工廠, 他有很是多的實現類,咱們這裏把主要的核心類圖展現:
對上圖作簡單的說明:函數
BeanFactory 是頂層的容器接口,主要有三個子類接口 HierarchicalBeanFactory
、 AutowireCapableBeanFactory
、 ListableBeanFactory
ui
在繼承的關係中咱們能夠看到都是接口和抽象類爲主,多層次的封裝,最終的實現類如 DefaultListableBeanFactory
,還有相似 AbstractApplicationContext
的抽象子類,在spring中這些接口都有本身特定的使用場景,對每種場景中不一樣對象的建立傳遞到轉化的過程當中都進行了相應的控制限制,有很強的領域劃分,職責單一可擴展性極強this
public interface BeanFactory { /** * 主要敢於區分beanFactory與factoryBean,FactoryBean是spring內部生成對象的工廠即容器, * 在咱們經過過getBean獲取對象時獲得的是真實對象的代理對象,若是咱們要獲取產生對象代理的 * 工廠則須要加該前綴 */ String FACTORY_BEAN_PREFIX = "&"; /** * 返回一個instance的實列 經過beanName */ Object getBean(String name) throws BeansException; /** * 經過BeanName與class類型來獲取容器中的對象,多層限制校驗 */ <T> T getBean(String name, Class<T> requiredType) throws BeansException; /** * 經過BeanName 同時指定相應的構造函數或者工廠方法的參數列表 */ 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; <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType); <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType); /* * 校驗是否在IOC中存在 */ boolean containsBean(String name); /* * 校驗是單例或者原型模式 */ boolean isSingleton(String name) throws NoSuchBeanDefinitionException; boolean isPrototype(String name) throws NoSuchBeanDefinitionException; /* * 判斷是IOC中bean的類型是不是typTomatch的類型 */ boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException; //獲取指定bean的類型 @Nullable Class<?> getType(String name) throws NoSuchBeanDefinitionException; @Nullable Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException; // 獲取bean的 別名 String[] getAliases(String name); }
在spring中 BeanFactory
定義了 容器的行爲,可是並不會去實現這些,頂級接口製做了高度的抽象化處理,具體的容器的建立和運行們都是交給子類來實現,因此咱們要知道IOC是如何運轉的須要從spring中ico的實現子類來入門,如咱們讀取xml配置方式時的 ClasspathXmlApplicationContext
或者時在註解中使用的 AnnotationConfigApplicationContext
等,在這些具體的實現類中有容器初始化的具體流程
在上面類圖中 ApplicationContext
類是很是重要的一個接口,這是spring提供的一個高級接口,也是咱們之後接觸最多的容器
beandefinition 是spring中對對象關係,對象建立等一系列的定義模型,其本質實際上是一個Map集合,其類圖咱們能夠看一下:
在咱們建立初始化容器時,也就是bean工廠時,會根據這個工廠建立相應的 BeanDefinitionReader 對象,這個reader對象是一個資源解析器,這個解析的過程是複雜的在咱們後邊的解析中會具體來看各自的實現
所屬包 org.springframework.core.io.ResourceLoader
,這是spring用來進行統一資源加載的頂級接口,裏面定義行爲,實現讓具體的子類實現,類圖咱們能夠看一下
類圖中展現的是 ResourceLoader
的核心實現, 在 spring
中容器也有實現該接口,關於統一資源加載的運轉後期會專門說明
所屬包 org.springframework.core.io.Resource
, 該類是 spring
中資源加載的策略實現頂層接口,該類的每一個實現類都是對某一種資源的訪問策略,類圖:
咱們在springMvc中很熟悉一個核心控制器 DispatcherServlet
, 這個類作了一個集中分發和 web
容器初始的功能,首先咱們來看一下類圖
DispatcherServlet
繼承了 HttpServlet
,咱們熟悉 HttpServlet
是屬於Servlet的 ,那麼它必然有個 init()
的初始化方法,咱們經過查看,能夠看到在 HttpServletBean
中重寫了 init
方法/** * 重寫了init方法,對ServletContext初始化 * 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 { // Set bean properties from init parameters. //讀取初始化參數 如web.xml中 init-param 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. //初始化容器 是讓子類FrameworkServlet具體實現的 initServletBean(); }
init
方法中具體的容器實例方法 FrameworkServlet
來實現的,咱們跟進去看一下 initialServletBean
的具體實現/** * Overridden method of {@link HttpServletBean}, invoked after any bean properties * have been set. Creates this servlet's WebApplicationContext. * 構建 web 上下文容器 */ @Override protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'"); if (logger.isInfoEnabled()) { logger.info("Initializing Servlet '" + getServletName() + "'"); } long startTime = System.currentTimeMillis(); try { //容器初始化 this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException | RuntimeException ex) { logger.error("Context initialization failed", ex); throw ex; } if (logger.isDebugEnabled()) { String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data"; logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value); } if (logger.isInfoEnabled()) { logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms"); } }
initWebApplicationContext()
正是容器初始化的方法,咱們繼續跟進,咱們如今是看容器初始化,其餘暫時過掉,後面講springmvc時在系統講解/** * 初始化web容器 WebApplicationContext * 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; //構建父子容器的關係,這裏判斷當前容器是否有, // 若存在則做爲子容器來給他設置父容器rootcontext 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根容器中尋找,找到則賦值 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(); } //若上面尋找也沒找到,則這裏進行容器的賦值 構建一個容器,可是這個容器並無初始化 只是創建了引用 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. synchronized (this.onRefreshMonitor) { onRefresh(wac); } } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }
onRefresh()
方法, 跟進找到 DispatcherServlet
中的實現類,其中又調用了 initStrategies()
方法,繼續進入/** * This implementation calls {@link #initStrategies}. */ @Override protected void onRefresh(ApplicationContext context) { initStrategies(context); } /** * 初始化容器,進行springmvc的9大組件初始化 * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */ protected void initStrategies(ApplicationContext context) { //多文件上傳組件 initMultipartResolver(context); //國際化組件,也就是本地語言環境 initLocaleResolver(context); //初始化主題模板處理器 initThemeResolver(context); //初始化HandMapping映射 initHandlerMappings(context); //初始化HandlerAdapters參數適配器 initHandlerAdapters(context); //初始化一場攔截組件 initHandlerExceptionResolvers(context); //初始化視圖預處理解析器, initRequestToViewNameTranslator(context); //初始化視圖解析器 initViewResolvers(context); //初始化FlashMap initFlashMapManager(context); }
IOC容器的初始化有多種方式,能夠是配置文件也能夠爲 Javaconfig
的方式,常見的如 ClassPathXmlApplicationContext
ApplicationContext
, ResourceLoader
是全部資源加載的基類,咱們能夠發現全部的IOC容器都是繼承了 BeanFactory
,這也說明了全部的容器本質上都是一個bean工廠ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(CONTEXT_WILDCARD);
那麼在這個建立容器的內部具體是如何構建加載容器的,咱們能夠進入看一下
//調用的構造函數 public ClassPathXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); }
這裏有調用的一個構造函數,這個纔是真真執行的過程,咱們發現內部執行力 refresh()
方法
public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { //調用父類的構造函數進行資源加載設置 super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
AnnotationConfigApplicationContext
、 FileSystemXmlApplicationContext
、 XmlWebApplicationContext
這些類都調用了 refresh()
方法,這個方法是他們父類 AbstractApplicationContext
實現的 ,這裏應用的了裝飾器模式策略模式本文由AnonyStar 發佈,可轉載但需聲明原文出處。
仰慕「優雅編碼的藝術」 堅信熟能生巧,努力改變人生
歡迎關注微信公帳號 :coder簡碼 獲取更多優質文章
更多文章關注筆者博客 :IT簡碼