struts2请求过程源码分析

Struts2是Struts社区和WebWork社区的共同成果,我们甚至可以说,Struts2是WebWork的升级版,他采用的正是WebWork的核心,所以,Struts2并不是一个不成熟的产品,相反,构建在WebWork基础之上的Struts2是一个运行稳定、性能优异、设计成熟的WEB框架。

  我这里的struts2源码是从官网下载的一个最新的struts-2.3.15.1-src.zip,将其解压即可。里面的目录页文件非常的多,我们只需要定位到struts-2.3.15.1\src\core\src\main\java\org\apache\struts2查看源文件。目录结构如下图

  Struts2框架的正常运行,除了占核心地位的xwork的支持以外,Struts2本身也提供了许多类,这些类被分门别类组织到不同的包中。从源代码中发现,基本上每一个Struts2类都访问了WebWork提供的功能,从而也可以看出Struts2与WebWork千丝万缕的联系。但无论如何,Struts2的核心功能比如将请求委托给哪个Action处理都是由xwork完成的,Struts2只是在WebWork的基础上做了适当的简化、加强和封装,并少量保留Struts1.x中的习惯。

以下是包说明:

struts2 架构图如下图所示:

依照上图,我们可以看出一个请求在struts的处理大概有如下步骤:

  1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求;

  2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin);

  3、接着StrutsPrepareAndExecuteFilter被调用,StrutsPrepareAndExecuteFilter询问ActionMapper来决定这个请求是否需要调用某个Action;

  4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy;

  5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类;

  6、ActionProxy创建一个ActionInvocation的实例。

  7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。

  8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper。

strut2源码分析:

  首先我们使用struts2框架都会在web.xml中注册和映射struts2,配置内容如下:

<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>

注:在早期的struts2中,都是使用FilterDispathcer,从Struts 2.1.3开始,它已不推荐使用。如果你使用的Struts的版本 >= 2.1.3,推荐升级到新的Filter,StrutsPrepareAndExecuteFilter。在此研究的是StrutsPrepareAndExecuteFilter。

  StrutsPrepareAndExecuteFilter中的方法:

web容器一启动,就会初始化核心过滤器StrutsPrepareAndExecuteFilter,并执行初始化方法,初始化方法如下:

 1 public void init(FilterConfig filterConfig) throws ServletException { 2         InitOperations init = new InitOperations(); 3         Dispatcher dispatcher = null; 4         try { 5             //封装filterConfig,其中有个主要方法getInitParameterNames将参数名字以String格式存储在List中 6             FilterHostConfig config = new FilterHostConfig(filterConfig); 7             //初始化struts内部日志 8             init.initLogging(config); 9             //创建dispatcher ,并初始化10             dispatcher = init.initDispatcher(config);11             init.initStaticContentLoader(config, dispatcher);12             //初始化类属性:prepare 、execute13             prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);14             execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);15             this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);16             //回调空的postInit方法17             postInit(dispatcher, filterConfig);18         } finally {19             if (dispatcher != null) {20                 dispatcher.cleanUpAfterInit();21             }22             init.cleanup();23         }24     }

关于封装filterConfig,首先看下FilterHostConfig ,源码如下:

 1 public class FilterHostConfig implements HostConfig { 2  3     private FilterConfig config; 4     //构造方法 5     public FilterHostConfig(FilterConfig config) { 6         this.config = config; 7     } 8     //根据init-param配置的param-name获取param-value的值   9     public String getInitParameter(String key) {10         return config.getInitParameter(key);11     }12     //返回初始化参数名的迭代器 13     public Iterator<String> getInitParameterNames() {14         return MakeIterator.convert(config.getInitParameterNames());15     }16     //返回Servlet上下文17     public ServletContext getServletContext() {18         return config.getServletContext();19     }20 }

  只有短短的几行代码,getInitParameterNames是这个类的核心,将Filter初始化参数名称有枚举类型转为Iterator。此类的主要作为是对filterConfig 封装。

  接下来,看下StrutsPrepareAndExecuteFilter中init方法中dispatcher = init.initDispatcher(config);这是初始化dispatcher的,是通过init对象的initDispatcher方法来初始化的,init是InitOperations类的对象,我们看看InitOperations中initDispatcher方法:

1  public Dispatcher initDispatcher( HostConfig filterConfig ) {2         Dispatcher dispatcher = createDispatcher(filterConfig);3         dispatcher.init();4         return dispatcher;5     }

  创建Dispatcher,会读取 filterConfig 中的配置信息,将配置信息解析出来,封装成为一个Map,然后根绝servlet上下文和参数Map构造Dispatcher :

 1 private Dispatcher createDispatcher( HostConfig filterConfig ) { 2         //存放参数的Map 3         Map<String, String> params = new HashMap<String, String>(); 4         //将参数存放到Map 5         for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) { 6             String name = (String) e.next(); 7             String value = filterConfig.getInitParameter(name); 8             params.put(name, value); 9         }10         //根据servlet上下文和参数Map构造Dispatcher 11         return new Dispatcher(filterConfig.getServletContext(), params);12     }

  这样dispatcher对象创建完成,接着就是dispatcher对象的初始化,打开Dispatcher类,看到它的init方法如下:

 1 public void init() { 2  3         if (configurationManager == null) { 4             configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); 5         } 6  7         try { 8             init_FileManager(); 9             //加载org/apache/struts2/default.properties10             init_DefaultProperties(); // [1]11             //加载struts-default.xml,struts-plugin.xml,struts.xml12             init_TraditionalXmlConfigurations(); // [2]13             init_LegacyStrutsProperties(); // [3]14             //用户自己实现的ConfigurationProviders类 15             init_CustomConfigurationProviders(); // [5]16             //Filter的初始化参数 17             init_FilterInitParameters() ; // [6]18             init_AliasStandardObjects() ; // [7]19 20             Container container = init_PreloadConfiguration();21             container.inject(this);22             init_CheckWebLogicWorkaround(container);23 24             if (!dispatcherListeners.isEmpty()) {25                 for (DispatcherListener l : dispatcherListeners) {26                     l.dispatcherInitialized(this);27                 }28             }29         } catch (Exception ex) {30             if (LOG.isErrorEnabled())31                 LOG.error("Dispatcher initialization failed", ex);32             throw new StrutsException(ex);33         }34     }

  这里主要是加载一些配置文件的,将按照顺序逐一加载:default.properties,struts-default.xml,struts-plugin.xml,struts.xml,……关于文件是如何加载的,大家可以自己取看源文件,主要是由xwork核心类加载的,代码在xwork-core\src\main\java\com\opensymphony\xwork2\config\providers包里面。

  现在,我们回到StrutsPrepareAndExecuteFilter类中,刚才我们分析了StrutsPrepareAndExecuteFilter类的init方法,该方法在web容器一启动就会调用的,当用户访问某个action的时候,首先调用核心过滤器StrutsPrepareAndExecuteFilter的doFilter方法,该方法内容如下:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

HttpServletResponse response = (HttpServletResponse) res;

try {

//设置编码和国际化

prepare.setEncodingAndLocale(request, response);

//创建action上下文

prepare.createActionContext(request, response);

prepare.assignDispatcherToThread();

if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {

chain.doFilter(request, response);

} else {

request = prepare.wrapRequest(request);

ActionMapping mapping = prepare.findActionMapping(request, response, true);

//如果mapping为空,则认为不是调用action,会调用下一个过滤器链,直到获取到mapping才调用action

if (mapping == null) {

boolean handled = execute.executeStaticResourceRequest(request, response);

if (!handled) {

chain.doFilter(request, response);

}

} else {

//执行action

execute.executeAction(request, response, mapping);

}

}

} finally {

prepare.cleanupRequest(request);

}

}

下面对doFilter方法中的重点部分一一讲解:

(1)prepare.setEncodingAndLocale(request, response);

  第8行是调用prepare对象的setEncodingAndLocale方法,prepare是PrepareOperations类的对象,PrepareOperations类是用来做请求准备工作的。我们看下PrepareOperations类中的setEncodingAndLocale方法:

public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {2         dispatcher.prepare(request, response);3     }

在这方法里面我们可以看到它只是调用了dispatcher的prepare方法而已,下面我们看看dispatcher的prepare方法:

public void prepare(HttpServletRequest request, HttpServletResponse response) {

String encoding = null;

if (defaultEncoding != null) {

encoding = defaultEncoding;

}

// check for Ajax request to use UTF-8 encoding strictly http://www.w3.org/TR/XMLHttpRequest/#the-send-method

if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {

encoding = "UTF-8";

}

Locale locale = null;

if (defaultLocale != null) {

locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());

}

if (encoding != null) {

applyEncoding(request, encoding);

}

if (locale != null) {

response.setLocale(locale);

}

if (paramsWorkaroundEnabled) {

request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request

}

}

我们可以看到该方法只是简单的设置了encoding 和locale ,做的只是一些辅助的工作。

(2)prepare.createActionContext(request, response)

  我们回到StrutsPrepareAndExecuteFilter的doFilter方法,看到第10行代码:prepare.createActionContext(request, response);这是action上下文的创建,ActionContext是一个容器,这个容易主要存储request、session、application、parameters等相关信 息.ActionContext是一个线程的本地变量,这意味着不同的action之间不会共享ActionContext,所以也不用考虑线程安全问 题。其实质是一个Map,key是标示request、session、……的字符串,值是其对应的对象,我们可以看到com.opensymphony.xwork2.ActionContext类中时如下定义的:

1 static ThreadLocal<ActionContext> actionContext = new ThreadLocal<ActionContext>();

我们看下PrepareOperations类的createActionContext方法:

/**

* Creates the action context and initializes the thread local

*/

public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {

ActionContext ctx;

Integer counter = 1;

Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);

if (oldCounter != null) {

counter = oldCounter + 1;

}

//此处是从ThreadLocal中获取此ActionContext变量

ActionContext oldContext = ActionContext.getContext();

if (oldContext != null) {

// detected existing context, so we are probably in a forward

ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));

} else {

ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();

stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));

//stack.getContext()返回的是一个Map<String,Object>,根据此Map构造一个ActionContext

ctx = new ActionContext(stack.getContext());

}

request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);

//将ActionContext存到ThreadLocal

ActionContext.setContext(ctx);

return ctx;

}

上面第18行代码中dispatcher.createContextMap,如何封装相关参数:

public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,

ActionMapping mapping, ServletContext context) {

// request map wrapping the http request objects

Map requestMap = new RequestMap(request);

// parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately

Map params = new HashMap(request.getParameterMap());

// session map wrapping the http session

Map session = new SessionMap(request);

// application map wrapping the ServletContext

Map application = new ApplicationMap(context);

//requestMap、params、session等Map封装成为一个上下文Map

Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);

if (mapping != null) {

extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);

}

return extraContext;

}

(3)request = prepare.wrapRequest(request)

  我们再次回到StrutsPrepareAndExecuteFilter的doFilter方法中,看到第15行:request = prepare.wrapRequest(request);这一句是对request进行包装的,我们看下prepare的wrapRequest方法:

 1 public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException { 2         HttpServletRequest request = oldRequest; 3         try { 4             // Wrap request first, just in case it is multipart/form-data 5             // parameters might not be accessible through before encoding (ww-1278) 6             request = dispatcher.wrapRequest(request, servletContext); 7         } catch (IOException e) { 8             throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e); 9         }10         return request;11     }

由第6行我们可以看到它里面调用的是dispatcher的wrapRequest方法,并且将servletContext对象也传进去了,我们看下dispatcher的wrapRequest:

public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {

// don‘t wrap more than once

if (request instanceof StrutsRequestWrapper) {

return request;

}

String content_type = request.getContentType();

//如果content_type是multipart/form-data类型,则将request包装成MultiPartRequestWrapper对象,否则包装成StrutsRequestWrapper对象

if (content_type != null && content_type.contains("multipart/form-data")) {

MultiPartRequest mpr = getMultiPartRequest();

LocaleProvider provider = getContainer().getInstance(LocaleProvider.class);

request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext), provider);

} else {

request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);

}

return request;

}

此次包装根据请求内容的类型不同,返回不同的对象,如果为multipart/form-data类型,则返回MultiPartRequestWrapper类型的对象,该对象服务于文件上传,否则返回StrutsRequestWrapper类型的对象,MultiPartRequestWrapper是StrutsRequestWrapper的子类,而这两个类都是HttpServletRequest接口的实现。

(4)ActionMapping mapping = prepare.findActionMapping(request, response, true)

  包装request后,通过ActionMapper的getMapping()方法得到请求的Action,Action的配置信息存储在ActionMapping对象中,如StrutsPrepareAndExecuteFilter的doFilter方法中第16行:ActionMapping mapping = prepare.findActionMapping(request, response, true);我们找到prepare对象的findActionMapping方法:

public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {

//首先从request对象中取mapping对象,看是否存在

ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);

//不存在就创建一个

if (mapping == null || forceLookup) {

try {

//首先创建ActionMapper对象,通过ActionMapper对象创建mapping对象

mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());

if (mapping != null) {

request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);

}

} catch (Exception ex) {

dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);

}

}

return mapping;

}

下面是ActionMapper接口的实现类DefaultActionMapper的getMapping()方法的源代码:

public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {

ActionMapping mapping = new ActionMapping();

//获得请求的uri,即请求路径URL中工程名以后的部分,如/userAction.action

String uri = getUri(request);

//修正url的带;jsessionid 时找不到的bug

int indexOfSemicolon = uri.indexOf(";");

uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;

//删除扩展名,如.action或者.do

uri = dropExtension(uri, mapping);

if (uri == null) {

return null;

}

//从uri中分离得到请求的action名、命名空间。

parseNameAndNamespace(uri, mapping, configManager);

//处理特殊的请求参数

handleSpecialParameters(request, mapping);

//如果允许动态方法调用,即形如/userAction!getAll.action的请求,分离action名和方法名

return parseActionName(mapping);

}

下面对getMapping方法中的重要部分一一讲解:

  ①:parseNameAndNamespace(uri, mapping, configManager)

  我们主要看下第14行的parseNameAndNamespace(uri, mapping, configManager);这个方法的主要作用是分离出action名和命名空间:

 1 protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) { 2         String namespace, name; 3         int lastSlash = uri.lastIndexOf("/"); //最后的斜杆的位置 4         if (lastSlash == -1) { 5             namespace = ""; 6             name = uri; 7         } else if (lastSlash == 0) { 8             // ww-1046, assume it is the root namespace, it will fallback to 9             // default10             // namespace anyway if not found in root namespace.11             namespace = "/";12             name = uri.substring(lastSlash + 1);13         //允许采用完整的命名空间,即设置命名空间是否必须进行精确匹配14         } else if (alwaysSelectFullNamespace) {15             // Simply select the namespace as everything before the last slash16             namespace = uri.substring(0, lastSlash);17             name = uri.substring(lastSlash + 1);18         } else {19             // Try to find the namespace in those defined, defaulting to ""20             Configuration config = configManager.getConfiguration();21             String prefix = uri.substring(0, lastSlash); //临时的命名空间,将会用来进行匹配22             namespace = "";//将命名空间暂时设为""23             boolean rootAvailable = false;//rootAvailable作用是判断配置文件中是否配置了命名空间"/"24             // Find the longest matching namespace, defaulting to the default25             for (Object cfg : config.getPackageConfigs().values()) { //循环遍历配置文件中的package标签26                 String ns = ((PackageConfig) cfg).getNamespace();    //获取每个package标签的namespace属性27                 //进行匹配28                 if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == ‘/‘)) {29                     if (ns.length() > namespace.length()) {30                         namespace = ns;31                     }32                 }33                 if ("/".equals(ns)) {34                     rootAvailable = true;35                 }36             }37 38             name = uri.substring(namespace.length() + 1);39 40             // Still none found, use root namespace if found41             if (rootAvailable && "".equals(namespace)) {42                 namespace = "/";43             }44         }45 46         if (!allowSlashesInActionNames) {47             int pos = name.lastIndexOf(‘/‘);48             if (pos > -1 && pos < name.length() - 1) {49                 name = name.substring(pos + 1);50             }51         }52         //将分离后的acion名和命名空间保存到mapping对象53         mapping.setNamespace(namespace);54         mapping.setName(cleanupActionName(name));55     }

  看到上面代码的第14行,参数alwaysSelectFullNamespace我们可以通过名字就能大概猜出来"允许采用完整的命名空间",即设置命名空间是否必须进行精确匹配,true必须,false可以模糊匹配,默认是false。进行精确匹配时要求请求url中的命名空间必须与配置文件中配置的某个命名空间必须相同,如果没有找到相同的则匹配失败。这个参数可通过struts2的"struts.mapper.alwaysSelectFullNamespace"常量配置,如:<constant name="struts.mapper.alwaysSelectFullNamespace" value="true" />。当alwaysSelectFullNamespace为true时,将uri以lastSlash为分割,左边的为namespace,右边的为name。如:http://localhost:8080/myproject/home/actionName!method.action,此时uri为/home/actionName!method.action(不过前面把后缀名去掉了,变成/home/actionName!method),lastSlash的,当前值是5,这样namespace为"/home", name为actionName!method。

  我们再看到上面代码第18行到第44行:当上面的所有条件都不满足时,其中包括alwaysSelectFullNamespace 为false(命名空间进行模糊匹配),将由此部分处理,进行模糊匹配。第1句,通过configManager.getConfiguration()从配置管理器中获得配置对象Configuration,Configuration中存放着struts2的所有配置,形式是将xml文档的相应元素封装为java bean,如<package>元素被封装到PackageConfig类中,这个一会儿会用到。第2句按lastSlash将uri截取出prefix,这是一个临时的命名空间,之后将会拿prefix进行模糊匹配。第3句namespace = "",将命名空间暂时设为""。第4句创建并设置rootAvailable,rootAvailable作用是判断配置文件中是否配置了命名空间"/",true为配置了,false未配置,下面for语句将会遍历我们配置的所有包(<package>),同时设置rootAvailable。第5句for,通过config.getPackageConfigs()获得所有已经配置的包,然后遍历。String ns = ((PackageConfig) cfg).getNamespace()获得当前包的命名空间ns,之后的if句是进行模糊匹配的核心,我摘出来单独说,如下:

1 if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == ‘/‘)) {2                     if (ns.length() > namespace.length()) {3                         namespace = ns;4                     }5                 }

  ns != null && prefix.startsWith(ns)这部分判断当ns不等于空并且ns是prefix的前缀。prefix.length() == ns.length()当二者长度相等时,结合前面部分就是ns是prefix的前缀并且二者长度相等,最终结论就是ns和prefix相等。如果前面的条件不成立,则说明prefix的长度大于ns。prefix.charAt(ns.length()) == ‘/‘)意思是prefix中与ns不相等的字符中的第一个字符必须是"/",也就是说,在命名空间采用斜杠分级的形式中,ns必须是prefix的某一子集,如:/common/home 是用户配置的命名空间,则在http的请求url中,/common/home/index1、/common/home/index2、/common/home/index/aaa 都是正确的,都可以成功的匹配到/common/home,而/common/homeaa、/common/homea/aaa都是错误的。接着if (ns.length() > namespace.length()) 句,目的是找出字符长度最长的。因为命名空间采用的是分级的,则长度越长所表示的越精确,如/common/home/index比/common/home精确。之后将namespace = ns。

  我们接着往下看if ("/".equals(ns)) 当我们配置了"/"这个命名空间时,将rootAvailable = true。name = uri.substring(namespace.length() + 1)句不涉及到命名空间就不说了。if (rootAvailable && "".equals(namespace))如果通过上面的for循环没有找到匹配的命名空间即namespace的值仍然是当初设置的"",但却配置了"/"时,将命名空间设为"/"。

  我们再看到第46到51行那个if语句:

