Struts—相关总结
站在巨人的肩膀上:
请求在Struts2框架中的处理大概分为以下几个步骤:
当用户提交登录请求后,请求的URL为:“/strutsDeepen/loginAction.action”,请求会被Tomcat服务器接收到,Tomcat服务器会根据请求URL中的web上下文,也就是“/strutsDeepen”,来选择处理这个请求的Web应用,那就是由strutsDeepen这个web工程来处理这个请求。
•Web容器会去读取strutsDeepen这个工程的web.xml,在web.xml中进行匹配,发现后缀为“.action”的请求,由struts2这个过滤器来进行处理,根据Filter的配置,找到实际的类为FilterDispatcher。
•Web容器会获取FilterDispatcher这个类的实例,然后回调doFilter方法,进行真正的处理。FilterDispatcher作为前端控制器,是整个Struts2的调度中心。
注意:在架构图上,可以看到有三个过滤器层次,分别是ActionContextCleanUp、SiteMesh等其他过滤器和FilterDispatcher。这三个层次中,ActionContextCleanUp和FilterDispatcher是Struts2的过滤器,而SiteMeshSiteMesh等其他过滤器不是。
FilterDispatcher是任何一个Struts2应用都需要配置的,一般出现在过滤器链的最后;如果在FilterDispatcher前出现了如SiteMesh这种特殊的过滤器,还必须在SiteMesh前引用Struts2的ActionContextCleanUp过滤器。
•FilterDispatcher将请求转发给ActionMapper。ActionMapper负责识别当前的请求是否需要Struts2做出处理。
•ActionMapper告诉FilterDispatcher,需要处理这个请求,FilterDispatcher会停止过滤器链以后的部分,所以通常情况下:FilterDispatcher应该出现在过滤器链的最后。然后建立一个ActionProxy对象,这个对象作为Action与xwork之间的中间层,会代理Action的运行过程。
•ActionProxy对象刚被创建出来的时候,并不知道要运行哪个Action,它手里只有从FilterDispatcher中拿到的请求的URL。这时候,它去向ConfigurationManager询问到底要运行哪个Action。某个特定的URL由哪个Action响应由谁负责,定义在什么地方呢?没错,在struts.xml里面。而ConfigurationManager就是负责读取并管理struts.xml的,可以简单的理解为ConfigurationManager是struts.xml在内存中的映像。在服务器启动的时候,ConfigurationManager会一次性的把struts.xml中的所有信息读到内存里,并缓存起来,以保证ActionProxy拿着来访的URL向他询问要运行哪个Action的时候,就可以直接匹配、查找并回答了。
•ActionProxy拿到了运行哪个Action、相关的拦截器以及所有可能使用的result信息,就可以着手建立ActionInvocation对象了,ActionInvocation对象描述了Action运行的整个过程。
注意:Action运行绝不仅仅只是运行Action的execute方法这么简单,还包括其他部分,完整的调用过程由ActionInvocation对象负责。
•回忆一下,strutsDeepen中Action的execute方法运行的时候,是不是它的属性就已经有了请求中的参数呢?这说明,在execute方法之前,有人偷偷的帮我们做了这件事,把请求中的参数赋值到了Action的属性上,这个“有人”就是刚刚说的拦截器。拦截器的运行被分成两部分,一部分在Action之前运行,一部分在Result之后运行,而且顺序是刚好反过来的。也就是在Action执行前的顺序,比如是拦截器1、拦截器2、拦截器3,那么运行Result之后,再次运行拦截器的时候,顺序就变成拦截器3、拦截器2、拦截器1了。
总之ActionInvocation对象执行的时候比较复杂,会做很多事:
•首先,按照拦截器的引用顺序依次执行各个拦截器的前置部分;
•然后,执行Action的execute方法;
•然后,根据execute方法返回的结果,也就是Result,在struts.xml中匹配选择下一个页面;
•找到页面后,由于现在的页面一般都是模板页面,在页面上,可以通过Struts2自带的标签库来访问需要的数据,并生成最终页面;
•最后,ActionInvocation对象再按照拦截器的引用顺序的倒序依次执行各个拦截器的后置部分。
•ActionInvocation对象执行完毕后,实际上就已经得到响应对象了,也就是HttpServletResponse对象,最后按与过滤器器配置定义相反的顺序依次经过过滤器,向用户展示出响应的结果。
相关UML类图:
ContainerProvider主要负责除package以外节点的加载,包括 bean和constant节点,再加上 properties文件中的配置元素,这些容器配置元素。packageProvider主要负责package节点内容的加载,包括action,interceptor,result等事件对象。
Factory通过一层层的包装,调用内部类InternalFactory来执行具体操作,是装饰模式的一种实现。
在 Spring中我们是通过 @Autowired 注解或者 在 xml 文件中配置依赖关系的。在 struts2的ioc实现中,则也是通过注解的方式实现,即 @Inject 来实现的,一旦我们在任意对象的方法参数、构造参数、字段上加入@Inject注解声明,那么就是在告诉ioc容器:为他注入由容器管理的对象实例。
struts2的源码在创建Action的时候,调用 inject 方法对Action对象进行依赖注入。在 injectInternalBeans 方法内调用了 Containter容 器的 inject(Object o)方法对创建的Action对象进行依赖注入操作. 到这里,我们算是对 struts2的ioc容器有了个初步的了解,知道了如何使用struts2-ioc容器获取对象和进行依赖注入。
具体流程的代码实现
通过一下Web.XML配置的filter进入Struts.
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
首先执行StrutsPrepareAndExecuteFilter的Init()方法实现Struts的初始化准备工作,包括配置文件的加载,将配置文件的内容实例化为对象存放到Container中;初始化struts日志;初始化访问路径的正则表达式;初始化分发器等。(init方法是在WEB应用启动就会调用,doFilter则是在访问filter-mapping映射到的url时会调用。)
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
Dispatcher dispatcher = null;
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);
//初始化struts日志
init.initLogging(config);
//初始化分发器
dispatcher = init.initDispatcher(config);
//初始化与过滤器相关的静态内容加载器
init.initStaticContentLoader(config, dispatcher);
//创建request被处理前的系列操作对象
prepare = new PrepareOperations(dispatcher);
//创建处理request的系列操作对象
execute = new ExecuteOperations(dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
清空ActionContext
init.cleanup();
}
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
//父类向子类转:强转为http请求、响应
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
//设置编码和国际化
prepare.setEncodingAndLocale(request, response);
//创建action上下文
prepare.createActionContext(request, response);//创建ACTIONCONTEXT,并初始化Theadlocal
prepare.assignDispatcherToThread();//指派dispatcher给Theadlocal
if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
request = prepare.wrapRequest(request);
ActionMapping mapping = prepare.findActionMapping(request, response, true);//查找并选择创建ActionMapping
if (mapping == null) {//如果映射不存在
boolean handled = execute.executeStaticResourceRequest(request, response);//试图执行一个静态资源的请求
if (!handled) {
chain.doFilter(request, response);
}
} else { //如果存在映射
execute.executeAction(request, response, mapping);//执行action
}
}
} finally {
prepare.cleanupRequest(request);//清除request的Threadlocal
}
}
initDispatcher方法会先创建dispatcher,接着执行init()加载用户配置文件,资源文件以及默认的配置文件.Struts主要包括以下配置文件:(所有的应用级别的配置文件都不是必须的(web.xml除外).)
文件名 | 文件位置 | level | 说明 |
---|---|---|---|
web.xml | /WEB-INF | 应用级 | Struts2的入口程序定义, 运行参数定义 |
struts-default.xml | /WEB-INF/lib/struts2-core.jar!struts-default.xml | 框架级 | Struts2默认的框架级的配置定义, 包含所有Struts2的基本构成元素 |
struts.xml | /WEB-INF/classes | 应用级 | Struts2默认的应用级的主配置文件, 包含所有应用级别对框架级别默认行为的覆盖定义 |
default.properties | /WEB-INF/struts2-core.jar!org.apache.struts2.default.properties | 框架级 | Struts2默认的框架级的运行参数配置 |
struts.properties | /WEB-INF/classes | 应用级 | Struts2默认的应用级运行参数配置, 包含所有应用级别对框架级别的运行参数的覆盖定义 |
struts-plugin.xm | 插件所在jar文件的根目录 | 应用级 | Struts2所支持的插件形式的配置文件, 文件结构与Struts.xml一致. 其定义作为struts.xml的扩展, 也可以覆盖框架级别的行为定义 |
public Dispatcher initDispatcher( HostConfig filterConfig ) {
Dispatcher dispatcher = createDispatcher(filterConfig);
dispatcher.init();
return dispatcher;
}
private Dispatcher createDispatcher( HostConfig filterConfig ) {
//存放参数的Map
Map<String, String> params = new HashMap<String, String>();
//将参数存放到Map
for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) {
String name = (String) e.next();
String value = filterConfig.getInitParameter(name);
params.put(name, value);
}
//根据servlet上下文和参数Map构造Dispatcher
return new Dispatcher(filterConfig.getServletContext(), params);
}
public void init() {
if (configurationManager == null) {
configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
}
try {
init_FileManager();
//加载org/apache/struts2/default.properties
init_DefaultProperties(); // [1]
//加载struts-default.xml,struts-plugin.xml,struts.xml
init_TraditionalXmlConfigurations(); // [2]
init_LegacyStrutsProperties(); // [3]
//用户自己实现的ConfigurationProviders类
init_CustomConfigurationProviders(); // [5]
//Filter的初始化参数
init_FilterInitParameters() ; // [6]
init_AliasStandardObjects() ; // [7]
Container container = init_PreloadConfiguration();
container.inject(this);
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);
}
}
加载的资源文件的流程,由xwork核心类加载,通过IOC(控制反转,及依赖注入)实现,XmlConfigurationProvider实现。具体IOC时序图如下:
在Configuration中循环调用了每一个Provider实现类的 register()方法,这个register()方法是在 ContainterProvider接口中声明的,所以该方法的作用就是往IOC容器(Container)中注册将要被容器托管的对象,可以想象下ContainerProvider 的register()方法肯定就是解析各种形式的容器配置元素,转化为Java对象,然后注册到容器的构造者对象 ContainerBuilder中去,其中的 factory()就是这个注册的作用,待所有的ContainterProvider实现类将各自对应的容器配置元素都注册到ContainerBuilder中之后,Configuration调用ContainerBuilder的create()方法就能返回一个被正确初始化了的IOC容器Container了。
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {
if (LOG.isInfoEnabled()) {
LOG.info("Parsing configuration file [" + configFileName + "]");
}
Map<String, Node> loadedBeans = new HashMap<String, Node>();
for (Document doc : documents) {
Element rootElement = doc.getDocumentElement();
NodeList children = rootElement.getChildNodes();
int childSize = children.getLength();
for (int i = 0; i < childSize; i++) {
Node childNode = children.item(i);
if (childNode instanceof Element) {
Element child = (Element) childNode;
final String nodeName = child.getNodeName();
if ("bean".equals(nodeName)) {
String type = child.getAttribute("type");
String name = child.getAttribute("name");
String impl = child.getAttribute("class");
String onlyStatic = child.getAttribute("static");
String scopeStr = child.getAttribute("scope");
boolean optional = "true".equals(child.getAttribute("optional"));
//Scope是一个枚举类型,创建了InternalFactpry的一个匿名内部类(详见源码)
Scope scope = Scope.SINGLETON;
if ("default".equals(scopeStr)) {
scope = Scope.DEFAULT;
} else if ("request".equals(scopeStr)) {
scope = Scope.REQUEST;
} else if ("session".equals(scopeStr)) {
scope = Scope.SESSION;
} else if ("singleton".equals(scopeStr)) {
scope = Scope.SINGLETON;
} else if ("thread".equals(scopeStr)) {
scope = Scope.THREAD;
}
if (StringUtils.isEmpty(name)) {
name = Container.DEFAULT_NAME;
}
try {
Class cimpl = ClassLoaderUtil.loadClass(impl, getClass());
Class ctype = cimpl;
if (StringUtils.isNotEmpty(type)) {
ctype = ClassLoaderUtil.loadClass(type, getClass());
}
if ("true".equals(onlyStatic)) {
// Force loading of class to detect no class def found exceptions
cimpl.getDeclaredClasses();
containerBuilder.injectStatics(cimpl);
} else {
if (containerBuilder.contains(ctype, name)) {
Location loc = LocationUtils.getLocation(loadedBeans.get(ctype.getName() + name));
if (throwExceptionOnDuplicateBeans) {
throw new ConfigurationException("Bean type " + ctype + " with the name " +
name + " has already been loaded by " + loc, child);
}
}
// Force loading of class to detect no class def found exceptions
cimpl.getDeclaredConstructors();
if (LOG.isDebugEnabled()) {
LOG.debug("Loaded type:" + type + " name:" + name + " impl:" + impl);
}
//往容器构造对象ContainerBuilder中注册将要被容器接纳托管的对象
containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);
}
loadedBeans.put(ctype.getName() + name, child);
} catch (Throwable ex) {
if (!optional) {
throw new ConfigurationException("Unable to load bean: type:" + type + " class:" + impl, ex, childNode);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Unable to load optional class: #0", impl);
}
}
}
} else if ("constant".equals(nodeName)) {
String name = child.getAttribute("name");
String value = child.getAttribute("value");
props.setProperty(name, value, childNode);
} else if (nodeName.equals("unknown-handler-stack")) {
List<UnknownHandlerConfig> unknownHandlerStack = new ArrayList<UnknownHandlerConfig>();
NodeList unknownHandlers = child.getElementsByTagName("unknown-handler-ref");
int unknownHandlersSize = unknownHandlers.getLength();
for (int k = 0; k < unknownHandlersSize; k++) {
Element unknownHandler = (Element) unknownHandlers.item(k);
Location location = LocationUtils.getLocation(unknownHandler);
unknownHandlerStack.add(new UnknownHandlerConfig(unknownHandler.getAttribute("name"), location));
}
if (!unknownHandlerStack.isEmpty())
configuration.setUnknownHandlerStack(unknownHandlerStack);
}
}
}
}
}
// ContainerBuilder factory方法源码
/**
* Maps a dependency. All methods in this class ultimately funnel through
* here.
*/
private <T> ContainerBuilder factory(final Key<T> key,
InternalFactory<? extends T> factory, Scope scope) {
ensureNotCreated();
checkKey(key);
/**
这里的几个参数 key.getType()为 UserService.class ,key.getName()为 service1 , factory为包装了 locatableFactory的InternalFactory实例, 参数 scope为 Scope.SINGLETON,所以我们调用的就是 Scope.SINGLETON 这个*枚举实例上的 scopeFactory方法
*/
final InternalFactory<? extends T> scopedFactory = scope.scopeFactory(key.getType(), key.getName(), factory);
//把经过枚举Scope处理过的factory放入到一个容器内部的Map缓存中,这样容器才能根据 type和name的联合主键key从容器内部查找对应的对象工厂,然后返回对象。
factories.put(key, scopedFactory);
if (scope == Scope.SINGLETON) {
singletonFactories.add(new InternalFactory<T>() {
public T create(InternalContext context) {
try {
context.setExternalContext(ExternalContext.newInstance(
null, key, context.getContainerImpl()));
return scopedFactory.create(context);
} finally {
context.setExternalContext(null);
}
}
});
}
return this;
}
/**
* Scope of an injected objects.
*
* @author crazybob
*/
public enum Scope {
/**
* One instance per injection.
*/
DEFAULT {
@Override
<T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
InternalFactory<? extends T> factory) {
return factory;
}
},
/**
* One instance per container.
*/
SINGLETON {
@Override
<T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
final InternalFactory<? extends T> factory) {
return new InternalFactory<T>() {
T instance;
public T create(InternalContext context) {
synchronized (context.getContainer()) {
if (instance == null) {
instance = factory.create(context);
}
return instance;
}
}
@Override
public String toString() {
return factory.toString();
}
};
}
},
/**
* One instance per thread.
*
* <p><b>Note:</b> if a thread local object strongly references its {@link
* Container}, neither the {@code Container} nor the object will be
* eligible for garbage collection, i.e. memory leak.
*/
THREAD {
@Override
<T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
final InternalFactory<? extends T> factory) {
return new InternalFactory<T>() {
final ThreadLocal<T> threadLocal = new ThreadLocal<T>();
public T create(final InternalContext context) {
T t = threadLocal.get();
if (t == null) {
t = factory.create(context);
threadLocal.set(t);
}
return t;
}
@Override
public String toString() {
return factory.toString();
}
};
}
},
/*这里省略部分代码*/
容器中接纳和托管的不是对象本身,而是对象工厂,当我们需要容器提供一个对象的时候,容器是调用的对象工厂中的 create 方法来创建并返回对象的。而对于具体的对象创建方式,我们可以通过实现Factory接口,实现其create方法即可,这里的Factory实现类为LocatableFactory,其create方法为调用Container的重载方法 inject(Class cl) 创建并返回一个新对象,该对象已经接受过容器的依赖注入了。具体的 inject(Class cl)实现细节,我们留到后面介绍容器操作方法 inject 和 getInstance 的时候再详细说明。当然了,如果我们自定义了别的Factory实现类,我们在 create 方法中完全可以根据我们的需要实现任意的对象创建方法,比如: class.newInstance() 这种最基本的方式或者从 JSON串转换为一个JAVA对象,或者反序列化创建对象等等,这就是工厂方法模式的优点,创建对象的方式可以很灵活。
枚举Scope在其内部又创建了InternalFactpry的一个匿名内部类,在create方法中再次包装了factory以实现其单实例的功能,返回的就是又一次经过包装的InternalFactory。Scope.Thread也是类似,只不过它创建的对象声明周期为线程范围内,所以把他/她缓存在ThreadLocal 中。 而Scope.DEFAULT 则是不做处理 直接返回 factory,这样当调用create方法时候,每次都是创建一个新对象。
至此完整的实现了资源的加载。结合 之前给出的struts2-IOC容器的初始化时序图,我们可以清楚的看到,最后由 Configuration 调用 ContainerBuilder 的 create方法返回一个已经被初始化了的 IOC容器对象Container。