Spring-IOC

Spring-IOC

系列文章目录

  1. Spring-IOC
  2. Spring-SpringMVC-原理
  3. Spring-拦截器
  4. Spring-SpringMVC-全局异常处理

摘要

本文简要介绍IOC概念,并会将IOC与Spring关系,还会简要分析其实现原理。

1 IOC

1.1 IOC基本概念

IOC,全名Inversion of Control,翻译过来就是控制反转。也就是说,依赖的对象本来是用到时由使用者自己创建,但在IOC中将控制权交给第三方管理,来创建所需对象。

1.2 IOC好处

  1. 减少耦合
  2. 接口相关
  3. 模块热插拔,只需更改配置文件就可替换实现子类。

1.3 DI

2004年,Martin Fowler对IOC的“哪些方面的控制被反转了呢?”这个问题提出的结论是:“获得依赖对象的过程被反转”。

控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更贴合的名字依赖注入(Dependency Injection),简称为DI,即运行时,主动对象依赖IOC容器动态地将所需对象注入,提供使用。

也就是说,依赖注入(DI)和控制反转(IOC)只是从不同的角度来描述同一件事情:通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。

0x02 Spring-IOC简介

IOC
简单来说,Spring 启动时读取类似applicationContext.xml这样的bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。具体可以点击这里

Spring 的 IoC 容器在完成这些底层工作的基础上,还提供了 Bean 实例缓存、生命周期管理、 Bean 实例代理、事件发布、资源装载等高级服务。

3 Spring核心接口

3.1 BeanFactory

BeanFactory

3.1.1 基本概念
  1. BeanFactory是Spring框架最核心的接口,它提供了高级IoC的配置机制

  2. BeanFactory接口面向Spring本身,而非使用者。

  3. BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例目标Bean

  4. 若需要对bean定义后处理器,必须在代码中通过手工调用spring方法进行注册

  5. BeanFactory最重要的方法就是定义了getBean

  6. 上图中其他重要概念如下:

    • BeanDefinitionRegistry:bean在Spring内用BeanDefinition定义,包含了其配置信息。注册时使用BeanDefinitionRegistry的方法向IOC容器注册。
    • ListableBeanFactory:定义了访问Bean基本信息的一些方法
    • HierarchicalBeanFactory:父子级联 IoC 容器的接口,子容器可以通过接口方法访问父容器; 通过 HierarchicalBeanFactory 接口, Spring 的 IoC 容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的 Bean,但父容器不能访问子容器的 Bean。Spring 使用父子容器实现了很多功能,比如在 Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中。这样,展现层 Bean 就可以引用业务层和持久层的 Bean,而业务层和持久层的 Bean 则看不到展现层的 Bean。
    • ConfigurableBeanFactory:增强了 IoC 容器的可定制性:它定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法;
    • AutowireCapableBeanFactory:定义了将容器中的 Bean 按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法;
    • SingletonBeanRegistry:单例Bean相关方法,如registerSingleton, getSingleton
  7. 初始化 new XmlBeanFactory(new Resource())示例:

    ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    Resource res = resolver.getResource("classpath:com/chengc/test/spring/bean.xml");
    // 通过 Resource 装载 Spring 配置信息并启动 IoC 容器
    BeanFactory bf = new XmlBeanFactory(res);
    System.out.println("init BeanFactory");
    Car car = bf.getBean("car",Car.class);
    System.out.println("car bean is ready for use!");
    

    以上方式中XmlBeanFactory是最简陋的BeanFactory实现,现已被标注为@Deprecated

    他通过解析Spring xml配置,加载到Resource。注意,通过BeanFactory启动时,Bean的初始化会发生在对该Bean的第一次调用时。

    而且BeanFactory还会缓存单例模式的Bean,非首次调用getBean就会直接从IOC缓存获取。

    注意,在初始化 BeanFactory 时,必须为其提供一种日志框架,比如使用Log4J, 即在类路径下提供 Log4J 配置文件,这样启动 Spring 容器才不会报错。

    也可以这么使用:

    // 创建IoC配置文件的抽象资源(包含BenaDefinition定义信息)
    ClassPathResource res = new ClassPathResource("com/chengc/test/spring/bean.xml");
    // 创建BeanFactory
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    // 创建BeanDefinition Reader
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
    // 读取配置信息,完成bean的载入和注册
    reader.loadBeanDefinitions(resource);
    
3.1.2 源码解读

BeanFactory源码如下:

public interface BeanFactory {    
  // 用来获取FactoryBean
  // 比如myJndiObject是FactoryBean,
  // 则&myJndiObject会返回factory类而不是工厂返回的对象实例   
  String FACTORY_BEAN_PREFIX = "&"; 
   
  // 获取该名称在IOC容器中的Bean实例    
  Object getBean(String name) throws BeansException;    

  // 获取该名称和指定类型在IOC容器中的Bean实例  
  Object getBean(String name, Class requiredType) throws BeansException;    

  // 检查指定名称Bean是否存在于IOC容器    
  boolean containsBean(String name);    
 
  // 检查指定名称Bean在IOC容器内是否单例模式 
  boolean isSingleton(String name) throws NoSuchBeanDefinitionException;    
 
  // 返回指定名称Bean实例的Class   
  Class getType(String name) throws NoSuchBeanDefinitionException;    
 
  // 得到Bean的别名。如果根据别名来检索,那么其原名和其他别名也会一同返回  
  String[] getAliases(String name);    
}

这里我们也看看最简单的XmlBeanFactory:

public class XmlBeanFactory extends DefaultListableBeanFactory {
    // 就是用了他来读取xml配置中的bean定义,并注册到IOC容器(XmlReaderContext)
	private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

	// 使用指定beans资源创建XmlBeanFactory
	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}
	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		// 这里就在加载资源文件中的bean了
		this.reader.loadBeanDefinitions(resource);
	}

}

3.2 FactoryBean

与BeanFactory注意区别。

在向Spring注册Bean的时候,如果该类实现了FactoryBean接口,那么当获取Bean的时候是调用该Bean的getObject()方法返回的对象。

3.3 ApplicationContext

ApplicationContext

3.3.1 基本概念
  1. 面向使用Spring框架的开发者,一般我们都直接使用ApplicationContext文件中定义的 Bean 而不是BeanFactory
  2. 虽然ApplicationContext继承自BeanFactory但他与BeanFactory不同,ApplicationContext在初始化应用上下文时就实例化所有的单例的Bean,所以启动时会较慢一些。不过,可以通过<bean id="person" calss="com.chengc.test.spring.Person" lazy-init="true" />(或者使用注解@Lazy(true))将Bean的加载方式设为懒加载,那么他将不会在ApplicationContext启动时被实例化,而是在首次调用getBean时实例化。
  3. 若需要对bean定义后处理器,其会利用反射机制自动识别出配置文件中的Processor,并自动注册到应用上下文中。
  4. 实现了 ApplicationListener 事件监听接口的 Bean 可以接收到容器事件 , 并对事件进行响应处理 。 在 ApplicationContext 抽象实现类AbstractApplicationContext中,我们可以发现存在一个 ApplicationEventMulticaster,它负责保存所有监听器,以便在容器产生上下文事件时通知这些事件监听者。
  5. 上图中重要接口和接口:
    • ApplicationEventPublisher:让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。
    • MessageSource:为应用提供 i18n 国际化消息访问的功能;
    • ResourcePatternResolver : 所有 ApplicationContext 实现类都实现了类似于- PathMatchingResourcePatternResolver 的功能,可以通过带前缀的 Ant 风格的资源文件路径装载 Spring 的配置文件。
    • LifeCycle:该接口是 Spring 2.0 加入的,该接口提供了 start()和 stop()两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被 ApplicationContext 实现及具体 Bean 实现, ApplicationContext 会将 start/stop 的信息传递给容器中所有实现了该接口的 Bean,以达到管理和控制 JMX、任务调度等目的。
    • ConfigurableApplicationContext 继承于 ApplicationContext,它主要新增加了refresh()close()方法,让 ApplicationContext 具有启动、刷新和关闭应用上下文的能力:
      • 在应用上下文关闭的情况下调用 refresh()即可启动应用上下文;
      • 在已经启动的状态下,调用 refresh()则清除缓存并重新装载配置信息,
      • 而调用close()则可关闭应用上下文。
      • 这些接口方法为容器的控制管理带来了便利,但作为开发者,我们并不需要过多关心这些方法。
3.3.2 重要实现类
// bean定义文件位于classpath下
new ClassPathXmlApplicationContext("classpath:bean.xml") 

// bean定义文件位于文件目录
new FileSystemXmlApplicationContext("file:beans.xml")

// Bean注解方式
new AnnotationConfigApplicationContext(ConfigTest.class)
3.3.3 AnnotationConfigApplicationContext实例
// 使用@Configguration注解是告诉spring这个类是一个配置类,相当于我们的xml文件
@Configuration
// @ComponentScan则是指定需要spring来扫描的包,相当于xml中的context:component-scan属性。
@ComponentScan("demos.spring.initMethodTest.demo1")
public class ConfigTest {
/**
 * @Bean用于声明本方法产生一个Bean对象
 * 注意:本方法只会被Spring只会调用一次,并会将该Bean交给Spring管理。
 *
 * 默认bean的名称就是其方法名。但是也可以指定名称。
 */
@Bean(name="test1", initMethod = "init", destroyMethod = "destroy")
Test1 getTest1() {
    return new Test1();
}

public static void main(String[] args) {
    AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext(ConfigTest.class);
    Test1 test11 = (Test1)configApplicationContext.getBean("test1");
    test11.sayHello();
    configApplicationContext.close();
}
}

// 要注册的Bean类
public class Test1 {
    public void init() {
        System.out.println("this is init method1");
    }
    public Test1() {
        super();
        System.out.println("构造函数1");
    }
    public void sayHello(){
        System.out.println(this + " Test1 Hello");
    }

    public void destroy() {
        System.out.println("this is destroy method1");
    }
}
3.3.4 ApplicationContext初始化详解

SpringBean构建

3.3.4.1 初始化触发

当我们执行以下代码:当我们执行以下代码:

ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("applicationContext.xml");

就会触发整个Spring applicationContext初始化过程,我们先看一个重要方法AbstractApplicationContext.refresh,他里面包含了各个初始化流程:

public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		// 这里面就是包括了context配置读取、bean解析为BeanDefinition、注册
		// 还有parentBeanFactory的注册,这很关键
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// 对BeanFactory准备工作,使之可用
		// 比如会将内部用的单例对象放入单例缓存,如StandardEnvironment
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);

			// Invoke factory processors registered as beans in the context.
			// 这一步会加载bean的class到JVM
			invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			// 实例化bean时,会利用容器中注册的Bean后处理器(实现BeanPostProcessor接口的Bean)
			// 对已经完成属性设置工作的Bean进行后续加工,直接装配出一个准备就绪的Bean
			registerBeanPostProcessors(beanFactory);

			// Initialize message source for this context.
			initMessageSource();

			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
			onRefresh();

			// 检查listener类并注册到Spring
			registerListeners();

			// 前面会初始化一些对象
			// 这一步会初始化剩余的非懒初始化的单例对象,
			// 比如包括我们例子中的 test4,initOrDestroyTest
			// 我们后面会分析
			finishBeanFactoryInitialization(beanFactory);

			// 最后一步:发布refresh事件
			// 继承了ApplicationListener<ContextRefreshedEvent>
			// 的onApplicationEvent(ContextRefreshedEvent event)方法会触发
			// 比如DispatcherServlet就利用这一事件来执行initStrategies方法
			// 进行一些基础Bean的初始化
			finishRefresh();
		}

		catch (BeansException ex) {
			logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();

			// Reset 'active' flag.
			cancelRefresh(ex);

			// Propagate exception to caller.
			throw ex;
		}
	}
}
3.3.4.2 将Spring配置文件定位为Resource

来源于上面AbstractApplicationContext.refresh方法内的obtainFreshBeanFactory()

ApplicationContext的所有实现类都实现了ResourceLoader接口,因此可以直接调用getResource方法获取Resource。需要注意,不同的ApplicationContext实现类使用getResource方法获取的资源类型不同。
代码位于AbstractBeanDefinitionReader.loadBeanDefinitions

// 这里的resources在我们例子里就是`applicationContext.xml`
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
3.3.4.3 将以Resource定位好的资源载入到BeanDefinition

来源于上面AbstractApplicationContext.refresh方法内的obtainFreshBeanFactory()

BeanDefinition被用来描述那些具有属性值、构造函数参数值以及关于具体方法实现的更多信息的Bean实例。

BeanDefinition的根据定位到的Resource资源对象中的Bean生成,他们在Spring的 IoC容器内被表示为BeanDefinition。IoC容器对Bean的管理和DI等都是通过操作BeanDefinition来进行的

下面看看ClassPathXmlApplicationContext载入BeanDefinition的方法loadBeanDefinitions的源码,会调用父类AbstractXmlApplicationContextloadBeanDefinitions方法:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
	// 为beanFactory创建读取BeanDefinition的reader
	// 这里的beanFactory是`DefaultListableBeanFactory`实例
	XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

	// 为beanDefinitionReader配置上下文参数参数
	beanDefinitionReader.setEnvironment(this.getEnvironment());
	beanDefinitionReader.setResourceLoader(this);
	beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
	
	// 初始化beanDefinitionReader
	initBeanDefinitionReader(beanDefinitionReader);
	// 真正开始读取beanDefinition
	loadBeanDefinitions(beanDefinitionReader);
}

继续看loadBeanDefinitions(beanDefinitionReader)

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        // 获取所有已定位到的resource资源位置(用户定义)
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            //载入resources
            reader.loadBeanDefinitions(configResources);
        }
        // 获取所有本地配置文件的位置,比如applicationContext.xml
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);//载入resources
        }
}

跟着代码走会走到XmlBeanDefinitionReader.loadBeanDefinitions方法,部分核心代码如下:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    // 将applicationContext.xml置为读入流
    InputStream inputStream = encodedResource.getResource().getInputStream();
	try {
		InputSource inputSource = new InputSource(inputStream);
		if (encodedResource.getEncoding() != null) {
			inputSource.setEncoding(encodedResource.getEncoding());
		}
		// 读取该bean文件数据流
		return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
	}
	finally {
		inputStream.close();
	}
}

