您的位置:首页 > 其它

struct2源码解读(5)之解析bean标签

2015-11-06 12:06 423 查看
struct2源码解读之解析bean标签 上篇博文,我们大致分析了struct2是如何解析struct2配置文件的,包括default.properties和struct*.xml,但有些细节比较繁琐,如struct2是如何解析bean标签的和struct2是如何解析packag标签的,还没详细分析,这篇博文将详细解析struct2是如何解析bean标签的,package的解析将留在下篇博文进行讲解。
一、bean标签
我们先来看下bean标签是怎么样的?

代码清单:structs.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
<!--bean标签-->
<bean name="" type="" class="" scope="" static="" optional="">

</bean>
<struts>
bean标签有5个属性,name,type,class,scope,static,optional
name:它指定的Bean实例的名字,对于有相同type的多个Bean。则它们的name属性不能相同。
type:这个属性是个可选属性,它指定了Bean实例实现的Struts2的规范,该规范通常是通过某个接口或者在此前定义过的Bean,因此该属性值通常是个接口或者此前定义过的Bean的name属性值。如果需要将Bean的实例作为Strut2组件使用,则应该指定该属性的值。
class:这个属性是个必填属性,它指定了Bean实例的实现类。
scope:它指定Bean实例的作用域,该属性的值只能是default、singleton、request、session或thread之一
static:它指定Bean是否使用静态方法注入。通常而言,当指定了type属性时,该属性就不应该指定为true
optional:该属性是个可选属性,它指定Bean是否是一个可选Bean
二、获得bean标签信息
上篇博文我讲到,struct2用dom解析struct*.xml文件后,我们会得到一个document对象,然后用这个对象的getDocumentElement()方法获得根节点<struts>,然后再用getChildNodes,得到<struts>的子节点,通过循环遍历子节点,得到bean标签。
if ("bean".equals(nodeName)) {
//获得bean属性
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");
//optional属性默认为true
boolean optional = "true".equals(child.getAttribute("optional"));
//scope属性默认为singleton,这里可以看到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;
}
//name属性默认为default
if (StringUtils.isEmpty(name)) {
name = Container.DEFAULT_NAME;
}
//省略.对属性的封装,下面详解
}
得到bean标签后,获取它的属性值,给某些属性赋予初始值,下面就是对这些属性的封装
三、封装bean属性
代码清单:封装bean属性
//获取配置的class属性的实现类
Class cimpl = ClassLoaderUtil.loadClass(impl, getClass());
//这个ctype变量会作为这个bean的key值封装到containerBuilder中,这里设计一个变量,是为了区分取class还是type值,默认是class的属性值
Class ctype = cimpl;
if (StringUtils.isNotEmpty(type)) {
//如果type属性不为空,则去type的属性值为key值
ctype = ClassLoaderUtil.loadClass(type, getClass());
}
if ("true".equals(onlyStatic)) {//如果static属性为true
//通常而言,当指定了type属性时,该属性就不应该指定为true,所以这里用class为key值
//getDeclaredClasses这个是为了捕获异常
cimpl.getDeclaredClasses();
//封装静态类
containerBuilder.injectStatics(cimpl);
} else {//如果static属性为false
if (containerBuilder.contains(ctype, name)) {
//如果containerBuilder中已经有这个bean 则抛出异常信息.代码略。从这里可以看出封装是以name和ctype为key值的
}
//getDeclaredClasses这个是为了捕获异常
cimpl.getDeclaredConstructors();
//把这个bean的所有属性以name和class(type)属性值为主键封装到containerBuilder
containerBuilder
.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);
}
//把已解析过的节点放到一个map中缓存,loadBeans是一个map的名称
loadedBeans.put(ctype.getName() + name, child);
这个代码就是上面省略的那些代码,前后很简单,无非就是获取下配置class属性的实现类和把解析过的bean以ctype和name为key值放到一个map中缓存。重点是把bean属性封装到containerBuild对象。这里分为封装静态类和封装非静态类。

3.1.封装静态类
封装静态类也很简单,在injectStatics()这个方法里面,我们来看下这个方法
final List<Class<?>> staticInjections = new ArrayList<Class<?>>();
public ContainerBuilder injectStatics(Class<?>... types) {
staticInjections.addAll(Arrays.asList(types));
return this;
}
从这里可以看到,这里所谓的封装静态类,无非就是把class或者是type属性放到ContainerBuilder的一个类型为list的属性中。到时候我们用get方法,就可以访问到这个属性。

