1. 当Servlet容器接收到一个请求后,将请求交给你在wed.xml文件中配置的过滤器FilterDispatcher。
FilterDispatcher类的处理流程:
1.1 FilterDispatcher类实现了StrutsStatics, Filter这二个接口。StrutsStatics类定义了Struts2的常量。在这里不详细介绍了。主要介绍Filter接口类,它核心有三个主要方法,doFilter、init和destroy。
1.1.1 init方法的使用
首先创建一个FilterConfig类
通过该查询是否已经存在一个日志文件,如果不存在则创建一个日志文件。(2.0没)
private void initLogging() {
String factoryName = filterConfig.getInitParameter("loggerFactory");
if (factoryName != null) {
try {
Class cls = ClassLoaderUtils.loadClass(factoryName, this.getClass());
LoggerFactory fac = (LoggerFactory) cls.newInstance();
LoggerFactory.setLoggerFactory(fac);
} catch (InstantiationException e) {
System.err.println("Unable to instantiate logger factory: " + factoryName + ", using default");
e.printStackTrace();
} catch (IllegalAccessException e) {
System.err.println("Unable to access logger factory: " + factoryName + ", using default");
e.printStackTrace();
} catch (ClassNotFoundException e) {
System.err.println("Unable to locate logger factory class: " + factoryName + ", using default");
e.printStackTrace();
}
}
log = LoggerFactory.getLogger(FilterDispatcher.class);
}
接着调用Dispatcher createDispatcher()方法,获取wed.xml文件中的配置信息,并通过一个MAP对象进行存储。
protected Dispatcher createDispatcher(FilterConfig filterConfig) {
Map<String, String> params = new HashMap<String, String>();
for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements();) {
String name = (String) e.nextElement();
String value = filterConfig.getInitParameter(name);
params.put(name, value);
}
return new Dispatcher(filterConfig.getServletContext(), params);
}
对象例子
<init-param>
<param-name>encoding</param-name>
<param-value>gb2312</param-value>
</init-param>
接着把获取到的相关参数传给Dispatcher类。这个类主要实现对配置文件信息的获取,根据配置信息,让不同的action的结果返回到不同的页面。
进入到Dispatcher类,首先调用其init()方法,获取配置信息。
○1首先实例化一个ConfigurationManager对象。
○2接着调用init_DefaultProperties()方法,这个方法中是将一个DefaultPropertiesProvider对象追加到ConfigurationManager对象内部的ConfigurationProvider队列中。 DefaultPropertiesProvider的register()方法可以载入org/apache/struts2/default.properties中定义的属性。
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);
}
ConfigurationManager类中的addConfigurationProvider()方法
public void addConfigurationProvider(ConfigurationProvider provider) {
if (!configurationProviders.contains(provider)) {
configurationProviders.add(provider);
}
}
init_DefaultProperties()方法
private void init_DefaultProperties() {
configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());
}
○3接着调用init_TraditionalXmlConfigurations()方法,实现载入FilterDispatcher的配置中所定义的config属性。 如果用户没有定义config属性,struts默认会载入DEFAULT_CONFIGURATION_PATHS这个值所代表的xml文件。它的值为"struts-default.xml,struts-plugin.xml,struts.xml"。也就是说框架默认会载入这三个项目xml文件。如果文件类型是XML格式,则按照xwork-x.x.dtd模板进行读取。如果,是Struts的配置文件,则按struts-2.X.dtd模板进行读取。
private void init_TraditionalXmlConfigurations() {
String configPaths = initParams.get("config");
if (configPaths == null) {
configPaths = DEFAULT_CONFIGURATION_PATHS;
}
String[] files = configPaths.split("\\s*[,]\\s*");
for (String file : files) {
if (file.endsWith(".xml")) {
if ("xwork.xml".equals(file)) {
configurationManager.addConfigurationProvider(
new XmlConfigurationProvider(file, false));
} else {
configurationManager.addConfigurationProvider(
new StrutsXmlConfigurationProvider(file, false, servletContext));
}
} else {
throw new IllegalArgumentException("Invalid configuration file name");
}
}
}
XmlConfigurationProvider类对文件读取的模式
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {
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 (nodeName.equals("bean")) {
String type = child.getAttribute("type");
String name = child.getAttribute("name");
String impl = child.getAttribute("class");
String onlyStatic = child.getAttribute("static");
String scopeStr = child.getAttribute("scope");
boolean optional = "true".equals(child.getAttribute("optional"));
Scope scope = Scope.SINGLETON;
if ("default".equals(scopeStr)) {
scope = Scope.DEFAULT;
} else if ("request".equals(scopeStr)) {
scope = Scope.REQUEST;
} else if ("session".equals(scopeStr)) {
scope = Scope.SESSION;
} else if ("singleton".equals(scopeStr)) {
scope = Scope.SINGLETON;
} else if ("thread".equals(scopeStr)) {
scope = Scope.THREAD;
}
if (!TextUtils.stringSet(name)) {
name = Container.DEFAULT_NAME;
}
try {
Class cimpl = ClassLoaderUtil.loadClass(impl,
getClass());
Class ctype = cimpl;
if (TextUtils.stringSet(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));
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
.factory(ctype, name,
new LocatableFactory(name,
ctype, cimpl, scope,
childNode), scope);
}
loadedBeans.put(ctype.getName() + name, child);
} catch (Throwable ex) {
if (!optional) {
throw new ConfigurationException(
"Unable to load bean: type:" + type
+ " class:" + impl, ex,
childNode);
} else {
LOG.debug("Unable to load optional class: "
+ ex);
}
}
} else if (nodeName.equals("constant")) {
String name = child.getAttribute("name");
String value = child.getAttribute("value");
props.setProperty(name, value, childNode);
}
}
}
}
}
StrutsXmlConfigurationProvider类继承于它,获取大至相同。获取那些对象后,把它们追加到ConfigurationManager对象内部的ConfigurationProvider队列中。
○4 接着调用init_LegacyStrutsProperties()方法,创建一个LegacyPropertiesConfigurationProvider类,并将它追加到ConfigurationManager对象内部的ConfigurationProvider队列中。LegacyPropertiesConfigurationProvider类载入struts.properties中的配置,这个文件中的配置可以覆盖default.properties中的。其子类是DefaultPropertiesProvider类。
private void init_LegacyStrutsProperties() {
configurationManager.addConfigurationProvider(
new LegacyPropertiesConfigurationProvider());
}
○5接着调用init_ZeroConfiguration()方法,这次处理的是FilterDispatcher的配置中所定义的actionPackages属性。该参数的值是一个以英文逗号(,)隔开的字符串,每个字符串都是一个包空间,Struts 2框架将扫描这些包空间下的Action类。实现的是零配置文件信息获取。它能够能根据web.xml中配置的actionPackages自动扫描所有Action类,并猜测其NameSpace. 再利用CodeBehind猜测Result指向的jsp,实现了struts.xml的零配置(其实也不是完全没有struts.xml,而是指struts.xml的内容不会随action的增加而膨胀)。
如果有特殊的结果指向(如redirect类型的结果),在Action处用@Result配置。
如有package级的配置(如使用非默认的Interceptor栈),仍在struts.xml中定义package,用@ParentPackage指定。
不过,目前ZeroConfig的Annotation较少,只有@Result、@ParentPackage,@NameSpace(java的package名不符合约定规则时使用),还有exception-Mapping之类的配置没有包含。
private void init_ZeroConfiguration() {
String packages = initParams.get("actionPackages");
if (packages != null) {
String[] names = packages.split("\\s*[,]\\s*");
// Initialize the classloader scanner with the configured packages
if (names.length > 0) {
ClasspathConfigurationProvider provider =
new ClasspathConfigurationProvider(names);
provider.setPageLocator(
new ServletContextPageLocator(servletContext));
configurationManager.addConfigurationProvider(provider);
}
}
}
○6接着调用init_CustomConfigurationProviders()方法,此方法处理的是FilterDispatcher的配置中所定义的configProviders属性。负责载入用户自定义的ConfigurationProvider。
private void init_CustomConfigurationProviders() {
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);
}
}
}
}
○7接着调用init_MethodConfigurationProvider()方法,但该方法已经被注释了。
○8接着调用init_FilterInitParameters()方法,此方法用来处理FilterDispatcher的配置中所定义的所有属性。
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 {
props.putAll(initParams);
}
});
}
○9接着调用init_AliasStandardObjects()方法,并将一个BeanSelectionProvider类追加到ConfigurationManager对象内部的ConfigurationProvider队列中。BeanSelectionProvider类主要实现加载org/apache/struts2/struts-messages。
private void init_AliasStandardObjects() {
configurationManager.addConfigurationProvider(new BeanSelectionProvider());
}
○10接着调用init_PreloadConfiguration()方法,构建调用上边几步添加到ConfigurationManager的getConfiguration()获取当前XWork配置对象。
private Container init_PreloadConfiguration() {
Configuration config = configurationManager.getConfiguration();
Container container = config.getContainer();
boolean reloadi18n = Boolean.valueOf(container.getInstance(
String.class, StrutsConstants.STRUTS_I18N_RELOAD));
LocalizedTextUtil.setReloadBundles(reloadi18n);
ObjectTypeDeterminer objectTypeDeterminer = container.getInstance(ObjectTypeDeterminer.class);
ObjectTypeDeterminerFactory.setInstance(objectTypeDeterminer);
return container;
}
configurationManager.getConfiguration()方法
public synchronized Configuration getConfiguration() {
if (configuration == null) {
setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName));
try {
configuration.reload(getConfigurationProviders());
} catch (ConfigurationException e) {
setConfiguration(null);
throw e;
}
} else {
conditionalReload();
}
return configuration;
}
○11接着调用init_CheckConfigurationReloading(container)方法,检查配置重新加载。(具体怎样不清楚)
private void init_CheckConfigurationReloading(Container container) {
FileManager.setReloadingConfigs("true".equals(container.getInstance(
String.class, StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD)));
}
○12接着调用init_CheckWebLogicWorkaround(Container container)方法,初始化weblogic相关配置。
private void init_CheckWebLogicWorkaround(Container container) {
// test whether param-access workaround needs to be enabled
if (servletContext != null && servletContext.getServerInfo() != null
&& servletContext.getServerInfo().indexOf("WebLogic") >= 0) {
LOG.info("WebLogic server detected. Enabling Struts parameter access work-around.");
paramsWorkaroundEnabled = true;
} else {
paramsWorkaroundEnabled = "true".equals(container.getInstance(String.class,
StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND));
}
synchronized(Dispatcher.class) {
if (dispatcherListeners.size() > 0) {
for (DispatcherListener l : dispatcherListeners) {
l.dispatcherInitialized(this);
}
}
}
}
接着用FilterConfig类获取wed.xml配置文件中的“packages”参数,并获取参数所有的JAVA包名的列表的值,并调用parse(packages)方法,将数它们的值一个一个的获取到一个List对象中。
protected String[] parse(String packages) {
if (packages == null) {
return null;
}
List<String> pathPrefixes = new ArrayList<String>();
StringTokenizer st = new StringTokenizer(packages, ", \n\t");
while (st.hasMoreTokens()) {
String pathPrefix = st.nextToken().replace('.', '/');
if (!pathPrefix.endsWith("/")) {
pathPrefix += "/";
}
pathPrefixes.add(pathPrefix);
}
return pathPrefixes.toArray(new String[pathPrefixes.size()]);
}
1.1.2 doFilter方法的解释,这方法实现了Action的调用。(最核心这个了)
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
ServletContext servletContext = getServletContext();
String timerKey = "FilterDispatcher_doFilter: ";
try {
UtilTimerStack.push(timerKey);
request = prepareDispatcherAndWrapRequest(request, response);
ActionMapping mapping;
try {
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;
}
if (mapping == null) {
// there is no action in this request, should we look for a static resource?
String resourcePath = RequestUtils.getServletPath(request);
if ("".equals(resourcePath) && null != request.getPathInfo()) {
resourcePath = request.getPathInfo();
}
if (serveStatic && resourcePath.startsWith("/struts")) {
findStaticResource(resourcePath, indAndCheckResources(resourcePath), request, response);
} else {
// this is a normal request, let it pass through
chain.doFilter(request, response);
}
// The framework did its job here
return;
}
dispatcher.serviceAction(request, response, servletContext, mapping);
} finally {
try {
ActionContextCleanUp.cleanUp(req);
} finally {
UtilTimerStack.pop(timerKey);
}
}
}
首先实例化HttpServletRequest、HttpServletResponse、ServletContext这些对象。
接着调用UtilTimerStack.push()方法,但是搞不明这是有什么用的。
接着调用prepareDispatcherAndWrapRequest()方法。
protected HttpServletRequest prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {
Dispatcher du = Dispatcher.getInstance();
if (du == null) {
Dispatcher.setInstance(dispatcher);
dispatcher.prepare(request, response);
} else {
dispatcher = du;
}
try {
request = dispatcher.wrapRequest(request, getServletContext());
} catch (IOException e) {
String message = "Could not wrap servlet request with MultipartRequestWrapper!";
LOG.error(message, e);
throw new ServletException(message, e);
}
return request;
}
首先调用Dispatcher.getInstance()静态方法。在该方法中调用ThreadLocal类的get()方法,获取当前线程所对应的线程局部变量。并通过它的返回,实例化一个Dispatcher对象。因此Struts2框架为每一个线程都提供了一个Dispatcher对象,所以在编写Action的时候不需要考虑多线程的问题了。
private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>();
public static Dispatcher getInstance() {
return instance.get();
}
接着判断du是否为空,如果是第一次访问FilterDispatcher,那么du应该为null,这时要调用Dispatcher的prepare()方法,在该方法中主要实现对编码方式的设置。
public void prepare(HttpServletRequest request, HttpServletResponse response) {
String encoding = null;
if (defaultEncoding != null) {
encoding = defaultEncoding;
}
Locale locale = null;
if (defaultLocale != null) {
locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
}
if (encoding != null) {
try {
request.setCharacterEncoding(encoding);
} catch (Exception e) {
LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);
}
}
if (locale != null) {
response.setLocale(locale);
}
if (paramsWorkaroundEnabled) {
request.getParameter("foo");
}
}
接着调用dispatcher.wrapRequest(request, getServletContext())方法,对request对象进行包装(只需进行一次)。判断Content-Type是否是multipart/form-data,如果是的话返回一个MultiPartRequestWrapper的对象处理文件上传,否则返回StrutsRequestWrapper的对象处理普通请求。
public HttpServletRequest wrapRequest(HttpServletRequest request,
ServletContext servletContext) throws IOException {
if (request instanceof StrutsRequestWrapper) {
return request;
}
String content_type = request.getContentType();
if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
MultiPartRequest multi = getContainer().getInstance(MultiPartRequest.class);
request = new MultiPartRequestWrapper(multi, request,
getSaveDir(servletContext));
} else {
request = new StrutsRequestWrapper(request);
}
return request;
}
○1MultiPartRequestWrapper类的解释(网上找的解释,觉得很全,所以就用了拿来主义了)
Struts2的MultiPartRequestWrapper来分离请求中的数据。(注意:向服务器请求时,数据是以流的形式向服务器提交,内容是一些有规则东东,我们平时在jsp中用request内置对象取parameter时,实际上是由tomcat的HttpServletRequestWrapper类分解好了的,无需我们再分解这些东西了。)
MultiPartRequestWrapper这个类是Struts2的类,并且继承了tomcat的HttpServletRequestWrapper类,也是我们将用来代替HttpServletRequest这个类的类,看名字也知道,是对多媒体请求的包装类。Struts2本身当然不会再造个轮子,来解析请求,而是交由Apache的commons-fileupload组件来解析了。
在MultiPartRequestWrapper的构造方法中,会调用MultiPartRequest(默认为JakartaMultiPartRequest类)的parse方法来解析请求。
在Struts2的JakartaMultiPartRequest类的parse方法中才会真正来调用commons-fileupload组 件的ServletFileUpload类对请求进行解析,至此,Struts2已经实现了将请求转交commons-fileupload组件对请求解 析的全过程。剩下的就是等commons-fileupload组件对请求解析完毕后,拿到分解后的数据,根据field名,依次将分解后的field名 和值放到params(HashMap类型)里,同时JakartaMultiPartRequest类重置了HttpServletRequest的好 多方法,比如熟知的getParameter、getParameterNames、getParameterValues,实际上都是从解析后得到的那 个params对象里拿数据,在这个过程,commons-fileupload组件也乖乖的把上传的文件分析好 了,JakartaMultiPartRequest也毫不客气的把分解后的文件一个一个的放到了files(HashMap类型)中,实际上此 时,commons-fileupload组件已经所有要上传的文件上传完了。至此,Struts2实现了对HttpServletRequest类的包 装,当回到MultiPartRequestWrapper类后,再取一下上述解析过程中发生的错误,然后把错误加到了自己的errors列表中了。同样我们会发现在MultiPartRequestWrapper类中,也把HttpServletRequest类的好多方法重载了,毕竟是个包装类嘛,实际上对于上传文件的请求,在Struts2后期的处理中用到的request都是MultiPartRequestWrapper类对象,比如我们调用getParameter时,直接调用的是MultiPartRequestWrapper的getParameter方法,间接调的是JakartaMultiPartRequest类对象的getParameter方法。(注:从这里,我们就可以看出,JakartaMultiPartRequest是完全设计成可以替换的类了。 )
接着ActionMapper.getMapping(), ActionMapper类是一个接口类,其具体实现是由DefaultActionMapper类实现的。以便确定这个请求是否有对应的action调用。
DefaultActionMapper类中的getMapping()方法
public ActionMapping getMapping(HttpServletRequest request,
ConfigurationManager configManager) {
ActionMapping mapping = new ActionMapping();
String uri = getUri(request);
uri = dropExtension(uri);
if (uri == null) {
return null;
}
parseNameAndNamespace(uri, mapping, configManager);
handleSpecialParameters(request, mapping);
if (mapping.getName() == null) {
return null;
}
if (allowDynamicMethodCalls) {
// handle "name!method" convention.
String name = mapping.getName();
int exclamation = name.lastIndexOf("!");
if (exclamation != -1) {
mapping.setName(name.substring(0, exclamation));
mapping.setMethod(name.substring(exclamation + 1));
}
}
return mapping;
}
首先创建一个ActionMapping对象(关于ActionMapping类,它内部封装了如下5个字段)
private String name;// Action名
private String namespace;// Action名称空间
private String method;// 执行方法
private Map params;// 可以通过set方法设置的参数
private Result result;// 返回的结果
这些参数在配置文件中都是可设置的,确定了ActionMapping类的各个字段的值,就可以对请求的Action进行调用了。
接着调getUri(request)方法,它主要实现获取请求的URI。这个方法首先判断请求是否来自于一个jsp的include,如果是,那么请求 的"javax.servlet.include.servlet_path"属性可以获得include的页面uri,否则通过一般的方法获得请求的 uri,最后返回去掉ContextPath的请求路径,比如http://127.0.0.1:8087/test/jsp /index.jsp?param=1,返回的为/jsp/index.jsp。去掉了ContextPath和查询字符串等。
String getUri(HttpServletRequest request) {
String uri = (String)
request .getAttribute("javax.servlet.include.servlet_path");
if (uri != null) {
return uri;
}
uri = RequestUtils.getServletPath(request);
if (uri != null && !"".equals(uri)) {
return uri;
}
uri = request.getRequestURI();
return uri.substring(request.getContextPath().length());
}
接着调用dropExtension(uri)方法,该方法负责去掉Action的"扩展名"(默认为"action")
String dropExtension(String name) {
if (extensions == null) {
return name;
}
Iterator it = extensions.iterator();
while (it.hasNext()) {
String extension = "." + (String) it.next();
if (name.endsWith(extension)) {
name = name.substring(0, name.length() - extension.length());
return name;
}
}
return null;
}
接着调用parseNameAndNamespace()方法, 此方法用于解析Action的名称和命名空间,并赋给ActionMapping对象。
void parseNameAndNamespace(String uri, ActionMapping mapping,
ConfigurationManager configManager) {
String namespace, name;
/* 例如 http://127.0.0.1:8087/teststruts/namespace/name.action?param=1 */
/* dropExtension()后,获得uri为/namespace/name */
int lastSlash = uri.lastIndexOf("/");
if (lastSlash == -1) {
namespace = "";
name = uri;
} else if (lastSlash == 0) {
namespace = "/";
name = uri.substring(lastSlash + 1);
} else if (alwaysSelectFullNamespace) {
// alwaysSelectFullNamespace默认为false,代表是否将最后一个"/"前的字符全作为名称空间。
namespace = uri.substring(0, lastSlash);// 获得字符串 namespace
name = uri.substring(lastSlash + 1);// 获得字符串 name
} else {
/* 例如 http://127.0.0.1:8087/teststruts/namespace1/namespace2/
actionname.action?param=1 */
/* dropExtension()后,获得uri为/namespace1/namespace2/actionname */
Configuration config = configManager.getConfiguration();
String prefix = uri.substring(0, lastSlash);
// 获得 /namespace1/namespace2
namespace = "";
/*如果配置文件中有一个包的namespace是 /namespace1/namespace2,
那么namespace为/namespace1/namespace2,name为actionname */
/* 如果配置文件中有一个包的namespace是 /namespace1,
那么namespace为/namespace1,name为/namespace2/actionname*/
for (Iterator i = config.getPackageConfigs().values().iterator(); i.hasNext();) {
String ns = ((PackageConfig) i.next()).getNamespace();
if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {
if (ns.length() > namespace.length()) {
namespace = ns;
}
}
}
name = uri.substring(namespace.length() + 1);
}
if (!allowSlashesInActionNames && name != null) {
//allowSlashesInActionNames代表是否允许"/"出现在Action的名称中,默认为false
int pos = name.lastIndexOf('/');
if (pos > -1 && pos < name.length() - 1) {
name = name.substring(pos + 1);
}
}
// 以 name = /namespace2/actionname 为例,经过这个if块后,name = actionname
mapping.setNamespace(namespace);
mapping.setName(name);
}
接着调用handleSpecialParameters()方法, 该方法将请求参数中的重复项去掉.(但该方法存在问题,具体原因见“由IE浏览器引发的Struts2的Bug(submit无法传至服务器).doc”)
public void handleSpecialParameters(HttpServletRequest request,
ActionMapping mapping) {
// handle special parameter prefixes.
Set<String> uniqueParameters = new HashSet<String>();
Map parameterMap = request.getParameterMap();
for (Iterator iterator = parameterMap.keySet().iterator(); iterator
.hasNext();) {
String key = (String) iterator.next();
// Strip off the image button location info, if found
if (key.endsWith(".x") || key.endsWith(".y")) {
key = key.substring(0, key.length() - 2);
}
// Ensure a parameter doesn't get processed twice
if (!uniqueParameters.contains(key)) {
ParameterAction parameterAction = (ParameterAction) prefixTrie
.get(key);
if (parameterAction != null) {
parameterAction.execute(key, mapping);
uniqueParameters.add(key);
break;
}
}
}
}
接着判断Action的name有没有解析出来,如果没,直接返回NULL。
if (mapping.getName() == null) {
returnnull;
}
最后处理形如testAction!method格式的请求路径。
if (allowDynamicMethodCalls) {
// handle "name!method" convention.
String name = mapping.getName();
int exclamation = name.lastIndexOf("!");
//!是Action名称和方法名的分隔符
if (exclamation != -1) {
mapping.setName(name.substring(0, exclamation));
//提取左边为name
mapping.setMethod(name.substring(exclamation + 1));
//提取右边的method
}
}
ActionMapper.getMapping()流程图:
从代码中看出,getMapping()方法返回ActionMapping类型的对象,该对象包含三个参数:Action的name、namespace和要调用的方法method。
接着,判断如果getMapping()方法返回ActionMapping对象为null,则FilterDispatcher认为用户请求不是Action, 自然另当别论,FilterDispatcher会做一件非常有意思的事:如果请求以/struts开头,会自动查找在web.xml文件中配置的 packages初始化参数,就像下面这样(注意粗斜体部分):
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.FilterDispatcher
</filter-class>
<init-param>
<param-name>packages</param-name>
<param-value>com.lizanhong.action</param-value>
</init-param>
</filter>
FilterDispatcher会将com.lizanhong.action包下的文件当作静态资源处理,(但是Struts2.0和Struts2.1对其处理不同)Struts2.0只会显示出错信息,而Struts2.1接在页面上显示文件内容,不过会忽略 扩展名为class的文件。比如在com.lizanhong.action包下有一个aaa.txt的文本文件,其内容为“中华人民共和国”,访问 http://localhost:8081/Struts2Demo/struts/aaa.txt时会有如下图的输出
FilterDispatcher.findStaticResource()方法,就是负责查找静态资源的方法。
接着,如getMapping()方法返回ActionMapping对象不为null,则认为正在请求某个Action,并且运行dispatcher.serviceAction(request, response, servletContext, mapping)方法,该方法是ACTION处理的核心。在Dispatcher.serviceAction()方法中,先加载Struts2的配置文件,如果没有人为配置,则默认加载struts- default.xml、struts-plugin.xml和struts.xml,并且将配置信息保存在形如 com.opensymphony.xwork2.config.entities.XxxxConfig的类中。
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,ActionMapping mapping) throws ServletException {
Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
ValueStack stack = (ValueStack) request .getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
if (stack != null) {
extraContext.put(ActionContext.VALUE_STACK, ValueStackFactory.getFactory().createValueStack(stack));
}
String timerKey = "Handling request from Dispatcher";
//"Handling request from Dispatcher"表示处理请求调度
try {
UtilTimerStack.push(timerKey);
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
Configuration config = configurationManager.getConfiguration();
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, extraContext, true, false);
proxy.setMethod(method);
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
if (stack != null) {
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
}
} catch (ConfigurationException e) {
LOG.error("Could not find action or result", e);
sendError(request, response, context,HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
throw new ServletException(e);
} finally {
UtilTimerStack.pop(timerKey);
}
}
首先调用createContextMap()方法,这个方法首先创建了一个名称为extraContext的Map对象。它保存了request,session,application,mapping的信息,这些信息以后可以统一在此对象中查找。
Public Map<String,Object> createContextMap(HttpServletRequest request,
HttpServletResponse response,ActionMapping mapping, ServletContext context) {
Map requestMap = new RequestMap(request);// 封装了请求对象
Map params = null;// 封装了http参数
if (mapping != null) {
params = mapping.getParams();//从ActionMapping中获取Action的参数Map
}
Map requestParams = new HashMap(request.getParameterMap());
if (params != null) {
params.putAll(requestParams);// 并将请求中的参数也放入Map中
} else {
params = requestParams;
}
Map session = new SessionMap(request);// 封装了session
Map application = new ApplicationMap(context);// 封装了ServletContext
/*将各个Map放入extraContext中 */
Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
return extraContext;
}
接着判断request中是否已经有了一个ValueStack对象,将其保存下来,留待以后恢复,并把它进行一些封装后也存入extraContext中。
接下来是一些准备工作,如,获取了namespace,name,method等。
接着构建一个ActionProxy对象,它负责对真实的Action进行调用,并可以在调用Action前后调用拦截器(Interceptor),其默认实现StrutsActionProxyFactory类中的createActionProxy()方法。
public ActionProxy createActionProxy(String namespace, String actionName,
Map extraContext,boolean executeResult, boolean cleanupContext)throws Exception {
ActionProxy proxy = new StrutsActionProxy(namespace, actionName, extraContext, executeResult, cleanupContext);
container.inject(proxy);
proxy.prepare();
return proxy;
}
由上述的源代码可见,方法返回了一个StrutsActionProxy对象作为ActionProxy的默认实现。
其中proxy.prepare()方法,是用DefaultActionProxy类中的prepare()默认实现。
public void prepare() throws Exception {
String profileKey = "create DefaultActionProxy: ";
try {
UtilTimerStack.push(profileKey);
config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);
if (config == null && unknownHandler != null) {
config = unknownHandler.handleUnknownAction(namespace, actionName);
}
if (config == null) {
String message;
if ((namespace != null) && (namespace.trim().length() > 0)) {
message = calizedTextUtil.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);
}
invocation = new DefaultActionInvocation(objectFactory, unknownHandler, this, extraContext, true, actionEventListener);
resolveMethod();
} finally {
UtilTimerStack.pop(profileKey);
}
}
这里边创建了一个DefaultActionInvocation对象作为ActionInvocation对象的默认实现。
接着调用resolveMethod()方法
private void resolveMethod() {
if (!TextUtils.stringSet(this.method)) {
this.method = config.getMethodName();
if (!TextUtils.stringSet(this.method)) {
this.method = "execute";
}
}
}
这个方法实现了Action执行方法的设定,如果config中配置有方法名,那么就将这个方法名作为执行方法名,否则就用默认的execute。
接着运行proxy.setMethod(method)语句,这里主要为了设置Action调用中要执行的方法.如果没有方法被指定,将会由Action的配置来提供.
接着运行 request.setAttribute()方法,把ValueStack对象放在Request对象中,以便通过Request对象访问ValueStack中的对象.
接着判断ActionMapping.getResult()是否为空,如果不为空,则获取相关Result对象.
接着执行result.execute(proxy.getInvocation())方法.在proxy.getInvocation()方法的默认实现是DefaultActionProxy类的getInvocation()方法. getInvocation()方法获取一个DefaultActionInvocation对象, DefaultActionInvocation对象在定义了invoke()方法,该方法实现了截拦器的递归调用和执行Action的执行方法(如execute()方法).
如果不为空, 执行proxy.execute()方法. ActionProxy类是通过DefaultActionProxy类来具体实现的.
public String execute() throws Exception {
ActionContext nestedContext = ActionContext.getContext();
ActionContext.setContext(invocation.getInvocationContext());
String retCode = null;
String profileKey = "execute: ";
try {
UtilTimerStack.push(profileKey);
retCode = invocation.invoke();
} finally {
if (cleanupContext) {
ActionContext.setContext(nestedContext);
}
UtilTimerStack.pop(profileKey);
}
return retCode;
}
在其中调用了ActionInvocation类的invoke()方法,而其具体实现是由DefaultActionInvocation类的invoke()方法实现的. 该方法实现了截拦器的递归调用和执行Action的execute()方法.
public String invoke() throws Exception {
String profileKey = "invoke: ";
try {
UtilTimerStack.push(profileKey);
if (executed) {
throw new IllegalStateException("Action has already executed");
}
if (interceptors.hasNext()) {
//从截拦器集合中取出当前的截拦器
final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
UtilTimerStack.profile("interceptor: " + interceptor.getName(),new UtilTimerStack.ProfilingBlock<String>() {
public String doProfiling() throws Exception {
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
return null;
}
});
} else {
resultCode = invokeActionOnly();
}
if (!executed) {
if (preResultListeners != null) {
for (Iterator iterator = preResultListeners.iterator(); iterator.hasNext();) {
PreResultListener listener = (PreResultListener) iterator.next();
String _profileKey = "preResultListener: ";
try {
UtilTimerStack.push(_profileKey);
listener.beforeResult(this, resultCode);
} finally {
UtilTimerStack.pop(_profileKey);
}
}
}
if (proxy.getExecuteResult()) {
executeResult();
}
executed = true;
}
return resultCode;
} finally {
UtilTimerStack.pop(profileKey);
}
}
}
在上述代码实现递归调用截拦器是由Interceptor 类来实现的.
publicinterface Interceptor extends Serializable {
void destroy();
void init();
String intercept(ActionInvocation invocation) throws Exception;
}
所有的截拦器必须实现intercept方法,而该方法的参数恰恰又是ActionInvocation,所以,如果在intercept方法中调用 invocation.invoke(),invoke()方法中蓝色代码会再次执行,从Action的Intercepor列表中找到下一个截拦器,依此递归.
调用流程如下:
如果截拦器全部执行完毕,则调用invokeActionOnly()方法执行Action,invokeActionOnly()方法基本没做什么工作,只调用了invokeAction()方法。
public String invokeActionOnly() throws Exception {
return invokeAction(getAction(), proxy.getConfig());
}
DefaultActionInvocation.invokeAction()方法实现Action的调用.
protected String invokeAction(Object action, ActionConfig actionConfig)
throws Exception {
String methodName = proxy.getMethod();
if (LOG.isDebugEnabled()) {
LOG.debug("Executing action method = "
+ actionConfig.getMethodName());
}
String timerKey = "invokeAction: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);
Method method;
try {
method = getAction().getClass().getMethod(methodName,new Class[0]);
} catch (NoSuchMethodException e) {
try {
String altMethodName = "do"+ methodName.substring(0, 1).toUpperCase()+ methodName.substring(1);
method = getAction().getClass().getMethod(altMethodName,new Class[0]);
} catch (NoSuchMethodException e1) {
throw e;
}
}
Object methodResult = method.invoke(action, new Object[0]);
if (methodResult instanceof Result) {
this.result = (Result) methodResult;
return null;
} else {
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);
}
}
由这句Object methodResult = method.invoke(action, new Object[0]);可以看出,最后通过反射实现了Action的执行方法的调用。
接着返回invoke()方法,判断executed是否为false.如果是则调用了在PreResultListener中的定义的一些执行Result前的操作.
接着根据配置文件中的设置执行Result.其执行方法为executeResult()方法.
private void executeResult() throws Exception {
result = createResult();
String timerKey = "executeResult: " + getResultCode();
try {
UtilTimerStack.push(timerKey);
if (result != null) {
result.execute(this);
} else if (resultCode != null && !Action.NONE.equals(resultCode)) {
throw new ConfigurationException(
"No result defined for action "
+ getAction().getClass().getName()
+ " and result " + getResultCode(), proxy
.getConfig());
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("No result returned for action "
+ getAction().getClass().getName() + " at "
+ proxy.getConfig().getLocation());
}
}
} finally {
UtilTimerStack.pop(timerKey);
}
}
然后,返回到dispatcher.serviceAction()方法,完成调用.