继续看DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions方法核心代码:

// 这里的root就是xml文件的root节点,可以遍历xml文件内容
protected void doRegisterBeanDefinitions(Element root) {
	BeanDefinitionParserDelegate parent = this.delegate;
	// 会处理默认配置
	this.delegate = createDelegate(this.readerContext, root, parent);
	// 处理xml文件前需要做的,当前类实现为空
	// 可自定义子类来重写该方法,加入如权限控制等自定义功能
	preProcessXml(root);
	// 解析BeanDefinitions
	parseBeanDefinitions(root, this.delegate);
	// 处理xml文件后需要做的,当前类实现为空
	postProcessXml(root);
}

再看其parseBeanDefinitions方法:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
	if (delegate.isDefaultNamespace(root)) {
	// 默认命名空间
	    // 遍历xml文件节点
		NodeList nl = root.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			if (node instanceof Element) {
				Element ele = (Element) node;
				if (delegate.isDefaultNamespace(ele)) {
				    // 处理默认命名空间的`bean`,`alias`,`beans`,`import`标签
				    // 如 http://www.springframework.org/schema/beans
				    // 这个分支会处理定义在xml文件中如
				    // <bean id="initOrDestroyTest" class="demos.spring.initMethodTest.demo3.Test3" init-method="init" destroy-method="destroy">
				    // 将处理此类bean,转为BeanDefinition和注册到IOC
					parseDefaultElement(ele, delegate);
				}
				else {
				// 处理其他命名空间如http://www.springframework.org/schema/context
				// 如<context:component-scan base-package="demos.spring.initMethodTest.demo3"/>
				    // 调试代码发现,这个分支会处理scan即处理注解的类
				    // 主要是转为BeanDefinition和注册到IOC
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		delegate.parseCustomElement(root);
	}
}
3.3.4.4 将BeanDefiniton注册到容器中

来源于上面AbstractApplicationContext.refresh方法内的obtainFreshBeanFactory()

不管是在xml文件注册<bean>还是对类使用注解@Component,都会使用方法DefaultListableBeanFactory.registerBeanDefinition来注册已封装为BeanDefinition的Beans到Spring IOC容器。核心代码如下

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {
	synchronized (this.beanDefinitionMap) {
	    // beanName放入beanDefinitionNames
		this.beanDefinitionNames.add(beanName);
		// 以beanName为key,
		// 包装好的beanDefinition为value放入beanDefinitionMap
    	this.beanDefinitionMap.put(beanName, beanDefinition);
    }		
}

注册后beanDefinitionMap是一个ConcurrentHashMap,key为beanName,value为beanDefinition如图:
beanDefinitionMap

3.3.4.5 Bean的加载

来源于上面AbstractApplicationContext.refresh方法内的invokeBeanFactoryPostProcessors()

会调用DefaultListableBeanFactory.doGetBeanNamesForType方法,遍历DefaultListableBeanFactory.beanDefinitionNames,每个都调用AbstractBeanDefinition.resolveBeanClass进行代码加载:

public Class<?> resolveBeanClass(ClassLoader classLoader) throws ClassNotFoundException {
        // 例子className为:demos.spring.initMethodTest.demo3.Test3
		String className = getBeanClassName();
		if (className == null) {
			return null;
		}
		// 这里就是从缓存获取或是新加载Class
		Class<?> resolvedClass = ClassUtils.forName(className, classLoader);
		this.beanClass = resolvedClass;
		return resolvedClass;
	}

我们必须看看ClassUtils.forName:

public static Class<?> forName(String name, ClassLoader classLoader) throws ClassNotFoundException, LinkageError {
	Class<?> clazz = resolvePrimitiveClassName(name);
	if (clazz == null) {
	// 我们例子类clazz为null
	    // 先从静态的Class缓存获取Class
	    // 这个cache只装了通用Class
	    // 如java.lang.Object等
		clazz = commonClassCache.get(name);
	}
	if (clazz != null) {
	    // 如果已有,就直接返回该Class
		return clazz;
	}

	// 处理几种不同风格名称的数组,就是截取前面一段类名称,然后加载类
	if (name.endsWith(ARRAY_SUFFIX)) {
		String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
		Class<?> elementClass = forName(elementClassName, classLoader);
		return Array.newInstance(elementClass, 0).getClass();
	}
	...

	ClassLoader clToUse = classLoader;
	if (clToUse == null) {
	    // clToUse为空就获取默认ClassLoader
		clToUse = getDefaultClassLoader();
	}
	try {
	    // clToUse不为空直接用classLoader.loadClass装载目标类
	    // 否则用当前类的ClassLoader来加载目标类
		return (clToUse != null ? clToUse.loadClass(name) : Class.forName(name));
	}
	catch (ClassNotFoundException ex) {
		...
		throw ex;
	}
}
3.3.4.6 Bean的实例化

AbstractApplicationContext.finishBeanFactoryInitialization

前面我们提到过,他是Spring上下文初始化的倒数第二步,会初始化剩余的单例对象,并放入Spring容器内的单例缓存中,以便后续使用。在实例化过程中,使用BeanWrapper对Bean进行封装,BeanWrapper提供了很多以Java反射机制操作Bean的方法,它将结合该Bean的BeanDefinition以及容器中属性编辑器,完成Bean属性的设置工作;最后利用容器中注册的Bean后处理器(实现BeanPostProcessor接口的Bean)对已经完成属性设置工作的Bean进行后续加工,直接装配出一个准备就绪的Bean。

在该类的finishBeanFactoryInitialization方法中,有这样的代码:

// 提前初始化单例对象
beanFactory.preInstantiateSingletons();

跟下去,对beanDefinitionMap进行同步锁后遍历了beanDefinitionNames,随后调用会走到AbstractBeanFactory.doGetBean方法,走到下面代码分支(这里是我们例子中获取自定义bean):

Object bean;
Object sharedInstance = getSingleton(beanName);

//有一段getBean逻辑,后面再讲
...

if (!typeCheckOnly) {
// 如果不是只能有type来匹配bean
    // 标注该bean被创建
	markBeanAsCreated(beanName);
}

try {
	final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
	checkMergedBeanDefinition(mbd, beanName, args);
	
	// 如果有该Bean有@DependsOn注解,就先初始化依赖的那些Bean
	String[] dependsOn = mbd.getDependsOn();
	if (dependsOn != null) {
		for (String dependsOnBean : dependsOn) {
			getBean(dependsOnBean);
			registerDependentBean(dependsOnBean, beanName);
		}
}
	

if (mbd.isSingleton()) {
// 创建单例bean实例
	sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
		public Object getObject() throws BeansException {
			try {
			    // 创建bean时会调用该方法
				return createBean(beanName, mbd, args);
			}
			catch (BeansException ex) {
				destroySingleton(beanName);
				throw ex;
			}
		});
	// 得到实例化好的bean
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
// 创建多例bean实例
    // isPrototype的,每次都会创建新实例
	Object prototypeInstance = null;
	try {
		beforePrototypeCreation(beanName);
		// 创建新的bean实例
		prototypeInstance = createBean(beanName, mbd, args);
	}
	finally {
		afterPrototypeCreation(beanName);
	}
	// 按FactoryBean和非FactoryBean决定返回的真实对象实例
	bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

else {
// 处理比如request或session类的bean
 	String scopeName = mbd.getScope();
	final Scope scope = this.scopes.get(scopeName);
	if (scope == null) {
		throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
	}
	try {
		Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
			public Object getObject() throws BeansException {
				beforePrototypeCreation(beanName);
				try {
					return createBean(beanName, mbd, args);
				}
				finally {
					afterPrototypeCreation(beanName);
				}
			}
		});
		bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
	}
	catch (IllegalStateException ex) {
		throw new BeanCreationException(beanName,...)
	}
}	