3.2.封装非静态类
封装非静态类,用到了ContainerBuilder的factory方法
containerBuilder
.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);
在用这个方法之前,实例化了一个LocatableFactory对象,这个对象封装了bean的所有属性。这里用到了工厂模式。我们先了解总体的过程,后面再讨论这个工厂模式。这里你知道struct2把属性封装到一个LocatableFactory对象就可以了。我们来看看这个factory方法。
public <T> ContainerBuilder factory(final Class<T> type, final String name,
final Factory<? extends T> factory, Scope scope) {
//实例化一个internalFactory对象
InternalFactory<T> internalFactory =new InternalFactory<T>() { 代码略 };
//重载factory对象
return factory(Key.newInstance(type, name), internalFactory, scope);
}
上面这个factory重载了这个factory的方法。这个Key是一个静态类
class Key<T> {
final Class<T> type;
final String name;
final int hashCode;
}
我们来看看重载后的这个factory方法

//把封装了属性的factory对象保存到containerBuild的一个map中
final Map<Key<?>, InternalFactory<?>> factories =
new HashMap<Key<?>, InternalFactory<?>>();
//把singleton的factory对象保存到containerBuild的一个list中
final List<InternalFactory<?>> singletonFactories =
new ArrayList<InternalFactory<?>>();

private <T> ContainerBuilder factory(final Key<T> key,
InternalFactory<? extends T> factory, Scope scope) {
//先判断是否已经存在这个bean
//有create字段,默认为false,创建后置true,这里就是判断这个字段
ensureNotCreated();
//因为factory要保存到一个map中,这里判断是否contain key,如果我true抛出异常
checkKey(key);
//进一步封装成scopedFactory
final InternalFactory<? extends T> scopedFactory =
scope.scopeFactory(key.getType(), key.getName(), factory);
//保存到map中。struct2把ctype和name封装到一个Key对象,然后再以这个对象为key值
factories.put(key, scopedFactory);
if (scope == Scope.SINGLETON) {
//如果是SINGLETON,则把重新封装的factory放到一个list集合中
singletonFactories.add(new InternalFactory<T>() {
//接口实现类的动态构造方法
public T create(InternalContext context) {

}

});
}
return this;
}
由此我们大概知道了struct2封装bean的原理:struct2把bean的所有属性封装到一个实现factory接口的对象中,然后再用name和class(type)为键值把这个对象保存到ContainerBuilder的一个map类型的属性中(singleton的bean保存到list类型的属性中)。现在剩下的问题就是,struct2是如何设计把bean的属性封装到factory接口的实现类中的呢 ?

四、工厂模式
这里用到了工厂模式。什么是工厂模式?通俗的来说,就是实例化对象的时候不是用new,而是通过集成factory接口的实现类实例化。我们先来看看factory接口
public interface Factory<T> {

T create(Context context) throws Exception;
}
这个factory接口只有一个create()方法,这个方法返回的是一个Object T,也就是说通过调用实现了factory接口的对象的create()方法可以返回一个对象实例。我们可以在这个方法中设计自己所要返回的对象.我们以上面的例子做解析。
上面提到,struct2会把bean的所有属性封装到一个LocatableFactory对象中
new LocatableFactory(name, ctype, cimpl, scope, childNode)
我们来看看这个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;
}
}
这个对象实现了factory的接口,并封装了bean的属性,当我们实例化这个对象的时候,就把bean的属性值初始化到了这个对象的属性中。这个对象的create()方法就返回了class(type)属性中配置的对象实例。

而后面,struct2又把这个对象进一步封装成了InternalFactory对象。为什么要封装成InternalFactory对象呢?我们在实例化bean的时候,是不要是要考虑这个类的作用域?scope.scopeFactory()就是返回InternalFactory对象。scopeFactory()是一个抽象方法
abstract <T> InternalFactory<? extends T> scopeFactory(
Class<T> type, String name, InternalFactory<? extends T> factory);
当我们配置scope的时候,就会调用相应scope的scopeFactory()方法(这是java多态的一个特性)。
比如我们配置了scope=singleton.则会调用
代码清单:Scope类
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()) {
//singleto的 对象为空的才创建
if (instance == null) {
instance = factory.create(context);
}
return instance;
}
}

@Override
public String toString() {
return factory.toString();
}
};
}
}
这里返回了一个InternalFactory对象,把这个对象保存到containerBuild对象的一个map类型的属性中,将来在处理action请求的时候,找到这个对象,然后找到这个InternalFactory对象,然后通过这个对象的create方法,又调用上一个factory的create方法,最后把这个classs实例实例化出来了。
这里介绍了工厂的模式,同时也介绍了struct2是如何封装bean属性的。对于这个工厂模式,只是相对于new 一个对象的另外一个实例化对象的方法,这个方法的好处还是显而易见的,对于多参数构造一个对象的时候可考虑用工厂模式实例化这个对象,这样能简化代码也便于维护。
五、总结
现在来总结下上面的内容。struct2解析bean标签的时候,把属性name,class,type,scope,static,optional封装到了一个实现了factory的对象中,这个对象由作用域决定,然后把这个对象以name和class(type)为键值保存到containerBuild的一个map集合中(singleton的保存到list集合)。这个就是struct2解析xml文件时,解析bean 的过程。下篇博文会探讨如何解析package标签。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息