1 if (!allowSlashesInActionNames) {2      int pos = name.lastIndexOf(‘/‘);3      if (pos > -1 && pos < name.length() - 1) {4           name = name.substring(pos + 1);5      }6 }

  allowSlashesInActionNames代表是否允许"/"出现在Action的名称中,默认为false,如果不允许"/"出现在Action名中,并且这时的Action名中有"/",则取"/"后面的部分。

下面是命名空间匹配规则的总结:

(1). 如果请求url中没有命名空间时,将采用"/"作为命名空间。

(2). 当我们将常量 struts.mapper.alwaysSelectFullNamespace设为true时,那么请求url的命名空间必须和配置文件配置的完全相同才能匹配。

当将常量 struts.mapper.alwaysSelectFullNamespace设为false时,那么请求url的命名空间和配置文件配置的可按模糊匹配。规则:

  a.如果配置文件中配置了/common 而url中的命名空间/common、/common/home、/common/home/index等等都是可匹配的,即子命名空间可匹配父命名空间。

  b.如果对于某个url请求中的命名空间同时匹配了俩个或俩个以上的配置文件中配置的命名空间,则选字符最长的,如:当前请求的命名空间为/common/home/index/aaaa,  而我们在配置时同时配置               了/common/home、/common/home/index  则将会匹配命名空间最长的,即/common/home/index。