return (T) bean;

继续看看上面这个getSingleton(在其父类DefaultSingletonBeanRegistry)方法核心代码在做啥:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    // 获取singletonObjects同步锁
	synchronized (this.singletonObjects) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null) {
		// 首次肯定为null
			// 这一步很关键,初始化bean实例
			singletonObject = singletonFactory.getObject();
			// 这里就将初始化好的bean实例添加到单例缓存singletonObjects了
			addSingleton(beanName, singletonObject);
		}
		return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

还需要看看singletonFactory.getObject()用到的AbstractAutowireCapableBeanFactory.createBean方法内容:

protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
		throws BeanCreationException {
	// 确认该bean的Class已经被加载、连接了
	resolveBeanClass(mbd, beanName);

	// Prepare method overrides.
	try {
		mbd.prepareMethodOverrides();
	}
	catch (BeanDefinitionValidationException ex) {
		throw new ...
	}

	try {
		// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
		Object bean = resolveBeforeInstantiation(beanName, mbd);
		if (bean != null) {
			return bean;
		}
	}
	catch (Throwable ex) {
		throw new ...
	}

	Object beanInstance = doCreateBean(beanName, mbd, args);
	// 返回已经处理好的、实例化好的bean实例
	return beanInstance;
}

上面的doCreateBean方法最终会调用SimpleInstantiationStrategy.instantiate获得目标bean Class默认构造方法,然后使用BeanUtils.instantiateClass构建bean实例并返回。

需要注意的是,单例对象创建会放入缓存,prototype对象则是每次都创建新的。

3.3.4.7 单例Bean的内存缓存

我们看看前面提到的DefaultSingletonBeanRegistry.addSingleton方法:

protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
	    // 这就是最核心的单例对象缓存
		this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}

可以看到,其实Spring IOC容器内存放单例对象的缓存是一个名为singletonObjectsConcurrentHashMap,位于org.springframework.beans.factory.support.DefaultSingletonBeanRegistry

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
3.3.4.8 getBean

客户端获取Bean时,如果是单例对象,直接从Spring IOC容器内的单例对象缓存(singletonObjects)中获取;否则创建一个新的对象返回。

如果是prototype类型,就每次创建新的对象实例返回。

我们现在对以下代码进行解读:

Test3 t3 = (Test3)context1.getBean("initOrDestroyTest");

会触发AbstractApplicationContext.getBean

public Object getBean(String name) throws BeansException {
    // 这里的BeanFactory就是DefaultListableBeanFactory实例
	return getBeanFactory().getBean(name);
}

然后会走到AbstractBeanFactory.doGetBean,核心代码如下:

protected <T> T doGetBean(
		final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
		throws BeansException {
	final String beanName = transformedBeanName(name);
	Object bean;

	// 会优先从`DefaultSingletonBeanRegistry.singletonObjects`缓存中获取单例对象
	// 在前面提到的第6步`Bean的实例化`后放入singletonObjects
	Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
	    // 如果是非FactoryBean直接返回,否则返回该FactoryBean.getObject
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	}
	else {
		// Fail if we're already creating this bean instance:
		// We're assumably within a circular reference.
		if (isPrototypeCurrentlyInCreation(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}

		// 否则尝试用父context的BeanFactory查找Bean实例
		BeanFactory parentBeanFactory = getParentBeanFactory();
		if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
			// Not found -> check parent.
			String nameToLookup = originalBeanName(name);
			if (args != null) {
				// 委托给父BeanFactory.getBean
				return (T) parentBeanFactory.getBean(nameToLookup, args);
			}
			else {
				// 委托给父BeanFactory.getBean,无参数版本
				return parentBeanFactory.getBean(nameToLookup, requiredType);
			}
		}

			// 创建bean实例的代码,前面已经分析过
		    ...
	return (T) bean;
}		
3.3.5 ApplicationContext Bean初始化小结
  • 如果用户为Bean配置了lazy-init属性,则会在容器在解析注册Bean定义时进行预实例化,触发依赖注入。
  • 默认情况下,Bean是饿汉式初始化的。具体来说,Bean是在ApplicationContext初始化的时候就被读取(定位Resource -> 包装为BeanDefinition)、注册(BeanDefinition形式注册到IOC)、加载(遍历BeanDefinitions逐一loadClass)、实例化(获取默认的无参Constructor.newInstance()),放在单例缓存(singletonObjects)中。
  • 在Spring中,如果Bean定义的单态模式(Singleton),则容器在创建之前先从缓存(singletonObjects)中查找,以确保整个容器中只存在一个实例对象。
  • 如果Bean定义的是原型模式(Prototype),则容器每次都会创建一个新的实例对象。除此之外,Bean定义还可以扩展为指定其生命周期范围。例如scope设为RequestSession
  • Request
    对于每个http请求,Spring容器会创建一个 scope为Request 的bean 的新实例。即这种scope的bean是http请求之间相互隔离的。 当该次请求处理完毕,对应的bean实例也就销毁(被回收)了。
  • Session
    Http Session级别,每次创建新的,互相隔离。
3.3.6 属性值注入Bean对象
  • 对于集合类型的属性,将其属性值解析为目标类型的集合后直接赋值给属性。
  • 对于非集合类型的属性,大量使用了JDK的反射和内省机制,通过属性的getter方法(reader method)获取指定属性注入以前的值,同时调用属性的setter方法(writer method)为属性设置注入后的值。

3.4 WebApplicationContext

WebApplicationContext

3.4.1 要点
  1. WebApplicationContext面向Web应用,可从Web根目录相对路径加载配置文件来进行初始化。
  2. WebApplicationContext需要获得ServletContext实例引用,也就是说他必须要在拥有Web容器的前提下才能完成启动工作。WebApplicationContext对象将作为属性被放置到ServletContext中,以便Web应用环境可以访问Spring应用上下文。
3.4.2 配置方式

Spring分别提供了用于启动WebApplicationContext的Servlet和Listener,在web.xml中配置

org.springframework.web.context.ContextLoaderServlet
org.springframework.web.context.ContextLoaderListener
3.4.3 ServletContextListener

该类是ContextLoaderListener的父接口。实现该接口的类会在Web应用启动和结束的时候分别调用其中定义的两个重要方法一次:

public interface ServletContextListener extends EventListener {
	// 当Web应用的初始化进程正在执行。
	// 注意,会在该Web应用的所有filter和servlet被初始化前,
	// 就执行所有ServletContextListener实现类的该方法进行初始化通知
    public void contextInitialized ( ServletContextEvent sce );

	// 当ServletContext准备关闭时调用本方法进行通知。
	// 注意,会在该Web应用的所有filter和servlet被销毁(destroy())后,
	// 才执行所有ServletContextListener实现类的该方法进行初始化通知
    public void contextDestroyed ( ServletContextEvent sce );
}
  • 也就是说,我们可以利用contextInitialized方法调用早于所有servlet和filter初始化这一特点,进行最早的一些初始化行为
  • 也可以利用contextDestroyed方法调用晚于所有servlet和filter销毁这一特点,进行最后的一些如资源释放等收尾动作
3.4.4 ContextLoaderListener初始化方式

ContextLoaderListener继承自ServletContextListener,被用来初始化WebApplicationContext,部分源码如下:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
	// 在web.xml中配置了该ContextLoaderListener,Web应用启动时会调用该方法
	// initWebApplicationContext创建WebApplicationContext
	public void contextInitialized(ServletContextEvent event) {
		this.contextLoader = createContextLoader();
		if (this.contextLoader == null) {
			this.contextLoader = this;
		}
		this.contextLoader.initWebApplicationContext(event.getServletContext());
	}
}

继续看看contextLoader.initWebApplicationContext核心代码:

// 创建了WebApplicationContext
this.context = createWebApplicationContext(servletContext);
// 将WebApplicationContext作为属性放入servletContext
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

可以看到新创建的WebApplicationContext被放入了servletContext,key为ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"
WebApplicationContext和servletContext

我们也可以自己继承ContextLoaderListener,并配置到web.xml,在初始化WebApplicationContext之前加上自己的一些逻辑。

public class ContextLoaderListenerTest extends ContextLoaderListener {
    private static Logger LOG = LoggerFactory
            .getLogger(ContextLoaderListenerTest.class);

    /**
     * Parent ContextLoaderListener will Initialize the root web application context.
     */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        LOG.info("*************ContextLoaderListenerTest*************");
        ServletContext servletContext = servletContextEvent.getServletContext();
        LOG.info("contextConfigLocation"+servletContext.getInitParameter("contextConfigLocation"));
        LOG.info("key:"+servletContext.getInitParameter("username"));
        super.contextInitialized(servletContextEvent);
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        LOG.info("ContextLoaderListenerTest ContextDestroyed");
    }
}
3.4.5 WebApplicationContext启动小结
  1. 启动Web应用,Web容器读取web.xml配置文件,执行startContext方法初始化ServletContext,他在Web应用内全局共享。可通过其getInitParameter方法获取web.xml中配置的context-paramcontextConfigLocation
  2. web.xml中的ContextLoaderListener被触发contextInitialized事件,初始化WebApplicationContext
  3. 创建出一个WebApplicationContext的实现类XmlWebApplicationContext(默认,可通过context-param<param-name>contextClass指定),他即是Spring IOC容器。
  4. 上一步创建的XmlWebApplicationContext同时继承了ConfigurableWebApplicationContext,会把<context-param>中的contextConfigLocation指定的如classpath:spring/applicationContext.xml的资源位置引入。同时,该context含有ServletContext的引用。
  5. 创建完XmlWebApplicationContext后,会对其父类调用一次AbstractApplicationContext.refresh方法,对目标位置的bean进行装载、初始化等工作,与前面提到的ApplicationContext初始化流程相同。这个过程会比较慢。(注意,这里有一点不同,loadBeanDefinitions的时候是调用的XmlWebApplicationContext内的相关方法)
  6. 将WebApplicationContext放入servletContext
    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);这里就把初始化好的WebApplicationContext放入了servletContext。
  7. 到这里,ApplicationContext初始化完毕。这个阶段就已经将applicationContext.xml中的定义的bean或定义的scan范围中的注解引入的bean装载并初始化,加入IOC单例对象缓存了。
  8. 注意,这个阶段初始化bean一般不包括类似web-demo/src/main/webapp/WEB-INF/spring-web-servlet.xml中定义的@ControllerinterceptorVelocityConfigurerVelocityLayoutViewResolverCommonsMultipartResolver等。他们的初始化一般是在FrameworkServlet.initWebApplicationContext方法中创建了该servlet独有XmlWebApplicationContext之后,调用refresh方法后进行。
3.4.6 WebApplicationContext需要使用日志功能
  • 用户可以将Log4j配置文件放到类路径 WEB-INF/classes下,这时Log4j引擎即可顺利启动
  • 用户可以在web.xml中通过Log4jConfigServlet和Log4jConfigListener指定Log4j配置文件位置,但要注意启动顺序(load-on-startup)

3.5 Servlet-context.xml

  1. 在3.4.5小结讲述了ApplicationContext初始化步骤,随后Spring容器开始执行Servlet初始化。

  2. 比如我们在web.xml中通常都会定义的SpringMVC用的DispatcherServlet。他是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。

    DispatcherServlet在初始化的时候会建立自己的IoC上下文容器,来装载SpringMVC相关的Bean,这个servlet自己持有的上下文默认实现类也是XmlWebApplicationContext。

  3. 在DispatcherServlet建立自己的IoC context时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE从ServletContext中获取Root Context(即XmlWebApplicationContext)作为自己context的parent context

  4. 此后,DispatcherServlet开始初始化自己的context(initStrategies方法,大概的工作就是初始化处理器映射、视图解析等)。

  5. 初始化完毕后,Spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换)的属性为Key,也将其存到ServletContext中,以便后续使用。

  6. 这样每个servlet就持有自己的context,即拥有自己独立的bean空间。同时各个servlet共享相同的bean,即根XmlWebApplicationContext中定义的那些bean。

具体分析请参见Spring-SpringMVC-原理

3.6 RootContext和DispatcherServletContext关系

3.6.1 概念
  • Root XmlWebApplicationContext最先创建,一般会初始化非Controller的类,类似以下applicationContext.xml配置:

    <context:component-scan base-package="com.chengc.demos.web.demo1.*">
          <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
  • Servlet XmlWebApplicationContext会初始化一系列bean,一般包括类似web-demo/src/main/webapp/WEB-INF/spring-web-servlet.xml中定义的@ControllerinterceptorVelocityConfigurerVelocityLayoutViewResolverCommonsMultipartResolver等

  • Servlet XmlWebApplicationContext将Root XmlWebApplicationContext作为他的ParentContext,在getBean的时候会先尝试自己获取,如果获取不到就委托给ParentContext的BeanFactory获取;反之不行。这也就说明了为什么Controller内能引用@Service,@Repository等,但反之不行的道理。

3.6.2 问题
  • 不少开发者不知道Spring mvc里分有两个WebApplicationContext,导致各种重复构造bean,各种bean无法注入的问题。
  • 有一些bean,比如全局的aop处理的类,如果先root WebApplicationContext里初始化了,那么child WebApplicationContext里的初始化的bean就没有处理到;如果在child WebApplicationContext里初始化,在root WebApplicationContext里的类就没有办法注入了。
  • 区分哪些bean放在root/child很麻烦,不小心容易搞错,而且费心思。

所以可以将所有bean都放在 root WebApplicationContext中加载。

3.6.3 Springboot的玩法

Springboot默认情况下只有一个WebApplicationContext,即org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext

3.7 Resource和ResourceLoader

  • org.springframework.core.io.Resource接口是从实际类型的底层资源(例如 classpath , filesystem ,或者是 URL 网络资源, servletContext 等资源)中抽象出来的资源描述符,对具体资源解耦。
  • org.springframework.core.io.ResourceLoader使用了策略模式,被用来加载Resource。可通过传入Resource的信息,自动选择适合的底层Resource实现类,为生产对Resource的引用提供了极大的便利。

3.8 ApplicationListener

前面提到过的AbstractApplicationContext.refresh的最后一步发布refresh事件,会使得继承了ApplicationListener<ContextRefreshedEvent>onApplicationEvent(ContextRefreshedEvent event)方法触发。

比如DispatcherServlet就利用这一事件来执行initStrategies方法,进行一些基础Bean的初始化。具体代码如下:

4 Bean的常用配置

Bean配置

如果需要注册到Spring的构造函数带参数,可以通过类似以下方式配置:

<bean id="target" class="com.chengc.test.spring.Person">
    <!-- nickname为参数名称,value为值 -->
	<constructor-arg name="nickname" value="jerry"></constructor-arg>  
</bean>

5 Spring使用

5.1 Java加载ApplicationContext.xml的方式

参考Java类中加载Spring中的ApplicationContext.xml文件的方式

5.2 Java项目中获取ApplicationContext的方法

5.3 import标签

会将import标签指向的多个applicationContext.xml文件和本applicationContext.xml文件合并,最终合并为一个ApplicationContext,所以这些文件内定义的JavaBean可互相引用。

6 Spring-IOC缺点

  • 性能损耗
  • 引入Spring框架较重

7 IOC其他方案

7.1 Goole-Guice

本节内容转自 https://www.cnblogs.com/whitewolf/p/4185908.html
Guice由Google大牛Bob lee开发,是一款绝对轻量级的java IOC容器框架。

feature如下:

  • 速度快,号称比spring快100倍。
  • 无外部配置(如需要使用外部可以可以选用Guice的扩展包)
  • 完全基于·annotation·特性,支持重构,代码静态检查
  • 简单,快速,基本没有学习成本
  • Guice和spring各有所长,Guice更适合与嵌入式或者高性能但项目简单方案,如OSGI容器,spring更适合大型项目组织

更多好文

0xFF 参考文档

Spring的IOC原理[通俗解释一下]

Spring 3.x 企业应用实战—— IoC 概述

Spring的FactoryBean接口理解

《Spring3.X企业应用开发实战》学习笔记–IoC和AOP

根据Spring中的核心IoC深度理解设计模式(一)

Spring IOC原理总结

扯谈 spring mvc 之 WebApplicationContext 的继承关系

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值