您的位置:首页 > 编程语言 > Java开发

Spring IoC原理理解

2017-12-21 16:43 281 查看
IoC与DI

Java IoC简单例子

IoC源码

IoC与DI

IoC,Inversion of Control,控制倒转。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。

DI,Dependency Injection,依赖注入。在系统运行中,动态的向某个对象提供它所需要的其他对象。

控制的什么被反转了?就是:获得依赖对象的方式反转了。

IoC举例

如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、微信号、qq号、电话号等等,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

那么IoC是如何做的呢?有点类似婚介所,一个婚介所有很多男男女女的个人信息,我可以向婚介所提出要求,比如长得像高圆圆,身材像林熙蕾等等,然后婚介所就会按照我们的要求,提供符合要求的一个mm信息,我们只需要去和mm谈朋友就好了。如果婚介所给我们的人选不符合要求,我们就提出不交往。整个过程不再由我自己来控制,而是由婚介所类控制中间环节。Spring开发也是如此,所有的类都会在Spring容器中登记,告诉Spring你是什么,你需要什么,然后Spring会在系统运行到适当的时候,把你需要的东西主动给你,同时也把你交给其他需要的你的类。所有类的创建、销毁都由Spring来控制,也就是说控制对象生存周期不再是引用它的对象,而是Spring。对于某个对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以就叫控制反转。

Java IoC简单例子

首先创建动物接口Animal

package com.feiniu.springframework.test;

public interface Animal {
public void say();
}


接着创建动物实现类Cat

package com.feiniu.springframework.test;

public class Cat implements Animal{

private String name;

@Override
public void say() {
System.out.println("I am " + name + "!");
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}


然后创建配置文件applicationContext.xml以及配置Cat类

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:task="http://www.springframework.org/schema/task" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd" default-autowire="byName">

<bean id="animal" class="com.feiniu.springframework.test.Cat">
<property name="name" value="kitty" />
</bean>

</beans>


最后创建测试类TestAnimal

package com.feiniu.springframework.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class TestAnimal {
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext(
"applicationContext.xml");
Animal animal = (Animal) context.getBean("animal");
animal.say();
}
}


执行main方法控制台结果

I am kitty!

IoC源码

我们来看看控制台输出的I am kitty背后spring究竟做了哪些事情。作为开发人员一般很熟悉applicationContext.xml,关于测试类TestAnimal有使用了FileSystemXmlApplicationContext,那么我们就从这里入手,源码调用类步骤如下:

(1)FileSystemXmlApplicationContext继承自AbstractXmlApplicationContext;

(2)继承AbstractRefreshableConfigApplicationContext;

(3)继承AbstractRefreshableApplicationContext;

(4)继承AbstractApplicationContext;

(5)实现接口ConfigurableApplicationContext;

(6)继承接口ApplicationContext;

(7)继承接口ListableBeanFactory,HierarchicalBeanFactory;

(8)继承接口BeanFactory。

由此可以看出,接口BeanFactory就是Spring源码的入口。

接下来关注FileSystemXmlApplicationContext类构造方法

public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}


然后调用带有多个参数的构造方法

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
//S1.调用父类容器的构造方法为容器设置好Bean资源加载器
super(parent);
//S2.告诉读取器Bean定义资源文件的定位路径
setConfigLocations(configLocations);
//S3.刷新
if (refresh) {
refresh();
}
}


S1.调用父类容器的构造方法为容器设置好Bean资源加载器

super(parent);

S1-1最终调用AbstractApplicationContext类构造方法AbstractApplicationContext,代码如下:

public AbstractApplicationContext(ApplicationContext parent) {
this.parent = parent;
this.resourcePatternResolver = getResourcePatternResolver();
this.environment = createEnvironment();
}


this.parent = parent;复制对象

this.resourcePatternResolver = getResourcePatternResolver();解析资源模式

this.environment = createEnvironment();创建环境参数

S1-1-1其实getResourcePatternResolver()方法就是保存了resourceLoader对象,后续解析资源路径会用到的

protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}


S1-1-1-1实现类PathMatchingResourcePatternResolver的构造方法代码如下:

public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}


S1-1-2加载环境配置createEnvironment()方法

protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}


类StandardEnvironment 继承了AbstractEnvironment 类,代码如下:

public class StandardEnvironment extends AbstractEnvironment {

public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

}


AbstractEnvironment类的构造方法加载了log信息设置,代码如下:

public AbstractEnvironment() {
String name = getClass().getSimpleName();
if (this.logger.isDebugEnabled()) {
this.logger.debug(format("Initializing new %s", name));
}
customizePropertySources(this.propertySources);
if (this.logger.isDebugEnabled()) {
this.logger.debug(format(
"Initialized %s with PropertySources %s", name, this.propertySources));
}
}


在这里整个S1流程走完了,整理一下步骤:

(1)调用父类构造方法;

(2)保存了一个ApplicationContext容器对象

(3)保存了一个ResourceLoader对象

(4)加载配置参数

S2.告诉读取器Bean定义资源文件的定位路径

setConfigLocations(configLocations);

调用了父类AbstractRefreshableConfigApplicationContext的setConfigLocations方法,代码如下:

public void setConfigLocations(String[] locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
//将字符串解析为路径
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}


然后调用了resolvePath方法,代码如下:

/**
* 解决给定的路径,用相应的替换占位符系统属性值,如有必要。应用于配置位置。
*/
protected String resolvePath(String path) {
return this.getEnvironment().resolveRequiredPlaceholders(path);
}


接下来调用了接口PropertyResolver的resolveRequiredPlaceholders方法,

实现类AbstractPropertyResolver实现了该方法,代码如下:

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (strictHelper == null) {
strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, strictHelper);
}


创建了占位符帮助对象createPlaceholderHelper,然后执行了解析占位符方法doResolvePlaceholders,代码如下:

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, new PlaceholderResolver() {
public String resolvePlaceholder(String placeholderName) {
return getProperty(placeholderName);
}
});
}


具体的解析代码就不展开了。

S3.刷新

refresh();

调用了父类AbstractApplicationContext的refresh()方法,代码如下:

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// 准备这个上下文刷新。
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
// 告诉子类刷新内部的bean工厂。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
// 准备在这种情况下使用的bean工厂。
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
// 允许在上下文子类中对bean工厂进行后处理。
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
// 在上下文中调用注册为bean的工厂处理器。
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
// 注册拦截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.
// 在特定的上下文子类中初始化其他特殊的bean。
onRefresh();

// Check for listener beans and register them.
// 检查监听器bean并注册它们。
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
// 实例化所有剩下的(非惰性初始化)单例。
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
// 最后一步:发布相应的事件。
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
// 销毁已经创建的单例资源。
destroyBeans();

// Reset 'active' flag.
// 重置“active”标志为false。
cancelRefresh(ex);

// Propagate exception to caller.
// 传播异常给调用者。
throw ex;
}
}
}


S3-1准备这个上下文刷新。

prepareRefresh();

调用了

/**
* 准备这个上下文进行刷新,设置其启动日期和活动标志以及执行任何财产来源的初始化。
*/
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();

synchronized (this.activeMonitor) {
this.active = true;
}

if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}

// 在上下文环境中初始化任何占位符属性源
initPropertySources();

// 验证标记为必需的所有属性是可解析的
// 请参阅ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
}


/**
* 用实际实例替换任何存根属性源。
* @请参阅 org.springframework.core.env.PropertySource.StubPropertySource
* @请参阅 org.springframework.web.context.support.WebApplicationContextUtils#initServletPropertySources
*/
protected void initPropertySources() {
// For subclasses: do nothing by default.
// 对于子类:默认情况下不做任何事情。
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: