從新認識 Spring IOC

spring IOC 剖析

再品IOC與DI

  • IOC(Inversion of Control) 控制反轉:所謂控制反轉,就是把原先咱們代碼裏面須要實現的對象創 建、依賴的代碼,反轉給容器來幫忙實現。那麼必然的咱們須要建立一個容器,同時須要一種描述來讓 容器知道須要建立的對象與對象的關係。這個描述最具體表現就是咱們所看到的配置文件。java

  • DI(Dependency Injection) 依賴注入:就是指對象是被動接受依賴類而不是本身主動去找,換句話說就 是指對象不是從容器中查找它依賴的類,而是在容器實例化對象的時候主動將它依賴的類注入給它。web

上面是 iocDI 的通俗理解,咱們也能夠用咱們現有的知識來思考這兩點的實現,其實二者主要仍是依賴與反射機制來實現這些功能,那麼咱們爲了提出一些關鍵問題,來跟着關鍵問題來看下具體的流程。spring

  • spring 中對象之間的關係如何來表現
    在咱們配置文件中或者javaconfig中均又相應的方式來提現微信

  • 描述對象之間關係的文件或者信息存在哪裏
    可能存在於classpat、fileSystem、url或者context中,mvc

  • 對於不一樣的存儲位置和文件格式,其實的描述是不相同的,如何作到統一解析和聲明
    咱們能夠想一下,將這些外部的信息按照模型,進行轉換,在內部維護一個統一的模型對象app

  • 如何對這些信息進行不一樣的解析
    根據各自的特性指定相應的策略來進行解析ide

IOC 容器的核心類

1. BeanFactory

springBeanFactory 是頂層的容器接口,咱們能夠看出來其實 spring 中容器的本質就是工廠, 他有很是多的實現類,咱們這裏把主要的核心類圖展現:beanFactory類圖.png
對上圖作簡單的說明:函數

  • BeanFactory 是頂層的容器接口,主要有三個子類接口 HierarchicalBeanFactoryAutowireCapableBeanFactoryListableBeanFactoryui

  • 在繼承的關係中咱們能夠看到都是接口和抽象類爲主,多層次的封裝,最終的實現類如 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提供的一個高級接口,也是咱們之後接觸最多的容器

2. BeanDefinition

beandefinition 是spring中對對象關係,對象建立等一系列的定義模型,其本質實際上是一個Map集合,其類圖咱們能夠看一下:
BeanDefinition類圖.png

3. BeanDefinitionReader

在咱們建立初始化容器時,也就是bean工廠時,會根據這個工廠建立相應的 BeanDefinitionReader 對象,這個reader對象是一個資源解析器,這個解析的過程是複雜的在咱們後邊的解析中會具體來看各自的實現

4. ResourceLoader

所屬包 org.springframework.core.io.ResourceLoader ,這是spring用來進行統一資源加載的頂級接口,裏面定義行爲,實現讓具體的子類實現,類圖咱們能夠看一下
ResourceLoader類圖.png
類圖中展現的是 ResourceLoader 的核心實現, 在 spring 中容器也有實現該接口,關於統一資源加載的運轉後期會專門說明

5. Resource

所屬包 org.springframework.core.io.Resource , 該類是 spring 中資源加載的策略實現頂層接口,該類的每一個實現類都是對某一種資源的訪問策略,類圖:
resource類圖.png

Web IOC 容器初識

咱們在springMvc中很熟悉一個核心控制器 DispatcherServlet , 這個類作了一個集中分發和 web 容器初始的功能,首先咱們來看一下類圖
dispatcherServlet類圖.png

  • 咱們能夠看到 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容器初始化

IOC容器的初始化有多種方式,能夠是配置文件也能夠爲 Javaconfig 的方式,常見的如 ClassPathXmlApplicationContext
ioc類圖.png

  • IOC中主要過程能夠概述爲_定位_、加載註冊 三個基本過程,咱們常見的容器都是 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();
		}
	}
  • 你們能夠本身看一下,其實像 AnnotationConfigApplicationContextFileSystemXmlApplicationContextXmlWebApplicationContext 這些類都調用了 refresh() 方法,這個方法是他們父類 AbstractApplicationContext 實現的 ,這裏應用的了裝飾器模式策略模式

本文由AnonyStar 發佈,可轉載但需聲明原文出處。
仰慕「優雅編碼的藝術」 堅信熟能生巧,努力改變人生
歡迎關注微信公帳號 :coder簡碼 獲取更多優質文章
更多文章關注筆者博客 :IT簡碼

相關文章
相關標籤/搜索