(3).最后,如果请求的命名空间在配置中没有匹配到时,将采用""作为命名空间。如果没有设置为""的命名空间将抛出404错误。

  ②:parseActionName(mapping)

  好了,到这里parseNameAndNamespace方法已经分析完了,我们再次回到getMapping方法中去,看到16行:handleSpecialParameters(request, mapping);好像是处理特殊参数的函数吧,里面有点看不懂,暂时就不管,以后有时间再研究。我们看到18行:return parseActionName(mapping);主要是用来处理形如/userAction!getAll.action的请求,分离action名和方法名:

 1 protected ActionMapping parseActionName(ActionMapping mapping) { 2         if (mapping.getName() == null) { 3             return null; 4         } 5         //如果允许动态方法调用 6         if (allowDynamicMethodCalls) { 7             // handle "name!method" convention. 8             String name = mapping.getName(); 9             int exclamation = name.lastIndexOf("!");10             //如果包含"!"就进行分离11             if (exclamation != -1) {12                 //分离出action名13                 mapping.setName(name.substring(0, exclamation));14                 //分离出方法名15                 mapping.setMethod(name.substring(exclamation + 1));16             }17         }18         return mapping;19     }

  到此为止getMapping方法已经分析结束了!

(5)execute.executeAction(request, response, mapping)

  上面我们分析完了mapping的获取,继续看doFilter方法:

 1 //如果mapping为空,则认为不是调用action,会调用下一个过滤器链,直到获取到mapping才调用action 2                 if (mapping == null) { 3                     boolean handled = execute.executeStaticResourceRequest(request, response); 4                     if (!handled) { 5                         chain.doFilter(request, response); 6                     } 7                 } else { 8                     //执行action 9                     execute.executeAction(request, response, mapping);10                 }

  如果mapping对象不为空,则会执行action,我们看到上面代码第9行:execute是ExecuteOperations类的对象,ExecuteOperations类在包org.apache.struts2.dispatcher.ng下面,我们找到它里面的executeAction方法:

1 public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {2         dispatcher.serviceAction(request, response, servletContext, mapping);3     }

我们可以看到它里面只是简单的调用了dispatcher的serviceAction方法:我们找到dispatcher的serviceAction方法:

 1 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, 2                               ActionMapping mapping) throws ServletException { 3         //封转上下文环境,主要将requestMap、params、session等Map封装成为一个上下文Map 4         Map<String, Object> extraContext = createContextMap(request, response, mapping, context); 5  6         // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action 7         ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); 8         boolean nullStack = stack == null; 9         if (nullStack) {10             ActionContext ctx = ActionContext.getContext();11             if (ctx != null) {12                 stack = ctx.getValueStack();13             }14         }15         if (stack != null) {16             extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));17         }18 19         String timerKey = "Handling request from Dispatcher";20         try {21             UtilTimerStack.push(timerKey);22             String namespace = mapping.getNamespace();//从mapping对象获取命名空间23             String name = mapping.getName();          //获取请求的action名24             String method = mapping.getMethod();      //获取请求方法25             //得到配置对象26             Configuration config = configurationManager.getConfiguration();27             //根据执行上下文参数,命名空间,名称等创建用户自定义Action的代理对象  28             ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(29                     namespace, name, method, extraContext, true, false);30 31             request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());32 33             // if the ActionMapping says to go straight to a result, do it!34             //如果配置文件中执行的这个action配置了result,就直接转到result35             if (mapping.getResult() != null) {36                 Result result = mapping.getResult();37                 result.execute(proxy.getInvocation());38             } else {39                 proxy.execute();40             }41 42             // If there was a previous value stack then set it back onto the request43             if (!nullStack) {44                 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);45             }46         } catch (ConfigurationException e) {47             // WW-2874 Only log error if in devMode48             if (devMode) {49                 String reqStr = request.getRequestURI();50                 if (request.getQueryString() != null) {51                     reqStr = reqStr + "?" + request.getQueryString();52                 }53                 LOG.error("Could not find action or result\n" + reqStr, e);54             } else {55                 if (LOG.isWarnEnabled()) {56                     LOG.warn("Could not find action or result", e);57                 }58             }59             sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);60         } catch (Exception e) {61             if (handleException || devMode) {62                 sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);63             } else {64                 throw new ServletException(e);65             }66         } finally {67             UtilTimerStack.pop(timerKey);68         }69     }

  最后通过Result完成页面跳转!

总结:以前总是只会用struts2框架,对里面的原理没有一个很清晰的认识,这两天花时间把struts2框架的源码分析了一下,对它的工作原理有个更深的认识。既然是开源框架,有时间久得去研究研究它的源码,不然开源就失去了意义了,不要只停留在会用的层面上!

转自:http://www.cnblogs.com/liuling/p/2013-8-10-01.html

时间: 2024-10-13 04:51:51

struts2请求过程源码分析的相关文章

配置一个逻辑CPU专用于实时任务----Kithara RTS工程源码分析

本文以windows实时拓展Kithara RTS安装目录下的smp文件夹内的DedicatedRealTimeTask项目为例,讲解使实时任务以独占一个逻辑CPU的方式运行,并实现任务间的同步. 目前多核计算机已经普及,多数的PC都是多核的.针对这种多核结构,我们设想把计算机划分为不同的硬件区间,其中一部分用于被实时任务专用,另一部分是被windows使用的,两者之间互不干扰,这样实时任务可以实现更好的实时性能.这种硬件划分,一般是按照物理CPU的核心数,即逻辑CPU的数量来配置的.可以配置一

spark源码阅读--shuffle读过程源码分析

shuffle读过程源码分析 上一篇中,我们分析了shuffle在map阶段的写过程.简单回顾一下,主要是将ShuffleMapTask计算的结果数据在内存中按照分区和key进行排序,过程中由于内存限制会溢写出多个磁盘文件,最后会对所有的文件和内存中剩余的数据进行归并排序并溢写到一个文件中,同时会记录每个分区(reduce端分区)的数据在文件中的偏移,并且把分区和偏移的映射关系写到一个索引文件中. 好了,简单回顾了写过程后,我们不禁思考,reduce阶段的数据读取的具体过程是什么样的?数据读取的

Flume-NG启动过程源码分析(三)(原创)

上一篇文章分析了Flume如何加载配置文件的,动态加载也只是重复运行getConfiguration(). 本篇分析加载配置文件后各个组件是如何运行的? 加载完配置文件订阅者Application类会收到订阅信息执行: @Subscribe public synchronized void handleConfigurationEvent(MaterializedConfiguration conf) { stopAllComponents(); startAllComponents(conf)

【Java】【Flume】Flume-NG启动过程源码分析(一)

从bin/flume 这个shell脚本可以看到Flume的起始于org.apache.flume.node.Application类,这是flume的main函数所在. main方法首先会先解析shell命令,如果指定的配置文件不存在就甩出异常. 根据命令中含有"no-reload-conf"参数,决定采用那种加载配置文件方式:一.没有此参数,会动态加载配置文件,默认每30秒加载一次配置文件,因此可以动态修改配置文件:二.有此参数,则只在启动时加载一次配置文件.实现动态加载功能采用了

【Java】【Flume】Flume-NG启动过程源码分析(二)

本节分析配置文件的解析,即PollingPropertiesFileConfigurationProvider.FileWatcherRunnable.run中的eventBus.post(getConfiguration()).分析getConfiguration()方法.此方法在AbstractConfigurationProvider类中实现了,并且这个类也初始化了三大组件的工厂类:this.sourceFactory = new DefaultSourceFactory();this.s

【Java】【Flume】Flume-NG启动过程源码分析(三)

本篇分析加载配置文件后各个组件是如何运行的? 加载完配置文件订阅者Application类会收到订阅信息执行: @Subscribe public synchronized void handleConfigurationEvent(MaterializedConfiguration conf) { stopAllComponents(); startAllComponents(conf); } MaterializedConfiguration conf就是getConfiguration()

Activity启动流程源码分析之Launcher启动(二)

1.前述 在前一篇文章中我们简要的介绍Activity的启动流程Activity启动流程源码分析之入门(一),当时只是简单的分析了一下流程,而且在上一篇博客中我们也说了Activity的两种启动方式,现在我们就来分析其中的第一种方式--Launcher启动,这种启动方式的特点是会创建一个新的进程来加载相应的Activity(基于Android5.1源码). 2.Activity启动流程时序图 好啦,接下来我们先看一下Launcher启动Activity的时序图: 好啦,接下来我们将上述时序图用代

Android系统默认Home应用程序(Launcher)的启动过程源码分析

在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还须要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应用程序就是Launcher了,本文将详细分析Launcher应用程序的启动过程. Android系统的Home应用程序Launcher是由ActivityManagerService启动的,而ActivityManagerService和PackageManagerService一样,都是在开机时由

嵌入式linux开发uboot移植(三)——uboot启动过程源码分析

嵌入式linux开发uboot移植(三)--uboot启动过程源码分析 一.uboot启动流程简介 与大多数BootLoader一样,uboot的启动过程分为BL1和BL2两个阶段.BL1阶段通常是开发板的配置等设备初始化代码,需要依赖依赖于SoC体系结构,通常用汇编语言来实现:BL2阶段主要是对外部设备如网卡.Flash等的初始化以及uboot命令集等的自身实现,通常用C语言来实现. 1.BL1阶段 uboot的BL1阶段代码通常放在start.s文件中,用汇编语言实现,其主要代码功能如下: