Struts2源码解析2
看了前面一节对Struts2各个模块运行有了大概了解,也对调用的函数有了一定的了解,本节希望打断点跑一个Struts2例子!
还是放在struts2结构图:
一:项目启动后解析web.xml文件,会解析到配置的StrutsPrepareAndExecuteFilter的过滤器。
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>Struts2Demo</display-name> <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> <welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
</web-app>
在此研究的是StrutsPrepareAndExecuteFilter。
二:StrutsPrepareAndExecuteFilter
StrutsPrepareAndExecuteFilter中的方法:
void init(FilterConfig filterConfig) | 继承自Filter,过滤器的初始化 |
doFilter(ServletRequest req, ServletResponse res, FilterChain chain) | 继承自Filter,执行过滤器 |
void destroy() | 继承自Filter,用于资源释放 |
void postInit(Dispatcher dispatcher, FilterConfig filterConfig) | Callback for post initialization(一个空的方法,用于方法回调初始化) |
web容器一启动,就会初始化核心过滤器StrutsPrepareAndExecuteFilter,并执行初始化方法,初始化方法如下:
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
Dispatcher dispatcher = null;
try {
//封装filterConfig,其中有个主要方法getInitParameterNames将参数名字以String格式存储在List
FilterHostConfig config = new FilterHostConfig(filterConfig);
//初始化struts内部日志
init.initLogging(config);
//创建dispatcher ,并初始化
dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);
//初始化类属性:prepare 、execute
prepare = new PrepareOperations(dispatcher);
execute = new ExecuteOperations(dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
//回调空的postInit方法
postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}
关于封装filterConfig,首先看下FilterHostConfig ,源码如下:
public class FilterHostConfig implements HostConfig { private FilterConfig config;
//构造方法
public FilterHostConfig(FilterConfig config) {
this.config = config;
}
//根据init-param配置的param-name获取param-value的值
public String getInitParameter(String key) {
return config.getInitParameter(key);
}
//返回初始化参数名的迭代器
public Iterator<String> getInitParameterNames() {
return MakeIterator.convert(config.getInitParameterNames());
}
//返回Servlet上下文
public ServletContext getServletContext() {
return config.getServletContext();
}
}
只有短短的几行代码,getInitParameterNames是这个类的核心,将Filter初始化参数名称有枚举类型转为Iterator。此类的主要作为是对filterConfig 封装。
接下来,看下StrutsPrepareAndExecuteFilter中init方法中dispatcher = init.initDispatcher(config);这是初始化dispatcher的,是通过init对象的initDispatcher方法来初始化的,init是InitOperations类的对象,我们看看InitOperations中initDispatcher方法:
public Dispatcher initDispatcher( HostConfig filterConfig ) {
Dispatcher dispatcher = createDispatcher(filterConfig);
dispatcher.init();
return dispatcher;
}
创建Dispatcher,会读取 filterConfig 中的配置信息,将配置信息解析出来,封装成为一个Map,然后根绝servlet上下文和参数Map构造Dispatcher :
private Dispatcher createDispatcher( HostConfig filterConfig ) {
//存放参数的Map
Map<String, String> params = new HashMap<String, String>();
//将参数存放到Map,这个地方就用到前面提到FilterHostConfig的转为list功能
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);
}
这样dispatcher对象创建完成,接着就是dispatcher对象的初始化,打开Dispatcher类,看到它的init方法如下:
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);
}
}
这里主要是加载一些配置文件的,将按照顺序逐一加载: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 {
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
//设置编码和国际化
prepare.setEncodingAndLocale(request, response);
//创建action上下文
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
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 {
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) {
dispatcher.prepare(request, response);
}
在这方法里面我们可以看到它只是调用了dispatcher的prepare方法而已,下面我们看看dispatcher的prepare方法:
/**
* Prepare a request, including setting the encoding and locale.
*
* @param request The request
* @param response The response
*/
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类中时如下定义的:
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));
//stack.getContext()返回的是一个Map<String,Object>,根据此Map构造一个ActionContext
ctx = new ActionContext(stack.getContext());
}
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
ActionContext.setContext(ctx); //将ActionContext存到ThreadLocal
return ctx;
}
上面第18行代码中dispatcher.createContextMap,如何封装相关参数:
/**
* Create a context map containing all the wrapped request objects
*
* @param request The servlet request
* @param response The servlet response
* @param mapping The action mapping
* @return A map of context objects
*
* @since 2.3.17
*/
public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
ActionMapping mapping) { // 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(servletContext); Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response); 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方法:
/**
* Wraps the request with the Struts wrapper that handles multipart requests better
* @return The new request, if there is one
* @throws ServletException
*/
public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
HttpServletRequest request = oldRequest;
try {
// Wrap request first, just in case it is multipart/form-data
// parameters might not be accessible through before encoding (ww-1278)
request = dispatcher.wrapRequest(request);
ServletActionContext.setRequest(request);
} catch (IOException e) {
throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e);
}
return request;
}
由第11行我们可以看到它里面调用的是dispatcher的wrapRequest方法,我们看下dispatcher的wrapRequest:
public HttpServletRequest wrapRequest(HttpServletRequest request) 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(), provider, disableRequestAttributeValueStackLookup);
} 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) {
if (dispatcher.isHandleException() || dispatcher.isDevMode()) {
dispatcher.sendError(request, response, 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 = RequestUtils.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名和命名空间:
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) {
// ww-1046, assume it is the root namespace, it will fallback to
// default
// namespace anyway if not found in root namespace.
namespace = "/";
name = uri.substring(lastSlash + 1);
//允许采用完整的命名空间,即设置命名空间是否必须进行精确匹配
} else if (alwaysSelectFullNamespace) {
// Simply select the namespace as everything before the last slash
namespace = uri.substring(0, lastSlash);
name = uri.substring(lastSlash + 1);
} else {
// Try to find the namespace in those defined, defaulting to ""
Configuration config = configManager.getConfiguration();
String prefix = uri.substring(0, lastSlash); //临时的命名空间,将会用来进行匹配
namespace = "";//将命名空间暂时设为""
boolean rootAvailable = false;//rootAvailable作用是判断配置文件中是否配置了命名空间"/"
// Find the longest matching namespace, defaulting to the default
for (Object cfg : config.getPackageConfigs().values()) { //循环遍历配置文件中的package标签
String ns = ((PackageConfig) cfg).getNamespace(); //获取每个package标签的namespace属性
//进行匹配
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 = uri.substring(namespace.length() + 1); // Still none found, use root namespace if found
if (rootAvailable && "".equals(namespace)) {
namespace = "/";
}
} if (!allowSlashesInActionNames) {
int pos = name.lastIndexOf('/');
if (pos > -1 && pos < name.length() - 1) {
name = name.substring(pos + 1);
}
}
//将分离后的acion名和命名空间保存到mapping对象
mapping.setNamespace(namespace);
mapping.setName(cleanupActionName(name));
}
看到上面代码的第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句是进行模糊匹配的核心,我摘出来单独说,如下:
if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {
if (ns.length() > namespace.length()) {
namespace = ns;
}
}
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语句:
if (!allowSlashesInActionNames) {
int pos = name.lastIndexOf('/');
if (pos > -1 && pos < name.length() - 1) {
name = name.substring(pos + 1);
}
}
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名和方法名:
protected ActionMapping parseActionName(ActionMapping 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)); //分离出action名 mapping.setMethod(name.substring(exclamation + 1)); //分离出方法名
}
}
return mapping;
}
到此为止getMapping方法已经分析结束了!
(5)execute.executeAction(request, response, mapping)
上面我们分析完了mapping的获取,继续看doFilter方法:
//如果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);
}
如果mapping对象不为空,则会执行action,我们看到上面代码第9行:execute是ExecuteOperations类的对象,ExecuteOperations类在包org.apache.struts2.dispatcher.ng下面,我们找到它里面的executeAction方法:
public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
dispatcher.serviceAction(request, response, mapping);
}
我们可以看到它里面只是简单的调用了dispatcher的serviceAction方法:我们找到dispatcher的serviceAction方法:
/**
* Load Action class for mapping and invoke the appropriate Action method, or go directly to the Result.
* <p/>
* This method first creates the action context from the given parameters,
* and then loads an <tt>ActionProxy</tt> from the given action name and namespace.
* After that, the Action method is executed and output channels through the response object.
* Actions not found are sent back to the user via the {@link Dispatcher#sendError} method,
* using the 404 return code.
* All other errors are reported by throwing a ServletException.
*
* @param request the HttpServletRequest object
* @param response the HttpServletResponse object
* @param mapping the action mapping object
* @throws ServletException when an unknown error occurs (not a 404, but typically something that
* would end up as a 5xx by the servlet container)
*
* @since 2.3.17
*/
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
throws ServletException {
//封转上下文环境,主要将requestMap、params、session等Map封装成为一个上下文Map
Map<String, Object> extraContext = createContextMap(request, response, mapping); // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
boolean nullStack = stack == null;
if (nullStack) {
ActionContext ctx = ActionContext.getContext();
if (ctx != null) {
stack = ctx.getValueStack();
}
}
if (stack != null) {
extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
} String timerKey = "Handling request from Dispatcher";
try {
UtilTimerStack.push(timerKey);
String namespace = mapping.getNamespace();//从mapping对象获取命名空间
String name = mapping.getName();//获取请求的action名
String method = mapping.getMethod();//获取请求方法
//根据执行上下文参数,命名空间,名称等创建用户自定义Action的代理对象
ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false); request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
//如果配置文件中执行的这个action配置了result,就直接转到result
// if the ActionMapping says to go straight to a result, do it!
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
} // If there was a previous value stack then set it back onto the request
if (!nullStack) {
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
}
} catch (ConfigurationException e) {
logConfigurationException(request, e);
sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
if (handleException || devMode) {
sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
} else {
throw new ServletException(e);
}
} finally {
UtilTimerStack.pop(timerKey);
}
}
最后通过Result完成页面跳转!
下面我们来看看ActionInvocation是如何工作的:
ActionInvocation是Xworks 中Action 调度的核心。而对Interceptor 的调度,也正是由ActionInvocation负责。ActionInvocation 是一个接口,而DefaultActionInvocation 则是Webwork 对ActionInvocation的默认实现。
总计struts的步骤就是以下步骤
Struts流程:
一、 Tomcat服务器启动
1. 服务器启动,加载自身config/web.xml, 以及webapps下所有项目的web.xml
2. 创建过滤器对象
创建struts核心过滤器实例
3. 初始化
执行过滤器init()方法
---> 加载默认Properties配置文件
---> 加载xml
struts-default.xml, 【所有需要创建的bean、默认的包(结果类型、所有拦截器、默认的拦截器栈)】
struts-plugin.xml, 【插件配置】
struts.xml 【自定义配置】
二、 用户访问
用户每次访问,执行doFilter()方法:
1. 每次访问struts,都会创建ActionContext对象 【 prepare.createActionContext(request, response);】
1.1 然后,创建ValuStack值栈对象
ValueStack 结构
|-- root 根元素数据
|-- context 非根元素(map)
1.2 给ValustStack封装数据, 所有的map数据都放入值栈
--> 把代表request/session/application的map、以及HttpServletRequest等对象都放入值栈中
1.3 把值栈的map数据,拷贝一份给ActionContext!
所以,我们可以通过ActionContext获取struts运行的数据!
2. ActionMapping
struts把请求的参数(路径等信息), 封装为一个ActionMapping对象!
判断:
---> 如果请求静态资源(jsp), 就放行
---> 如果请求的是action,才执行Action(的过程)
3. 如果访问的是struts的action, 执行:execute.executeAction(request, response, mapping);
(Action创建、拦截器的调用)
4. ConfigurationManager 配置管理器类,管理各配置文件
5. 根据配置管理器对象, 创建Action的代理 , 通过Action的代理,创建Action实例!
6. 再依次默认的18个拦截器
提几个比较好的流程图:
步骤:
1、客户端请求,生成相应的HttpServletRequest对象
2、Struts2核心过滤器,StrutsPrepareAndExecuteFilter进行拦截,(如果满足要求,进行拦截,不满足要求,放行)
3、ActionMapper动作映射类,将请求的路径与struts.xml中的配置进行对比,找到匹配的动作类
4、经过核心过滤器,然后创建匹配的动作类的代理类对象,进行一系列的配置,(读取配置文件struts.xml,确定执行那个方法)
5、通过一些拦截器,对数据进行预处理,比如说请求参数的封装就是在这进行
6、进入动作方法,进行业务处理
7、动作方法返回结果视图的名字,到ActionMapper找到对应的结果视图,进行结果视图预处理
8、反向经过5中的拦截器
9、生成HttpServletResponse对象,返回给客户端。
https://blog.csdn.net/q547550831/article/details/53267815----不错
https://blog.csdn.net/chainiao_zhang/article/details/76825171----面试题
原文:https://blog.csdn.net/weixin_40234548/article/details/81359958
https://www.cnblogs.com/duanxz/p/5441342.html----------主要参考
Struts2源码解析2的更多相关文章
- Struts2源码解析-----转载
前面一节描述的Struts2很多东西,这节是对源码进行分析描述,通过这一节应该对struts2有了清楚认识! 还是把Struts2这个框图贴出来: 第一步:HttpServletRequest进入到S ...
- Struts2 源码分析——配置管理之PackageProvider接口
本章简言 上一章讲到关于ContainerProvider的知识.让我们知道struts2是如何注册相关的数据.也知道如何加载相关的配置信息.本章笔者将讲到如何加载配置文件里面的package元素节点 ...
- Struts2源码浅析-ConfigurationProvider
ConfigurationProvider接口 主要完成struts配置文件 加载 注册过程 ConfigurationProvider接口定义 public interface Configurat ...
- Struts2源码阅读(一)_Struts2框架流程概述
1. Struts2架构图 当外部的httpservletrequest到来时 ,初始到了servlet容器(所以虽然Servlet和Action是解耦合的,但是Action依旧能够通过httpse ...
- Spring MVC工作原理及源码解析(三) HandlerMapping和HandlerAdapter实现原理及源码解析
1.HandlerMapping实现原理及源码解析 在前面讲解Spring MVC工作流程的时候我们说过,前端控制器收到请求后会调⽤处理器映射器(HandlerMapping),处理器映射器根据请求U ...
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
- 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...
- 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...
- 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例
前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...
随机推荐
- 理解block和inode
什么是block和inode? 定义:block就像是杯子 inode就像是杯子的编号,因为杯子太多了 1.根据文件的大小,在磁盘中储存时会占用一个或多个block:那么究竟多大的文件会使用一个blo ...
- vscode中使用markdown(转)
vscode 是微软推出一款轻量级的文本编辑工具,类似于sublime,由于其拥有丰富的插件,安装使用也非常简单,所以深受广大程序员的喜爱. markdown 是一种可以使用普通文本编辑器编写的标记语 ...
- flutter 底部按钮切换页面
界面如图: 我们就从上节里面的app.dartt修改 目录:lib lib/story 其它两个目录一样. 图片配置一下 app.dart import 'package:flutter/materi ...
- Cache-control使用Cache-control:private学习笔记【转载】
网页缓存由 HTTP消息头中的Cache-control控制,常见取值有private.no-cache.max-age.must- revalidate等,默认为private 其作用根据不同的重新 ...
- go 0000
1,函数不能比较 函数默认都是值传递, 除了 map 值, slice (切片) channel (管道) interface 默认以这4个默认 引用传递 两个 defer ...
- 使用keil5,加断点调试后,停止运行的问题
加上断点调试,执行到断点的时就出现程序停止运行的提示. 原因:是工程路径存放太深.
- 使用 webpack 搭建多入口项目
闲来无事,学习一下怎么用 webpack 自定义多入口项目的打包 项目github地址:https://github.com/xiaoliwang2016/webpack-demo 先来看一下目录结构 ...
- ORM项目中小知识点积累
申明:一下内容均建立在零基础小白的角度上,大佬们求放过~ 1.如何通过类建立外键关联 2.模板语言固定搭配 3.浏览器报错处理 4.后台取值方式 5.外键管理修改相关操作 6.两种(给后台偷偷传递消息 ...
- 浏览器端时间循环与nodejs端时间循环的不同之处(宏任务与微任务)
浏览器端与node端都有宏任务与微任务的概念.字面意思上看宏任务就是耗时间比较长的任务,而微任务是耗时短的任务. 在浏览器端,宏任务包括setTimeout,setInterval,微任务则包括Pro ...
- 一些java的demo
//1. false System.out.println(Integer.parseInt("01")>10); //2. abcdefghijklmnopqrstuvwx ...