重新認識 Spring IOC

spring IOC 剖析

再品IOC與DI

  • IOC(Inversion of Control) 控制反轉:所謂控制反轉,就是把原先我們代碼裡面需要實現的對象創 建、依賴的代碼,反轉給容器來幫忙實現。那麼必然的我們需要創建一個容器,同時需要一種描述來讓 容器知道需要創建的對象與對象的關係。這個描述最具體表現就是我們所看到的配置文件。
  • DI(Dependency Injection) 依賴注入:就是指對象是被動接受依賴類而不是自己主動去找,換句話說就 是指對象不是從容器中查找它依賴的類,而是在容器實例化對象的時候主動將它依賴的類注入給它。

上面是 ioc 和 DI 的通俗理解,我們也可以用我們現有的知識來思考這兩點的實現,其實兩者主要還是依賴與反射機制來實現這些功能,那麼我們為了提出一些關鍵問題,來跟著關鍵問題來看下具體的流程。

  • 在 spring 中對象之間的關係如何來表現 在我們配置文件中或者javaconfig中均有相應的方式來提現
  • 描述對象之間關係的文件或者信息存在哪裡 可能存在於classpat、fileSystem、url或者context中,
  • 對於不同的存儲位置和文件格式,其實的描述是不相同的,如何做到統一解析和聲明 我們可以想一下,將這些外部的信息按照模型,進行轉換,在內部維護一個統一的模型對象
  • 如何對這些信息進行不同的解析 根據各自的特性指定相應的策略來進行解析

IOC 容器的核心類

1. BeanFactory

在 spring 中 BeanFactory 是頂層的容器接口,我們可以看出來其實 spring 中容器的本質就是工廠, 他有非常多的實現類,我們這裡把主要的核心類圖展示:

重新認識 Spring IOC

對上圖做簡單的說明:

  • BeanFactory 是頂層的容器接口,主要有三個子類接口 HierarchicalBeanFactory 、 AutowireCapableBeanFactory 、 ListableBeanFactory
  • 在繼承的關係中我們可以看到都是接口和抽象類為主,多層次的封裝,最終的實現類如 DefaultListableBeanFactory ,還有類似 AbstractApplicationContext 的抽象子類,在spring中這些接口都有自己特定的使用場景,對每種場景中不同對象的創建傳遞到轉化的過程中都進行了相應的控制限制,有很強的領域劃分,職責單一可擴展性極強
<code>public interface BeanFactory {
/**
* 主要勇於區分beanFactory與factoryBean,FactoryBean是spring內部生成對象的工廠即容器,
* 在我們通過過getBean獲取對象時得到的是真實對象的代理對象,如果我們要獲取產生對象代理的
* 工廠則需要加該前綴
*/
String FACTORY_BEAN_PREFIX = "&";
/**
* 返回一個instance的實列 通過beanName
*/
Object getBean(String name) throws BeansException;
/**
* 通過BeanName與class類型來獲取容器中的對象,多層限制校驗
*/
T getBean(String name, Class requiredType) throws BeansException;
/**
* 通過BeanName 同時指定相應的構造函數或者工廠方法的參數列表
*/
Object getBean(String name, Object... args) throws BeansException;
T getBean(Class requiredType) throws BeansException;
T getBean(Class requiredType, Object... args) throws BeansException;
ObjectProvider getBeanProvider(Class requiredType);
ObjectProvider 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);
}
/<code>
  • 在spring中 BeanFactory 定義了 容器的行為,但是並不會去實現這些,頂級接口製作了高度的抽象化處理,具體的容器的創建和運行們都是交給子類來實現,所以我們要知道IOC是如何運轉的需要從spring中ico的實現子類來入門,如我們讀取xml配置方式時的 ClasspathXmlApplicationContext 或者是在註解中使用的 AnnotationConfigApplicationContext 等,在這些具體的實現類中有容器初始化的具體流程
  • 在上面類圖中 ApplicationContext 類是非常重要的一個接口,這是spring提供的一個高級接口,也是我們以後接觸最多的容器

2. BeanDefinition

beandefinition 是spring中對對象關係,對象創建等一系列的定義模型,其本質其實是一個Map集合,其類圖我們可以看一下:

重新認識 Spring IOC

3. BeanDefinitionReader

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

4. ResourceLoader

所屬包 org.springframework.core.io.ResourceLoader ,這是spring用來進行統一資源加載的頂級接口,裡面定義行為,實現讓具體的子類實現,類圖我們可以看一下

重新認識 Spring IOC

類圖中展示的是 ResourceLoader 的核心實現, 在 spring 中容器也有實現該接口,關於統一資源加載的運轉後期會專門說明

5. Resource

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

重新認識 Spring IOC

Web IOC 容器初識

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

重新認識 Spring IOC

  • 我們可以看到 DispatcherServlet 繼承了 HttpServlet ,我們熟悉 HttpServlet 是屬於Servlet的 ,那麼它必然有個 init() 的初始化方法,我們通過查看,可以看到在 HttpServletBean 中重寫了 init 方法
<code> /** * 重寫了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(); }/<code>
  • 我們可以看到, init 方法中具體的容器實例方法 FrameworkServlet 來實現的,我們跟進去看一下 initialServletBean 的具體實現
<code>    /**
* 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");
}
}/<code>
  • 我們看到 initWebApplicationContext() 正是容器初始化的方法,我們繼續跟進,我們現在是看容器初始化,其他暫時過掉,後面講springmvc時在系統講解
<code>    /** 

* 初始化web容器 WebApplicationContext * Initialize and publish the WebApplicationContext for this servlet. *

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; }
}

/<code>
  • 我們看到真真初始化容器的方法 onRefresh() 方法, 跟進找到 DispatcherServlet 中的實現類,其中又調用了 initStrategies() 方法,繼續進入
<code> /**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}

/**
* 初始化容器,進行springmvc的9大組件初始化 * Initialize the strategy objects that this servlet uses. *

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); }
}


/<code>
  • IOC容器初始化 IOC容器的初始化有多種方式,可以是配置文件也可以為 Javaconfig 的方式,常見的如 ClassPathXmlApplicationContext
  • IOC中主要過程可以概述為定位、加載、註冊 三個基本過程,我們常見的容器都是 ApplicationContext , ResourceLoader 是所有資源加載的基類,我們可以發現所有的IOC容器都是繼承了 BeanFactory ,這也說明了所有的容器本質上都是一個bean工廠
  • 我們可以通過下面的代碼獲取容器
<code>ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(CONTEXT_WILDCARD); /<code>
  • 那麼在這個創建容器的內部具體是如何構建加載容器的,我們可以進入看一下 //調用的構造函數
<code>    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[]{configLocation}, true, null);
}/<code>
  • 這裡有調用的一個構造函數,這個才是真真執行的過程,我們發現內部執行力 refresh() 方法
<code>    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
//調用父類的構造函數進行資源加載設置
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}/<code>
  • 大家可以自己看一下,其實像 AnnotationConfigApplicationContext 、 FileSystemXmlApplicationContext 、 XmlWebApplicationContext 這些類都調用了 refresh() 方法,這個方法是他們父類 AbstractApplicationContext 實現的 ,這裡應用的了裝飾器模式策略模式


分享到:


相關文章: