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标签。