struts2源码分析-IOC容器的实现机制(上篇)

Posted Java精讲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了struts2源码分析-IOC容器的实现机制(上篇)相关的知识,希望对你有一定的参考价值。

说起 IOC 容器,依赖注入等名词,大家的第一印象往往是spring,因为spring刚出道的时候招牌就是 IOC和AOP等核心功能,而且我们在应用程序中使用spring最多的功能之一也是其 IOC 容器提供的。而 struts2做为一个 web层的MVC实现框架,其核心功能主要是帮助我们处理 http请求,但是 struts2本身也包含了一个 IOC 容器,用来支撑struts2的运行环境,并具备对象的获取和依赖注入功能,在搭建 ssh架构的时候,如果我们不配置 struts2和spring整合的插件,不把 Action转交给 spring-ioc来托管,那么struts2自身的ioc容器也足以满足我们的需求。而且个人以为 相比于 spring的ioc实现, struts2-ioc 的源代码更加精致、小巧,便于研究和学习。

一、struts2-IOC容器简介

对于 一个ioc 容器来说,其核心功能是对象获取和依赖注入,在struts2 中容器的接口表示如下:

public interface Container extends Serializable {

String DEFAULT_NAME = "default";

void inject(Object o);

<T> T inject(Class<T> implementation);

<T> T getInstance(Class<T> type, String name);

<T> T getInstance(Class<T> type);

Set<String> getInstanceNames(Class<?> type);

void setScopeStrategy(Scope.Strategy scopeStrategy);

void removeScopeStrategy();
}

Containter接口的表示方法中,我们可以非常直观的看出 IOC容器的对象获取和依赖注入这两个功能被表示为重载方法 getInstance 和 inject,这里我先对getInstance(Class<T> type, String name) ``和 inject(Object )这个方法进行简单的分析。

getInstance(Class<T> type, String name) 方法是从struts2容器中获取对象,那么strus2-ioc管理的是哪些对象呢?或者说其管理的对象范围。我们都知道struts2程序启动初始化时会去加载至少3个xml配置文件: struts-default.xml struts-plugin.xml 和 struts.xml,其中前两者是struts2自带的框架级别的配置,后者是我们自己项目中的应用级别的配置,那么ioc容器所管理的对象就是在这其中配置的对象,我以我们开发中常见的 struts.xml为例,现在我想将两个 Service 对象放入 struts2-ioc中,配置如下:

 <bean  type="com.service.UserService" name="service1" class="com.service.impl.UserServiceImp1" />
<bean type="com.service.UserService" name="service2" class="com.service.impl.UserServiceImp2" />

如果我想从 struts2-ioc 容器中获取 UserServiceImp1 对象,代码为:ActionContext.getContainter().getInstance(UserService.class,"service1"); 大家应该看明白 getInstance(Class<T> type, String name) 方法的使用方式了吧,其中的type即为 配置文件中声明的接口类型(当然也可以是具体类), class 为其中一种具体实现方式,而 name 为 标识,所以结论就是 以 typename作为联合主键从ioc容器管理的对象中获取相应的对象!

对于 inject(Object o),顾名思义,从字面上来看就知道是进行依赖注入操作,这里要强调的是,该方法是为参数Object o 这个对象注入在容器中管理的对象,建立起参数对象Object o 和容器管理对象之间的依赖关系, 这个参数对象 Object o 可以是容器管理的对象,也可以是我们自定义的对象,即它可以是任意对象,不必和getInstance(Class<T> type, String name)方法那样只能是容器管理的对象。 那么容器怎么判断应该为该任意对象的哪些字段或者方法参数实施依赖注入呢?在 Spring中我们是通过 @Autowired 注解或者 在 xml 文件中配置依赖关系的。在 struts2的ioc实现中,则也是通过注解的方式实现,即 @Inject 来实现的,一旦我们在任意对象的方法参数、构造参数、字段上加入@Inject注解声明,那么就是在告诉ioc容器:请为我注入由容器管理的对象实例吧。

@Target({METHOD, CONSTRUCTOR, FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface Inject {
String value() default DEFAULT_NAME;

boolean required() default true;
}

以上 Inject 注解中的 value字段对应的就是之前我们在 xml 配置文件中的 name 属性,如之前的 "service1" 或 "service 2" ,其默认值为 "default"。Struts2通过此Inject注解,架起了任意对象与ioc容器进行通信的桥梁,使得受ioc容器管理的对象能够注入到任意对象对应的带Inject注解的方法参数和字段上。

下面我就以struts2中创建一个Action对象并进行依赖注入为例子,看看实际开发中是如何使用该功能的。

public class InjectAction extends ActionSupport {
@Inject(value = "service1")
private UserService service1;

@Override
public String execute() throws Exception {
UserService service2 = ActionContext.getContext().getContainer().getInstance(UserService.class, "service2");
return SUCCESS;
}
}

我们看到了,在实例字段中我们加入了 @Inject 声明进行依赖注入,那么结合之前所述可以推测到,struts2的源码必定在创建Action的时候,调用的 inject 方法对Action对象进行依赖注入,果不其然,源码如下:

 public Object buildAction(String actionName, String namespace, ActionConfig config, Map<String, Object> extraContext) throws Exception {
return buildBean(config.getClassName(), extraContext);
}

public Object buildBean(String className, Map<String, Object> extraContext) throws Exception {
return buildBean(className, extraContext, true);
}

public Object buildBean(String className, Map<String, Object> extraContext, boolean injectInternal) throws Exception {
Class clazz = getClassInstance(className);
Object obj = buildBean(clazz, extraContext);
if (injectInternal) {
injectInternalBeans(obj);
}
return obj;
}
protected Object injectInternalBeans(Object obj) {
if (obj != null && container != null) {
container.inject(obj);
}
return obj;
}

injectInternalBeans方法内调用了 Containter容器的 inject(Object o)方法对创建的Action对象进行依赖注入操作。 到这里,我们算是对 struts2的ioc容器有了个初步的了解,知道了如何使用struts2-ioc容器获取对象和进行依赖注入。那么接下来,我们就要深入到struts2的ioc容器实现部分,看看其到底是如何实现获取对象和依赖注入功能的。

二、Struts2-IOC容器初始化详解

提到IOC容器的初始化就不得不提到struts2 中另一种元素:package 的初始化,因为在struts2的初始化过程中不仅对容器初始化,也包含了对package这种事件映射元素的初始化,这里先简单说下这两者的区别,我们来看看常见的struts.xml中的配置:

<struts>
<bean type="com.service.UserService" name="service1" class="com.service.impl.UserServiceImp1" />
<bean type="com.service.UserService" name="service2" class="com.service.impl.UserServiceImp2" />
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
<constant name="struts.devMode" value="false" />

<package name="default" namespace="/" extends="struts-default">

<global-results>
<result name="error">/error.jsp</result>
</global-results>

<global-exception-mappings>
<exception-mapping exception="java.lang.Exception" result="error"/>
</global-exception-mappings>

<action name="injectAction" class="app.InjectAction">
<result name="success">/index.jsp</result>
</action>
</package>

</struts>

其中的 beanconstant节点,再加上 properties文件中的配置元素,这三者称为容器配置元素,即是由容器管理的对象,在容器初始化过程中要注册到容器中去。而对于 Package节点,里面包含了 action,interceptor,result等运行时的事件映射节点,这些节点元素并不需要纳入容器中管理。所以这里一个结论就是 struts2初始化的核心就是对容器配置元素和事件映射元素这两种不同元素的初始化过程,再进一步的讲就是将以各种形式配置的这两种元素转换为JAVA对象并统一管理的过程。由于这两种配置元素都有各种各样的表现形式,如之前看到的 xml配置的形式,属性文件properties的形式,还有我们进行扩展时自定义的其他形式,struts2插件中的 annotation形式等等。所以struts2在加载这些缤纷繁杂的各种配置形式时做了一定的考虑。

针对两种不同的元素类型,struts2设计了两个接口: ContainterProviderPackageProvider,它们对外提供的功能主要就是从配置文件中加载对应的元素并转换为JAVA对象。这里的 ConfigurationProvider 则是应用了java中的接口多重继承机制,目的在于对两种Provider加载器进一步抽象,使得我们不必关心具体的实现方式是针对哪种类型的加载器,提供一个统一的接口和操作方法;而且对于诸如struts.xml这样的配置文件来说,其中即包括了容器配置元素也包括了Package事件映射元素,那么对应的加载器必须同时具备ContainterProviderPackageProvider的接口方法,这样就只需要实现 ConfigurationProvider 接口即可。而对于只需要处理一种元素类型的加载器来说,个人认为只需要实现ContainterProviderPackageProvider的其中一个接口即可。比方说在 struts2-plugin中的注解插件包中,我们将 Package中的元素以注解的方式进行配置,那么struts2在初始化的时候除了从struts.xml中读取package元素外,还需要扫描所有的Action类,读取其中我们配置的package元素注解,并转化为java对象,对于这样的注解加载器来说,由于只需要处理Pakacge元素的加载,所以provider实现类只需要实现PackageProvider接口,翻开源代码查看,也确实验证了我的想法,这里又不花太多篇幅了,这部分的源码在 struts2-convention-plugin 下,有兴趣的话可以看看。在struts2的核心包中,具体的Provider 实现类有多种。

DefaultPropertiesProvider主要是处理属性文件形式的配置文件,而 StrtusXmlConfigurationProvider则主要是处理以 XML形式存在的配置文件,比如strtus-default.xml , struts.xml和 struts-plugin.xml。之前提到过,加载器的作用就是在struts初始化的时候将各种不同形式的配置文件中的元素转换为java对象,进一步的理解就是将容器配置元素和package事件处理元素读取到系统中来并建立对应的Java对象,struts初始化的最终结果就是读取了所有容器配置元素并创建出Container(IOC容器)对象和PackageConfig(pckage事件处理对象),我们知道,无论是Container对象还是PackageConfig对象,在创建它们的时候都不可能是一个简单的new操作就可以的,因为它们二者的内部结构是复杂的,前者必须是包含了所有的容器配置对象而后者必须包含了所有的package时间配置对象,为了创建这两个内部结构复杂的对象,struts2在这里使用了构造者模式,即存在一个构造者对象分别为 containterPackageConfig收集参数字段,前者是收集容器配置对象,后者是收集package事件映射对象,而各种Provider实现类就是往构造者对象中进行注册读取的配置对象,也就是构造者对象的参数收集过程。

这里我们首先关注的是和Provider有关的部分,我们看到 在Configuration中循环调用了每一个Provider实现类的 register()方法,这个register()方法是在 ContainterProvider接口中声明的,所以该方法的作用就是往IOC容器(Container)中注册将要被容器托管的对象,可以想象下ContainerProvider 的register()方法肯定就是解析各种形式的容器配置元素,转化为Java对象,然后注册到容器的构造者对象 ContainerBuilder中去,其中的 factory()就是这个注册的作用,待所有的ContainterProvider实现类将各自对应的容器配置元素都注册到ContainerBuilder中之后,Configuration调用ContainerBuildercreate()方法就能返回一个被正确初始化了的IOC容器Container了。接下来我以一个Provider的实现类StrutsXmlConfigurationProvider为例子,来具体看一看它的register()方法。StrutsXmlConfigurationProvider主要是负责将 struts-default.xmlstruts-plugin.xmlstruts.xml中的容器配置元素和package配置元素加载到系统中来,它实现了 ConfigurationProvider接口,所以其具体的 register()方法就是解析xml文件中配置的容器配置元素并注册到ContainerBuilder中去,而具体的loadPackage()方法就是解析xml配置文件中的package元素并注册到package构造者对象PackageConfig.Builder中去(这是一个内部类)。

public class StrutsXmlConfigurationProvider implements ConfigurationProvider{
// 此处省略N多代码
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {
if (LOG.isInfoEnabled()) {
LOG.info("Parsing configuration file [" + configFileName + "]");
}
Map<String, Node> loadedBeans = new HashMap<String, Node>();
for (Document doc : documents) {
Element rootElement = doc.getDocumentElement();
NodeList children = rootElement.getChildNodes();
int childSize = children.getLength();

for (int i = 0; i < childSize; i++) {
Node childNode = children.item(i);

if (childNode instanceof Element) {
Element child = (Element) childNode;

final String nodeName = child.getNodeName();

if ("bean".equals(nodeName)) {
String type = child.getAttribute("type");
String name = child.getAttribute("name");
String impl = child.getAttribute("class");
String onlyStatic = child.getAttribute("static");
String scopeStr = child.getAttribute("scope");
boolean optional = "true".equals(child.getAttribute("optional"));
Scope scope = Scope.SINGLETON;

if ("default".equals(scopeStr)) {
scope = Scope.DEFAULT;
} else if ("request".equals(scopeStr)) {
scope = Scope.REQUEST;
} else if ("session".equals(scopeStr)) {
scope = Scope.SESSION;
} else if ("singleton".equals(scopeStr)) {
scope = Scope.SINGLETON;
} else if ("thread".equals(scopeStr)) {
scope = Scope.THREAD;
}

if (StringUtils.isEmpty(name)) {
name = Container.DEFAULT_NAME;
}

try {
Class cimpl = ClassLoaderUtil.loadClass(impl, getClass());
Class ctype = cimpl;
if (StringUtils.isNotEmpty(type)) {
ctype = ClassLoaderUtil.loadClass(type, getClass());
}
if ("true".equals(onlyStatic)) {
// Force loading of class to detect no class def found exceptions
cimpl.getDeclaredClasses();
containerBuilder.injectStatics(cimpl);
} else {
if (containerBuilder.contains(ctype, name)) {
Location loc = LocationUtils.getLocation(loadedBeans.get(ctype.getName() + name));
if (throwExceptionOnDuplicateBeans) {
throw new ConfigurationException("Bean type " + ctype + " with the name " +
name + " has already been loaded by " + loc, child);
}
}

// Force loading of class to detect no class def found exceptions
cimpl.getDeclaredConstructors();

if (LOG.isDebugEnabled()) {
LOG.debug("Loaded type:" + type + " name:" + name + " impl:" + impl);
}
// LocatableFactory 的create方法中真正调用 containterImpl中的 inject(Class clas)方法进行对象的创建和注入;
//有别于 inject(Object obj),后者不必对构造函数参数注入和创建对象,因为传入的已经是个创建出的对象了,前者则是Class对象。
//将该 LocatableFactory对象注册到 containerBuilder 中去,此时还没有调用 LocatableFactory的create方法。
containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);
}
loadedBeans.put(ctype.getName() + name, child);
} catch (Throwable ex) {
if (!optional) {
throw new ConfigurationException("Unable to load bean: type:" + type + " class:" + impl, ex, childNode);
} else {
LOG.debug("Unable to load optional class: " + ex);
}
}
} else if ("constant".equals(nodeName)) {
String name = child.getAttribute("name");
String value = child.getAttribute("value");
props.setProperty(name, value, childNode);
} else if (nodeName.equals("unknown-handler-stack")) {
List<UnknownHandlerConfig> unknownHandlerStack = new ArrayList<UnknownHandlerConfig>();
NodeList unknownHandlers = child.getElementsByTagName("unknown-handler-ref");
int unknownHandlersSize = unknownHandlers.getLength();

for (int k = 0; k < unknownHandlersSize; k++) {
Element unknownHandler = (Element) unknownHandlers.item(k);
unknownHandlerStack.add(new UnknownHandlerConfig(unknownHandler.getAttribute("name")));
}

if (!unknownHandlerStack.isEmpty())
configuration.setUnknownHandlerStack(unknownHandlerStack);
}
}
}
}
}
}

代码开头的两个for循环就是对加载进来的Dom4j的 Document对象进行遍历,处理其中的每一个xml节点。再次拿起之前我们在 struts.xml 中配置的 Bean节点为例:

<bean type="com.service.UserService" name="service1" class="com.service.impl.UserServiceImp1" /> 

我们结合以上的代码来看看struts是如何将这个容器配置元素注册到ContainerBuilder中去的。首先在 if("bean".equals(nodename)) 中对这个 bean节点进行出来,刚开始的几行代码很容易看明白,无非就是读取这个bean节点中的各个属性,这里重点是:type,name,class,scope这四个属性,对于前三者在之前已经介绍过了,对于scope属性,就是说当我们在程序中向容器要这个对象的时候,容器是返回一个new的新对象呢,还是单例对象或者其他形式的对象,即这个对象的作用域范围,就像我们在spring中注册一个bean 元素的时候,不是也可以指定它的作用范围吗?这里和spring中的类似,通过容器获取对象的时候容器默认返回的是一个单实例对象,我们可以通过bean节点的scope属性改变:

                        Scope scope = Scope.SINGLETON;

if ("default".equals(scopeStr)) {
scope = Scope.DEFAULT;
} else if ("request".equals(scopeStr)) {
scope = Scope.REQUEST;
} else if ("session".equals(scopeStr)) {
scope = Scope.SESSION;
} else if ("singleton".equals(scopeStr)) {
scope = Scope.SINGLETON;
} else if ("thread".equals(scopeStr)) {
scope = Scope.THREAD;
}

这里的 Scope是一个枚举类型,稍后我会介绍到,这里我们只需要知道它定义了对象的不同作用域,每一个Scope枚举实例代表一个作用域。我们看到bean的默认作用域范围是 Scope.SINGLETON,即单实例。default 代表的是每次返回一个新对象,request代表request请求作用域,session代表会话作用域,thread代表一个线程范围内的作用域,即ThreadLocal作用域内。接下来我们重点看看这行代码:

containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope)

这行代码的作用就是往容器构造对象ContainerBuilder中注册将要被容器接纳托管的对象,我们之前说过,在调用 Container.getInstance 方法的时候,IOC容器是以参数 typename作为联合主键从容器中获取对象的,那么这里在初始化时往容器中注册对象的时候 typename就是这个联合主键了,其对应的值一般认为就是对应的对象了,但是这里貌似不对,应该我们看到的值是 LocatableFactory,这好像是一个对象工厂,而不是对象本身吧。我们进入LocatableFactory内部看看究竟:

public class LocatableFactory<T> extends Located implements Factory<T> {


private Class implementation;
private Class type;
private String name;
private Scope scope;

public LocatableFactory(String name, Class type, Class implementation, Scope scope, Object location) {
this.implementation = implementation;
this.type = type;
this.name = name;
this.scope = scope;
setLocation(LocationUtils.getLocation(location));
}

@SuppressWarnings("unchecked")
public T create(Context context) {
Object obj = context.getContainer().inject(implementation);
return (T) obj;
}
// 这里省略N多代码

咦,这里面看到了create方法,它根据我们传入的 Class 对象返回了一个Object实例,再来看看 Factory接口:

public interface Factory<T> {
T create(Context context) throws Exception;
}

看到这里,我们仿佛明白了,容器中接纳和托管的原来不是对象本身,而是对象工厂,当我们需要容器提供一个对象的时候,容器是调用的对象工厂中的 create方法来创建并返回对象的。而对于具体的对象创建方式,我们可以通过实现Factory接口,实现其create方法即可,这里的Factory实现类为LocatableFactory,其create方法为调用Container的重载方法 inject(Class cl) 创建并返回一个新对象,该对象已经接受过容器的依赖注入了。具体的 inject(Class cl)实现细节,我们留到后面介绍容器操作方法inject和 getInstance 的时候再详细说明。当然了,如果我们自定义了别的Factory实现类,我们在 create 方法中完全可以根据我们的需要实现任意的对象创建方法,比如:class.newInstance()这种最基本的方式或者从 JSON串转换为一个JAVA对象,或者反序列化创建对象等等,这就是工厂方法模式的优点,创建对象的方式可以很灵活。

Factory 的代码注释上写到: A custom factory (客户端的 Factory), 啥意思? 难道还有容器自己内部的 Factory ?我们跟随这行源代码:

containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);

进入到容器构造对象内部探个究竟,

  public final class ContainerBuilder {
// 此处省略N多代码

public <T> ContainerBuilder factory(final Class<T> type, final String name,
final Factory<? extends T> factory, Scope scope) {
InternalFactory<T> internalFactory =
new InternalFactory<T>() {

public T create(InternalContext context) {
try {
Context externalContext = context.getExternalContext();
return factory.create(externalContext);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

@Override
public String toString() {
return new LinkedHashMap<String, Object>() {{
put("type", type);
put("name", name);
put("factory", factory);
}}.toString();
}
};

return factory(Key.newInstance(type, name), internalFactory, scope);
}
}

果然,我们在 ContainerBuilder中发现 我们传入的Factory被一个匿名内部类 internalFactory 给包装了,看起来这个 InternalFactory似乎和我们之前的 Factory有相同的接口,我们来看看它的源码:

interface InternalFactory<T> extends Serializable {

T create(InternalContext context);
}

果然和我们的想法一样,从名称上来看,貌似这个 InternalFactory是在容器内部使用的,而Factory则是我们客户端程序员使用的,它将在 ContainerBuilderfactory重载方法中被InternalFactory包装,不知道大家发现了没有,这里其实就是一个装饰着模式的运用,目标对象为我们传入的 LocatableFactory ,它的接口类型为Factory,在这里它被具有相同接口方法的 InternalFactory 包装,当调用internalFactorycreate方法时,加入了额外的代码 Context externalContext = context.getExternalContext();主要是获取context上下文,然后才调用目标对象上的create方法:return factory.create(externalContext); 我们继续看到该 factory方法的最后一行代码为:

  return factory(Key.newInstance(type, name), internalFactory, scope);

这里调用了factory的另一个重载方法,我们进入看看它又做了哪些事情:

  private <T> ContainerBuilder factory(final Key<T> key,
InternalFactory<? extends T> factory, Scope scope) {
ensureNotCreated();
checkKey(key);
final InternalFactory<? extends T> scopedFactory = scope.scopeFactory(key.getType(), key.getName(), factory);
factories.put(key, scopedFactory);
if (scope == Scope.SINGLETON) {
singletonFactories.add(new InternalFactory<T>() {
public T create(InternalContext context) {
try {
context.setExternalContext(ExternalContext.newInstance(
null, key, context.getContainerImpl()));
return scopedFactory.create(context);
} finally {
context.setExternalContext(null);
}
}
});
}
return this;
}

我们重点关注的是这两行代码:

    final InternalFactory<? extends T> scopedFactory = scope.scopeFactory(key.getType(), key.getName(), factory);
factories.put(key, scopedFactory);

先看第一行代码,结合我们之前的例子,这里的几个参数 key.getType()为 UserService.class ,key.getName()为 service1 , factory为包装了locatableFactoryInternalFactory实例, 参数 scope为 Scope.SINGLETON,所以我们调用的就是Scope.SINGLETON这个枚举实例上的 scopeFactory方法,我们来看看 Scope枚举的部分源码:


public enum Scope {


DEFAULT {
@Override
<T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
InternalFactory<? extends T> factory) {
return factory;
}
},


SINGLETON {
@Override
<T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
final InternalFactory<? extends T> factory) {
return new InternalFactory<T>() {
T instance;
public T create(InternalContext context) {
synchronized (context.getContainer()) {
if (instance == null) {
instance = factory.create(context);
}
return instance;
}
}

@Override
public String toString() {
return factory.toString();
}
};
}
},


THREAD {
@Override
<T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
final InternalFactory<? extends T> factory) {
return new InternalFactory<T>() {
final ThreadLocal<T> threadLocal = new ThreadLocal<T>();
public T create(final InternalContext context) {
T t = threadLocal.get();
if (t == null) {
t = factory.create(context);
threadLocal.set(t);
}
return t;
}

@Override
public String toString() {
return factory.toString();
}
};
}
},
// 这里省略部分代码
  abstract <T> InternalFactory<? extends T> scopeFactory(
Class<T> type, String name, InternalFactory<? extends T> factory);

可以很清楚的看到,在Scope枚举中声明了一个抽象方法scopeFactory,所以每一个枚举实例都实现了这个方法,它们各自实现了创建了不同生命周期的对象,还是以我们之前配置的例子来说明:

<bean  type="com.service.UserService" name="service1" class="com.service.impl.UserServiceImp1" />

这里并没有配置 bean节点的 scope属性,但是结合我们之前的源码分析可以知道,其默认值为 singleton,所以我刚才写道 “ 参数 scope为 Scope.SINGLETON,我们调用的就是 Scope.SINGLETON 这个枚举实例上的 scopeFactory方法。” 在其内部又创建了InternalFactpry的一个匿名内部类,在create方法中再次包装了factory以实现其单实例的功能,返回的就是又一次经过包装的InternalFactoryScope.Thread也是类似,只不过它创建的对象声明周期为线程范围内,所以把他/她缓存在ThreadLocal 中。 而Scope.DEFAULT 则是不做处理 直接返回 factory,这样当调用create方法时候,每次都是创建一个新对象。

让我们的视线重新回到 ContainerBuilder中的这两行代码:

    final InternalFactory<? extends T> scopedFactory = scope.scopeFactory(key.getType(), key.getName(), factory);
factories.put(key, scopedFactory);

第一行我们刚才已经介绍过了,那么第二行的作用就是把经过枚举Scope处理过的factory放入到一个容器内部的Map缓存中,这样容器才能根据 typename的联合主键key从容器内部查找对应的对象工厂,然后返回对象。 factories 作为ContainerBuilder内部属性的定义如下:

public final class ContainerBuilder {

final Map<Key<?>, InternalFactory<?>> factories =
new HashMap<Key<?>, InternalFactory<?>>();
final List<InternalFactory<?>> singletonFactories =
new ArrayList<InternalFactory<?>>();
boolean created;
// 省略N多代码
}

看到了吧,容器内部缓存的确实是对象工厂。至于 singletonFactories 列表,则是包含了所有配置为 scope="singleton" 的对象工厂,如果 boolean created值为true 的话,那么在 ContainerBuilder创建容器对象 Container的时候,会先调用scope="singleton" 的对象工厂的create方法,即初始化容器的时候把对象生命周期为单实例的对象先创建出来。

至此,我们已经以一个容器配置元素。

<bean  type="com.service.UserService" name="service1" class="com.service.impl.UserServiceImp1" />

为例子,讲述他它如何注册到容器创建者对象ContainerBuilder中的流程走了一遍,那么整个容器的初始化过程也就是将各种配置形式的所有容器配置元素注册到容器创建者对象 ContainerBuilder中去,结合 之前给出的struts2-IOC容器的初始化时序图,我们可以清楚的看到,最后由 Configuration 调用 ContainerBuilder 的 create方法返回一个已经被初始化了的 IOC容器对象Container

  public final class ContainerBuilder {
// 此处省略N多代码
// //如果 boolean created(loadSingletons)值为true 的话,先调用scope="singleton" 的对象工厂的create方法,
public Container create(boolean loadSingletons) {
ensureNotCreated();
created = true;
final ContainerImpl container = new ContainerImpl(
new HashMap<Key<?>, InternalFactory<?>>(factories));
   //把对象生命周期为单实例的对象先创建出来.
if (loadSingletons) {
container.callInContext(new ContainerImpl.ContextualCallable<Void>() {
public Void call(InternalContext context) {
for (InternalFactory<?> factory : singletonFactories) {
factory.create(context);
}
return null;
}
});
}
container.injectStatics(staticInjections);
return container;
}
}

其中 final ContainerImpl container = new ContainerImpl( new HashMap<Key<?>, InternalFactory<?>>(factories)) 为具体创建了一个容器对象,这里是Container接口的具体实现类 ContainerImpl

class ContainerImpl implements Container {

final Map<Key<?>, InternalFactory<?>> factories;
final Map<Class<?>,Set<String>> factoryNamesByType;

ContainerImpl(Map<Key<?>, InternalFactory<?>> factories) {
this.factories = factories;
Map<Class<?>,Set<String>> map = new HashMap<Class<?>,Set<String>>();
for (Key<?> key : factories.keySet()) {
Set<String> names = map.get(key.getType());
if (names == null) {
names = new HashSet<String>();
map.put(key.getType(), names);
}
names.add(key.getName());
}

for (Entry<Class<?>,Set<String>> entry : map.entrySet()) {
entry.setValue(Collections.unmodifiableSet(entry.getValue()));
}

this.factoryNamesByType = Collections.unmodifiableMap(map);
}
// 此处省略N多代码
}

这个构造函数主要做两件事,其一是为 Key(type,name) --- InternalFactory Map实例字段 赋值,其来源就是 ContainerBuilder中的factories。 其二为将 type 和 name 的一对多关系保存在 Map实例字段 factoryNamesByType 中,以如下为例:

<bean  type="com.service.UserService" name="service1" class="com.service.impl.UserServiceImp1" />    
<bean type="com.service.UserService" name="service2" class="com.service.impl.UserServiceImp2" />

那么 factoryNamesByType 的值就是 [ UserService.class ,["service1","service2]" ]

到此为止,关于Struts2-IOC容器的初始化过程的重点部分已经讲述完毕了,我们发现其重点就是对象工厂的层层包装,即装饰模式的运用,这样当我们要容器要一个对象实例的时候,就会触发一系列的InternalFactory.create方法调用。核心结论是容器初始化完毕后其内部的Map字段factories中缓存的是 对象工厂,而不是对象实例本身。

让我们回到 struts2-IOC容器的初始化时的 Configuration 接口上来,因为它把控着容器的初始化逻辑:

 public class DefaultConfiguration implements Configuration {
protected Container container;
// 这里省略部分代码
public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException {
packageContexts.clear();
loadedFileNames.clear();
List<PackageProvider> packageProviders = new ArrayList<PackageProvider>();

ContainerProperties props = new ContainerProperties();
ContainerBuilder builder = new ContainerBuilder();
//循环每一个ContainterProvider,将他们各自对应的容器配置元素注册到ContainerBuilder中去,
//我们之前例子中的 StrutsXmlConfigurationProvider 也是在这里被调用 register 方法的。
for (final ContainerProvider containerProvider : providers)
{
// 这里对于资源文件的处理分两步走
containerProvider.init(this); //第一步是初始化,即完成格式转换,如xml格式的转换为 document对象集合.
containerProvider.register(builder, props); // 第二步是将资源文件中配置的对象注册到容器构造者对象中去。
}

props.setConstants(builder);

//将自己,即 this 本身注册到容器中去,其缓存的对象工厂被调用create方法的时候返回的就是当前对象本身.
builder.factory(Configuration.class, new Factory<Configuration>() {
public Configuration create(Context context) throws Exception {
return DefaultConfiguration.this;
}
});
//调用 ContainerBuilder的create方法创建并返回容器对象Container,这里的false参数表示延迟创建singleton对象.
container = builder.create(false);
// 此处省略部分代码
}
}

以上代码执行完毕后,Struts2-IOC容器就被正确的初始化创建出来了!之前说过,struts2在初始化的过程中,主要是对容器对象和package事件映射对象初始化,那么在这个方法的代码中接下来的事情就是顺便把PackageConfig这个对象也一起初始化掉:

   public class DefaultConfiguration implements Configuration {
//这里省略部分代码
public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException {
//这里省略部分代码
// Process the configuration providers first
//循环调用每一个providers,完成对PackageConfig对象的初始化
for (final ContainerProvider containerProvider : providers)
{
if (containerProvider instanceof PackageProvider) {
//容器已经被正确创建出来了,可以进行依赖注入操作了。
container.inject(containerProvider);
((PackageProvider)containerProvider).loadPackages();
packageProviders.add((PackageProvider)containerProvider);
}
}

// Then process any package providers from the plugins
Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class);
if (packageProviderNames != null) {
//循环调用其他providers加载器,主要来源于插件中直接实现PackageProvider接口的类,或用户另外自己扩展的.
for (String name : packageProviderNames) {
PackageProvider provider = container.getInstance(PackageProvider.class, name);
provider.init(this);
provider.loadPackages();
packageProviders.add(provider);
}
}

//对所有PackageConfig进行naemspace级别的重新匹配,这样当struts2框架响应http请求时,
//能根据namespace进行url匹配
rebuildRuntimeConfiguration();
}
}

至此,struts2的两类配置元素ContainerPackageConfig 已经初始化完毕了。在下一篇文章中,我将重点分析 Container中的重载方法 getInstance 和inject 的具体实现,这两个方法也是任何IOC容器最被常用的方法,我们将看到在Struts2-IOC容器中是如何实现的,敬请期待!

整理自:https://blog.csdn.net/fcbayernmunchen/article/details/7686385

--------------- END ---------------

每一篇文章都是小编精心为您准备的,写文不易,费时费脑;『分享』+『点赞』+『在看』三连击是小编坚持的最大动力,拜谢!

以上是关于struts2源码分析-IOC容器的实现机制(上篇)的主要内容,如果未能解决你的问题,请参考以下文章

Spring深度分析IoC/DI机制 配置文件式 基本实现

Spring IOC容器核心流程源码分析

深入浅出Spring原理及实战「原理分析专题」不看源码就带你剖析AOP容器核心流程以及运作原理

spring源码分析IOC容器初始化(总结)

spring源码分析spring ioc容器之前生今世--DefaultListableBeanFactory源码解读

spring源码分析IOC容器初始化