从新认识 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简码

相关文章
相关标签/搜索