- 浏览: 11192 次
最新评论
很多人可能不理解struts2的整个运行过程,或者没有深入了解过,今天就带着大家探究一下这个过程。
我是根据代码走向分析的,分析的代码是按xwork 2.1.5和struts 2.1.8.1版本的。
这个过程是从客户端发起访问,到服务端响应,并返回结果的整个过程。
第一部分:一切从FilterDispatcher开始
FilterDispatcher作为struts2的一个核心过滤器(Filter),接受所有客户端的请求,经过一系列的处理才到我们的Action。对于Filter,他首先执行init方法初始化,因此我们先看看FilterDispatcher的初始化做了什么。
FilterDispatcher.init
init的主要工作是创建并初始化了dispatcher (分发器),分发器里面涉及到很多重要的操作,比如说加载配置、创建内部容器、以至创建ObjectFactory、创建以及调度Action等等,都是在dispatcher 中完成的,所以我们主要关注这个dispatcher。
值得一提的是@Inject注解,struts2也有依赖注入功能,在struts2中通过这个注解标识当前类的成员需要注入值。当然如果我们普通开发人员使用到注入功能,需要把使用到依赖注入功能的类在配置文件中用<bean>标签配置好才能生效。
比如这样:
struts-plugin.xml
接着我们看具体dispatcher 的创建过程,createDispatcher方法。
FilterDispatcher.createDispatcher
Dispatcher.Dispatcher
当前基本构造成了dispatcher的基本对象,接着是对dispatcher初始化,初始化里面做了几件事:
1、构造了以struts2为基础(可以集成spring)的配置管理器(ConfigurationManager)
2、向配置管理器加入配置供给器(ConfigurationProvider),为内部容器(Container的)构建提供了基础。
3、构建内部容器,为各个struts2的基础类以及开发人员配置的类提供相关实例的方法,并且提供相关的依赖注入的功能。
4、设置是否允许重新加载xml配置文件
5、通知相关的分发器监听器初始化完成消息
我们关注前三点。这里有一个比较重要的类 ———— Container,该类提供了struts2的基础类以及在配置文件配置了的类的实例化以及依赖注入的方法。Container里面包含了一个工厂map,里面包含了相关类的内部工厂类(InternalFactory),InternalFactory 里面有一个create方法,用来创建相关类的实例;可以通过调用Container.getInstance(Class c)方法,获取类类型c描述的类的实例,详细后面说面。
接下来我们看Dispatcher.init方法
Dispatcher.init
Dispatcher.init_DefaultProperties
Dispatcher.init_TraditionalXmlConfigurations
Dispatcher.init_LegacyStrutsProperties
Dispatcher.init_CustomConfigurationProviders
Dispatcher.init_FilterInitParameters
Dispatcher.init_AliasStandardObjects
Dispatcher.init_PreloadConfiguration
从上面代码可以看出struts2对配置文件的加载顺序:
1、org/apache/struts2/default.properties
2、struts-default.xml
3、struts-plugin.xml(如果有)
4、struts.xml
5、struts.properties(如果有)
6、default.properties(如果有)
7、struts.custom.properties(如果有)
org/apache/struts2/default.properties是配置了struts2的常量的默认值,比如struts.i18n.encoding、struts.action.extension、struts.i18n.reload等常量。
struts-default.xml里面配置了struts2的核心bean,以及用constant配置的常量,比如ObjectFactory、ActionProxyFactory、ActionMapper等bean。
struts-plugin.xml是struts2的插件的配置文件,比如struts2-spring-plugin。
struts.xml是使用struts2时候基本配置,比如action、result、interceptor、constant等。
struts.properties、default.properties、struts.custom.properties是原来旧版本的一些配置。
注意:后面加载的配置文件可以覆盖前面的配置文件的配置
从上面代码可以知道,init_XXXX的方法是为配置管理器收集配置供给器的信息,而最后调用init_PreloadConfiguration方法,构建出配置对象和容器,他们是struts2中非常重要的两个对象,配置对象里面包含了struts2的所有的配置信息,容器里面包含了struts2中所有的对象的工厂对象。以后使用到的Action、拦截器(Interceptor)、Result等对象都是通过他们两个协同创建出来的。
接下来我们看看getConfiguration是怎么创建配置和容器的。
ConfigurationManager.getConfiguration
上面代码就是创建出配置对象,并根据之前ConfigurationManager.addConfigurationProvider加入的供给器,并以他为参数调用DefaultConfiguration.reloadContainer方法来构建容器,当然DefaultConfiguration.reloadContainer也可以用作重加载容器。最后的conditionalReload是根据容器在构建时候一些供给器的特殊需要,判断是否需要重新加载容器。
接下来我们要看DefaultConfiguration.reloadContainer方法,具体看看他是怎么构建容器的。
DefaultConfiguration.reloadContainer
上面的操作比较多也比较复杂,我们一个个分析。
首先我们分析ContainerProvider.register,这个方法会调用我们之前在配置管理器收集好的供给器的register方法,通过这个方法将配置在配置文件的类,以工厂对象的方式放入了容器构造器当中,为后面容器所用。
值得一提的是DefaultPropertiesProvider、StrutsXmlConfigurationProvider、BeanSelectionProvider供给器,他们提供了很多重要的对象的工厂对象和struts的默认常量,DefaultPropertiesProvider执行配置文件(org/apache/struts2/default.properties)的解析,并把这些常量储存在容器配置对象中;StrutsXmlConfigurationProvider执行了配置文件(struts-default.xml)里面的<bean>和<constant>等标签的解析,以及将解析出来的类创建工厂对象,还有创建servletContext的工厂对象,然后将这些工厂对象一同放入到容器构造器中;BeanSelectionProvider提供了struts2的核心类的工厂对象,是以别名的形式提供的,就是如果已经在容器构造器中存在了相应类的工厂对象,就重新起一个别名(这个别名是default,这样做的意义是把这些已经生成的工厂对象设置成默认工厂对象,为生成指定对象时候提供默认生成方式),然后把别名和工厂对象,以key-value形式重新放到容器构造器中,就是同一个工厂对象可能有两个不同的名称(key)指向;如果没有相应的工厂对象就新建一个对应的工厂对象,放入容器构造器中。总结来说,ContainerProvider.register方法都是将XML文件、Properties文件以及struts2核心类等等转换成相应的工厂对象,然后储存入容器构造器的过程。
下面分析一下主要的DefaultPropertiesProvider、StrutsXmlConfigurationProvider、BeanSelectionProvider供给器的inti、register方法
DefaultPropertiesProvider.init(LegacyPropertiesConfigurationProvider.init)
DefaultPropertiesProvider.register
DefaultPropertiesProvider.loadSettings(LegacyPropertiesConfigurationProvider.loadSettings)
StrutsXmlConfigurationProvider.init(XmlConfigurationProvider.init)
StrutsXmlConfigurationProvider.register
XmlConfigurationProvider.register
BeanSelectionProvider.init
BeanSelectionProvider.register
BeanSelectionProvider.alias
针对上面的代码解析一下,可能很多人都不理解上面的操作,为什么要这样操作,其实跟struts2的org/apache/struts2/default.properties和struts-default.xml有关,先看一下两个配置文件的部分内容:
default.properties
struts-default.xml
很明显,default.properties和struts-default.xml的常量都可以普通值或者指定具体的实现类,例如
,虽然struts.objectFactory这里被注释了,但是之前的版本是在这里指定struts使用哪个框架的,struts2默认认为是ObjectFactory是使用spring提供的。不过现在修改了,通过程序确认是使用那个框架,就是我们上面代码,
有些人会觉得奇怪,只要配置"struts"、"spring"就可以知道是哪个具体实现类了吗?当然不是那么简单,这要看struts-default.xml核心类的bean定义,
这些bean都定义了一个name属性,当程序在解析struts-default.xml时候将name属性值和type属性值组成一个Key类型的对象,而class属性值将会用来创建出Class对象(java.lang.Class的实例),然后再根据这个Class对象创建出该类型的工厂对象,最后将工厂对象储存入容器构造器中,以后用作容器构建。其实这些操作就是前面StrutsXmlConfigurationProvider.register执行的操作,只是Key对象的创建是在容器构造器内部完成的。如果后面要实例化这些bean,通过Container.getInstance(type, name)就可以了,type是struts-default.xml里面bean标签的type属性值,name就是bean标签的name属性值。可能是觉得要写两个参数太麻烦,于是struts决定为要设置默认的name,就是在struts-default.xml的bean标签中,如果没有指定name属性的值,或者指定了空值,解析这个bean标签时候,就用"default"作为name,所以对应这个bean的工厂的那个key就是Key(type,"default"),这个也就是这个bean的默认工厂对象。因此,为了统一起见,struts2要为他自己的核心类都指定了默认的name和默认工厂对象,为此他的提供了一个BeanSelectionProvider供给器,专门针对这些操作,也就是我们上面的BeanSelectionProvider.register和BeanSelectionProvider.alias执行的操作,而且为了不影响原来添加的工厂对象,struts2把这个操作放到最后;注意,BeanSelectionProvider.alias并没有覆盖原来的工厂对象,而是以别名的形式多添加了一个而已。
除了在常量上面填写bean的名字,还可以填上具体的实现类的详细类名,在BeanSelectionProvider.alias那里可以体现得出来,他在builder.contains(type, foundName)没有找到对应的工厂时候就认为这个常量可能设置的是具体的类,于是通过ClassLoaderUtil.loadClass尝试加载,如果加载成功就也这个类创建默认工厂对象。
于是,有了这些默认工厂对象的,我们获取实例的时候就只需要调用Container.getInstance(type)就可以了获取到默认的实现类的实例。builder.contains(type)是查询是否包含type的默认工厂的,凡是调用Container或者ContainerBuilder没有带name的方法,都相当于带了"default"这个名称。
无论是Container.getInstance(type),还是Key(type,name)中的type,指的都是bean标签的type属性值,这个type通常都不是真正的实现类,而是一个较为抽象的父类,其目的就是延展了这些类的扩展性、加强封装性和可维护性,只要继承了这个type的抽象父类,写上了一些子类的细节实现,然后在自己配置文件上写上子类的bean,然后修改一下常量的值就可以使用自己的实现,根本不需要改原来的代码,而且其他开发人员根本无法根据这个抽象的父类知道具体的子类及其实现细节。
值得注意的是先加载的配置文件的常量会被后加载的配置文件的同名常量所覆盖,比如说我在default.properties配置了
,而在struts-default.xml配置了
,最终就等于struts.objectFactory = spring。因为容器配置对象(ContainerProperties)的底层实现是Hashtable,而且容器配置对象是以常量名作为Hashtable的key的,另外容器构造器(ContainerBuilder)也是会覆盖的,因为容器构造器储存factory使用的HashMap,使用Key类作为HashMap的key,具体的工厂对象作为value,而工厂对象储存入容器构造器时候,Key类是以type和name创建的,所以只要type和name一样的的情况下就会覆盖原来的工厂对象。
好了现在回到DefaultConfiguration.reloadContainer上,到这里基本上说完容器供给器(ContainerProvider)的init和register过程。接下来的是props.setConstants(builder),这个操作是把容器配置对象内储存的常量设置到容器构造器中,这个过程需要把常量转换成工厂对象,再储存到容器构造器。
DefaultConfiguration.ContainerProperties.setConstants
重点说一下DefaultConfiguration.createBootstrapContainer方法,先说明一下,这个方法在当前的版本是留作扩展使用的,并没有正真的意思,他的操作没有影响到其他的对象,虽然修改过ActionContext,但是DefaultConfiguration.reloadContainer最后被清空了。本人在网上面找过相关资料,才发现这个方法在struts 2.3.15.1这个版本有重大作用,在2.3.15.1版本,DefaultConfiguration.createBootstrapContainer创建出来的容器是一个引导容器,独立于我们用于创建普通的对象的主容器容器。引导容器作用是struts2的核心类的初始化,核心类主要是使用引导容器的工厂对象和注入依赖功能,还有容器供给器的依赖注入也是使用引导容器的,不再使用主容器,退出DefaultConfiguration.reloadContainer方法时候也通过ActionContext.setContext(null)清空了引导容器使用过的Action环境,很明显的一个意图,struts想隔离开核心类和普通的类,以保证其核心代码的安全。不过现在我们讲的这个版本,DefaultConfiguration.createBootstrapContainer方法还是没有实质意义的。
接下来我们继续解析DefaultConfiguration.reloadContainer里面的builder.create(false),这个操作相当重要,通过这个操作出了我们的主容器(Container),之前我们的容器供给器注册过程,就是容器构造器和容器配置对象搜集信息,为创建容器做的准备。因此这个容器包含了之前所有供给器提供的常量和bean的工厂对象,其作用就是负责这些bean的实例、依赖注入,还有负责常量的获取。以后的ObjectFactory对象也是通过这个容器创建,而ObjectFactory对象在创建Action、Result、Interceptor也是通过容器将创建出来的对象注入依赖的属性的,虽然那些实例不是通过容器创建,但是依赖注入依然是使用容器的,可以看出主容器在sturts2中也是举足轻重的。
接下来看看builder.create(false)的实现代码,
ContainerBuilder.create
ContainerBuilder.create是创建容器的方法,从容器构造器传给容器的工厂对象,都是之前容器构造器在容器供给器inti和register时候收集的,容器构造器收集了容器供给器提供的常量和配置等等信息,然后构造出容器,因此容器是拥有这些常量和配置里面指定的bean的实例化的能力。说到容器的实例化的能力,其实实例化的过程有一部分是依赖于容器的依赖注入功能的,容器实例化类的时候是通过构造函数注入(ConstructorInjector)方式,通过反射获取指定类的构造函数,如果有无参数的构造函数就直接使用无参数构造函数完成初始化,如果只有有参数的构造函数,就分析参数上面有没有注入的标志,如果有就通过容器的参数注入(ParameterInjector)方式注入依赖值给这个参数,然后通过这些有值的参数调用这个构造函数,达到实例化的目地,实例化之后还需要对其成员进行注入依赖,容器对成员注入是使用成员注入(MemberInjector,分别有MethodInjector和FieldInjector,针对方法还是属性判断使用哪一种方式)的方式,对静态成员还有静态注入(StaticInjector,其实质是通过MemberInjector实现的,只不过不需要穿入实例对象)方式;对于容器提供的依赖注入能力,其前提条件就是使用注入的类要配置在bean里面,也就是说类要交给容器管理,并且在使用注入依赖的属性、参数、方法要使用@Inject注解去指明,否则没有办法注入,或者注入了null,或者出现异常。
继续回到DefaultConfiguration.reloadContainer方法,我们接下来研究container.getInstance(ObjectFactory.class),这个操作非常重要,通过这个操作,创建出我们的ObjectFactory,注意是container.getInstance(ObjectFactory.class),这里只是指明了类型,没有指明用什么name的工厂对象,意思就是使用默认工厂对象创建这个ObjectFactory的实例,根据之前的介绍,可以看到struts2的struts.objectFactory被注释了,并没有指定,意味着是使用struts作为name的ObjectFactory的bean,就是使用org.apache.struts2.impl.StrutsObjectFactory作为真正的ObjectFactory实现。
介绍一下,我们之后的Action、Result、Interceptor等等一系列与我们密切相关的对象都是通过ObjectFactory创建的,它可以说是struts的心脏。ObjectFactory实例化对象的默认方式不是使用容器的,而是通过一般的Class.newIntance()方法,而注入才是借助容器的inject(Container.inject)方法给实例化后的对象注入依赖值,对于这样的操作,一个解析就是避免影响核心类的性能,如果把所有的Action都交给容器来管理,会造成容器过于庞大,因为有时候Action可能会是成千上万的,这样会拖慢容器的执行;另外一个原因,因为Action、Result、Interceptor等都是需要多例的,而非单例,方便起见就直接用ObjectFactory,通过Class.newIntance()创建一个实例好了;估计还有一个方面想就是为了扩展,因为如果都交给容器,其他集成框架无法获取到struts2的容器的,无法操作他,所以为了扩展就使用了Class.newIntance()方法实例化。
下面看一下Container.getInstance代码
Container.getInstance(ContainerImpl.getInstance)
介绍一下里面为何使用到ContainerImpl.callInContext方法,
ContainerImpl.callInContext
其实strtus2的目的是为了屏蔽了ContainerImpl的实现细节,并且留下了扩展,可以通过实现ContextualCallable接口,扩展获取内部环境(InternalContext)的一些细节。
对于ContainerImpl里面使用到的内部环境(InternalContext)和外部环境(ExternalContext)。内部环境是针对创建普通开发人员在struts2常量配置的自定义类,例如,
default.properties
内部环境主要用于注入,他支持普通注入和循环依赖注入,由于是使用普通开发人员自定义类,如果里面用到struts2的核心对象,需要注入核心对象,所以直接使用内部环境;外部环境只是一个快照的环境,他是针对struts2核心对象构造时候使用,因为他们可以直接使用容器,就没有必要透露太多细节了。内部环境和外部环境一般是工厂对象create方法传入的参数,其实struts2针对普通开发人员在常量配置的类和他自己的核心类,使用了两种不同工厂对象的,只不过这两个工厂对象的细节,被struts2用接口的形式屏蔽了。XmlConfigurationProvider.register里面使用到的
XmlConfigurationProvider.register
就是使用外部定义的工厂类————LocatableFactory。于是容器构造器对这些使用外部定义工厂创建的工厂对象只提供外部环境(ExternalContext)作为create参数。众所周知,XmlConfigurationProvider.register是用来解析struts2的配置文件的bean的,很多核心类的bean都在这里解析,为何这里使用了外部定义的工厂类?其实是对容器构造器的扩展,允许传入其他工厂对象,完成一些特殊功能,比如上面的LocatableFactory,可以根据工厂知道具体的bean定义在哪个Document对象。另外,容器构造器只传入外部环境是为了隐蔽一些实现细节,保证核心的安全。对于普通开发人员在常量指定的自定义类,就使用容器构造器内部实现了InternalFactory接口的内部工厂,传入参数也是内部环境,暴露出更多核心信息,起主要原因上面讲过,是为了实例化自定义类使用到的核心对象,BeanSelectionProvider.alias里面就是使用内部工厂的最好例子
BeanSelectionProvider.alias
由于Container和ContainerBuilder的源代码比较多、比较复杂,这里不一一详细解析,如果想知道容器和容器构造器的详细实现,各位可以自行查看源代码。
继续回到DefaultConfiguration.reloadContainer,分析下面的代码,他是我们Action、Result、Interceptor等等配置的解析的核心过程,我们接下来详细解读一下。
DefaultConfiguration.reloadContainer
StrutsXmlConfigurationProvider.loadPackages
XmlConfigurationProvider.loadPackages
XmlConfigurationProvider.addPackage
XmlConfigurationProvider.loadPackages方法,首先是从之前加载好的Document加载package元素,然后通过addPackage方法将package元素转换成PackageConfig对象,通过这个对象加载出Interceptor、Result、DefaultAction(注意是加载出)等对象,然后再向这个PackageConfig对象加入ResultType、Action等对象,注意这里指的Config对象,并不是真正意义上可以运行的对象。好接下来分析buildPackageContext和addAction,其中addAction方法比较重要,该方法是Action元素的解析过程,addAction方法执行过程中加入了对用的Interceptor映射(InterceptorMapping)、Result配置对象(ResultConfig)、异常匹配(ExceptionMapping)等到其对应的Action配置对象中(ActionConfig),并将Action配置对象加入到package配置对象(PackageConfig)中。
XmlConfigurationProvider.buildPackageContext
XmlConfigurationProvider.addAction
XmlConfigurationProvider.buildResults
针对上面的代码说面一下,config.getDefaultResultParam()获取到的是这个result所对应的result-type对应的类的"DEFAULT_PARAM"属性的值,result-type对应的类,比如result-type="redirect"对应"org.apache.struts2.dispatcher.ServletRedirectResult",这些都在struts-default.xml指定了的。对于这个"DEFAULT_PARAM"属性的值,被指定为"location",在ObjectFactory真正创建Result对象时候,会通过反射将这个对象对应的Result配置对象的"location"参数,设置到Result对象的"location"属性值(这个属性在StrutsResultSupport定义),而这个"location"属性的值,就是我们result标签的默认值,就是
这个"something",在后面result控制页面(视图)跳转的时候用到这个值。注意config.getDefaultResultParam()获取到的值是在XmlConfigurationProvider.addResultTypes方法内通过ResultTypeConfig.Builder.defaultResultParam方法设置进去的,具体这个方法我就不在这里分析了!接下来我们继续分析XmlConfigurationProvider.buildInterceptorList方法。
XmlConfigurationProvider.buildInterceptorList
从buildPackageContext到addAction,再到buildResults,还有buildInterceptorList,整个过程都是根据我们配置的struts.xml解析的,struts.xml的配置可以覆盖struts2的默认配置。同时这个过程代码也不太复杂,所以大家都应该看得懂。值得注意的一个地方就是我们最后的Action配置对象的构建,其实他还是未完整的,我们都知道,一个Action的执行过程需要一个struts的默认Interceptor和默认Action,还有默认Result等协同完成的,还有我们之前如果没有指定action的类,还要指定默认类,但是这里只是解析了我们自己配置的那些Interceptor、Action、Result,所以在DefaultConfiguration.reloadContainer的最后有一个rebuildRuntimeConfiguration方法,这个方法就是在最后帮我们的Action配置对象之类的配置对象加回的默认的配置对象,使他们完整,可以在后面转换成真正的对象,完整执行整个调用过程。
好,我们回到DefaultConfiguration.reloadContainer,分析rebuildRuntimeConfiguration方法,注意跟上去,方法执行过程都是一个套一个执行的,现在都分析得比较深入了,注意要一个个返回到DefaultConfiguration.reloadContainer。
DefaultConfiguration.rebuildRuntimeConfiguration
DefaultConfiguration.buildRuntimeConfiguration
DefaultConfiguration.buildFullActionConfig
看到了吧!之前构建出来的不完整的action配置对象,在这里都重新帮他们构建完整了,组成运行时配置对象。
好,到这里为止,全部的配置都在这里解析完成,并能够组织起来,构成运行时的结构了。我们的DefaultConfiguration.reloadContainer方法完成了。总结一下,这个DefaultConfiguration.reloadContainer方法初始化了容器,也解析完了配置文件,生成好运行时的配置对象,他的任务完成了。返回到configurationManager.getConfiguration方法,这个操作返回了初始化好的configuration对象,再返回到Dispatcher.init_PreloadConfiguration方法,他根据configuration对象,获取容器,再设置国际化的配置,最后返回容器,再返回到Dispatcher.init方法,他获取通过之前获取的容器,把他自身也注入依赖值,最后返回到FilterDispatcher.init,struts2的核心分发器,然后通过容器把核心分发器也注入依赖值。
第二部分:doFilter的工作过程
好,核心分发器的init方法完成了,他的初始化时候就完成了基础对象,使执行doFilter时候可以使用!好,现在分发器可以开始处理请求了!我们开始分析FilterDispatcher.doFilter方法。
FilterDispatcher.doFilter
这里是doFilter的整体操作,我们分析一些重要的操作,首先struts2准备好ValueStack、ActionContext等基本的要素,然后将servlet的request封装成自己的request,再通过actionMapper获取匹配到的action的映射对象,最后调用dispatcher.serviceAction方法执行我们的action、拦截器等。
我们分析一下actionMapper的获取,这个对象是在FilterDispatcher.init时候通过容器注入给核心分发器的,所以我们先看看其setter方法。
FilterDispatcher.setActionMapper
参数参数类型是ActionMapper也就是说容器注入要调用getInstance(ActionMapper.class)方法,也就是说调用getInstance(ActionMapper.class,"default")方法,根据配置文件default.properties和struts-default.xml,还有BeanSelectionProvider.register和BeanSelectionProvider.alias指出,
default.properties
struts-default.xml
BeanSelectionProvider.register
BeanSelectionProvider.alias
由于struts.mapper.class被注释了,所以在props.getProperty(key, DEFAULT_BEAN_NAME)一定是返回struts的,配置文件struts-default.xml包含了这个type为ActionMapper.class、name为"struts"的bean,所以容器构造器一定已经包含这个工厂对象,所以给这个工厂对象起一个别名,type为ActionMapper.class、name为"default",因此,在FilterDispatcher.setActionMapper注入的,就是org.apache.struts2.dispatcher.mapper.DefaultActionMapper这个实现类。
通过这样我们可以确认注入的实现类了,回来FilterDispatcher.doFilter,我们接下来分析actionMapper.getMapping方法,
DefaultActionMapper.getMapping
DefaultActionMapper.parseNameAndNamespace
DefaultActionMapper.handleSpecialParameters
上面比较难理解的是prefixTrie ———— 前缀字典树,上面针对get方法,就是只要当前变量key前面部分匹配到prefixTrie内部的某个key,就会返回这个key的value,相当于key.startsWith(prefixTrie某个key),就会返回这个key的value,这里返回了parameterAction,参数内指定的行为,例如参数内包含action:、redirect:、redirectAction:等字符串开头时候就当指定命令处理,然后调用parameterAction.execute,修改mapping的属性值。struts2可以直接通过request上的这些参数控制跳转等操作。好,我们看一下prefixTrie的初始化过程,貌似之前的struts2系统远程控制漏洞最是这里埋下的隐患!大家注意一下,记得更新!
DefaultActionMapper.DefaultActionMapper
注意REDIRECT_ACTION_PREFIX和REDIRECT_PREFIX的execute,如果是redirect的操作,都会为mapping添加result(就是这行代码:mapping.setResult(redirect)),如果mapping有了result,就会在Dispatcher.serviceAction执行result.execute,而不执行proxy.execute的操作,proxy.execute就是我们的action。凡是redirect都是会执行result.execute通知客户端重新请求某个指定的网络地址,达到重定向的效果。
关于struts2远程漏洞,上面说过,漏洞主要在于struts2允许request中的参数,支持以action:、redirect:、redirectAction:等字符串开头的参数,来控制页面(视图)跳转之类的操作,但是struts2忘记了,在ParameterAction.execute执行的时候,会把这些指令的后面全部的参数都放进去了,而且在后面的Result对象调用execute方法,真正执行跳转页面(视图)的时候,会使用StrutsResultSupport.conditionalParse方法,执行对request参数中的OGNL表达式的解析执行,可以通过OGNL表达式执行Java方法的调用。因此,如果有人在请求中加入Java的Runtime.exec方法调用,通过Java调用windows的命令,获取系统权限是轻而易举的!
上面就简单介绍了一下漏洞,接下来我们继续。上面的parameterAction.execute(key, mapping)代码可能会修改mapping的结果,从而影响我们回来的action的调用,实现通过request参数控制页面(视图)跳转!我们接下来解析DefaultActionMapper.parseActionName,
DefaultActionMapper.parseActionName
返回到DefaultActionMapper.getMapping,最终mapping已经设置好相应的值,可以为后面的操作服务了!我们也返回到FilterDispatcher.doFilter方法,忽略掉那些静态资源访问的代码,直接分析Dispatcher.serviceAction。Dispatcher.serviceAction是我们所有action的统一处理入口,进入这个方法,就是开始了action的处理了。Dispatcher.serviceAction主要是完成以下几个工作,
1、创建Action对象,以代理对象的形式
2、执行mapping.execute或者开始调用拦截器(就是proxy.execute);如果执行了mapping.execute(执行Result返回客户端),就停止下面操作了
3、通过反射调用Action的指定方法,并返回
4、执行Result返回客户端,可以是页面(视图)跳转或者其他操作
这些都是重点啊!我们先总体看看Dispatcher.serviceAction方法
Dispatcher.serviceAction
我们重点看Action代理对象的创建过程,在这个过程中Action已经被创建了,并且对应的代理对象也创建好了。action代理对象的创建是通过工厂对象创建的,上面使用了容器的getInstance(ActionProxyFactory.class)获取工厂对象的实例,之前讲过如何知道创建出来的实例是具体哪一个类,我就不再说了,这里getInstance(ActionProxyFactory.class)得到的实例是org.apache.struts2.impl.StrutsActionProxyFactory类的实例,我们看一下StrutsActionProxyFactory.createActionProxy方法,
StrutsActionProxyFactory.createActionProxy(DefaultActionProxyFactory.createActionProxy)
StrutsActionProxyFactory.createActionProxy
StrutsActionProxy.prepare
DefaultActionProxy.prepare
DefaultActionInvocation.init
DefaultActionInvocation.createAction
到这里,action基本上创建好了,但是action的属性还没有设置好的,struts2是支持通过客户端POST、GET方式传过来的参数值自动设置到action对应的名称的属性上(通过getter、setter方法),这个操作在action代理执行时候,调用拦截器来设置,主要设置参数的是这个拦截器:
struts-default.xml
这个拦截器在后面action代理执行时候会说明,这里先提一下。基本上,action代理对象,action调用对象,action都已经创建好并且基本初始化好了,我们回到Dispatcher.serviceAction,先看看当mapping有Result的情况,这个时候就是request参数里面有控制页面(视图)重定向的参数(redirect:、redirectAction:开头的参数),具体生成Result就是DefaultActionMapper.getMapping方法,这个时候会执行result.execute(proxy.getInvocation()),这个result对象的实例是ServletRedirectResult的实例,从DefaultActionMapper可以知道,我们看一下他们的代码,
ServletRedirectResult.execute(StrutsResultSupport.execute)
ServletRedirectResult.doExecute
如果是result.execute直接执行的情况,到这里doFilter对一个请求的处理过程就执行完了。我们该分析重点 ———— proxy.execute,我们action执行的过程,当然在action真正执行之前,当然还有拦截器的执行,然后action执行完后返回一个resultCode,再根据resultCode找到对应的Result,执行页面(视图)跳转,返回客户端之前当然还会生成视图那些,关于视图的我就不说了。从之前的代码可以知道proxy是StrutsActionProxy的实例,那么下面看代码,
StrutsActionProxy.execute
从之前的代码可以知道invocation是DefaultActionInvocation的实例,那么下面看代码,
DefaultActionInvocation.invoke
先关注拦截器调用的那行代码 ———— interceptor.getInterceptor().intercept,如果你写过自己的拦截器你应该知道,在拦截器中一定要再调用DefaultActionInvocation.invoke,使其余拦截器执行下去,否则action将不会调用到,当然如果你自己的业务逻辑要在某种情况中断现在的调用就没有办法,但是一定有另一种情况下让其余拦截器执行下去的情况的,这个是一个递归的过程,之所以DefaultActionInvocation使用interceptors这个迭代器就是这样原因,每次调用DefaultActionInvocation.invoke,interceptors的内部指针就会下移一个,执行执行完,interceptors.hasNext()返回false,然后开始执行真正的action。
我们之前不是说过action的属性还没有初始化吗!其实属性的的初始化是依靠拦截器完成的,具体是com.opensymphony.xwork2.interceptor.ParametersInterceptor这个拦截器,我们分析一下他的代码,从intercept方法看起,
ParametersInterceptor.intercept(MethodFilterInterceptor.intercept)
ParametersInterceptor.doIntercept
setParameters开始对action设置属性值,不过不是直接针对action对象设置,我们简单看一下这些代码,
ParametersInterceptor.setParameters
上面的操作有人可能觉得奇怪,根本没有对action设置值啊!!其实已经设置了,是通过newStack.setValue设置进去的,还记得action已经被设置进去valuestack的root吗!这个newStack.setValue是针对root设置值的,前面说过不是直接通过action设置的,明白了吧,所以action实际已经设置了属性的值!
值得注意的是这里因为也是涉及到参数里面可以调用OGNL表达式,这里也会引起struts2的远程漏洞,同之前说的差不多。
好!返回DefaultActionInvocation.invoke,继续看我们的两个重点关注的地方,我们先看invokeActionOnly,当拦截器执行完时候,执行action的地方,我们看代码,
DefaultActionInvocation.invokeActionOnly
DefaultActionInvocation.invokeAction
从上面可以看出,这个就是我们的Action的方法执行的地方,然后根据返回值,如果这个值是Result类型,就直接用这个Result来执行会面的操作;如果是String类型,就是resultCode,就用这个resultCode来获取Result,就是匹配result标签里面的name,这些配置早就储存在result配置对象了,然后用获取到的Result执行下面的操作,我们回到DefaultActionInvocation.invoke,看executeResult里面的处理过程,
DefaultActionInvocation.executeResult
上面情况我们先看看createResult方法,我们假设返回的Result是一个org.apache.struts2.dispatcher.ServletDispatcherResult类型,就是默认类型,Action的默认页面(视图)跳转方式,需要返回值,有视图。我们先看代码
DefaultActionInvocation.createResult
关于之前那个默认参数字段("DEFAULT_PARAM")的问题,其值是"location",其实这个DEFAULT_PARAM是用来指定Result接受result标签body部分的值的属性名称,就是这个部分,
所以这里就是指定了location属性,location属性是定义在org.apache.struts2.dispatcher.StrutsResultSupport,所有的Result都基本上继承自这个类,该属性用来指定页面(视图)跳转要跳转到哪个页面(视图),所以才设置"DEFAULT_PARAM"就指定了这个属性默认直接result标签body部分的值。具体问题就是这个location属性是在那里被赋值的,有两种情况,如果是通过action:、redirect:、redirectAction:等字符串开头的request参数来产生的Result,他是通过ParameterAction.execute执行时候,new一个Result,然后将action:、redirect:、redirectAction:等字符后面的一部分直接设置进去location属性的;另外一个种情况就是我们上面createResult的情况,通过ObjectFactory创建,具体分析代码,
ObjectFactory.buildResult
当创建Result成功就可以执行了,前面说过假设是返回默认的Result ———— org.apache.struts2.dispatcher.ServletDispatcherResult,我们针对ServletDispatcherResult.execute来展开,
ServletDispatcherResult.execute(StrutsResultSupport.execute)
ServletDispatcherResult.doExecute
到这里,result执行完了,跳转到指定的页面(视图),然后正真生成好页面,再返回给客户端。从doFilter开始,获取到映射对象(Mapping),到serviceAction,创建action代理对象,然后执行action、拦截器、Result,最终返回页面(视图),再返回到客户端。等一系列过程都完成了。FilterDispatcher的整个过程,从初始化(init)到提供服务(doFilter),主要的过程都分析了,至于destroy那些就不说了。关于struts2的执行流程大部分已经分析了,其余太过复杂或者篇幅过长的就没分析了。下面再分析一个,struts2集成spring,主要是针对struts2-spring-plugin分析。
第三部分:Struts2集成Spring
第三部分要求有一定spring基础才能看懂!
struts2不单单可以单独工作,而且还支持集成其他的框架一起协同工作,而且集成起来也不太难,只需要一个集成插件就可以了。我们主要说的是struts2和spring集成,其实主要是说那个集成的插件,因为只需要这个插件,struts2就可以跟spring协同运行了,如此神奇一定分析。简单说一下,集成spring之后,struts2的Action、Interceptor、Result等一些列的对象都可以通过spring来管理,就是直接象spring普通bean一样,配置在spring配置文件中,这样这些对象的生命周期都归spring控制,从构造实例到释放资源。通过这样可以方便bean集中管理,struts2只需要管理展现层的逻辑就可以了,不过struts2的配置有一些改变,就是可以在action标签的class填上spring中定义的bean的name就可以获取到了,当然也可以想原来一样直接写完整的class,两种都可以。不过在某些情况下,第一种写法会有问题!这些我们后面会解析!我们先统一看一下这个struts2-spring-plugin。
我是用的插件是struts2-spring-plugin-2.0.8,里面有一些文件,版本说明文件、StrutsSpringObjectFactory.class、struts-plugin.xml等文件,主要就只有两个 ———— StrutsSpringObjectFactory.class、struts-plugin.xml文件,这两个文件就完成了我们的集成功能了,当然前提就是我们已经配置好spring的基本环境,就是web.xml要加上这个监听器,
web.xml
才能构建基本spring环境。好!我们假设我们已经配置好了spring基本的环境了。我们先看看struts-plugin.xml配置文件,
struts-plugin.xml
在上面的代码我们明显看到struts-plugin.xml中,添加了一个type为com.opensymphony.xwork2.ObjectFactory,name为"spring",class为org.apache.struts2.spring.StrutsSpringObjectFactory的bean,并且以constant标签的形式,指定了struts.objectFactory常量的值是"spring",这里明显就是要用这个StrutsSpringObjectFactory来代替原本的StrutsObjectFactory!因为ObjectFactory是创建Action、Interceptor、Result等实例的工厂,是创建这些对象的必经之路!spring通过替换这个ObjectFactroy把自己spring容器内的bean传给struts2使用,不过这个过程是怎么实现的呢,后面会说!先分析这些配置的解析过程吧!
我们应该记得上面说过struts2配置文件加载顺序吧!简单复习一下:
1、org/apache/struts2/default.properties
2、struts-default.xml
3、struts-plugin.xml(如果有)
4、struts.xml
5、struts.properties(如果有)
6、default.properties(如果有)
7、struts.custom.properties(如果有)
由于org/apache/struts2/default.properties没有指定struts.objectFactory常量的值,所以在后面StrutsXmlConfigurationProvider供给器注册,就是解析配置文件过程,当解析到struts-plugin.xml时候,供给器直接将struts.objectFactory常量,设置成"spring",并且储存在了容器配置对象中;同时,供给器也将struts-plugin.xml中name为"spring"的bean创建了对应的工厂对象,以type为com.opensymphony.xwork2.ObjectFactory,name为"spring",构成Key对象,并储存在容器构造器中。那么,当在BeanSelectionProvider注册时候,通过调用下面这段代码,
BeanSelectionProvider.register
BeanSelectionProvider.alias
可以看出,最后通过BeanSelectionProvider供给器,把spring提供的org.apache.struts2.spring.StrutsSpringObjectFactory设置为默认工厂对象,就是凡是struts2用到的ObjectFactory,都是使用org.apache.struts2.spring.StrutsSpringObjectFactory生成的实例,通过这种方式,spring成功替换了ObjectFactory,从根本上控制了Action、Interceptor、Result等类的实例的获取,通过springIOC管理这些类对应的bean,这样struts2就可以和spring完美的集成在一起了!值得一提的,虽然可以让spring管理Action、Interceptor、Result等类的bean,但是一般情况下只用于管理Action的bean,并且对于Action、Result类的bean最好在springIOC中设置Scope为prototype,就是多例的bean,因为spring默认是单例的,否则strus2会出问题!
注意这里还有两个拦截器,默认情况下是不使用的,当时有必要可以通过package中extends方式集成这个spring-default,struts2支持继承多个package,然后既可以使用了,加载配置生成配置对象时候就可以生成对应的拦截器了!ActionAutowiringInterceptor拦截器是用来为action注入依赖值的,在spring中没有设置自动装配时候,可以通过这个拦截器自动注入依赖值给action;SessionContextAutowiringInterceptor拦截器,这个拦截器已经不被支持了,已经在struts2中去掉,所以这里不说了,以后简单介绍一下第一个ActionAutowiringInterceptor拦截器。
好,我们先看一下,StrutsSpringObjectFactory是怎么实现通过插件将spring的bean传给struts2的,
StrutsSpringObjectFactory
简单说一下spring的WebApplicationContext,这个是spring的重要的环境,所有的bean都管理在里面,当spring初始化完之后,这个WebApplicationContext就会被放到servletContext中,方便后面使用,所以struts2-spring的集成插件就是利用这个原理,通过servletContext获取到WebApplicationContext,获取到他就可以获取到spring的bean,获取到spring的bean就可以在struts2中使用这些bean,进行各种操作,具体怎么获取这些bean后面详细说。
上面就是一个完整的StrutsSpringObjectFactory,他就实现了一个构造函数,而具体的怎么获取spring的bean之类的细节都是在其父类SpringObjectFactory中实现的,而这个构造函数只是负责设置值,不过里面设置的值都是关键的值,比如this.setApplicationContext(appContext)、this.setAutowireStrategy(type)、this.setAlwaysRespectAutowireStrategy那些操作,我先说说this.setApplicationContext(appContext),这个比较重要,
StrutsSpringObjectFactory.setApplicationContext(SpringObjectFactory.setApplicationContext)
这里重点是autoWiringFactory和findAutoWiringBeanFactory方法两个,autoWiringFactory是使用spring提供的ObjectFactory创建实例时候为实例对象自动装配属性值的工厂对象,这个工厂是在spring环境中查找对应属性的bean,然后注入属性中。spring自身进行自动装配也是使用autoWiringFactory进行的,如果没有配置自动装配就要自己在bean写明属性值的bean,而这里默认就是使用这个自动装配工厂对象,为struts2的指定对象进行自动装配了;findAutoWiringBeanFactory方法是通过已经提供的Application环境,在Application环境里面查找这个工厂,递归父环境查找,直到找到或者为空。我们先看看这个findAutoWiringBeanFactory代码。
SpringObjectFactory.findAutoWiringBeanFactory
所以通过StrutsSpringObjectFactory.setApplicationContext就可以同时设置了自动装配工厂,于是,两个重要的对象都初始化好了!接下来就是this.setAutowireStrategy(type)、this.setAlwaysRespectAutowireStrategy操作,其实这两个操作都是直接设置值,重要的是这两个setter方法对应的属性,就是autowireStrategy、alwaysRespectAutowireStrategy两个属性,默认autowireStrategy为AutowireCapableBeanFactory.AUTOWIRE_BY_NAME,默认alwaysRespectAutowireStrategy为false;autowireStrategy是用来指定自动装配的方式,就是自动装配是根据bean名、类型、构造函数、默认、不使用自动装配中的哪一种方式,跟spring的自动装配是一个原理的,struts2借用了这个,可以通过struts2的struts.objectFactory.spring.autoWire常量指定;alwaysRespectAutowireStrategy就是指是不是始终遵循autowireStrategy的设置,可以通过struts2的struts.objectFactory.spring.autoWire.alwaysRespect常量指定。
先简单介绍一下StrutsSpringObjectFactory怎么进行创建实例的处理,在StrutsSpringObjectFactory中创建实例,如果是使用第一种配置方式,就是在action配置文件中,class属性写的bean的名或者ID,那么,StrutsSpringObjectFactory会直接通过spring环境去取指定bean名或者ID的对象,不实用struts2自身的容器构建;如果是使用第二种方式,就是按原来那样写上完整class名,那么,spring默认就会根据构造函数方式,创建出对应的bean,然后通过spring先对这个bean的属性进行注入依赖,然后再让struts2通过struts2的容器注入依赖。由于StrutsSpringObjectFactory继承于SpringObjectFactory,SpringObjectFactory继承于ObjectFactory,所以当创建实例时候会调用buildBean方法,因此我们也重点关注这个方法,同时由于SpringObjectFactory覆盖了这个方法,我们针对SpringObjectFactory类。下面具体看一下代码,
SpringObjectFactory.buildBean
这个autoWireBean是一个提供spring和struts2混合的自动装配的方法,之前我们简单说了一下,我们现在看看代码,
SpringObjectFactory.autoWireBean
从上面的代码,再结合刚才的对这个过程的简单介绍,可以知道StrutsSpringObjectFactory是怎么创建实例了吧!这里就不再多说了!不过说一下为什么,要使用autoWireBean进行两个不同环境的装配!
由于spring中只能提供spring环境中的属性值,struts2容器只能提供struts2的属性值,比如action中要使用service管理业务逻辑,这些service一般都在spring中管理,因为有AOP那些框架,用起来方便,所以这些要使用spring的装配;而象valuestack这些(假如要直接用),就只有在strus2环境中才有,所以要使用struts2装配。这些道理很简单!
一开始说的在集成时候配置action的class属性的两种方式还记得吗!?不是说过第一种配置方式有些情况会产生问题吗!我在这里说一下,根据上面第一个SpringObjectFactory.buildBean代码,如果使用第一种配置方式,就是直接在class属性设置bean名,由于appContext.getBean(beanName)可以直接获取到bean,然后注入了struts2属性之后就直接返回了,因此啊,autowireStrategy和alwaysRespectAutowireStrategy两个属性就完全不生效了,现在的装配方式完全取决于spring的自动装配!注意,别以为injectInternalBeans会根据autowireStrategy和alwaysRespectAutowireStrategy属性装配,因为injectInternalBeans是父类ObjectFactory的方法,而autowireStrategy和alwaysRespectAutowireStrategy是SpringObjectFactory的属性,是毫不相干的!因此,如果是想使用autowireStrategy和alwaysRespectAutowireStrategy两个配置,就不能使用第一种配置方式!
最后一个是对ActionAutowiringInterceptor拦截器的简单介绍,看代码吧!
ActionAutowiringInterceptor.intercept
从代码可以看出这个操作是为action注入spring和struts2两个环境的依赖属性值的,在spring中没有设置自动装配时候,可以通过这个拦截器自动注入依赖值给action,这个主要是针对spring出现状况时候应急使用的!可以在struts2的配置文件中,通过在package的extends属性加入spring-default,继承这个package来使用这个拦截器。
上面已经基本上完整的介绍了struts2-spring-plugin,从spring的ObjectFactory怎么替换原来struts2的ObjectFactory,到StrutsSpringObjectFactory创建bean的过程,然后到ActionAutowiringInterceptor拦截器的简单介绍,整个插件就全部讲解完毕。
第四部分:总结
上面已经讲述了struts2的整个运行过程,从FilterDispatcher的init开始,涉及到struts2的容器创建、依赖注入、配置文件解析、常量解析,然后action配置对象、result配置对象、result类型配置对象等生成,再然后运行时配置对象的生成,组装起之前的配置对象,生成一个完整的运行时配置对象,初始化完成,到FilterDispatcher的doFilter,涉及到mapping对象的获取、代理对象的创建、Action的执行、Result对象的创建、Result对象的执行,最后解析视图,返回客户端,整个struts2的运行过程。
之后,有讲述了struts2-spring-plugin,从ObjectFactory的替代,到StrutsSpringObjectFactory创建bean的过程,整个过程都完结。
对于这篇文章,上面那些源代码都是我自己看完,然后总结出来的精髓,然后通过网上资料的搜集、整理、加上自己的描述,写出来的!绝对原创,里面已经包含了一些网上面提到的一些比较有少人解决总结的问题,还有一些比较少人解决的问题,里面都基本上有说到!希望对大家有帮助,因为完成得比较仓促,可能有一些词汇没有把握准,或者有一些错误的见解、错字、语法错误等问题,请大家指出来!欢迎大家转载!!
我是根据代码走向分析的,分析的代码是按xwork 2.1.5和struts 2.1.8.1版本的。
这个过程是从客户端发起访问,到服务端响应,并返回结果的整个过程。
第一部分:一切从FilterDispatcher开始
FilterDispatcher作为struts2的一个核心过滤器(Filter),接受所有客户端的请求,经过一系列的处理才到我们的Action。对于Filter,他首先执行init方法初始化,因此我们先看看FilterDispatcher的初始化做了什么。
FilterDispatcher.init
public void init(FilterConfig filterConfig) throws ServletException { try { //获取web.xml的Filter内的init-param参数配置 this.filterConfig = filterConfig; initLogging();//初始化日志 //根据参数创建分发器 dispatcher = createDispatcher(filterConfig); dispatcher.init();//初始化 //通过内部容器给当前过滤器的成员注入值(被@Inject标识过的成员才会被注入) dispatcher.getContainer().inject(this); //.......省略部分代码 } finally { ActionContext.setContext(null); } }
init的主要工作是创建并初始化了dispatcher (分发器),分发器里面涉及到很多重要的操作,比如说加载配置、创建内部容器、以至创建ObjectFactory、创建以及调度Action等等,都是在dispatcher 中完成的,所以我们主要关注这个dispatcher。
值得一提的是@Inject注解,struts2也有依赖注入功能,在struts2中通过这个注解标识当前类的成员需要注入值。当然如果我们普通开发人员使用到注入功能,需要把使用到依赖注入功能的类在配置文件中用<bean>标签配置好才能生效。
比如这样:
struts-plugin.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 type="com.opensymphony.xwork2.ObjectFactory" name="spring" class="org.apache.struts2.spring.StrutsSpringObjectFactory" /> <!-- 省略 --> </struts>
接着我们看具体dispatcher 的创建过程,createDispatcher方法。
FilterDispatcher.createDispatcher
protected Dispatcher createDispatcher(FilterConfig filterConfig) { Map<String, String> params = new HashMap<String, String>(); //遍历filterConfig把init-param全部拿出来,就是那些在web.xml配置在Filter标签里面的初始化参数 for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements();) { String name = (String) e.nextElement(); String value = filterConfig.getInitParameter(name); params.put(name, value); } //构造dispatcher return new Dispatcher(filterConfig.getServletContext(), params); }
Dispatcher.Dispatcher
public Dispatcher(ServletContext servletContext, Map<String, String> initParams) { this.servletContext = servletContext; this.initParams = initParams; }
当前基本构造成了dispatcher的基本对象,接着是对dispatcher初始化,初始化里面做了几件事:
1、构造了以struts2为基础(可以集成spring)的配置管理器(ConfigurationManager)
2、向配置管理器加入配置供给器(ConfigurationProvider),为内部容器(Container的)构建提供了基础。
3、构建内部容器,为各个struts2的基础类以及开发人员配置的类提供相关实例的方法,并且提供相关的依赖注入的功能。
4、设置是否允许重新加载xml配置文件
5、通知相关的分发器监听器初始化完成消息
我们关注前三点。这里有一个比较重要的类 ———— Container,该类提供了struts2的基础类以及在配置文件配置了的类的实例化以及依赖注入的方法。Container里面包含了一个工厂map,里面包含了相关类的内部工厂类(InternalFactory),InternalFactory 里面有一个create方法,用来创建相关类的实例;可以通过调用Container.getInstance(Class c)方法,获取类类型c描述的类的实例,详细后面说面。
接下来我们看Dispatcher.init方法
Dispatcher.init
public void init() { //如果配置管理器为空,则创建一个 if (configurationManager == null) { //默认使用struts为基础创建配置管理器 configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); } try { //向配置管理器加入默认配置供给器(DefaultPropertiesProvider),为解析org/apache/struts2/default.properties提供基础 init_DefaultProperties(); // [1] //加入传统XML配置供给器(XmlConfigurationProvider),为解析struts-default.xml,struts-plugin.xml,struts.xml等配置提供基础 init_TraditionalXmlConfigurations(); // [2] //加入旧版struts配置供给器(LegacyPropertiesConfigurationProvider), //为解析旧版的配置文件stuts.properties,default.properties,struts.custom.properties等(如果有这些文件)提供基础,起到兼容作用 init_LegacyStrutsProperties(); // [3] //加入自定义的供给器,向配置管理器加入自己创建的配置供给器 init_CustomConfigurationProviders(); // [5] //加入Filter初始化参数供给器,将Filter的初始化参数放入到容器配置(ContainerProperties)中,为构建容器提供基础 init_FilterInitParameters() ; // [6] //加入别名标准对象供给器(BeanSelectionProvider),为容器的构建提供基础,该供给器为容器加入struts2基础类的工厂 init_AliasStandardObjects() ; // [7] //预加载配置,构建容器 Container container = init_PreloadConfiguration(); container.inject(this); //是否允许重新加载XML配置 init_CheckConfigurationReloading(container); init_CheckWebLogicWorkaround(container); //触发监听器初始化事件 if (!dispatcherListeners.isEmpty()) { for (DispatcherListener l : dispatcherListeners) { l.dispatcherInitialized(this); } } } catch (Exception ex) { if (LOG.isErrorEnabled()) LOG.error("Dispatcher initialization failed", ex); throw new StrutsException(ex); } }
Dispatcher.init_DefaultProperties
//加入默认配置供给器,解析org/apache/struts2/default.properties private void init_DefaultProperties() { configurationManager.addConfigurationProvider(new DefaultPropertiesProvider()); }
Dispatcher.init_TraditionalXmlConfigurations
//加入传统XML配置供给器,解析struts-default.xml,struts-plugin.xml,struts.xml的配置 private void init_TraditionalXmlConfigurations() { //读取Filter的初始参数,通过参数获取配置文件 String configPaths = initParams.get("config"); //如果没有配置,按默认配置获取 if (configPaths == null) { //struts-default.xml,struts-plugin.xml,struts.xml configPaths = DEFAULT_CONFIGURATION_PATHS; } String[] files = configPaths.split("\\s*[,]\\s*"); for (String file : files) { if (file.endsWith(".xml")) { if ("xwork.xml".equals(file)) { //加入解析xwork.xml的配置供给器 configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false)); } else { //加入解析struts-default.xml,struts-plugin.xml,struts.xml的配置供给器(默认情况) configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext)); } } else { throw new IllegalArgumentException("Invalid configuration file name"); } } }
Dispatcher.init_LegacyStrutsProperties
//加入解析旧版struts配置供给器,旧版struts2的一些properties配置文件, //例如struts.properties、default.properties(不同于init_DefaultProperties的default.properties)、struts.custom.properties等 private void init_LegacyStrutsProperties() { //stuts.properties,default.properties,struts.custom.properties configurationManager.addConfigurationProvider(new LegacyPropertiesConfigurationProvider()); }
Dispatcher.init_CustomConfigurationProviders
//加入自定义的供给器 private void init_CustomConfigurationProviders() { //根据Filter初始化参数获取自定义的配置供给器 String configProvs = initParams.get("configProviders"); if (configProvs != null) { String[] classes = configProvs.split("\\s*[,]\\s*"); for (String cname : classes) { try { //实例化对应的类 Class cls = ClassLoaderUtils.loadClass(cname, this.getClass()); ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance(); //加入对应的配置供给器 configurationManager.addConfigurationProvider(prov); } catch (InstantiationException e) { throw new ConfigurationException("Unable to instantiate provider: "+cname, e); } catch (IllegalAccessException e) { throw new ConfigurationException("Unable to access provider: "+cname, e); } catch (ClassNotFoundException e) { throw new ConfigurationException("Unable to locate provider class: "+cname, e); } } } }
Dispatcher.init_FilterInitParameters
//加入Filter初始化参数供给器,将Filter的初始化参数放入到容器配置 private void init_FilterInitParameters() { configurationManager.addConfigurationProvider(new ConfigurationProvider() { public void destroy() {} public void init(Configuration configuration) throws ConfigurationException {} public void loadPackages() throws ConfigurationException {} public boolean needsReload() { return false; } public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { //将Filter的初始化参数放入到容器配置 props.putAll(initParams); } }); }
Dispatcher.init_AliasStandardObjects
//加入别名标准对象供给器(BeanSelectionProvider),为容器的构建提供基础 private void init_AliasStandardObjects() { configurationManager.addConfigurationProvider(new BeanSelectionProvider()); }
Dispatcher.init_PreloadConfiguration
//预加载配置,构建容器 private Container init_PreloadConfiguration() { //获取配置(Configuration),如果配置对象未初始化则初始化配置,并构建容器 Configuration config = configurationManager.getConfiguration(); //获取容器,容器在getConfiguration时候已经构建好 Container container = config.getContainer(); //是否允许重新加载国际化配置 boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD)); LocalizedTextUtil.setReloadBundles(reloadi18n); return container; }
从上面代码可以看出struts2对配置文件的加载顺序:
1、org/apache/struts2/default.properties
2、struts-default.xml
3、struts-plugin.xml(如果有)
4、struts.xml
5、struts.properties(如果有)
6、default.properties(如果有)
7、struts.custom.properties(如果有)
org/apache/struts2/default.properties是配置了struts2的常量的默认值,比如struts.i18n.encoding、struts.action.extension、struts.i18n.reload等常量。
struts-default.xml里面配置了struts2的核心bean,以及用constant配置的常量,比如ObjectFactory、ActionProxyFactory、ActionMapper等bean。
struts-plugin.xml是struts2的插件的配置文件,比如struts2-spring-plugin。
struts.xml是使用struts2时候基本配置,比如action、result、interceptor、constant等。
struts.properties、default.properties、struts.custom.properties是原来旧版本的一些配置。
注意:后面加载的配置文件可以覆盖前面的配置文件的配置
从上面代码可以知道,init_XXXX的方法是为配置管理器收集配置供给器的信息,而最后调用init_PreloadConfiguration方法,构建出配置对象和容器,他们是struts2中非常重要的两个对象,配置对象里面包含了struts2的所有的配置信息,容器里面包含了struts2中所有的对象的工厂对象。以后使用到的Action、拦截器(Interceptor)、Result等对象都是通过他们两个协同创建出来的。
接下来我们看看getConfiguration是怎么创建配置和容器的。
ConfigurationManager.getConfiguration
public synchronized Configuration getConfiguration() { //如果配置对象未创建,则默认以struts基础构建一个 if (configuration == null) { //创建配置对象 setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName)); try { //通过配置对象,并以容器供给器(ContainerProvider)(就是前面的配置供给器, //前面已经通过ConfigurationManager.addConfigurationProvider放入到ConfigurationManager.containerProviders中)为基础构(重加载)建出一个容器。 //ConfigurationProvider是ContainerProvider的一个子接口。 configuration.reloadContainer(getContainerProviders()); } catch (ConfigurationException e) { setConfiguration(null); throw new ConfigurationException("Unable to load configuration.", e); } } else { //根据实际判断是否需要重加载容器,如果需要则所有配置都会重新加载一次 conditionalReload(); } return configuration; }
上面代码就是创建出配置对象,并根据之前ConfigurationManager.addConfigurationProvider加入的供给器,并以他为参数调用DefaultConfiguration.reloadContainer方法来构建容器,当然DefaultConfiguration.reloadContainer也可以用作重加载容器。最后的conditionalReload是根据容器在构建时候一些供给器的特殊需要,判断是否需要重新加载容器。
接下来我们要看DefaultConfiguration.reloadContainer方法,具体看看他是怎么构建容器的。
DefaultConfiguration.reloadContainer
public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException { packageContexts.clear(); loadedFileNames.clear(); List<PackageProvider> packageProviders = new ArrayList<PackageProvider>(); //创建一个容器配置对象(ContainerProperties) ContainerProperties props = new ContainerProperties(); //创建一个容器构造器(ContainerBuilder) ContainerBuilder builder = new ContainerBuilder(); //将之前的容器供给器初始化并注册到容器构造器和容器配置对象中 //容器构造器是用来创建容器的,这个注册过程是为容器构造器添加相应类的工厂对象, //这些工厂对象在容器构造器构建容器的时候传给容器,使容器拥有所有struts2对象的实例化的能力。 //在供给器注册过程中,一些struts2的常量会记录在容器配置对象中,方便以后容器根据配置生成对应类的实例。 for (final ContainerProvider containerProvider : providers){ containerProvider.init(this); containerProvider.register(builder, props); } //将常量也存放到容器构造器中,也是通过工厂对象的形式。 props.setConstants(builder); //将当前对象(DefaultConfiguration)也放到容器构造器的工厂对象中,以便后边的类依赖注入时候使用。 builder.factory(Configuration.class, new Factory<Configuration>() { public Configuration create(Context context) throws Exception { return DefaultConfiguration.this; } }); ActionContext oldContext = ActionContext.getContext(); try { //createBootstrapContainer实质是用来扩展的方法,他构建出的bootstrap是一个引导容器, //将struts2的核心对象和一般的对象隔离开,保护核心对象的安全。不过在现在这个版本bootstrap还没有实质的作用,只是作为以后的版本的扩展方法。 Container bootstrap = createBootstrapContainer(); //构建出以核心对象为基础的Action环境(又叫上下文),不过现在无实际意义 setContext(bootstrap); //我们真正使用的容器,所有对象都通过这个容器实例化 container = builder.create(false); //这个也没有实际意义,因为被bootstrap使用了 setContext(container); //构建出objectFactory对象,这个是重要的对象,这个objectFactory将为我们使用的Action、拦截器、Result等提供实例对象 objectFactory = container.getInstance(ObjectFactory.class); //下面的操作也比较重要 //因为通过容器供给器解析XML和Properties文件, //构建出我们的Action配置对象、Result配置对象、Interceptor映射对象、包配置对象、异常配置对象等等(注意是配置对象,还不是真正可以运行的对象) //不过上面提到的众多对象都是主要是StrutsXmlConfigurationProvider和XmlConfigurationProvider供给器对象提供的, //因此我们也只针对他们展开 for (final ContainerProvider containerProvider : providers){ if (containerProvider instanceof PackageProvider) { container.inject(containerProvider); //从PackageProvider.loadPackages开始构建我们的配置对象,这里会调用对应供给器的loadPackages方法, //解析配置文件,构造出对应的配置对象,并储存在对应的供给器中 ((PackageProvider)containerProvider).loadPackages(); //将构建好配置对象的供给器保存起来,方便后面使用 packageProviders.add((PackageProvider)containerProvider); } } //为插件解析相应的配置文件,操作跟上面差不多,只是针对的是插件,不详细说 Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class); if (packageProviderNames != null) { for (String name : packageProviderNames) { PackageProvider provider = container.getInstance(PackageProvider.class, name); provider.init(this); provider.loadPackages(); packageProviders.add(provider); } } //下面的重做也比较重要 //构建出运行时配置对象,加入一些struts2默认的配置对象 rebuildRuntimeConfiguration(); } finally { //清空当前的Action环境,保护核心代码安全,不过现在没有实质作用 if (oldContext == null) { ActionContext.setContext(null); } } //返回包供给器 return packageProviders; }
上面的操作比较多也比较复杂,我们一个个分析。
首先我们分析ContainerProvider.register,这个方法会调用我们之前在配置管理器收集好的供给器的register方法,通过这个方法将配置在配置文件的类,以工厂对象的方式放入了容器构造器当中,为后面容器所用。
值得一提的是DefaultPropertiesProvider、StrutsXmlConfigurationProvider、BeanSelectionProvider供给器,他们提供了很多重要的对象的工厂对象和struts的默认常量,DefaultPropertiesProvider执行配置文件(org/apache/struts2/default.properties)的解析,并把这些常量储存在容器配置对象中;StrutsXmlConfigurationProvider执行了配置文件(struts-default.xml)里面的<bean>和<constant>等标签的解析,以及将解析出来的类创建工厂对象,还有创建servletContext的工厂对象,然后将这些工厂对象一同放入到容器构造器中;BeanSelectionProvider提供了struts2的核心类的工厂对象,是以别名的形式提供的,就是如果已经在容器构造器中存在了相应类的工厂对象,就重新起一个别名(这个别名是default,这样做的意义是把这些已经生成的工厂对象设置成默认工厂对象,为生成指定对象时候提供默认生成方式),然后把别名和工厂对象,以key-value形式重新放到容器构造器中,就是同一个工厂对象可能有两个不同的名称(key)指向;如果没有相应的工厂对象就新建一个对应的工厂对象,放入容器构造器中。总结来说,ContainerProvider.register方法都是将XML文件、Properties文件以及struts2核心类等等转换成相应的工厂对象,然后储存入容器构造器的过程。
下面分析一下主要的DefaultPropertiesProvider、StrutsXmlConfigurationProvider、BeanSelectionProvider供给器的inti、register方法
DefaultPropertiesProvider.init(LegacyPropertiesConfigurationProvider.init)
public void init(Configuration configuration) throws ConfigurationException { //空实现 }
DefaultPropertiesProvider.register
public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { Settings defaultSettings = null; try { //加载配置文件,生成常量集合的实例 defaultSettings = new PropertiesSettings("org/apache/struts2/default"); } catch (Exception e) { throw new ConfigurationException("Could not find or error in org/apache/struts2/default.properties", e); } //调用父类方法,将解析好的常量储存在容器配置对象中 loadSettings(props, defaultSettings); }
DefaultPropertiesProvider.loadSettings(LegacyPropertiesConfigurationProvider.loadSettings)
//调用父类的方法,即是LegacyPropertiesConfigurationProvider.loadSettings protected void loadSettings(LocatableProperties props, final Settings settings) { //settings就是所有常量的集合,通过PropertiesSettings生成 for (Iterator i = settings.listImpl(); i.hasNext(); ) { String name = (String) i.next(); //储存在容器配置对象中 props.setProperty(name, settings.getImpl(name), settings.getLocationImpl(name)); } }
StrutsXmlConfigurationProvider.init(XmlConfigurationProvider.init)
//调用父类的init,即是XmlConfigurationProvider.init public void init(Configuration configuration) { this.configuration = configuration; //获取已经加载过的文件,加载过的文件则不加载 this.includedFileNames = configuration.getLoadedFileNames(); //加载当前的供给器的配置文件名,如果没有找到对应文件则不进行任何操作 //默认情况下struts-default.xml,struts-plugin.xml,struts.xml等几个文件 loadDocuments(configFileName); }
StrutsXmlConfigurationProvider.register
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { //容器构造器是否包含servletContext的工厂,如果没有则创建工厂,加入到容器构造器中 if (servletContext != null && !containerBuilder.contains(ServletContext.class)) { containerBuilder.factory(ServletContext.class, new Factory<ServletContext>() { public ServletContext create(Context context) throws Exception { //直接返回当前的servletContext return servletContext; } }); } //调用父类的注册,StrutsXmlConfigurationProvider的父类是XmlConfigurationProvider, //父类是用来解析配置文件中的<bean>和<constant>等标签 super.register(containerBuilder, props); }
XmlConfigurationProvider.register
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { //省略部分日志代码...... Map<String, Node> loadedBeans = new HashMap<String, Node>(); //遍历所有配置文件,以Document对象的形式 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(); //解析bean标签 if ("bean".equals(nodeName)) { //type和name会用作组成Key对象,用作容器构造器里面的工厂map的key, //唯一标识储存在map中的factory对象 String type = child.getAttribute("type"); String name = child.getAttribute("name"); //真正用作创建对象的类,或者叫实现类 String impl = child.getAttribute("class"); //是否只需要注入静态成员,如果是则将该类储存到容器构造器的staticInjections中, //当构造容器时候就马上对只需要静态注入的类进行相关静态成员的依赖注入, //就是在容器创建时候就将该类的静态成员初始化完成 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; } //如果没有指定名称,则使用"default" 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)) { //强制触使JVM加载该类 cimpl.getDeclaredClasses(); //需要注入静态成员,将该类储存到容器构造器的staticInjections中 containerBuilder.injectStatics(cimpl); } else {//不需要注入静态成员 //这里不允许重复的工厂方法到容器构造器中 if (containerBuilder.contains(ctype, name)) { //省略部分异常处理代码...... } //强制触使JVM加载该类 cimpl.getDeclaredConstructors(); //省略部分日志代码...... //创建相应的工厂对象,并方法到容器构造器中 containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope); } //记录已经记载的bean loadedBeans.put(ctype.getName() + name, child); } catch (Throwable ex) { //省略异常处理代码...... } } else if ("constant".equals(nodeName)) {//加载配置文件的常量 String name = child.getAttribute("name"); String value = child.getAttribute("value"); //将常量储存在容器配置对象中 props.setProperty(name, value, childNode); } //省略部分代码...... } } } }
BeanSelectionProvider.init
public void init(Configuration configuration) throws ConfigurationException { //空实现 }
BeanSelectionProvider.register
public void register(ContainerBuilder builder, LocatableProperties props) { //alias针对的都是为struts配置具体实现类的常量,因此这些常量可以直接指定具体的类或者类的bean名 //alias操作为下面的类的工厂对象设置别名("default") //alias意义是把这些已经生成的工厂对象设置成默认工厂对象,为生成指定类的实例时候提供默认生成方式 alias(ObjectFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY, builder, props); alias(XWorkConverter.class, StrutsConstants.STRUTS_XWORKCONVERTER, builder, props); alias(TextProvider.class, StrutsConstants.STRUTS_XWORKTEXTPROVIDER, builder, props, Scope.DEFAULT); alias(ActionProxyFactory.class, StrutsConstants.STRUTS_ACTIONPROXYFACTORY, builder, props); alias(ObjectTypeDeterminer.class, StrutsConstants.STRUTS_OBJECTTYPEDETERMINER, builder, props); alias(ActionMapper.class, StrutsConstants.STRUTS_MAPPER_CLASS, builder, props); alias(MultiPartRequest.class, StrutsConstants.STRUTS_MULTIPART_PARSER, builder, props, Scope.DEFAULT); alias(FreemarkerManager.class, StrutsConstants.STRUTS_FREEMARKER_MANAGER_CLASSNAME, builder, props); alias(VelocityManager.class, StrutsConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME, builder, props); alias(UrlRenderer.class, StrutsConstants.STRUTS_URL_RENDERER, builder, props); alias(ActionValidatorManager.class, StrutsConstants.STRUTS_ACTIONVALIDATORMANAGER, builder, props); alias(ValueStackFactory.class, StrutsConstants.STRUTS_VALUESTACKFACTORY, builder, props); alias(ReflectionProvider.class, StrutsConstants.STRUTS_REFLECTIONPROVIDER, builder, props); alias(ReflectionContextFactory.class, StrutsConstants.STRUTS_REFLECTIONCONTEXTFACTORY, builder, props); alias(PatternMatcher.class, StrutsConstants.STRUTS_PATTERNMATCHER, builder, props); alias(StaticContentLoader.class, StrutsConstants.STRUTS_STATIC_CONTENT_LOADER, builder, props); alias(UnknownHandlerManager.class, StrutsConstants.STRUTS_UNKNOWN_HANDLER_MANAGER, builder, props); //省略将常量储存入容器配置对象的代码...... }
BeanSelectionProvider.alias
//为指定类的工厂对象设置别名(default) void alias(Class type, String key, ContainerBuilder builder, Properties props, Scope scope) { //type是类型;key是指定在default.properties或者struts-default.xml配置的常量名,比如struts.action.extension、struts.freemarker.manager.classname等 //props是容器配置对象,scope是指定生成的工厂对象是否产生单例对象 //这里的builder.contains(type)相当于调用builder.contains(type, "default") //判断是否已经设置了默认的工厂对象,如果设置了则跳过; if (!builder.contains(type)) { //从容器配置对象获取指定常量(key)的值,如果没有找到值,则使用DEFAULT_BEAN_NAME("struts") String foundName = props.getProperty(key, DEFAULT_BEAN_NAME); //容器构造器是否包含类型为type,名称是foundName的工厂对象(foundName不可能等于"default") if (builder.contains(type, foundName)) { if (LOG.isDebugEnabled()) { LOG.info("Choosing bean ("+foundName+") for "+type); } //如果包含这个工厂对象,为这个工厂对象设置一个别名Container.DEFAULT_NAME("default"),为此这个工厂对象被设置成创建该类型实例的默认工厂对象 builder.alias(type, foundName, Container.DEFAULT_NAME); } else {//如果找不到,就认为foundName是一个类的名称,因为常量的值,可以是类名 try { //尝试加载该类 Class cls = ClassLoaderUtil.loadClass(foundName, this.getClass()); if (LOG.isDebugEnabled()) { LOG.debug("Choosing bean ("+cls+") for "+type); } //加载成功则创建这个类的工厂对象,并设置为该类的默认工厂对象 builder.factory(type, cls, scope); } catch (ClassNotFoundException ex) {//加载不成功 if (LOG.isDebugEnabled()) { LOG.debug("Choosing bean ("+foundName+") for "+type+" to be loaded from the ObjectFactory"); } if (DEFAULT_BEAN_NAME.equals(foundName)) {//判断foundName是否"struts",如果是,则不需要任何操作,因为本来struts就是默认,不需要添加struts作为默认。 //如果是struts默认实现则不需要任何操作 } else {//如果不是struts默认,也不是一个指定的类,那很可能是使用了其他的框架来获取该bean,比如spring等 if (ObjectFactory.class != type) { //如果不是ObjectFactory类型,则为其创建委托工厂类,在运行时候根据具体的框架获取该bean的实例 builder.factory(type, new ObjectFactoryDelegateFactory(foundName, type), scope); } else { //因为ObjectFactory类型已经根据具体框架的配置创建了,不能再这样指定 throw new ConfigurationException("Cannot locate the chosen ObjectFactory implementation: "+foundName); } } } } } else { LOG.warn("Unable to alias bean type "+type+", default mapping already assigned."); } }
针对上面的代码解析一下,可能很多人都不理解上面的操作,为什么要这样操作,其实跟struts2的org/apache/struts2/default.properties和struts-default.xml有关,先看一下两个配置文件的部分内容:
default.properties
struts.i18n.encoding=UTF-8 #struts.objectFactory = spring struts.objectFactory.spring.autoWire = name struts.objectFactory.spring.useClassCache = true struts.objectFactory.spring.autoWire.alwaysRespect = false #struts.objectTypeDeterminer = tiger #struts.objectTypeDeterminer = notiger struts.multipart.saveDir= struts.multipart.maxSize=2097152 #struts.mapper.class = org.apache.struts2.dispatcher.mapper.DefaultActionMapper struts.action.extension=action,, struts.serve.static=true struts.serve.static.browserCache=true
struts-default.xml
<bean class="com.opensymphony.xwork2.ObjectFactory" name="xwork" /> <bean type="com.opensymphony.xwork2.ObjectFactory" name="struts" class="org.apache.struts2.impl.StrutsObjectFactory" /> <bean type="com.opensymphony.xwork2.ActionProxyFactory" name="xwork" class="com.opensymphony.xwork2.DefaultActionProxyFactory"/> <bean type="com.opensymphony.xwork2.ActionProxyFactory" name="struts" class="org.apache.struts2.impl.StrutsActionProxyFactory"/> <bean type="com.opensymphony.xwork2.conversion.ObjectTypeDeterminer" name="tiger" class="com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer"/> <bean type="com.opensymphony.xwork2.conversion.ObjectTypeDeterminer" name="notiger" class="com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer"/> <bean type="com.opensymphony.xwork2.conversion.ObjectTypeDeterminer" name="struts" class="com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer"/> <bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="struts" class="org.apache.struts2.dispatcher.mapper.DefaultActionMapper" /> <bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="composite" class="org.apache.struts2.dispatcher.mapper.CompositeActionMapper" /> <bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="restful" class="org.apache.struts2.dispatcher.mapper.RestfulActionMapper" /> <bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="restful2" class="org.apache.struts2.dispatcher.mapper.Restful2ActionMapper" /> <constant name="struts.multipart.handler" value="jakarta" />
很明显,default.properties和struts-default.xml的常量都可以普通值或者指定具体的实现类,例如
<!-- 设置的是普通的值,不是具体的类 --> #struts.objectFactory = spring <!-- 设置的是具体的类 --> #struts.mapper.class = org.apache.struts2.dispatcher.mapper.DefaultActionMapper
,虽然struts.objectFactory这里被注释了,但是之前的版本是在这里指定struts使用哪个框架的,struts2默认认为是ObjectFactory是使用spring提供的。不过现在修改了,通过程序确认是使用那个框架,就是我们上面代码,
//BeanSelectionProvider.alias //如果找不到对应的常量,就用DEFAULT_BEAN_NAME代替(比如ObjectFactory,default.properties里面的struts.objectFactory被注释了, //所以很可能在容器配置对象props找不到,于是就用DEFAULT_BEAN_NAME代替,就相当于在default.properties设置了struts.objectFactory=struts),DEFAULT_BEAN_NAME值为"struts" String foundName = props.getProperty(key, DEFAULT_BEAN_NAME);
有些人会觉得奇怪,只要配置"struts"、"spring"就可以知道是哪个具体实现类了吗?当然不是那么简单,这要看struts-default.xml核心类的bean定义,
<bean class="com.opensymphony.xwork2.ObjectFactory" name="xwork" /> <bean type="com.opensymphony.xwork2.ObjectFactory" name="struts" class="org.apache.struts2.impl.StrutsObjectFactory" />
这些bean都定义了一个name属性,当程序在解析struts-default.xml时候将name属性值和type属性值组成一个Key类型的对象,而class属性值将会用来创建出Class对象(java.lang.Class的实例),然后再根据这个Class对象创建出该类型的工厂对象,最后将工厂对象储存入容器构造器中,以后用作容器构建。其实这些操作就是前面StrutsXmlConfigurationProvider.register执行的操作,只是Key对象的创建是在容器构造器内部完成的。如果后面要实例化这些bean,通过Container.getInstance(type, name)就可以了,type是struts-default.xml里面bean标签的type属性值,name就是bean标签的name属性值。可能是觉得要写两个参数太麻烦,于是struts决定为要设置默认的name,就是在struts-default.xml的bean标签中,如果没有指定name属性的值,或者指定了空值,解析这个bean标签时候,就用"default"作为name,所以对应这个bean的工厂的那个key就是Key(type,"default"),这个也就是这个bean的默认工厂对象。因此,为了统一起见,struts2要为他自己的核心类都指定了默认的name和默认工厂对象,为此他的提供了一个BeanSelectionProvider供给器,专门针对这些操作,也就是我们上面的BeanSelectionProvider.register和BeanSelectionProvider.alias执行的操作,而且为了不影响原来添加的工厂对象,struts2把这个操作放到最后;注意,BeanSelectionProvider.alias并没有覆盖原来的工厂对象,而是以别名的形式多添加了一个而已。
除了在常量上面填写bean的名字,还可以填上具体的实现类的详细类名,在BeanSelectionProvider.alias那里可以体现得出来,他在builder.contains(type, foundName)没有找到对应的工厂时候就认为这个常量可能设置的是具体的类,于是通过ClassLoaderUtil.loadClass尝试加载,如果加载成功就也这个类创建默认工厂对象。
于是,有了这些默认工厂对象的,我们获取实例的时候就只需要调用Container.getInstance(type)就可以了获取到默认的实现类的实例。builder.contains(type)是查询是否包含type的默认工厂的,凡是调用Container或者ContainerBuilder没有带name的方法,都相当于带了"default"这个名称。
无论是Container.getInstance(type),还是Key(type,name)中的type,指的都是bean标签的type属性值,这个type通常都不是真正的实现类,而是一个较为抽象的父类,其目的就是延展了这些类的扩展性、加强封装性和可维护性,只要继承了这个type的抽象父类,写上了一些子类的细节实现,然后在自己配置文件上写上子类的bean,然后修改一下常量的值就可以使用自己的实现,根本不需要改原来的代码,而且其他开发人员根本无法根据这个抽象的父类知道具体的子类及其实现细节。
值得注意的是先加载的配置文件的常量会被后加载的配置文件的同名常量所覆盖,比如说我在default.properties配置了
struts.objectFactory = spring
,而在struts-default.xml配置了
<constant name="struts.objectFactory" value="struts" /> <constant name="struts.objectFactory" value="spring" />
,最终就等于struts.objectFactory = spring。因为容器配置对象(ContainerProperties)的底层实现是Hashtable,而且容器配置对象是以常量名作为Hashtable的key的,另外容器构造器(ContainerBuilder)也是会覆盖的,因为容器构造器储存factory使用的HashMap,使用Key类作为HashMap的key,具体的工厂对象作为value,而工厂对象储存入容器构造器时候,Key类是以type和name创建的,所以只要type和name一样的的情况下就会覆盖原来的工厂对象。
好了现在回到DefaultConfiguration.reloadContainer上,到这里基本上说完容器供给器(ContainerProvider)的init和register过程。接下来的是props.setConstants(builder),这个操作是把容器配置对象内储存的常量设置到容器构造器中,这个过程需要把常量转换成工厂对象,再储存到容器构造器。
DefaultConfiguration.ContainerProperties.setConstants
public void setConstants(ContainerBuilder builder) { //keySet是Hashtable的方法,ContainerProperties属于其子类,keySet是获取所有常量名 for (Object keyobj : keySet()) { String key = (String)keyobj; //容器构造器内部为这个工厂对象创建Key(String.class, key)对象,最后这个工厂对象会以Key对象为key储存到容器构造器内部的factory Map中。 builder.factory(String.class, key, new LocatableConstantFactory<String>(getProperty(key), getPropertyLocation(key))); } }
重点说一下DefaultConfiguration.createBootstrapContainer方法,先说明一下,这个方法在当前的版本是留作扩展使用的,并没有正真的意思,他的操作没有影响到其他的对象,虽然修改过ActionContext,但是DefaultConfiguration.reloadContainer最后被清空了。本人在网上面找过相关资料,才发现这个方法在struts 2.3.15.1这个版本有重大作用,在2.3.15.1版本,DefaultConfiguration.createBootstrapContainer创建出来的容器是一个引导容器,独立于我们用于创建普通的对象的主容器容器。引导容器作用是struts2的核心类的初始化,核心类主要是使用引导容器的工厂对象和注入依赖功能,还有容器供给器的依赖注入也是使用引导容器的,不再使用主容器,退出DefaultConfiguration.reloadContainer方法时候也通过ActionContext.setContext(null)清空了引导容器使用过的Action环境,很明显的一个意图,struts想隔离开核心类和普通的类,以保证其核心代码的安全。不过现在我们讲的这个版本,DefaultConfiguration.createBootstrapContainer方法还是没有实质意义的。
接下来我们继续解析DefaultConfiguration.reloadContainer里面的builder.create(false),这个操作相当重要,通过这个操作出了我们的主容器(Container),之前我们的容器供给器注册过程,就是容器构造器和容器配置对象搜集信息,为创建容器做的准备。因此这个容器包含了之前所有供给器提供的常量和bean的工厂对象,其作用就是负责这些bean的实例、依赖注入,还有负责常量的获取。以后的ObjectFactory对象也是通过这个容器创建,而ObjectFactory对象在创建Action、Result、Interceptor也是通过容器将创建出来的对象注入依赖的属性的,虽然那些实例不是通过容器创建,但是依赖注入依然是使用容器的,可以看出主容器在sturts2中也是举足轻重的。
接下来看看builder.create(false)的实现代码,
ContainerBuilder.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) { //这里调用工厂对象的create,纯粹为单例的工厂对象创建出实例, //以后就不用再创建,提高效率 factory.create(context); } return null; } }); } //这里为静态成员注入依赖,只对静态成员注入依赖,这些通过在bean添加static="true"指定的, //具体的xml解析是XmlConfigurationProvider.register方法 container.injectStatics(staticInjections); return container; }
ContainerBuilder.create是创建容器的方法,从容器构造器传给容器的工厂对象,都是之前容器构造器在容器供给器inti和register时候收集的,容器构造器收集了容器供给器提供的常量和配置等等信息,然后构造出容器,因此容器是拥有这些常量和配置里面指定的bean的实例化的能力。说到容器的实例化的能力,其实实例化的过程有一部分是依赖于容器的依赖注入功能的,容器实例化类的时候是通过构造函数注入(ConstructorInjector)方式,通过反射获取指定类的构造函数,如果有无参数的构造函数就直接使用无参数构造函数完成初始化,如果只有有参数的构造函数,就分析参数上面有没有注入的标志,如果有就通过容器的参数注入(ParameterInjector)方式注入依赖值给这个参数,然后通过这些有值的参数调用这个构造函数,达到实例化的目地,实例化之后还需要对其成员进行注入依赖,容器对成员注入是使用成员注入(MemberInjector,分别有MethodInjector和FieldInjector,针对方法还是属性判断使用哪一种方式)的方式,对静态成员还有静态注入(StaticInjector,其实质是通过MemberInjector实现的,只不过不需要穿入实例对象)方式;对于容器提供的依赖注入能力,其前提条件就是使用注入的类要配置在bean里面,也就是说类要交给容器管理,并且在使用注入依赖的属性、参数、方法要使用@Inject注解去指明,否则没有办法注入,或者注入了null,或者出现异常。
继续回到DefaultConfiguration.reloadContainer方法,我们接下来研究container.getInstance(ObjectFactory.class),这个操作非常重要,通过这个操作,创建出我们的ObjectFactory,注意是container.getInstance(ObjectFactory.class),这里只是指明了类型,没有指明用什么name的工厂对象,意思就是使用默认工厂对象创建这个ObjectFactory的实例,根据之前的介绍,可以看到struts2的struts.objectFactory被注释了,并没有指定,意味着是使用struts作为name的ObjectFactory的bean,就是使用org.apache.struts2.impl.StrutsObjectFactory作为真正的ObjectFactory实现。
介绍一下,我们之后的Action、Result、Interceptor等等一系列与我们密切相关的对象都是通过ObjectFactory创建的,它可以说是struts的心脏。ObjectFactory实例化对象的默认方式不是使用容器的,而是通过一般的Class.newIntance()方法,而注入才是借助容器的inject(Container.inject)方法给实例化后的对象注入依赖值,对于这样的操作,一个解析就是避免影响核心类的性能,如果把所有的Action都交给容器来管理,会造成容器过于庞大,因为有时候Action可能会是成千上万的,这样会拖慢容器的执行;另外一个原因,因为Action、Result、Interceptor等都是需要多例的,而非单例,方便起见就直接用ObjectFactory,通过Class.newIntance()创建一个实例好了;估计还有一个方面想就是为了扩展,因为如果都交给容器,其他集成框架无法获取到struts2的容器的,无法操作他,所以为了扩展就使用了Class.newIntance()方法实例化。
下面看一下Container.getInstance代码
Container.getInstance(ContainerImpl.getInstance)
//根据type获取实例 public <T> T getInstance(final Class<T> type) { //通过接口(ContextualCallable)调用另一个getInstance方法,通过从指定的context获取实例 return callInContext(new ContextualCallable<T>() { public T call(InternalContext context) { return getInstance(type, context); } }); } <T> T getInstance(Class<T> type, InternalContext context) { //获取type为类型,name为"default"的实例,也就是说使用这个类型的默认的工厂对象创建这个type的实例 //调用另外一个getInstance实例化该类型 return getInstance(type, DEFAULT_NAME, context); } <T> T getInstance(Class<T> type, String name, InternalContext context) { ExternalContext<?> previous = context.getExternalContext(); //通过type和name————"default"指定Key对象, Key<T> key = Key.newInstance(type, name); context.setExternalContext(ExternalContext.newInstance(null, key, this)); try { //通过Key对象获取对应的工厂对象 InternalFactory o = getFactory(key); if (o != null) { //通过该工厂对象创建type类型实例 return getFactory(key).create(context); } else { return null; } } finally { context.setExternalContext(previous); } }
介绍一下里面为何使用到ContainerImpl.callInContext方法,
ContainerImpl.callInContext
<T> T callInContext(ContextualCallable<T> callable) { //localContext是线程变量,通过线程变量获取内部环境 Object[] reference = localContext.get(); if (reference[0] == null) { //如果没有获取,则创建一个,放入数组内 reference[0] = new InternalContext(this); try { //调用getInstance(final Class<T> type)里面创建的ContextualCallable实现类对象来调用call方法 return callable.call((InternalContext)reference[0]); } finally { //清空变量 reference[0] = null; } } else { //如果有,则直就用这个对象调用,这里有可能并发执行,可能是为了兼容 return callable.call((InternalContext)reference[0]); } }
其实strtus2的目的是为了屏蔽了ContainerImpl的实现细节,并且留下了扩展,可以通过实现ContextualCallable接口,扩展获取内部环境(InternalContext)的一些细节。
对于ContainerImpl里面使用到的内部环境(InternalContext)和外部环境(ExternalContext)。内部环境是针对创建普通开发人员在struts2常量配置的自定义类,例如,
default.properties
#可以使用自己创建的类 struts.configuration=org.apache.struts2.config.DefaultConfiguration
内部环境主要用于注入,他支持普通注入和循环依赖注入,由于是使用普通开发人员自定义类,如果里面用到struts2的核心对象,需要注入核心对象,所以直接使用内部环境;外部环境只是一个快照的环境,他是针对struts2核心对象构造时候使用,因为他们可以直接使用容器,就没有必要透露太多细节了。内部环境和外部环境一般是工厂对象create方法传入的参数,其实struts2针对普通开发人员在常量配置的类和他自己的核心类,使用了两种不同工厂对象的,只不过这两个工厂对象的细节,被struts2用接口的形式屏蔽了。XmlConfigurationProvider.register里面使用到的
XmlConfigurationProvider.register
containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);
就是使用外部定义的工厂类————LocatableFactory。于是容器构造器对这些使用外部定义工厂创建的工厂对象只提供外部环境(ExternalContext)作为create参数。众所周知,XmlConfigurationProvider.register是用来解析struts2的配置文件的bean的,很多核心类的bean都在这里解析,为何这里使用了外部定义的工厂类?其实是对容器构造器的扩展,允许传入其他工厂对象,完成一些特殊功能,比如上面的LocatableFactory,可以根据工厂知道具体的bean定义在哪个Document对象。另外,容器构造器只传入外部环境是为了隐蔽一些实现细节,保证核心的安全。对于普通开发人员在常量指定的自定义类,就使用容器构造器内部实现了InternalFactory接口的内部工厂,传入参数也是内部环境,暴露出更多核心信息,起主要原因上面讲过,是为了实例化自定义类使用到的核心对象,BeanSelectionProvider.alias里面就是使用内部工厂的最好例子
BeanSelectionProvider.alias
//省略部分代码...... try { Class cls = ClassLoaderUtil.loadClass(foundName, this.getClass()); if (LOG.isDebugEnabled()) { LOG.debug("Choosing bean ("+cls+") for "+type); } //这里针对在常量里面写了类名的常量,一般就是那些使用自定义类的常量 //没有为这些对象创建工厂对象,就是使用容器构造器的内部工厂 builder.factory(type, cls, scope); } catch (ClassNotFoundException ex) { //省略部分代码...... }
由于Container和ContainerBuilder的源代码比较多、比较复杂,这里不一一详细解析,如果想知道容器和容器构造器的详细实现,各位可以自行查看源代码。
继续回到DefaultConfiguration.reloadContainer,分析下面的代码,他是我们Action、Result、Interceptor等等配置的解析的核心过程,我们接下来详细解读一下。
DefaultConfiguration.reloadContainer
//这是原来DefaultConfiguration.reloadContainer的代码 for (final ContainerProvider containerProvider : providers){ if (containerProvider instanceof PackageProvider) { //通过主容器为这些供给器注入依赖值 container.inject(containerProvider); //loadPackages是重点,我们着重这里 //Action、Result、Interceptor等等配置的解析的核心过程, //主要是StrutsXmlConfigurationProvider、XmlConfigurationProvider两个供给器实现的, //因为只有他们有实际实现loadPackages方法,其他的供给器都是空实现 ((PackageProvider)containerProvider).loadPackages(); packageProviders.add((PackageProvider)containerProvider); } }
StrutsXmlConfigurationProvider.loadPackages
public void loadPackages() { //这个还是引导容器初始化产生的Action环境,不过后面的操作并没有读取过里面的值,没有意义 ActionContext ctx = ActionContext.getContext(); ctx.put(reloadKey, Boolean.TRUE); //调用父类的loadPackages(XmlConfigurationProvider.loadPackages) super.loadPackages(); }
XmlConfigurationProvider.loadPackages
public void loadPackages() throws ConfigurationException { //保存要刷新的PackageConfig对象,决定是否刷新Package List<Element> reloads = new ArrayList<Element>(); //遍历之前初始化好的配置文件,这些配置文件已经是Document对象形式表示的 for (Document doc : documents) { Element rootElement = doc.getDocumentElement(); NodeList children = rootElement.getChildNodes(); int childSize = children.getLength(); //读取一个配置文件的节点,一个配置文件可能有多个package 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(); //如果是package元素 if ("package".equals(nodeName)) { //调用addPackage方法,解析这个package元素,生成PackageConfig对象 //addPackage内部已经通过configuration.addPackageConfig方法加入了这些PackageConfig, //这个configuration就是我们之前的DefaultConfiguration PackageConfig cfg = addPackage(child); if (cfg.isNeedsRefresh()) { //如果需要刷新记录起来 reloads.add(child); } } } } loadExtraConfiguration(doc); } if (reloads.size() > 0) { //重新加载packages reloadRequiredPackages(reloads); } for (Document doc : documents) { loadExtraConfiguration(doc); } //清理工作 documents.clear(); configuration = null; }
XmlConfigurationProvider.addPackage
protected PackageConfig addPackage(Element packageElement) throws ConfigurationException { //构建出packageConfig对象,注意他是一个Config类型的对象,还不是真正可以运行的对象 PackageConfig.Builder newPackage = buildPackageContext(packageElement); if (newPackage.isNeedsRefresh()) { //是否重新加载,重新加载是由于加载父类失败导致的。如果重新加载,则马上返回, //通过XmlConfigurationProvider.loadPackages里面调用reloadRequiredPackages重新加载 return newPackage.build(); } if (LOG.isDebugEnabled()) { LOG.debug("Loaded " + newPackage); } //向这个package添加Result类型(还有默认Result类型), //ResultType就是指chain、dispatcher(默认)、redirect、redirectAction、freemaker 、stream等 addResultTypes(newPackage, packageElement); //从这个package加载拦截器和拦截器栈 loadInterceptors(newPackage, packageElement); //从这个package加载默认拦截器 loadDefaultInterceptorRef(newPackage, packageElement); //从这个package加载默认类引用(default-class-ref) loadDefaultClassRef(newPackage, packageElement); //从这个package加载全局的Result(global-results) loadGlobalResults(newPackage, packageElement); //从这个package加载全局异常映射句柄(global-exception-mappings) loadGobalExceptionMappings(newPackage, packageElement); //根据package节点,获取Action元素 NodeList actionList = packageElement.getElementsByTagName("action"); //这个是重点,解析Action元素,向package加入Action for (int i = 0; i < actionList.getLength(); i++) { Element actionElement = (Element) actionList.item(i); //调用addAction方法向package加入Action addAction(actionElement, newPackage); } //从这个package加载默认Action loadDefaultActionRef(newPackage, packageElement); //构建这个package PackageConfig cfg = newPackage.build(); //向之前的DefaultConfiguration加入了这些PackageConfig, configuration.addPackageConfig(cfg.getName(), cfg); return cfg; }
XmlConfigurationProvider.loadPackages方法,首先是从之前加载好的Document加载package元素,然后通过addPackage方法将package元素转换成PackageConfig对象,通过这个对象加载出Interceptor、Result、DefaultAction(注意是加载出)等对象,然后再向这个PackageConfig对象加入ResultType、Action等对象,注意这里指的Config对象,并不是真正意义上可以运行的对象。好接下来分析buildPackageContext和addAction,其中addAction方法比较重要,该方法是Action元素的解析过程,addAction方法执行过程中加入了对用的Interceptor映射(InterceptorMapping)、Result配置对象(ResultConfig)、异常匹配(ExceptionMapping)等到其对应的Action配置对象中(ActionConfig),并将Action配置对象加入到package配置对象(PackageConfig)中。
XmlConfigurationProvider.buildPackageContext
protected PackageConfig.Builder buildPackageContext(Element packageElement) { //获取继承属性 String parent = packageElement.getAttribute("extends"); //获取表示抽象的属性 String abstractVal = packageElement.getAttribute("abstract"); boolean isAbstract = Boolean.valueOf(abstractVal).booleanValue(); //获取package名 String name = StringUtils.defaultString(packageElement.getAttribute("name")); //获取命名空间名称 String namespace = StringUtils.defaultString(packageElement.getAttribute("namespace")); if (StringUtils.isNotEmpty(packageElement.getAttribute("externalReferenceResolver"))) { throw new ConfigurationException("The 'externalReferenceResolver' attribute has been removed. Please use " + "a custom ObjectFactory or Interceptor.", packageElement); } //构建基本的package配置对象,这个对象只包含package的属性,并不包含他的子元素 PackageConfig.Builder cfg = new PackageConfig.Builder(name) .namespace(namespace) .isAbstract(isAbstract) .location(DomHelper.getLocationObject(packageElement)); //是否包含父package,如果有则加入来 if (StringUtils.isNotEmpty(StringUtils.defaultString(parent))) { //加入父package List<PackageConfig> parents = ConfigurationUtil.buildParentsFromString(configuration, parent); if (parents.size() <= 0) { //如果是加载不到,要求刷新 cfg.needsRefresh(true); } else { //加载成功,加入到现在的package配置对象中 cfg.addParents(parents); } } return cfg; }
XmlConfigurationProvider.addAction
protected void addAction(Element actionElement, PackageConfig.Builder packageContext) throws ConfigurationException { //Action名 String name = actionElement.getAttribute("name"); //对应的class String className = actionElement.getAttribute("class"); //指定Action执行的方法 String methodName = actionElement.getAttribute("method"); Location location = DomHelper.getLocationObject(actionElement); if (location == null) { LOG.warn("location null for " + className); } //设置方法名,如果没有设置则为空 methodName = (methodName.trim().length() > 0) ? methodName.trim() : null; if (StringUtils.isEmpty(className)) { //如果没有指定类名,则会使用package的default-class-ref代替 } else { //验证一下Action if (!verifyAction(className, name, location)) { if (LOG.isErrorEnabled()) LOG.error("Unable to verify action [#0] with class [#1], from [#2]", name, className, location.toString()); return; } } Map<String, ResultConfig> results; try { //根据当前Action节点,构建出Result,根据ResultType和返回标识来构建Result的,默认的ResultType是"dispatcher",默认的返回标识是Action.SUCCESS results = buildResults(actionElement, packageContext); } catch (ConfigurationException e) { throw new ConfigurationException("Error building results for action " + name + " in namespace " + packageContext.getNamespace(), e, actionElement); } //根据当前的Action来构建出这个Action标签内部指定的拦截器(Interceptor) List<InterceptorMapping> interceptorList = buildInterceptorList(actionElement, packageContext); //根据当前的Action来构建出异常映射,就是所异常出现时候返回到哪些页面提示 List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(actionElement, packageContext); //根据Action内的一些属性(Result、Interceptor、Exception等),构建出Action配置对象 ActionConfig actionConfig = new ActionConfig.Builder(packageContext.getName(), name, className) .methodName(methodName) .addResultConfigs(results) .addInterceptors(interceptorList) .addExceptionMappings(exceptionMappings) //获取参数,动态调用时候Action可以带参数 .addParams(XmlHelper.getParams(actionElement)) .location(location) .build(); //放入package配置对象内 packageContext.addActionConfig(name, actionConfig); if (LOG.isDebugEnabled()) { LOG.debug("Loaded " + (StringUtils.isNotEmpty(packageContext.getNamespace()) ? (packageContext.getNamespace() + "/") : "") + name + " in '" + packageContext.getName() + "' package:" + actionConfig); } }
XmlConfigurationProvider.buildResults
//构建Result配置对象 protected Map<String, ResultConfig> buildResults(Element element, PackageConfig.Builder packageContext) { NodeList resultEls = element.getElementsByTagName("result"); Map<String, ResultConfig> results = new LinkedHashMap<String, ResultConfig>(); //遍历Result标签,解析出Result for (int i = 0; i < resultEls.getLength(); i++) { Element resultElement = (Element) resultEls.item(i); //确保这些Result节点只在Action标签下面,没有定义在其他标签下面 if (resultElement.getParentNode().equals(element) || resultElement.getParentNode().getNodeName().equals(element.getNodeName())) { //获取Result的名称,就是返回标识,默认是Action.SUCCESS String resultName = resultElement.getAttribute("name"); //获取Result的类型,就是执行跳转的方式,默认是"dispatcher" String resultType = resultElement.getAttribute("type"); //指定默认值(Action.SUCCESS) if (StringUtils.isEmpty(resultName)) { resultName = Action.SUCCESS; } //指定默认值("dispatcher") if (StringUtils.isEmpty(resultType)) { //根据struts-default.xml里面配置的result-type来获取默认值, //就是在result-type配置了属性default="true"的就是默认值,普通开发人员可以指定自己的类作为默认 //struts2默认是"dispatcher" resultType = packageContext.getFullDefaultResultType(); //没有指定,或者指定为空,无效 if (StringUtils.isEmpty(resultType)) { throw new ConfigurationException("No result type specified for result named '" + resultName + "', perhaps the parent package does not specify the result type?", resultElement); } } //根据resultT类型的名称,获取对应的result类型的result类型配置对象(ResultTypeConfig) ResultTypeConfig config = packageContext.getResultType(resultType); //获取不到,就是指定了无效的名称,抛出异常 if (config == null) { throw new ConfigurationException("There is no result type defined for type '" + resultType + "' mapped with name '" + resultName + "'." + " Did you mean '" + guessResultType(resultType) + "'?", resultElement); } //获取指定resultT类型的类名 String resultClass = config.getClazz(); //获取失败,就是在result-type标签没有指定类的情况,抛出异常 if (resultClass == null) { throw new ConfigurationException("Result type '" + resultType + "' is invalid"); } //获取参数,参数就是在result标签内部指定了param标签 Map<String, String> resultParams = XmlHelper.getParams(resultElement); //没有指定参数,直接解析result的body if (resultParams.size() == 0){ if (resultElement.getChildNodes().getLength() >= 1) { resultParams = new LinkedHashMap<String, String>(); //获取result-type指定的类的默认参数字段("DEFAULT_PARAM")的值(静态字段的值), //其默认值是"location",意思是网络定位,控制跳转页面(视图) //这个"location"就是用来标识接收result标签默认参数值的属性的名称,这样可以通过反射来设置这个值 String paramName = config.getDefaultResultParam(); //只有获取到这个"DEFAULT_PARAM",就是找到相关的网络位置,才执行下面的解析 if (paramName != null) { //解析我们的文本域,就是result标签下面直接填写的值 StringBuilder paramValue = new StringBuilder(); for (int j = 0; j < resultElement.getChildNodes().getLength(); j++) { if (resultElement.getChildNodes().item(j).getNodeType() == Node.TEXT_NODE) { String val = resultElement.getChildNodes().item(j).getNodeValue(); if (val != null) { //这个一般只有一个这样的默认值 paramValue.append(val); } } } String val = paramValue.toString().trim(); if (val.length() > 0) { resultParams.put(paramName, val); } } else { LOG.warn("no default parameter defined for result of type " + config.getName()); } } } //在result配置的参数可以覆盖result-type指定的参数 Map<String, String> params = new LinkedHashMap<String, String>(); Map<String, String> configParams = config.getParams(); if (configParams != null) { params.putAll(configParams); } params.putAll(resultParams); //构建出Result配置对象 ResultConfig resultConfig = new ResultConfig.Builder(resultName, resultClass) //这些参数是用于在ObjectFactory真正创建Result对象时候,通过反射设置给Result对象的属性值的, .addParams(params) .location(DomHelper.getLocationObject(element)) .build(); //将解析出来的Result配置对象放入List中 results.put(resultConfig.getName(), resultConfig); } } return results; }
针对上面的代码说面一下,config.getDefaultResultParam()获取到的是这个result所对应的result-type对应的类的"DEFAULT_PARAM"属性的值,result-type对应的类,比如result-type="redirect"对应"org.apache.struts2.dispatcher.ServletRedirectResult",这些都在struts-default.xml指定了的。对于这个"DEFAULT_PARAM"属性的值,被指定为"location",在ObjectFactory真正创建Result对象时候,会通过反射将这个对象对应的Result配置对象的"location"参数,设置到Result对象的"location"属性值(这个属性在StrutsResultSupport定义),而这个"location"属性的值,就是我们result标签的默认值,就是
<result ...>something<!-- 这个就是默认值("location"属性的值)--></result>
这个"something",在后面result控制页面(视图)跳转的时候用到这个值。注意config.getDefaultResultParam()获取到的值是在XmlConfigurationProvider.addResultTypes方法内通过ResultTypeConfig.Builder.defaultResultParam方法设置进去的,具体这个方法我就不在这里分析了!接下来我们继续分析XmlConfigurationProvider.buildInterceptorList方法。
XmlConfigurationProvider.buildInterceptorList
//构建Interceptor映射对象 protected List<InterceptorMapping> buildInterceptorList(Element element, PackageConfig.Builder context) throws ConfigurationException { List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(); //在Action标签下获取Interceptor标签 NodeList interceptorRefList = element.getElementsByTagName("interceptor-ref"); //遍历所有Interceptor标签 for (int i = 0; i < interceptorRefList.getLength(); i++) { Element interceptorRefElement = (Element) interceptorRefList.item(i); //确保这些interceptor-ref节点只在Action标签下面,没有定义在其他标签下面 if (interceptorRefElement.getParentNode().equals(element) || interceptorRefElement.getParentNode().getNodeName().equals(element.getNodeName())) { //这里实例化出我们的Interceptor,并放入InterceptorMapping对象中管理, //lookupInterceptorReference内部用ObjectFactory作为参数传入了InterceptorBuilder.constructInterceptorReference中, //使其构建出InterceptorMapping对象,构建InterceptorMapping过程中,已经通过ObjectFactory实例化了指定的Interceptor List<InterceptorMapping> interceptors = lookupInterceptorReference(context, interceptorRefElement); //放入List中 interceptorList.addAll(interceptors); } } return interceptorList; }
从buildPackageContext到addAction,再到buildResults,还有buildInterceptorList,整个过程都是根据我们配置的struts.xml解析的,struts.xml的配置可以覆盖struts2的默认配置。同时这个过程代码也不太复杂,所以大家都应该看得懂。值得注意的一个地方就是我们最后的Action配置对象的构建,其实他还是未完整的,我们都知道,一个Action的执行过程需要一个struts的默认Interceptor和默认Action,还有默认Result等协同完成的,还有我们之前如果没有指定action的类,还要指定默认类,但是这里只是解析了我们自己配置的那些Interceptor、Action、Result,所以在DefaultConfiguration.reloadContainer的最后有一个rebuildRuntimeConfiguration方法,这个方法就是在最后帮我们的Action配置对象之类的配置对象加回的默认的配置对象,使他们完整,可以在后面转换成真正的对象,完整执行整个调用过程。
好,我们回到DefaultConfiguration.reloadContainer,分析rebuildRuntimeConfiguration方法,注意跟上去,方法执行过程都是一个套一个执行的,现在都分析得比较深入了,注意要一个个返回到DefaultConfiguration.reloadContainer。
DefaultConfiguration.rebuildRuntimeConfiguration
public void rebuildRuntimeConfiguration() { //将这个运行时配置对象赋值 runtimeConfiguration = buildRuntimeConfiguration(); }
DefaultConfiguration.buildRuntimeConfiguration
//构建运行时配置对象 protected synchronized RuntimeConfiguration buildRuntimeConfiguration() throws ConfigurationException { Map<String, Map<String, ActionConfig>> namespaceActionConfigs = new LinkedHashMap<String, Map<String, ActionConfig>>(); Map<String, String> namespaceConfigs = new LinkedHashMap<String, String>(); //遍历所有的package配置对象 for (PackageConfig packageConfig : packageContexts.values()) { //不能为抽象,抽象的package不指定action标签 if (!packageConfig.isAbstract()) { String namespace = packageConfig.getNamespace(); //根据namespace从namespaceActionConfigs,获取Action配置对象 //namespaceActionConfigs是根据namespace(package的命名空间)来区分action配置对象的 Map<String, ActionConfig> configs = namespaceActionConfigs.get(namespace); //没有相关Action配置在这个package下 if (configs == null) { configs = new LinkedHashMap<String, ActionConfig>(); } //获取所有的配置对象 Map<String, ActionConfig> actionConfigs = packageConfig.getAllActionConfigs(); //为每个action配置对象构建完整的配置对象 for (Object o : actionConfigs.keySet()) { String actionName = (String) o; ActionConfig baseConfig = actionConfigs.get(actionName); //buildFullActionConfig构建完整的配置对象,我们着重这里 configs.put(actionName, buildFullActionConfig(packageConfig, baseConfig)); } //把完整的action配置对象覆盖回原来的配置对象 namespaceActionConfigs.put(namespace, configs); if (packageConfig.getFullDefaultActionRef() != null) { //把全部的默认action配置对象放入namespaceConfigs中,这些DefaultActionRef是struts2指定的默认action namespaceConfigs.put(namespace, packageConfig.getFullDefaultActionRef()); } } } //创建运行时配置类的实例,就是运行时配置对象,并返回 return new RuntimeConfigurationImpl(namespaceActionConfigs, namespaceConfigs); }
DefaultConfiguration.buildFullActionConfig
//构建完整的action配置对象 private ActionConfig buildFullActionConfig(PackageConfig packageContext, ActionConfig baseConfig) throws ConfigurationException { Map<String, String> params = new TreeMap<String, String>(baseConfig.getParams()); Map<String, ResultConfig> results = new TreeMap<String, ResultConfig>(); if (!baseConfig.getPackageName().equals(packageContext.getName()) && packageContexts.containsKey(baseConfig.getPackageName())) { results.putAll(packageContexts.get(baseConfig.getPackageName()).getAllGlobalResults()); } else { results.putAll(packageContext.getAllGlobalResults()); } //放入全部的result配置对象 results.putAll(baseConfig.getResults()); //加入默认的result配置对象的实现 setDefaultResults(results, packageContext); List<InterceptorMapping> interceptors = new ArrayList<InterceptorMapping>(baseConfig.getInterceptors()); if (interceptors.size() <= 0) { String defaultInterceptorRefName = packageContext.getFullDefaultInterceptorRef() //需要配置默认的InterceptorMapping if (defaultInterceptorRefName != null) { //加入默认的InterceptorMapping interceptors.addAll(InterceptorBuilder.constructInterceptorReference(new PackageConfig.Builder(packageContext), defaultInterceptorRefName, new LinkedHashMap<String, String>(), packageContext.getLocation(), objectFactory)); } } return new ActionConfig.Builder(baseConfig) .addParams(params) //重新加入完整的result配置对象 .addResultConfigs(results) //如果这个action没有指定class,就使用默认的类作为action类,之前没有指定action类的话,在这里添加默认action类 .defaultClassName(packageContext.getDefaultClassRef()) //重新加入完整的InterceptorsMapping配置对象 .interceptors(interceptors) .addExceptionMappings(packageContext.getAllExceptionMappingConfigs()) .build(); }
看到了吧!之前构建出来的不完整的action配置对象,在这里都重新帮他们构建完整了,组成运行时配置对象。
好,到这里为止,全部的配置都在这里解析完成,并能够组织起来,构成运行时的结构了。我们的DefaultConfiguration.reloadContainer方法完成了。总结一下,这个DefaultConfiguration.reloadContainer方法初始化了容器,也解析完了配置文件,生成好运行时的配置对象,他的任务完成了。返回到configurationManager.getConfiguration方法,这个操作返回了初始化好的configuration对象,再返回到Dispatcher.init_PreloadConfiguration方法,他根据configuration对象,获取容器,再设置国际化的配置,最后返回容器,再返回到Dispatcher.init方法,他获取通过之前获取的容器,把他自身也注入依赖值,最后返回到FilterDispatcher.init,struts2的核心分发器,然后通过容器把核心分发器也注入依赖值。
第二部分:doFilter的工作过程
好,核心分发器的init方法完成了,他的初始化时候就完成了基础对象,使执行doFilter时候可以使用!好,现在分发器可以开始处理请求了!我们开始分析FilterDispatcher.doFilter方法。
FilterDispatcher.doFilter
//核心分发器开始工作 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; //获取servletContext ServletContext servletContext = getServletContext(); String timerKey = "FilterDispatcher_doFilter: "; try { //重新创建ValueStack,这里的ValueStack是我们后面的action用到的那个ValueStack ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); //用指定的ValueStack来创建一个新的ActionContext,并设置为本线程的ActionContext, //注意这里已经不是之前的引导容器创建的ActionContext了,而是我们action使用的那个ActionContext, //引导容器的ActionContext已经在DefaultConfiguration.reloadContainer方法退出时候被清空了, //现在的ValueStack和ActionContext都是全新的。 ActionContext ctx = new ActionContext(stack.getContext()); ActionContext.setContext(ctx); UtilTimerStack.push(timerKey); //封装成struts2自己的request(StrutsRequestWrapper),里面包含了ValueStack等必要的对象 request = prepareDispatcherAndWrapRequest(request, response); ActionMapping mapping; try { //获取映射对象,就是找到匹配action,把他封装成Mapping对象 //actionMapper是通过容器依赖注入的。 mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager()); } catch (Exception ex) { log.error("error getting ActionMapping", ex); //发生异常就直接跳到错误页面 dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); return; } //如果没有找到匹配,有可能是没有找对对应的action或者是访问静态资源的请求 if (mapping == null) { //如果是针对访问静态资源,就组装好路径 String resourcePath = RequestUtils.getServletPath(request); if ("".equals(resourcePath) && null != request.getPathInfo()) { resourcePath = request.getPathInfo(); } //确定是不是真的访问静态资源,如果是就返回相关资源,然后退出 if (staticResourceLoader.canHandle(resourcePath)) { staticResourceLoader.findStaticResource(resourcePath, request, response); } else { //如果没有找到对应的action,则交给其他Filter,最终交给我们的application server, //如果都没有找到相关匹配操作,直接由application server抛出404错误 chain.doFilter(request, response); } //如果是访问资源的操作,就在这里返回 return; } //这个就是统一执行我们action,和相关拦截器的操作 dispatcher.serviceAction(request, response, servletContext, mapping); } finally { try { //清理现场 ActionContextCleanUp.cleanUp(req); } finally { UtilTimerStack.pop(timerKey); } } } }
这里是doFilter的整体操作,我们分析一些重要的操作,首先struts2准备好ValueStack、ActionContext等基本的要素,然后将servlet的request封装成自己的request,再通过actionMapper获取匹配到的action的映射对象,最后调用dispatcher.serviceAction方法执行我们的action、拦截器等。
我们分析一下actionMapper的获取,这个对象是在FilterDispatcher.init时候通过容器注入给核心分发器的,所以我们先看看其setter方法。
FilterDispatcher.setActionMapper
//之前说过是注入的标识 @Inject public void setActionMapper(ActionMapper mapper) { actionMapper = mapper; }
参数参数类型是ActionMapper也就是说容器注入要调用getInstance(ActionMapper.class)方法,也就是说调用getInstance(ActionMapper.class,"default")方法,根据配置文件default.properties和struts-default.xml,还有BeanSelectionProvider.register和BeanSelectionProvider.alias指出,
default.properties
#struts.mapper.class=org.apache.struts2.dispatcher.mapper.DefaultActionMapper
struts-default.xml
<bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="struts" class="org.apache.struts2.dispatcher.mapper.DefaultActionMapper" /> <bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="composite" class="org.apache.struts2.dispatcher.mapper.CompositeActionMapper" /> <bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="restful" class="org.apache.struts2.dispatcher.mapper.RestfulActionMapper" /> <bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="restful2" class="org.apache.struts2.dispatcher.mapper.Restful2ActionMapper" />
BeanSelectionProvider.register
public void register(ContainerBuilder builder, LocatableProperties props) { //省略部分代码...... alias(ActionMapper.class, StrutsConstants.STRUTS_MAPPER_CLASS, builder, props); //省略部分代码...... }
BeanSelectionProvider.alias
void alias(Class type, String key, ContainerBuilder builder, Properties props, Scope scope) { //省略部分代码...... //这里如果没有在常量配置文件中找到对应的配置值,就会用"struts"代替 String foundName = props.getProperty(key, DEFAULT_BEAN_NAME); //容器构造器是否包含type为ActionMapper.class、name为"struts"的工厂对象,明显包含了! if (builder.contains(type, foundName)) { if (LOG.isDebugEnabled()) { LOG.info("Choosing bean ("+foundName+") for "+type); } //给这个工厂对象起一个别名,type为ActionMapper.class、name为"default" builder.alias(type, foundName, Container.DEFAULT_NAME); } //省略部分代码...... }
由于struts.mapper.class被注释了,所以在props.getProperty(key, DEFAULT_BEAN_NAME)一定是返回struts的,配置文件struts-default.xml包含了这个type为ActionMapper.class、name为"struts"的bean,所以容器构造器一定已经包含这个工厂对象,所以给这个工厂对象起一个别名,type为ActionMapper.class、name为"default",因此,在FilterDispatcher.setActionMapper注入的,就是org.apache.struts2.dispatcher.mapper.DefaultActionMapper这个实现类。
通过这样我们可以确认注入的实现类了,回来FilterDispatcher.doFilter,我们接下来分析actionMapper.getMapping方法,
DefaultActionMapper.getMapping
//根据request获取action的映射对象 public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { //创建一个原始的映射对象 ActionMapping mapping = new ActionMapping(); //获取url String uri = getUri(request); int indexOfSemicolon = uri.indexOf(";"); uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri; //去掉扩展名(默认是action) uri = dropExtension(uri, mapping); if (uri == null) { return null; } //转换出namespace和name(name可能包含了action和方法名,动态调用的情况) parseNameAndNamespace(uri, mapping, configManager); //获取特殊的参数,例如转定向命令 handleSpecialParameters(request, mapping); if (mapping.getName() == null) { return null; } //转换action名,针对是动态调用 parseActionName(mapping); return mapping; }
DefaultActionMapper.parseNameAndNamespace
protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) { String namespace, name; //从最后开始查找"/" int lastSlash = uri.lastIndexOf("/"); //没有找到"/",直接访问网站根目录 if (lastSlash == -1) { namespace = ""; name = uri; } else if (lastSlash == 0) { //"/"在最前面,默认的namespace为"/",访问根网站目录下的action namespace = "/"; name = uri.substring(lastSlash + 1); } else if (alwaysSelectFullNamespace) { //是否允许全部作为namespace,就是除了name之外,其他就是name,默认alwaysSelectFullNamespace为false namespace = uri.substring(0, lastSlash); name = uri.substring(lastSlash + 1); } else { //这个是最多的情况 Configuration config = configManager.getConfiguration(); //获取前缀 String prefix = uri.substring(0, lastSlash); namespace = ""; boolean rootAvailable = false; //遍历所有namespace去匹配prefix for (Object cfg : config.getPackageConfigs().values()) { //查找最长匹配namespace的prefix String ns = ((PackageConfig) cfg).getNamespace(); if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) { //查找最长匹配 if (ns.length() > namespace.length()) { namespace = ns; } } //只匹配到"/" if ("/".equals(ns)) { rootAvailable = true; } } //找出name name = uri.substring(namespace.length() + 1); //默认的namespace"/" if (rootAvailable && "".equals(namespace)) { namespace = "/"; } } //是否允许"/"出现在action名中,默认allowSlashesInActionNames为false if (!allowSlashesInActionNames && name != null) { int pos = name.lastIndexOf('/'); //在name中找到"/",只有在通过用((PackageConfig) cfg).getNamespace()匹配prefix才有这种可能 if (pos > -1 && pos < name.length() - 1) { name = name.substring(pos + 1); } } //设置namespace和name mapping.setNamespace(namespace); mapping.setName(name); }
DefaultActionMapper.handleSpecialParameters
//获取特殊的参数 public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) { Set<String> uniqueParameters = new HashSet<String>(); //获取请求参数 Map parameterMap = request.getParameterMap(); for (Iterator iterator = parameterMap.keySet().iterator(); iterator.hasNext();) { String key = (String) iterator.next(); //去掉图标按钮的坐标值,去掉干扰 if (key.endsWith(".x") || key.endsWith(".y")) { key = key.substring(0, key.length() - 2); } //确保每个参数都处理一次 if (!uniqueParameters.contains(key)) { //匹配前缀字典树(Trie,字典树),通过初始化好的字典树, //获取指定的参数名(注意,字典树的get方法不是完全相等,只要这个key.startsWith(prefixTrie某个key), //就可以获取这个key的value)的特殊parameterAction,这个parameterAction, //如果请求参数包含这些字符串(例如action:、redirect:、redirectAction:等)开头就会匹配到这里定义的某个ParameterAction ParameterAction parameterAction = (ParameterAction) prefixTrie.get(key); if (parameterAction != null) { //如果匹配到这些parameterAction,马上执行操作,这些操作可能会修改了mapping的属性值 parameterAction.execute(key, mapping); uniqueParameters.add(key); break; } } } }
上面比较难理解的是prefixTrie ———— 前缀字典树,上面针对get方法,就是只要当前变量key前面部分匹配到prefixTrie内部的某个key,就会返回这个key的value,相当于key.startsWith(prefixTrie某个key),就会返回这个key的value,这里返回了parameterAction,参数内指定的行为,例如参数内包含action:、redirect:、redirectAction:等字符串开头时候就当指定命令处理,然后调用parameterAction.execute,修改mapping的属性值。struts2可以直接通过request上的这些参数控制跳转等操作。好,我们看一下prefixTrie的初始化过程,貌似之前的struts2系统远程控制漏洞最是这里埋下的隐患!大家注意一下,记得更新!
DefaultActionMapper.DefaultActionMapper
public DefaultActionMapper() { prefixTrie = new PrefixTrie() { {//非静态块,在该对象每次调用构造函数都执行一次 put(METHOD_PREFIX, new ParameterAction() {//针对method:的执行方式 public void execute(String key, ActionMapping mapping) { if (allowDynamicMethodCalls) {//是否允许动态调用方法 //设置动态调用方法,这里覆盖了原来的设置 mapping.setMethod(key.substring(METHOD_PREFIX.length())); } } }); put(ACTION_PREFIX, new ParameterAction() {//针对action:的执行方式 public void execute(String key, ActionMapping mapping) { String name = key.substring(ACTION_PREFIX.length()); if (allowDynamicMethodCalls) {//是否允许动态调用方法 int bang = name.indexOf('!'); if (bang != -1) { String method = name.substring(bang + 1); //设置方法 mapping.setMethod(method); name = name.substring(0, bang); } } //设置action名称 mapping.setName(name); } }); put(REDIRECT_PREFIX, new ParameterAction() {//针对redirect:的执行方式 public void execute(String key, ActionMapping mapping) { ServletRedirectResult redirect = new ServletRedirectResult(); container.inject(redirect); //设置重定向的位置(网络地址) redirect.setLocation(key.substring(REDIRECT_PREFIX.length())); //注意,这里添加了Result mapping.setResult(redirect); } }); put(REDIRECT_ACTION_PREFIX, new ParameterAction() {//针对redirectAction:的执行方式 public void execute(String key, ActionMapping mapping) { String location = key.substring(REDIRECT_ACTION_PREFIX.length()); ServletRedirectResult redirect = new ServletRedirectResult(); container.inject(redirect); String extension = getDefaultExtension(); if (extension != null && extension.length() > 0) { location += "." + extension; } //设置重定向的位置(网络地址) redirect.setLocation(location); //注意,这里添加了Result mapping.setResult(redirect); } }); } }; }
注意REDIRECT_ACTION_PREFIX和REDIRECT_PREFIX的execute,如果是redirect的操作,都会为mapping添加result(就是这行代码:mapping.setResult(redirect)),如果mapping有了result,就会在Dispatcher.serviceAction执行result.execute,而不执行proxy.execute的操作,proxy.execute就是我们的action。凡是redirect都是会执行result.execute通知客户端重新请求某个指定的网络地址,达到重定向的效果。
关于struts2远程漏洞,上面说过,漏洞主要在于struts2允许request中的参数,支持以action:、redirect:、redirectAction:等字符串开头的参数,来控制页面(视图)跳转之类的操作,但是struts2忘记了,在ParameterAction.execute执行的时候,会把这些指令的后面全部的参数都放进去了,而且在后面的Result对象调用execute方法,真正执行跳转页面(视图)的时候,会使用StrutsResultSupport.conditionalParse方法,执行对request参数中的OGNL表达式的解析执行,可以通过OGNL表达式执行Java方法的调用。因此,如果有人在请求中加入Java的Runtime.exec方法调用,通过Java调用windows的命令,获取系统权限是轻而易举的!
上面就简单介绍了一下漏洞,接下来我们继续。上面的parameterAction.execute(key, mapping)代码可能会修改mapping的结果,从而影响我们回来的action的调用,实现通过request参数控制页面(视图)跳转!我们接下来解析DefaultActionMapper.parseActionName,
DefaultActionMapper.parseActionName
//转换action名称,主要作用是在动态调用action方法时候,将action名和方法名分开 //"name!method"的这个格式 protected ActionMapping parseActionName(ActionMapping mapping) { if (mapping.getName() == null) { return mapping; } //是否允许动态调用 if (allowDynamicMethodCalls) { //用于"name!method"的转换 String name = mapping.getName(); int exclamation = name.lastIndexOf("!"); //如果有"!"才执行操作,所以这里不影响之前DefaultActionMapper.handleSpecialParameters的操作 if (exclamation != -1) { mapping.setName(name.substring(0, exclamation)); mapping.setMethod(name.substring(exclamation + 1)); } } return mapping; }
返回到DefaultActionMapper.getMapping,最终mapping已经设置好相应的值,可以为后面的操作服务了!我们也返回到FilterDispatcher.doFilter方法,忽略掉那些静态资源访问的代码,直接分析Dispatcher.serviceAction。Dispatcher.serviceAction是我们所有action的统一处理入口,进入这个方法,就是开始了action的处理了。Dispatcher.serviceAction主要是完成以下几个工作,
1、创建Action对象,以代理对象的形式
2、执行mapping.execute或者开始调用拦截器(就是proxy.execute);如果执行了mapping.execute(执行Result返回客户端),就停止下面操作了
3、通过反射调用Action的指定方法,并返回
4、执行Result返回客户端,可以是页面(视图)跳转或者其他操作
这些都是重点啊!我们先总体看看Dispatcher.serviceAction方法
Dispatcher.serviceAction
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException { //创建额外环境,将request、response、mapping、context、ValueStack、Application等对象封装进去,以方便创建action代理对象, //createActionProxy会将extraContext作为参数传入,作为创建action代理对象时候使用的内部环境 //注意request里面包含了客户端提交过来的参数(可以是GET、POST方式提交的参数),所以action代理对象的内部环境包含了这些参数 Map<String, Object> extraContext = createContextMap(request, response, mapping, context); //获取ValueStack,如果有 ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); boolean nullStack = stack == null; if (nullStack) { //如果没有,那么从线程获取一个ValueStack ActionContext ctx = ActionContext.getContext(); if (ctx != null) { stack = ctx.getValueStack(); } } if (stack != null) { //将ValueStack克隆一个,放入额外环境,保存最原始的ValueStack,不受影响 extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack)); } String timerKey = "Handling request from Dispatcher"; try { UtilTimerStack.push(timerKey); String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); //获取配置对象 Configuration config = configurationManager.getConfiguration(); //创建Action代理对象,注意,额外环境已经作为参数传入createActionProxy了。这里是第一个重点 ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, method, extraContext, true, false); //上面操作可能修改了ValueStack,把修改过ValueStack放进去request,因为下面的execute可能用得着里面的参数 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); //注意这里就是我们第二个重点,之前在getMapping那里解析request的参数,如果有参数以REDIRECT_ACTION_PREFIX和REDIRECT_PREFIX开头, //就会直接执行这里的result.execute,而不执行proxy.execute。 if (mapping.getResult() != null) { Result result = mapping.getResult(); //直接执行result.execute不执行proxy.execute result.execute(proxy.getInvocation()); } else { //在request的参数没有REDIRECT_ACTION_PREFIX和REDIRECT_PREFIX开头,则正常执行proxy.execute proxy.execute(); } //再把修改过的ValueStack设置到request中,在Result之后返回视图时候可以用得着这些参数, //因为Action执行方法时候可能会改变了ValueStack的里面的值 if (!nullStack) { request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); } } catch (ConfigurationException e) { if(devMode) { LOG.error("Could not find action or result", e); } else { LOG.warn("Could not find action or result", e); } //捕捉到错误跳转到报错页面 sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e); } catch (Exception e) { //捕捉到错误跳转到报错页面 sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } finally { UtilTimerStack.pop(timerKey); } }
我们重点看Action代理对象的创建过程,在这个过程中Action已经被创建了,并且对应的代理对象也创建好了。action代理对象的创建是通过工厂对象创建的,上面使用了容器的getInstance(ActionProxyFactory.class)获取工厂对象的实例,之前讲过如何知道创建出来的实例是具体哪一个类,我就不再说了,这里getInstance(ActionProxyFactory.class)得到的实例是org.apache.struts2.impl.StrutsActionProxyFactory类的实例,我们看一下StrutsActionProxyFactory.createActionProxy方法,
StrutsActionProxyFactory.createActionProxy(DefaultActionProxyFactory.createActionProxy)
//由于StrutsActionProxyFactory继承了DefaultActionProxyFactory,并且StrutsActionProxyFactory没有覆盖这个方法, //所以是调用父类的DefaultActionProxyFactory.createActionProxy public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) { //创建一个默认action调用对象 ActionInvocation inv = new DefaultActionInvocation(extraContext, true); //为这个对象注入依赖值 container.inject(inv); //调用另一个方法创建action代理,注意,StrutsActionProxyFactory覆盖了这个方法,所以调用的是StrutsActionProxyFactory.createActionProxy return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext); }
StrutsActionProxyFactory.createActionProxy
public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) { //创建StrutsActionProxy action代理对象 StrutsActionProxy proxy = new StrutsActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext); //为这个对象注入依赖值 container.inject(proxy); //调用这个方法,为调用前做一些准备工作,我们关注这个方法 proxy.prepare(); return proxy; }
StrutsActionProxy.prepare
protected void prepare() { //调用父类的prepare(就是DefaultActionProxy.prepare) super.prepare(); }
DefaultActionProxy.prepare
//调用prepare,为调用做准备 protected void prepare() { String profileKey = "create DefaultActionProxy: "; try { UtilTimerStack.push(profileKey); //根据namespace和action名获取对应的action配置对象 config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName); if (config == null && unknownHandlerManager.hasUnknownHandlers()) { //获取失败,使用未知action匹配(如果有) config = unknownHandlerManager.handleUnknownAction(namespace, actionName); } //如果还是获取失败,抛出异常 if (config == null) { String message; if ((namespace != null) && (namespace.trim().length() > 0)) { message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_PACKAGE_ACTION_EXCEPTION, Locale.getDefault(), new String[]{namespace, actionName}); } else { message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_ACTION_EXCEPTION, Locale.getDefault(), new String[]{actionName}); } throw new ConfigurationException(message); } //分析方法,通过action配置对象的method名,解析出真正调用时候的方法名(默认是"execute"),赋值给this.method resolveMethod(); //如果解析出来的是失效的method,抛出异常 if (!config.isAllowedMethod(method)) { throw new ConfigurationException("Invalid method: "+method+" for action "+actionName); } //初始化调用对象,就是我们构造这个对象时候传入来的DefaultActionInvocation对象 invocation.init(this); } finally { UtilTimerStack.pop(profileKey); } }
DefaultActionInvocation.init
//初始化调用对象 public void init(ActionProxy proxy) { //先将代理对象赋值给this.proxy属性 this.proxy = proxy; //根据extraContext创建contextMap,用来封装一些action创建相关的信息进去,方便createAction时候使用 Map<String, Object> contextMap = createContextMap(); ActionContext actionContext = ActionContext.getContext(); //把action调用对象设置给action环境,方便后面使用 if (actionContext != null) { actionContext.setActionInvocation(this); } //创建出我们的Action,action的信息是this.proxy(代理对象)提供的 createAction(contextMap); //是否将action放入ValueStack和contextMap if (pushAction) { //这个操作很重要,他是将action放入ValueStack的根(root), //使通过视图返回页面时候可以通过OGNL表达式不带"#"号直接访问action的属性值 stack.push(action); contextMap.put("action", action); } //根据contextMap专门创建一个新的调用环境 invocationContext = new ActionContext(contextMap); invocationContext.setName(proxy.getActionName()); //获取对应action的那些InterceptorMapping List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors()); //赋值给当前对象的拦截器映射迭代对象 interceptors = interceptorList.iterator(); }
DefaultActionInvocation.createAction
//创建action实例 protected void createAction(Map<String, Object> contextMap) { String timerKey = "actionCreate: " + proxy.getActionName(); try { UtilTimerStack.push(timerKey); //通过objectFactory创建action实例 action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap); } catch (InstantiationException e) { throw new XWorkException("Unable to intantiate Action!", e, proxy.getConfig()); } catch (IllegalAccessException e) { throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig()); } catch (Exception e) {//报错信息 String gripe = ""; if (proxy == null) { gripe = "Whoa! No ActionProxy instance found in current ActionInvocation. This is bad ... very bad"; } else if (proxy.getConfig() == null) { gripe = "Sheesh. Where'd that ActionProxy get to? I can't find it in the current ActionInvocation!?"; } else if (proxy.getConfig().getClassName() == null) { gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'"; } else { gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ", defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'"; } gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]"); throw new XWorkException(gripe, e, proxy.getConfig()); } finally { UtilTimerStack.pop(timerKey); } //如果有相关监听器,调用监听器 if (actionEventListener != null) { action = actionEventListener.prepare(action, stack); } }
到这里,action基本上创建好了,但是action的属性还没有设置好的,struts2是支持通过客户端POST、GET方式传过来的参数值自动设置到action对应的名称的属性上(通过getter、setter方法),这个操作在action代理执行时候,调用拦截器来设置,主要设置参数的是这个拦截器:
struts-default.xml
<!-- 设置action的属性 --> <interceptor name="params" class="com.opensymphony.xwork2.interceptor.ParametersInterceptor"/>
这个拦截器在后面action代理执行时候会说明,这里先提一下。基本上,action代理对象,action调用对象,action都已经创建好并且基本初始化好了,我们回到Dispatcher.serviceAction,先看看当mapping有Result的情况,这个时候就是request参数里面有控制页面(视图)重定向的参数(redirect:、redirectAction:开头的参数),具体生成Result就是DefaultActionMapper.getMapping方法,这个时候会执行result.execute(proxy.getInvocation()),这个result对象的实例是ServletRedirectResult的实例,从DefaultActionMapper可以知道,我们看一下他们的代码,
ServletRedirectResult.execute(StrutsResultSupport.execute)
//ServletRedirectResult继承于StrutsResultSupport,并且ServletRedirectResult没有覆盖execute, //所以实际是调用StrutsResultSupport.execute public void execute(ActionInvocation invocation) throws Exception { //注意这里就是之前说的struts2漏洞产生的地方, //StrutsResultSupport.conditionalParse会调用OGNL去解析location里面的表达式, //而这个location就是我们之前设置进去的包括了request参数的redirect:、redirectAction:后缀部分, //如果包含了恶意代码会轻易控制服务器。 lastFinalLocation = conditionalParse(location, invocation); //ServletRedirectResult覆盖了这个方法,实际调用ServletRedirectResult.doExecute doExecute(lastFinalLocation, invocation); }
ServletRedirectResult.doExecute
//执行ServletRedirectResult.doExecute,实现redirect:、redirectAction:控制的页面(视图)重定向,不过正常的页面重定向也是用这个方法 protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { ActionContext ctx = invocation.getInvocationContext(); HttpServletRequest request = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST); HttpServletResponse response = (HttpServletResponse) ctx.get(ServletActionContext.HTTP_RESPONSE); //是否路径Url,就是相对路径那些 if (isPathUrl(finalLocation)) { //不是从根目录开始的,从当前位置开始的 if (!finalLocation.startsWith("/")) { ActionMapping mapping = actionMapper.getMapping(request, Dispatcher.getInstance().getConfigurationManager()); String namespace = null; if (mapping != null) { namespace = mapping.getNamespace(); } //如果有namespace,就加上name,避免出错 if ((namespace != null) && (namespace.length() > 0) && (!"/".equals(namespace))) { finalLocation = namespace + "/" + finalLocation; } else { finalLocation = "/" + finalLocation; } } //为finalLocation加上根目录的完整路径,相当于绝对路径 if (prependServletContext && (request.getContextPath() != null) && (request.getContextPath().length() > 0)) { finalLocation = request.getContextPath() + finalLocation; } ResultConfig resultConfig = invocation.getProxy().getConfig().getResults().get(invocation.getResultCode()); //是否获取到resultCode,就是action执行完返回的字符串,一般来说直接执行result.execute,这个值都为空 if (resultConfig != null ) { //获取result标签指定的参数(param) Map resultConfigParams = resultConfig.getParams(); //将这些参数作为request参数,这里的conditionalParse不会有产生漏洞,因为是我们自己定义的param参数 for (Iterator i = resultConfigParams.entrySet().iterator(); i.hasNext();) { Map.Entry e = (Map.Entry) i.next(); if (!getProhibitedResultParams().contains(e.getKey())) { requestParameters.put(e.getKey().toString(), e.getValue() == null ? "" : conditionalParse(e.getValue().toString(), invocation)); String potentialValue = e.getValue() == null ? "" : conditionalParse(e.getValue().toString(), invocation); if (!supressEmptyParameters || ((potentialValue != null) && (potentialValue.length() > 0))) { requestParameters.put(e.getKey().toString(), potentialValue); } } } } StringBuilder tmpLocation = new StringBuilder(finalLocation); //把参数用"&"隔开合并到finalLocation中,参数都是"&"隔开的 UrlHelper.buildParametersString(requestParameters, tmpLocation, "&"); //UTF-8编码 finalLocation = response.encodeRedirectURL(tmpLocation.toString()); } if (LOG.isDebugEnabled()) { LOG.debug("Redirecting to finalLocation " + finalLocation); } //页面(视图)重定向 sendRedirect(response, finalLocation); }
如果是result.execute直接执行的情况,到这里doFilter对一个请求的处理过程就执行完了。我们该分析重点 ———— proxy.execute,我们action执行的过程,当然在action真正执行之前,当然还有拦截器的执行,然后action执行完后返回一个resultCode,再根据resultCode找到对应的Result,执行页面(视图)跳转,返回客户端之前当然还会生成视图那些,关于视图的我就不说了。从之前的代码可以知道proxy是StrutsActionProxy的实例,那么下面看代码,
StrutsActionProxy.execute
//action代理对象执行execute来调用我们真正的action执行处理 public String execute() throws Exception { ActionContext previous = ActionContext.getContext(); //使用调用对象的内部环境,这个内部环境就是之前的额外环境 ActionContext.setContext(invocation.getInvocationContext()); try { //执行action调用对象的调用方法 return invocation.invoke(); } finally { //是否恢复原来的环境,就是清理action执行后的环境, //一般都不清理,因为视图可能要使用到里面的一些值 if (cleanupContext) ActionContext.setContext(previous); } }
从之前的代码可以知道invocation是DefaultActionInvocation的实例,那么下面看代码,
DefaultActionInvocation.invoke
public String invoke() throws Exception { String profileKey = "invoke: "; try { UtilTimerStack.push(profileKey); //防止多次执行action,保证action只执行一次 if (executed) { throw new IllegalStateException("Action has already executed"); } //通过之前的拦截器映射迭代器迭代取出拦截器映射对象 if (interceptors.hasNext()) { //获取拦截器映射对象 final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next(); String interceptorMsg = "interceptor: " + interceptor.getName(); UtilTimerStack.push(interceptorMsg); try { //通过映射对象获取拦截器,执行拦截器,注意,intercept参数是当前的action调用对象, //其实这里面也调用DefaultActionInvocation.invoke,他们这样是递归调用,通过这样的方法遍历完interceptors resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); } finally { UtilTimerStack.pop(interceptorMsg); } } else { //当遍历完interceptors时候,执行正真的action操作,这里是一个重点 //返回的resultCode会用来匹配对应的Result resultCode = invokeActionOnly(); } //action是否已经被调用,用来判断是否第一次执行Result操作,这样写是因为action执行完后, //拦截器会一个一个执行下面的操作,然后返回上一个调用的拦截器,就是按调用拦截器时候反过来的顺序执行, //这是递归的原理,所以如果每个拦截器都执行Result那么程序就会出错, //所以当action执行完Result之后,就通过这种方式不允许拦截器执行Result if (!executed) { //第一次执行Result有效 //执行相关的监听器,Result执行前的监听器 if (preResultListeners != null) { for (Object preResultListener : preResultListeners) { PreResultListener listener = (PreResultListener) preResultListener; String _profileKey = "preResultListener: "; try { UtilTimerStack.push(_profileKey); listener.beforeResult(this, resultCode); } finally { UtilTimerStack.pop(_profileKey); } } } //如果支持执行Result,就执行,这个是构建action代理对象时候传入去的参数,是true if (proxy.getExecuteResult()) { //执行我们的Result,这里是另一个重点 executeResult(); } //标识已经被执行过 executed = true; } //返回执行代码 return resultCode; } finally { UtilTimerStack.pop(profileKey); } }
先关注拦截器调用的那行代码 ———— interceptor.getInterceptor().intercept,如果你写过自己的拦截器你应该知道,在拦截器中一定要再调用DefaultActionInvocation.invoke,使其余拦截器执行下去,否则action将不会调用到,当然如果你自己的业务逻辑要在某种情况中断现在的调用就没有办法,但是一定有另一种情况下让其余拦截器执行下去的情况的,这个是一个递归的过程,之所以DefaultActionInvocation使用interceptors这个迭代器就是这样原因,每次调用DefaultActionInvocation.invoke,interceptors的内部指针就会下移一个,执行执行完,interceptors.hasNext()返回false,然后开始执行真正的action。
我们之前不是说过action的属性还没有初始化吗!其实属性的的初始化是依靠拦截器完成的,具体是com.opensymphony.xwork2.interceptor.ParametersInterceptor这个拦截器,我们分析一下他的代码,从intercept方法看起,
ParametersInterceptor.intercept(MethodFilterInterceptor.intercept)
//ParametersInterceptor继承于MethodFilterInterceptor,并且ParametersInterceptor没有覆盖intercept, //所以实际是调用MethodFilterInterceptor.intercept public String intercept(ActionInvocation invocation) throws Exception { //判断这个调用对象是否允许执行,跟方法名有关,有些方法名可能被排除了 if (applyInterceptor(invocation)) { //执行真正的逻辑逻辑,我们先看这里,这个方法被ParametersInterceptor覆盖了, //所以调用ParametersInterceptor.doIntercept return doIntercept(invocation); } //如果调用对象不允许执行,则调用下一个拦截器。 return invocation.invoke(); }
ParametersInterceptor.doIntercept
//实现真正的拦截逻辑,给action属性设置值 public String doIntercept(ActionInvocation invocation) throws Exception { Object action = invocation.getAction(); //action是不是没有属性的,一般都是有属性的 if (!(action instanceof NoParameters)) { //获取调用对象内部的action环境,就是之前的额外环境 ActionContext ac = invocation.getInvocationContext(); //通过调用对象内部的action环境获取参数 final Map<String, Object> parameters = retrieveParameters(ac); if (LOG.isDebugEnabled()) { LOG.debug("Setting params " + getParameterLogMap(parameters)); } //有参数 if (parameters != null) { Map<String, Object> contextMap = ac.getContextMap(); try { ReflectionContextState.setCreatingNullObjects(contextMap, true); ReflectionContextState.setDenyMethodExecution(contextMap, true); ReflectionContextState.setReportingConversionErrors(contextMap, true); //上面的一些操作是保存好原来的一些对象 //获取valuestack ValueStack stack = ac.getValueStack(); //这里开始对action设置属性值,不过不是直接针对action对象设置 setParameters(action, stack, parameters); } finally { //还原对象 ReflectionContextState.setCreatingNullObjects(contextMap, false); ReflectionContextState.setDenyMethodExecution(contextMap, false); ReflectionContextState.setReportingConversionErrors(contextMap, false); } } } //调用其他拦截器 return invocation.invoke(); }
setParameters开始对action设置属性值,不过不是直接针对action对象设置,我们简单看一下这些代码,
ParametersInterceptor.setParameters
protected void setParameters(Object action, ValueStack stack, final Map<String, Object> parameters) { //省略部分代码...... //遍历所有参数,查找允许设置的参数 for (Map.Entry<String, Object> entry : params.entrySet()) { String name = entry.getKey(); boolean acceptableName = acceptableName(name) && (parameterNameAware == null || parameterNameAware.acceptableParameterName(name)); if (acceptableName) { //如果新的map中 acceptableParameters.put(name, entry.getValue()); } } //省略部分代码...... ValueStack newStack = valueStackFactory.createValueStack(stack); //遍历允许设置的参数,设置入newStack for (Map.Entry<String, Object> entry : acceptableParameters.entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); try { //向newStack设置值 newStack.setValue(name, value); } catch (RuntimeException e) { //省略部分代码...... } } //省略部分代码...... }
上面的操作有人可能觉得奇怪,根本没有对action设置值啊!!其实已经设置了,是通过newStack.setValue设置进去的,还记得action已经被设置进去valuestack的root吗!这个newStack.setValue是针对root设置值的,前面说过不是直接通过action设置的,明白了吧,所以action实际已经设置了属性的值!
值得注意的是这里因为也是涉及到参数里面可以调用OGNL表达式,这里也会引起struts2的远程漏洞,同之前说的差不多。
好!返回DefaultActionInvocation.invoke,继续看我们的两个重点关注的地方,我们先看invokeActionOnly,当拦截器执行完时候,执行action的地方,我们看代码,
DefaultActionInvocation.invokeActionOnly
//执行action public String invokeActionOnly() throws Exception { //获取action和对应的action配置对象 return invokeAction(getAction(), proxy.getConfig()); }
DefaultActionInvocation.invokeAction
protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception { //获取action执行的方法名称,默认是"execute",如果是动态调用方法,就是指定的方法名 String methodName = proxy.getMethod(); if (LOG.isDebugEnabled()) { LOG.debug("Executing action method = " + actionConfig.getMethodName()); } String timerKey = "invokeAction: " + proxy.getActionName(); try { UtilTimerStack.push(timerKey); //方法是否被调用 boolean methodCalled = false; Object methodResult = null; Method method = null; try { //获取action,然后根据methodName的名称,通过反射获取这个方法对象,这个是没有参数的方法 method = getAction().getClass().getMethod(methodName, new Class[0]); } catch (NoSuchMethodException e) { try { //如果找不到methodName的方法,就尝试查找"do"+methodName的方法 String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1); //获取对应的方法对象 method = getAction().getClass().getMethod(altMethodName, new Class[0]); } catch (NoSuchMethodException e1) { //再获取失败就用未知方法对象代替,这里判断有没有对应的未知对象句柄 if (unknownHandlerManager.hasUnknownHandlers()) { try { //获取对应的Result默认Result methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName); //设定方法已经执行,因为是默认Result methodCalled = true; } catch (NoSuchMethodException e2) { throw e; } } else { throw e; } } } //是否被执行了 if (!methodCalled) { //执行这个方法,设置action为执行实例,大多数情况都是执行这个方法,返回值大多数情况也是String类型 methodResult = method.invoke(action, new Object[0]); } //如果直接返回Result if (methodResult instanceof Result) { //赋值给this.explicitResult,留着后面用,如果this.explicitResult不为空, //就执行this.explicitResult这个Result,不执行其他 this.explicitResult = (Result) methodResult; //注入依赖值 container.inject(explicitResult); //返回空,因为已经有this.explicitResult作为Result return null; } else { //返回是字符串,后面会通过字符串来匹配result标签的name来获取Result return (String) methodResult; } } catch (NoSuchMethodException e) { throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + ""); } catch (InvocationTargetException e) {//异常处理 Throwable t = e.getTargetException(); //如果有监听器,执行对应监听器 if (actionEventListener != null) { //执行监听器,得到返回值 String result = actionEventListener.handleException(t, getStack()); //返回对应的结果 if (result != null) { return result; } } if (t instanceof Exception) { throw (Exception) t; } else { throw e; } } finally { UtilTimerStack.pop(timerKey); } }
从上面可以看出,这个就是我们的Action的方法执行的地方,然后根据返回值,如果这个值是Result类型,就直接用这个Result来执行会面的操作;如果是String类型,就是resultCode,就用这个resultCode来获取Result,就是匹配result标签里面的name,这些配置早就储存在result配置对象了,然后用获取到的Result执行下面的操作,我们回到DefaultActionInvocation.invoke,看executeResult里面的处理过程,
DefaultActionInvocation.executeResult
//执行Result private void executeResult() throws Exception { //先创建一个Result result = createResult(); String timerKey = "executeResult: " + getResultCode(); try { UtilTimerStack.push(timerKey); //如果创建成功 if (result != null) { //执行Result result.execute(this); } else if (resultCode != null && !Action.NONE.equals(resultCode)) {//如果有返回值,并且Action不是没有视图的就抛出异常 throw new ConfigurationException("No result defined for action " + getAction().getClass().getName() + " and result " + getResultCode(), proxy.getConfig()); } else {//没有视图,就是通过response直接写回响应给客户端哪些情况,比如json if (LOG.isDebugEnabled()) { LOG.debug("No result returned for action " + getAction().getClass().getName() + " at " + proxy.getConfig().getLocation()); } } } finally { UtilTimerStack.pop(timerKey); } }
上面情况我们先看看createResult方法,我们假设返回的Result是一个org.apache.struts2.dispatcher.ServletDispatcherResult类型,就是默认类型,Action的默认页面(视图)跳转方式,需要返回值,有视图。我们先看代码
DefaultActionInvocation.createResult
public Result createResult() throws Exception { //还记得DefaultActionInvocation.invokeAction,如果里面的Action执行直接返回Result, //那个Result就是explicitResult,那么就不用创建了,直接返回 if (explicitResult != null) { Result ret = explicitResult; explicitResult = null; return ret; } //获取action配置对象 ActionConfig config = proxy.getConfig(); //获取对应的result配置对象map,key为resultCode,value为result配置对象 Map<String, ResultConfig> results = config.getResults(); ResultConfig resultConfig = null; try { //根据返回值获取result配置对象 resultConfig = results.get(resultCode); } catch (NullPointerException e) { } //获取失败,尝试通过"*"获取通用的result配置对象 if (resultConfig == null) { resultConfig = results.get("*"); } //获取到result配置对象 if (resultConfig != null) { try { //通过ObjectFactory,根据result配置对象和调用对象的内部环境(额外环境),创建一个Result, //上面通过action:、redirect:、redirectAction:等字符串开头的request参数控制页面(视图)跳转的是自行new出来的,这里是通过ObjectFactory,通过反射创建的 return objectFactory.buildResult(resultConfig, invocationContext.getContextMap()); } catch (Exception e) { LOG.error("There was an exception while instantiating the result of type " + resultConfig.getClassName(), e); throw new XWorkException(e, resultConfig); } } else if (resultCode != null && !Action.NONE.equals(resultCode) && unknownHandlerManager.hasUnknownHandlers()) { //如果有返回值,并且Action不是没有视图的,就尝试使用默认的Result,通过未知对象指定 return unknownHandlerManager.handleUnknownResult(invocationContext, proxy.getActionName(), proxy.getConfig(), resultCode); } //返回空,认为是没有视图的情况 return null; }
关于之前那个默认参数字段("DEFAULT_PARAM")的问题,其值是"location",其实这个DEFAULT_PARAM是用来指定Result接受result标签body部分的值的属性名称,就是这个部分,
<result ...>something<!-- location属性接受的值 --></result>
所以这里就是指定了location属性,location属性是定义在org.apache.struts2.dispatcher.StrutsResultSupport,所有的Result都基本上继承自这个类,该属性用来指定页面(视图)跳转要跳转到哪个页面(视图),所以才设置"DEFAULT_PARAM"就指定了这个属性默认直接result标签body部分的值。具体问题就是这个location属性是在那里被赋值的,有两种情况,如果是通过action:、redirect:、redirectAction:等字符串开头的request参数来产生的Result,他是通过ParameterAction.execute执行时候,new一个Result,然后将action:、redirect:、redirectAction:等字符后面的一部分直接设置进去location属性的;另外一个种情况就是我们上面createResult的情况,通过ObjectFactory创建,具体分析代码,
ObjectFactory.buildResult
public Result buildResult(ResultConfig resultConfig, Map<String, Object> extraContext) throws Exception { //根据result配置对象获取result的类名 String resultClassName = resultConfig.getClassName(); Result result = null; //获取到类名 if (resultClassName != null) { //通过buildBean方法创建实例,其实最终都是通过反射,调用clazz.newInstance来实例化 result = (Result) buildBean(resultClassName, extraContext); //获取result配置对象里面的参数,就是我们result标签里面的param标签指定的参数, //还有对应的result-type里面指定的一些参数,重点是我们的"location"参数也在里面 Map<String, String> params = resultConfig.getParams(); if (params != null) { //遍历所有参数 for (Map.Entry<String, String> paramEntry : params.entrySet()) { try { //调用reflectionProvider.setProperty,通过反射,设置对应参数的值到result reflectionProvider.setProperty(paramEntry.getKey(), paramEntry.getValue(), result, extraContext, true); } catch (ReflectionException ex) { if (result instanceof ReflectionExceptionHandler) { ((ReflectionExceptionHandler) result).handle(ex); } } } } } //返回生成的result实例 return result; }
当创建Result成功就可以执行了,前面说过假设是返回默认的Result ———— org.apache.struts2.dispatcher.ServletDispatcherResult,我们针对ServletDispatcherResult.execute来展开,
ServletDispatcherResult.execute(StrutsResultSupport.execute)
//ServletDispatcherResult继承于StrutsResultSupport,并且ServletDispatcherResult没有覆盖execute, //所以实际是调用StrutsResultSupport.execute public void execute(ActionInvocation invocation) throws Exception { //处理一下参数,不过这里不会产生struts2远程漏洞,原因是location是我们自己开发是否设置的result标签的body值 lastFinalLocation = conditionalParse(location, invocation); //ServletDispatcherResult覆盖了这个方法,实际调用ServletDispatcherResult.doExecute doExecute(lastFinalLocation, invocation); }
ServletDispatcherResult.doExecute
public void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("Forwarding to location " + finalLocation); } //获取页面环境对象,一般都为空 PageContext pageContext = ServletActionContext.getPageContext(); //如果不为空,那么这个页面(视图)是被包含的页面(视图) if (pageContext != null) { //包含,把整个页面(视图)包含过去 pageContext.include(finalLocation); } else { //执行页面(视图)跳转 HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); //根据finalLocation,获取页面(视图)对象 RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation); //如果有带request参数,把request参数放入调用对象的内部环境 if (invocation != null && finalLocation != null && finalLocation.length() > 0 && finalLocation.indexOf("?") > 0) { String queryString = finalLocation.substring(finalLocation.indexOf("?") + 1); Map parameters = (Map) invocation.getInvocationContext().getContextMap().get("parameters"); Map queryParams = UrlHelper.parseQueryString(queryString, true); if (queryParams != null && !queryParams.isEmpty()) parameters.putAll(queryParams); } //如果页面(视图)为空,就是不存在,返回404错误 if (dispatcher == null) { response.sendError(404, "result '" + finalLocation + "' not found"); return; } //如果没有通过response返回,并且没有被包含 if (!response.isCommitted() && (request.getAttribute("javax.servlet.include.servlet_path") == null)) { request.setAttribute("struts.view_uri", finalLocation); request.setAttribute("struts.request_uri", request.getRequestURI()); //跳转页面(视图) dispatcher.forward(request, response); } else {//交给页面(视图)处理 dispatcher.include(request, response); } } }
到这里,result执行完了,跳转到指定的页面(视图),然后正真生成好页面,再返回给客户端。从doFilter开始,获取到映射对象(Mapping),到serviceAction,创建action代理对象,然后执行action、拦截器、Result,最终返回页面(视图),再返回到客户端。等一系列过程都完成了。FilterDispatcher的整个过程,从初始化(init)到提供服务(doFilter),主要的过程都分析了,至于destroy那些就不说了。关于struts2的执行流程大部分已经分析了,其余太过复杂或者篇幅过长的就没分析了。下面再分析一个,struts2集成spring,主要是针对struts2-spring-plugin分析。
第三部分:Struts2集成Spring
第三部分要求有一定spring基础才能看懂!
struts2不单单可以单独工作,而且还支持集成其他的框架一起协同工作,而且集成起来也不太难,只需要一个集成插件就可以了。我们主要说的是struts2和spring集成,其实主要是说那个集成的插件,因为只需要这个插件,struts2就可以跟spring协同运行了,如此神奇一定分析。简单说一下,集成spring之后,struts2的Action、Interceptor、Result等一些列的对象都可以通过spring来管理,就是直接象spring普通bean一样,配置在spring配置文件中,这样这些对象的生命周期都归spring控制,从构造实例到释放资源。通过这样可以方便bean集中管理,struts2只需要管理展现层的逻辑就可以了,不过struts2的配置有一些改变,就是可以在action标签的class填上spring中定义的bean的name就可以获取到了,当然也可以想原来一样直接写完整的class,两种都可以。不过在某些情况下,第一种写法会有问题!这些我们后面会解析!我们先统一看一下这个struts2-spring-plugin。
我是用的插件是struts2-spring-plugin-2.0.8,里面有一些文件,版本说明文件、StrutsSpringObjectFactory.class、struts-plugin.xml等文件,主要就只有两个 ———— StrutsSpringObjectFactory.class、struts-plugin.xml文件,这两个文件就完成了我们的集成功能了,当然前提就是我们已经配置好spring的基本环境,就是web.xml要加上这个监听器,
web.xml
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
才能构建基本spring环境。好!我们假设我们已经配置好了spring基本的环境了。我们先看看struts-plugin.xml配置文件,
struts-plugin.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 type="com.opensymphony.xwork2.ObjectFactory" name="spring" class="org.apache.struts2.spring.StrutsSpringObjectFactory" /> <!-- Make the Spring object factory the automatic default --> <constant name="struts.objectFactory" value="spring" /> <package name="spring-default"> <interceptors> <interceptor name="autowiring" class="com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor"/> <interceptor name="sessionAutowiring" class="org.apache.struts2.spring.interceptor.SessionContextAutowiringInterceptor"/> </interceptors> </package> </struts>
在上面的代码我们明显看到struts-plugin.xml中,添加了一个type为com.opensymphony.xwork2.ObjectFactory,name为"spring",class为org.apache.struts2.spring.StrutsSpringObjectFactory的bean,并且以constant标签的形式,指定了struts.objectFactory常量的值是"spring",这里明显就是要用这个StrutsSpringObjectFactory来代替原本的StrutsObjectFactory!因为ObjectFactory是创建Action、Interceptor、Result等实例的工厂,是创建这些对象的必经之路!spring通过替换这个ObjectFactroy把自己spring容器内的bean传给struts2使用,不过这个过程是怎么实现的呢,后面会说!先分析这些配置的解析过程吧!
我们应该记得上面说过struts2配置文件加载顺序吧!简单复习一下:
1、org/apache/struts2/default.properties
2、struts-default.xml
3、struts-plugin.xml(如果有)
4、struts.xml
5、struts.properties(如果有)
6、default.properties(如果有)
7、struts.custom.properties(如果有)
由于org/apache/struts2/default.properties没有指定struts.objectFactory常量的值,所以在后面StrutsXmlConfigurationProvider供给器注册,就是解析配置文件过程,当解析到struts-plugin.xml时候,供给器直接将struts.objectFactory常量,设置成"spring",并且储存在了容器配置对象中;同时,供给器也将struts-plugin.xml中name为"spring"的bean创建了对应的工厂对象,以type为com.opensymphony.xwork2.ObjectFactory,name为"spring",构成Key对象,并储存在容器构造器中。那么,当在BeanSelectionProvider注册时候,通过调用下面这段代码,
BeanSelectionProvider.register
//省略部分代码...... alias(ObjectFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY, builder, props); //省略部分代码......
BeanSelectionProvider.alias
//省略部分代码...... //当执行到com.opensymphony.xwork2.ObjectFactory类型,key为StrutsConstants.STRUTS_OBJECTFACTORY(struts.objectFactory)时候 //获取默认类型工厂对象,现在是没有的 if (!builder.contains(type)) { //现在可以获取到值,值为"spring" String foundName = props.getProperty(key, DEFAULT_BEAN_NAME); //从之前分析可以看出现在已经有一个type为com.opensymphony.xwork2.ObjectFactory,name为"spring"的工厂对象 if (builder.contains(type, foundName)) { if (LOG.isDebugEnabled()) { LOG.info("Choosing bean ("+foundName+") for "+type); } //为这个工厂对象设置别名"default",就是为对于com.opensymphony.xwork2.ObjectFactory类型设置默认工厂对象 builder.alias(type, foundName, Container.DEFAULT_NAME); } //省略部分代码...... } //省略部分代码......
可以看出,最后通过BeanSelectionProvider供给器,把spring提供的org.apache.struts2.spring.StrutsSpringObjectFactory设置为默认工厂对象,就是凡是struts2用到的ObjectFactory,都是使用org.apache.struts2.spring.StrutsSpringObjectFactory生成的实例,通过这种方式,spring成功替换了ObjectFactory,从根本上控制了Action、Interceptor、Result等类的实例的获取,通过springIOC管理这些类对应的bean,这样struts2就可以和spring完美的集成在一起了!值得一提的,虽然可以让spring管理Action、Interceptor、Result等类的bean,但是一般情况下只用于管理Action的bean,并且对于Action、Result类的bean最好在springIOC中设置Scope为prototype,就是多例的bean,因为spring默认是单例的,否则strus2会出问题!
注意这里还有两个拦截器,默认情况下是不使用的,当时有必要可以通过package中extends方式集成这个spring-default,struts2支持继承多个package,然后既可以使用了,加载配置生成配置对象时候就可以生成对应的拦截器了!ActionAutowiringInterceptor拦截器是用来为action注入依赖值的,在spring中没有设置自动装配时候,可以通过这个拦截器自动注入依赖值给action;SessionContextAutowiringInterceptor拦截器,这个拦截器已经不被支持了,已经在struts2中去掉,所以这里不说了,以后简单介绍一下第一个ActionAutowiringInterceptor拦截器。
好,我们先看一下,StrutsSpringObjectFactory是怎么实现通过插件将spring的bean传给struts2的,
StrutsSpringObjectFactory
/*这个是整个StrutsSpringObjectFactory类定义*/ package org.apache.struts2.spring; import com.opensymphony.xwork2.inject.Container; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.spring.SpringObjectFactory; import com.opensymphony.xwork2.util.logging.Logger; import com.opensymphony.xwork2.util.logging.LoggerFactory; import org.apache.struts2.StrutsConstants; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.web.context.WebApplicationContext; import javax.servlet.ServletContext; //StrutsSpringObjectFactory继承于SpringObjectFactory public class StrutsSpringObjectFactory extends SpringObjectFactory { private static final Logger LOG = LoggerFactory.getLogger(StrutsSpringObjectFactory.class); /* 注意,之前讲过,凡是有@Inject标识的方法、参数、属性都是在构造实例时候, 通过struts2的容器注入依赖值的,而这个是构造函数,在构造实例时候, 就把相关的值设置到对应的参数,然后调用构造函数,创建实例;当然是通过反射来调用这个构造函数。 */ @Inject public StrutsSpringObjectFactory( @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_AUTOWIRE,required=false) String autoWire, @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_AUTOWIRE_ALWAYS_RESPECT,required=false) String alwaysAutoWire, @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_USE_CLASS_CACHE,required=false) String useClassCacheStr, @Inject ServletContext servletContext, @Inject(StrutsConstants.STRUTS_DEVMODE) String devMode, @Inject Container container) { super(); //设置是否使用Class缓存 boolean useClassCache = "true".equals(useClassCacheStr); if (LOG.isInfoEnabled()) { LOG.info("Initializing Struts-Spring integration..."); } //通过指定的key(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)来获取spring的WebApplicationContext, //WebApplicationContext中包含了spring的bean,他就是spring的基本容器(环境、上下文), //这个对象在spring初始化完时候就会设置在servlet环境中,方便后面操作使用。 Object rootWebApplicationContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); //获取失败则直接返回,停止操作 if(rootWebApplicationContext instanceof RuntimeException){ RuntimeException runtimeException = (RuntimeException)rootWebApplicationContext; LOG.fatal(runtimeException.getMessage()); return; } //将之前获取的WebApplicationContext转换成ApplicationContext ApplicationContext appContext = (ApplicationContext) rootWebApplicationContext; //如果ApplicationContext是为空的,证明没有spring运行环境,提示没有spring运行环境 if (appContext == null) { // uh oh! looks like the lifecycle listener wasn't installed. Let's inform the user String message = "********** FATAL ERROR STARTING UP STRUTS-SPRING INTEGRATION **********\n" + "Looks like the Spring listener was not configured for your web app! \n" + "Nothing will work until WebApplicationContextUtils returns a valid ApplicationContext.\n" + "You might need to add the following to web.xml: \n" + " <listener>\n" + " <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>\n" + " </listener>"; LOG.fatal(message); return; } //通过struts2容器获取常量的值 String watchList = container.getInstance(String.class, "struts.class.reloading.watchList"); String acceptClasses = container.getInstance(String.class, "struts.class.reloading.acceptClasses"); String reloadConfig = container.getInstance(String.class, "struts.class.reloading.reloadConfig"); //是否需要重新加载环境 if ("true".equals(devMode) && StringUtils.isNotBlank(watchList) && appContext instanceof ClassReloadingXMLWebApplicationContext) { useClassCache = false; ClassReloadingXMLWebApplicationContext reloadingContext = (ClassReloadingXMLWebApplicationContext) appContext; reloadingContext.setupReloading(watchList.split(","), acceptClasses, servletContext, "true".equals(reloadConfig)); if (LOG.isInfoEnabled()) { LOG.info("Class reloading is enabled. Make sure this is not used on a production environment!", watchList); } //加载类加载器 setClassLoader(reloadingContext.getReloadingClassLoader()); //刷新环境 reloadingContext.refresh(); } //如果一切顺利,设置ApplicationContext到属性中保存,注意这里设计到一个重要的操作,后面分析 this.setApplicationContext(appContext); //设置struts2默认的自动装配策略 int type = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; // 默认 if ("name".equals(autoWire)) { type = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; } else if ("type".equals(autoWire)) { type = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE; } else if ("auto".equals(autoWire)) { type = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT; } else if ("constructor".equals(autoWire)) { type = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR; } else if ("no".equals(autoWire)) { type = AutowireCapableBeanFactory.AUTOWIRE_NO; } this.setAutowireStrategy(type); this.setUseClassCache(useClassCache); //设置是否始终遵从自动装配策略 this.setAlwaysRespectAutowireStrategy("true".equalsIgnoreCase(alwaysAutoWire)); if (LOG.isInfoEnabled()) { LOG.info("... initialized Struts-Spring integration successfully"); } } }
简单说一下spring的WebApplicationContext,这个是spring的重要的环境,所有的bean都管理在里面,当spring初始化完之后,这个WebApplicationContext就会被放到servletContext中,方便后面使用,所以struts2-spring的集成插件就是利用这个原理,通过servletContext获取到WebApplicationContext,获取到他就可以获取到spring的bean,获取到spring的bean就可以在struts2中使用这些bean,进行各种操作,具体怎么获取这些bean后面详细说。
上面就是一个完整的StrutsSpringObjectFactory,他就实现了一个构造函数,而具体的怎么获取spring的bean之类的细节都是在其父类SpringObjectFactory中实现的,而这个构造函数只是负责设置值,不过里面设置的值都是关键的值,比如this.setApplicationContext(appContext)、this.setAutowireStrategy(type)、this.setAlwaysRespectAutowireStrategy那些操作,我先说说this.setApplicationContext(appContext),这个比较重要,
StrutsSpringObjectFactory.setApplicationContext(SpringObjectFactory.setApplicationContext)
//StrutsSpringObjectFactory继承于SpringObjectFactory,并且StrutsSpringObjectFactory没有覆盖setApplicationContext, //所以实际是调用SpringObjectFactory.setApplicationContext //设置Application环境,并生成自动装配工厂对象 public void setApplicationContext(ApplicationContext appContext) throws BeansException { //设置Application环境 this.appContext = appContext; //这里是一个重点,这里是从Application环境中查找自动装配工厂对象 autoWiringFactory = findAutoWiringBeanFactory(this.appContext); }
这里重点是autoWiringFactory和findAutoWiringBeanFactory方法两个,autoWiringFactory是使用spring提供的ObjectFactory创建实例时候为实例对象自动装配属性值的工厂对象,这个工厂是在spring环境中查找对应属性的bean,然后注入属性中。spring自身进行自动装配也是使用autoWiringFactory进行的,如果没有配置自动装配就要自己在bean写明属性值的bean,而这里默认就是使用这个自动装配工厂对象,为struts2的指定对象进行自动装配了;findAutoWiringBeanFactory方法是通过已经提供的Application环境,在Application环境里面查找这个工厂,递归父环境查找,直到找到或者为空。我们先看看这个findAutoWiringBeanFactory代码。
SpringObjectFactory.findAutoWiringBeanFactory
//查找AutoWiringBeanFactory工厂(自动装配工厂) protected AutowireCapableBeanFactory findAutoWiringBeanFactory(ApplicationContext context) { if (context instanceof AutowireCapableBeanFactory) { //检测当前context是不是AutowireCapableBeanFactory工厂对象,如果是,就直接用 return (AutowireCapableBeanFactory) context; } else if (context instanceof ConfigurableApplicationContext) { //检测当前context是不是ConfigurableApplication环境对象,如果是从context获取工厂对象 return ((ConfigurableApplicationContext) context).getBeanFactory(); } else if (context.getParent() != null) { //递归地查找父环境对象,获取工厂对象 return findAutoWiringBeanFactory(context.getParent()); } return null; }
所以通过StrutsSpringObjectFactory.setApplicationContext就可以同时设置了自动装配工厂,于是,两个重要的对象都初始化好了!接下来就是this.setAutowireStrategy(type)、this.setAlwaysRespectAutowireStrategy操作,其实这两个操作都是直接设置值,重要的是这两个setter方法对应的属性,就是autowireStrategy、alwaysRespectAutowireStrategy两个属性,默认autowireStrategy为AutowireCapableBeanFactory.AUTOWIRE_BY_NAME,默认alwaysRespectAutowireStrategy为false;autowireStrategy是用来指定自动装配的方式,就是自动装配是根据bean名、类型、构造函数、默认、不使用自动装配中的哪一种方式,跟spring的自动装配是一个原理的,struts2借用了这个,可以通过struts2的struts.objectFactory.spring.autoWire常量指定;alwaysRespectAutowireStrategy就是指是不是始终遵循autowireStrategy的设置,可以通过struts2的struts.objectFactory.spring.autoWire.alwaysRespect常量指定。
先简单介绍一下StrutsSpringObjectFactory怎么进行创建实例的处理,在StrutsSpringObjectFactory中创建实例,如果是使用第一种配置方式,就是在action配置文件中,class属性写的bean的名或者ID,那么,StrutsSpringObjectFactory会直接通过spring环境去取指定bean名或者ID的对象,不实用struts2自身的容器构建;如果是使用第二种方式,就是按原来那样写上完整class名,那么,spring默认就会根据构造函数方式,创建出对应的bean,然后通过spring先对这个bean的属性进行注入依赖,然后再让struts2通过struts2的容器注入依赖。由于StrutsSpringObjectFactory继承于SpringObjectFactory,SpringObjectFactory继承于ObjectFactory,所以当创建实例时候会调用buildBean方法,因此我们也重点关注这个方法,同时由于SpringObjectFactory覆盖了这个方法,我们针对SpringObjectFactory类。下面具体看一下代码,
SpringObjectFactory.buildBean
//创建实例 public Object buildBean(String beanName, Map<String, Object> extraContext, boolean injectInternal) throws Exception { //默认为空 Object o = null; try { //这是第一种配置方式,直接写bean名,通过appContext(就是之前传过来的的Application环境)根据bean名获取bean o = appContext.getBean(beanName); } catch (NoSuchBeanDefinitionException e) { //第二种配置方式,填写完整class名,调用另外一个buildBean,我们接下去看。 Class beanClazz = getClassInstance(beanName); o = buildBean(beanClazz, extraContext); } //是否使用struts2的容器进行依赖注入,为相关属性赋值 //一般在使用第一种方式配置时候,需要进行这个操作,因为spring只是注入了spring的能提供的属性值, //如果这个bean还需要struts2的属性值,就需要这个操作 if (injectInternal) { //使用struts2的容器进行依赖注入 injectInternalBeans(o); } return o; } public Object buildBean(Class clazz, Map<String, Object> extraContext) throws Exception { Object bean; try { //这里就是alwaysRespectAutowireStrategy的用处,是否始终遵循autowireStrategy的配置,默认是不遵循 if (alwaysRespectAutowireStrategy) { //通过自动装配工厂创建bean,并使用指定自动装配策略 bean = autoWiringFactory.createBean(clazz, autowireStrategy, false); //如果是实现ApplicationContextAware的bean,为其注入Application环境 injectApplicationContext(bean); //通过struts2容器注入相关struts2的属性值,并返回bean return injectInternalBeans(bean); } else {//默认情况,不遵循autowireStrategy策略 //使用构造函数装配方式创建bean,就是通过构造函数中的参数注入属性值 bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false); bean = autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(bean, bean.getClass().getName()); //一些预处理 bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName()); //通过autoWireBean方法,自动装配bean return autoWireBean(bean, autoWiringFactory); } } catch (UnsatisfiedDependencyException e) { if (LOG.isErrorEnabled()) LOG.error("Error building bean", e); //如果出现异常,创建bean失败,使用父类的构建bean方式(就是通过放射创建),并使用autoWireBean来自动装配 return autoWireBean(super.buildBean(clazz, extraContext), autoWiringFactory); } }
这个autoWireBean是一个提供spring和struts2混合的自动装配的方法,之前我们简单说了一下,我们现在看看代码,
SpringObjectFactory.autoWireBean
//自动装配 public Object autoWireBean(Object bean, AutowireCapableBeanFactory autoWiringFactory) { //工厂对象当然不能为空 if (autoWiringFactory != null) { //使用自动装配工厂装配属性,从spring环境那边获取属性值并注入 autoWiringFactory.autowireBeanProperties(bean, autowireStrategy, false); } //如果是实现ApplicationContextAware的bean,为其注入Application环境 injectApplicationContext(bean); //使用struts2的容器,注入struts2的相关属性值,从struts2环境那边获取属性值注入的 injectInternalBeans(bean); //返回bean return bean; }
从上面的代码,再结合刚才的对这个过程的简单介绍,可以知道StrutsSpringObjectFactory是怎么创建实例了吧!这里就不再多说了!不过说一下为什么,要使用autoWireBean进行两个不同环境的装配!
由于spring中只能提供spring环境中的属性值,struts2容器只能提供struts2的属性值,比如action中要使用service管理业务逻辑,这些service一般都在spring中管理,因为有AOP那些框架,用起来方便,所以这些要使用spring的装配;而象valuestack这些(假如要直接用),就只有在strus2环境中才有,所以要使用struts2装配。这些道理很简单!
一开始说的在集成时候配置action的class属性的两种方式还记得吗!?不是说过第一种配置方式有些情况会产生问题吗!我在这里说一下,根据上面第一个SpringObjectFactory.buildBean代码,如果使用第一种配置方式,就是直接在class属性设置bean名,由于appContext.getBean(beanName)可以直接获取到bean,然后注入了struts2属性之后就直接返回了,因此啊,autowireStrategy和alwaysRespectAutowireStrategy两个属性就完全不生效了,现在的装配方式完全取决于spring的自动装配!注意,别以为injectInternalBeans会根据autowireStrategy和alwaysRespectAutowireStrategy属性装配,因为injectInternalBeans是父类ObjectFactory的方法,而autowireStrategy和alwaysRespectAutowireStrategy是SpringObjectFactory的属性,是毫不相干的!因此,如果是想使用autowireStrategy和alwaysRespectAutowireStrategy两个配置,就不能使用第一种配置方式!
最后一个是对ActionAutowiringInterceptor拦截器的简单介绍,看代码吧!
ActionAutowiringInterceptor.intercept
//拦截器逻辑 public String intercept(ActionInvocation invocation) throws Exception { //首先这个拦截器要初始化了 if (!initialized) { //从ActionContext中得到servlet环境(servletContext),然后从servlet环境根据指定key获取Application环境,就是spring的基本环境 ApplicationContext applicationContext = (ApplicationContext) ActionContext.getContext().getApplication().get(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (applicationContext == null) { LOG.warn("ApplicationContext could not be found. Action classes will not be autowired."); } else { //设置Application环境到属性中 setApplicationContext(applicationContext); //创建一个工厂对象 factory = new SpringObjectFactory(); //设置工厂对象的Application环境,并生成自动装配工厂对象(SpringObjectFactory的属性),就是上面说的那些 factory.setApplicationContext(getApplicationContext()); if (autowireStrategy != null) { //设置自动装配策略 factory.setAutowireStrategy(autowireStrategy.intValue()); } } initialized = true; } if (factory != null) { //从action调用对象获取到action Object bean = invocation.getAction(); //为action注入spring和struts2两个环境的依赖属性值 factory.autoWireBean(bean); //在action环境中设置入Application环境 ActionContext.getContext().put(APPLICATION_CONTEXT, context); } //继续执行下面的拦截器 return invocation.invoke(); }
从代码可以看出这个操作是为action注入spring和struts2两个环境的依赖属性值的,在spring中没有设置自动装配时候,可以通过这个拦截器自动注入依赖值给action,这个主要是针对spring出现状况时候应急使用的!可以在struts2的配置文件中,通过在package的extends属性加入spring-default,继承这个package来使用这个拦截器。
上面已经基本上完整的介绍了struts2-spring-plugin,从spring的ObjectFactory怎么替换原来struts2的ObjectFactory,到StrutsSpringObjectFactory创建bean的过程,然后到ActionAutowiringInterceptor拦截器的简单介绍,整个插件就全部讲解完毕。
第四部分:总结
上面已经讲述了struts2的整个运行过程,从FilterDispatcher的init开始,涉及到struts2的容器创建、依赖注入、配置文件解析、常量解析,然后action配置对象、result配置对象、result类型配置对象等生成,再然后运行时配置对象的生成,组装起之前的配置对象,生成一个完整的运行时配置对象,初始化完成,到FilterDispatcher的doFilter,涉及到mapping对象的获取、代理对象的创建、Action的执行、Result对象的创建、Result对象的执行,最后解析视图,返回客户端,整个struts2的运行过程。
之后,有讲述了struts2-spring-plugin,从ObjectFactory的替代,到StrutsSpringObjectFactory创建bean的过程,整个过程都完结。
对于这篇文章,上面那些源代码都是我自己看完,然后总结出来的精髓,然后通过网上资料的搜集、整理、加上自己的描述,写出来的!绝对原创,里面已经包含了一些网上面提到的一些比较有少人解决总结的问题,还有一些比较少人解决的问题,里面都基本上有说到!希望对大家有帮助,因为完成得比较仓促,可能有一些词汇没有把握准,或者有一些错误的见解、错字、语法错误等问题,请大家指出来!欢迎大家转载!!
相关推荐
WebWork的这些特性被集成到Struts 2中,使得Struts 2成为了一个高效且易用的MVC框架。 **Struts 2的生命周期**: 1. 用户发起HTTP请求。 2. Struts 2 框架接收到请求,解析请求参数。 3. 拦截器链开始执行,按照...
本资料包包含的是《Struts2深入详解》一书的源码分析,涵盖了从第一章到第五章的内容,并附带了相关的jar包,方便读者结合理论与实践进行学习。 首先,让我们从第一章开始,Struts2的基础知识。这一章通常会介绍...
Struts2框架详解 Struts2是Java Web开发中的一款广泛应用的开源MVC框架,它构建在Apache Struts 1的基础上,提供了更为强大的功能和更好的灵活性。Struts2旨在简化Web应用开发,通过将表现层与业务逻辑层解耦,提高...
Struts2是一个流行的Java web应用程序框架,用于构建MVC(模型-视图-控制器)架构的应用。它提供了许多内置功能,如国际化、异常处理、插件扩展等,以简化开发流程。在Struts2中,配置是至关重要的,因为它们定义了...
在深入详解Struts2的课本代码中,我们可以看到一系列章节对应的不同代码示例,包括`ch01`、`ch02`、`ch03_1`、`ch03_2`、`ch03_3`和`ch03_4`。这些章节通常会涵盖以下几个重要的知识点: 1. **Struts2基础概念**:...
Struts2可以方便地与Spring、Hibernate等其他框架集成,实现依赖注入、持久化等功能,构建完整的MVC应用。 **9. 异常处理** Struts2提供了一套完善的异常处理机制,允许开发者自定义错误页面和错误处理策略,确保...
### Struts1 框架详解 #### 一、Struts1 基础与架构 **1.1 J2EE技术栈** Struts1框架是基于Java2平台企业版(J2EE)构建的,它依赖于以下技术: - **Servlet**:处理HTTP请求的基础接口,用于创建Web应用中的...
### Struts2常量设置详解 #### 一、概述 在Struts2框架中,配置文件`struts.properties`用于定义一系列重要的系统级配置项,这些配置项对框架的行为模式和功能实现起着至关重要的作用。本文将详细介绍该文件中几个...
Struts2框架作为Java Web开发领域中的一款经典MVC(Model-View-Controller)框架,其工作原理涉及到了一系列复杂的组件交互与流程控制。本文将深入解析Struts2的工作流程,帮助开发者理解其核心机制。 ### 一、...
### Struts 2 properties文件详解 #### 概述 `struts.properties` 文件是Struts 2框架中的核心配置文件之一,它包含了Struts 2框架运行时所需的一系列配置属性。这些属性决定了Struts 2的行为特征以及与其他组件如...
### Struts2框架基础知识点详解 #### 一、Struts2框架概述 - **定义**:Struts2是由Apache软件基金会支持的一个开源框架,用于JavaEE应用的开发。它是Struts1的下一代版本,采用了MVC(Model-View-Controller)架构...
**Struts2详解:** Struts2是在Struts1的基础上进行改进的下一代框架,它吸收了其他优秀框架的特性,如WebWork,使得其在灵活性和性能上有了显著提升。 1. **Ognl(Object-Graph Navigation Language)**:Struts2...
1. **Filter Dispatcher**:这是Struts2的核心组件,作为Servlet Filter运行在Web应用中。当请求到达时,Filter Dispatcher会拦截请求并根据配置进行处理。它是整个框架的入口点,负责调度请求到相应的Action。 2. ...
本资料是关于Struts2的深入详解,包含了各个章节的源代码,有助于读者更好地理解和实践Struts2的核心概念和技术。 1. **MVC模式**:MVC模式是软件设计中的一种架构模式,将业务逻辑、数据和用户界面分离,使得...
本篇将深入探讨Struts1和Struts2的源代码,揭示其内部工作原理及核心组件。 **一、Struts1详解** 1. **架构概述**:Struts1基于Servlet API,通过FilterDispatcher拦截请求,将控制权交给ActionServlet。Action...