您的位置:首页 > 编程语言 > Java开发

[置顶] struts2执行流程

2018-02-14 20:01 549 查看
写在前面:struts2在web应用层面属于表示层框架,在MVC开发模式中属于C(Controller控制器),负责对M(Model模型)和V(View视图)进行解耦。struts2是在struts1和webwork的技术基础上进行了合并的全新的框架。struts2虽然和struts1在名字上很相似,但是却不是后者的升级版。struts2其实是以另一个表示层框架webwork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以struts2也可以理解为webwork的更新产品。从它们的处理请求的执行流程就可以看出相似点。

直观感受一下:

webwork:



struts2:



struts2和webwork都是通过一个FilterDispatcher过滤器来匹配客户端发送的所有请求(当然,现在struts2的过滤器名是StrutsPreparedAndExecuteFilter),不同于struts1是通过一个servlet来匹配所有请求,好了,这里先简单的了解一下struts2和struts1的区别, 在文章末尾会详细的介绍struts2和struts1的区别,开始进入主题。

struts2的执行流程(结合流程图分析):

客户端发送一个HTTP请求

该请求被struts2的核心过滤器StrutsPreparedAndExecuteFilter匹配(只要是在过滤器的url-pattern中配置了/*,那么任何请求都会进入该过滤器,无论该请求是否需要struts2来处理),当然,在进入这个过滤器之前会依次进入在web.xml中配置的位置在struts2过滤器之前的其他Filter或Servlet

struts2的过滤器会询问(形象一点的说法,其实就是调用方法)ActionMapper该请求是否有与之对应的业务控制类,如果没有,则放行,如果有,进入下一步执行流程

struts2通过ActionProxy实例化ActionInvocation,当然在这之前ActionProxy还会通过ConfigurationManager按序加载struts2的配置文件:default.properties, struts-default.xml, struts.properties, struts.xml…(先加载struts默认的,然后才是自己定义的),正是因为加载了这些配置文件所以struts才能找到相应的拦截器以及业务控制类。

ActionProxy初始化一个ActionInvocation并通过它的invoke来正式执行一系列的拦截器以及Action,在执行完Action之后会根据使用的模板(jsp, velocity, freemarker…)组装结果集Result,渲染页面

返回给客户端响应

接下来详细的分析一下:

1. 客户端发送一个HTTP请求

可能是一个登陆请求,也可能是查询某个功能列表的请求…

2. StrutsPreparedAndExecuteFilter过滤器拦截该请求

过滤器拦截到该请求的时候会调用doFilter方法,如下:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// 1.将ServletRequest和ServletResponse对象转换为HttpServletRequest和HttpServletResponse对象
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;

try {
// 2.对不由struts2处理的请求放行,这个excludedPatterns是一个List<Pattern>集合,里面存储了不被struts2的过滤器匹配的url
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
// 3.设置请求和相应的编码以及国际化的相关信息
prepare.setEncodingAndLocale(request, response);
// 4.创建一个Action的上下文,并初始化一个本地线程
// 源码注释:Creates the action context and initializes the thread local
prepare.createActionContext(request, response);
// 5.把dispatcher指派给本地线程
// 源码注释:Assigns the dispatcher to the dispatcher thread local
prepare.assignDispatcherToThread();
// 6.包装一下request防止它是一个multipart/form-data类型的请求
// 源码注释:Wrap request first, just in case it is multipart/form-data
request = prepare.wrapRequest(request);
// 7.查找ActionMapping信息(包括name,namespace,method,extention,params,result)
ActionMapping mapping = prepare.findActionMapping(request, response, true);
// 8.没有找到请求对应的业务控制类
if (mapping == null) {
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
// 9.找到了对应的业务控制类那就去执行该Action
execute.executeAction(request, response, mapping);
}
}
} finally {
// 10.释放掉这个Request所占用的一些内存空间
prepare.cleanupRequest(request);
}
}


2.1. 首先将ServletRequest和ServletResponse对象转换为HttpServletRequest和HttpServletResponse对象

2.2. 判断是否设置了不被struts2过滤器拦截的请求

if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
}


这个excludedPatterns是一个List< Pattern >集合,里面包含了不被struts2过滤器拦截的url。看这一句:prepare.isUrlExcluded(request, excludedPatterns),判断这个请求里面是否包含这样的url,跟进源码查看一具体的实现:

public boolean isUrlExcluded( HttpServletRequest request, List<Pattern> excludedPatterns ) {
if (excludedPatterns != null) {
// 1.获取当前请求中的uri
String uri = RequestUtils.getUri(request);
// 2.查看集合中是否有与之匹配的,有就返回true
for ( Pattern pattern : excludedPatterns ) {
if (pattern.matcher(uri).matches()) {
return true;
}
}
}
return false;
}


知道了拦截器是通过excludedPatterns来判断哪个url不被拦截,那么这个excludedPatterns的值是从哪里来的呢?初步猜测是在StrutsPreparedAndExecuteFilter初始化(init)的时候设置的…果不其然,看源码:

public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
Dispatcher dispatcher = null;
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);
init.initLogging(config);
dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);

prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
// 就是这里,创建了不匹配的url列表
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);

postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}


跟进buildExcludedPatternsList方法:

public List<Pattern> buildExcludedPatternsList( Dispatcher dispatcher ) {
// 由此可知是struts2是读取了STRUTS_ACTION_EXCLUDE_PATTERN常量的值来判断哪些请求不需要匹配
return buildExcludedPatternsList(dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN));
}

private List<Pattern> buildExcludedPatternsList( String patterns ) {
if (null != patterns && patterns.trim().length() != 0) {
List<Pattern> list = new ArrayList<Pattern>();
String[] tokens = patterns.split(",");
for ( String token : tokens ) {
list.add(Pattern.compile(token.trim()));
}
return Collections.unmodifiableList(list);
} else {
return null;
}
}


struts2是根据STRUTS_ACTION_EXCLUDE_PATTERN常量的值来判断哪些请求不需要匹配,所以我们如果想要设置某些请求不被struts2匹配就可以设置这个常量struts.action.excludePattern,多个pattern之间用逗号隔开(pattern的写法和web.xml中配置的类似)

2.3 设置请求和响应的编码以及国际化的相关信息

prepare.setEncodingAndLocale(request, response),是由Dispatcher在准备的过程中完成的,源码如下:

/**
* Sets the request encoding and locale on the response
* 设置请求和响应的国际化编码
*/
public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
dispatcher.prepare(request, response);
}


跟进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
}
}


设置编码的具体实现。嗯,代码写的蛮好…

2.4 创建一个Action的上下文,并初始化一个本地线程

/**
* Creates the action context and initializes the thread local
*/
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
ActionContext ctx;
// 计数器,作用:记录这个Action被访问的次数,与后续释放ActionContext的内存空间有关,初始化为1
Integer counter = 1;
Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
// 如果request域里面已经有一个计数器,说明这个Action已经被实例化调用过了,那么就将这个计数器的值加1
if (oldCounter != null) {
counter = oldCounter + 1;
}
// 和上面的计数器类似,尝试从request获取这个Action的上下文对象,如果存在就直接使用这个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 {
// 不存在就创建一个值栈(存储了与这个Action相关的一些属性)和一个ActionContext
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
ctx = new ActionContext(stack.getContext());
}
// 把这个计数器放到request域里面
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
// 跟进这个方法后有这么个注释:Sets the action context for the current thread(把这个Action上下文对象放入当前线程)
ActionContext.setContext(ctx);
return ctx;
}


2.4.1 创建Action计数器

这个计数器可是有点用处:它用来记录一个Action被调用的次数。那么为什么要记录它被调用的次数呢?这里先提前看一下doFilter方法的最后一步:prepare.cleanupRequest(request);这一步是用来清理掉该次请求所占用的内存,跟进源码:

/**
* Cleans up a request of thread locals
*/
public void cleanupRequest(HttpServletRequest request) {
// 获取request域中计数器
Integer counterVal = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
if (counterVal != null) {
// 这个计数器的值不为空就把它减一
counterVal -= 1;
// 重新放入request域,用CLEARUP_RECURSION_COUNTER这个常量保存
request.setAttribute(CLEANUP_RECURSION_COUNTER, counterVal);
// 关键:如果这个计数器在减一之后的值仍然大于0,那么就不释放它所占用的内存,记录一条日志就直接返回了
if (counterVal > 0 ) {
if (log.isDebugEnabled()) {
log.debug("skipping cleanup counter="+counterVal);
}
return;
}
}
// 否则就clearUp掉
// always clean up the thread request, even if an action hasn't been executed
try {
dispatcher.cleanUpRequest(request);
} finally {
ActionContext.setContext(null);
Dispatcher.setInstance(null);
}
}


相信看到这里,大家大致已经明白了这个计数器存在的意义:记录Action被请求的次数,如果请求的次数非常频繁,说明这个Action被调用的次数非常多,那么就暂时不释放掉它所占用的内存,反之,如果只请求了一次或者是几次,那么在这个Action执行完毕后就会释放掉它所占用的内存。

2.4.2 创建ActionContext

跟进源码看一下具体的实现:

public class ActionContext implements Serializable {

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

// 处理请求的Action的name
public static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContext.name";

// 与这个Action相关的值栈
public static final String VALUE_STACK = ValueStack.VALUE_STACK;

// session相关
public static final String SESSION = "com.opensymphony.xwork2.ActionContext.session";

// application域
public static final String APPLICATION = "com.opensymphony.xwork2.ActionContext.application";
...


这个ActionContext存储了一些与当前Action相关的信息。

2.5 把dispatcher指派给本地线程-prepare.assignDispatcherToThread()

其实从上一步我们就不难看出struts2为每一个Action创建一个线程,这也体现了struts2相比于struts1的优势:线程安全,因为每一个Action都由一个单独的线程来负责,不存在共享数据,所以安全。我们进入PrepareOperations类的assignDispatcherToThread()方法的源码看一下:

public void assignDispatcherToThread() {
// 将当前的这个dispatcher实例化
Dispatcher.setInstance(dispatcher);
}


再进入Dispatcher的setInstance方法中看一下:

public static void setInstance(Dispatcher instance) {
// 调用了这个Dispatcher的instance属性的set方法
// ps:这个instance是一个ThreadLocal对象
Dispatcher.instance.set(instance);
}


再进入set方法中看一下:

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}


终于找到根源了,获取到当前的线程,把dispatcher放入当前线程中。那么这个Dispatcher有什么作用呢?看一下源码,这个源码有点多,就摘抄一个重要的方法来看下吧:

/**
* 初始化一系列的配置文件:default.properties, struts-default.xml, struts.properties, struts.xml...文件,并且是按顺序加载
*/
public void init() {

if (configurationManager == null) {
configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME);
}

try {
init_FileManager();
init_DefaultProperties(); // [1]default.properties
init_TraditionalXmlConfigurations(); // [2]struts-default.xml...
init_LegacyStrutsProperties(); // [3]struts.properties
init_CustomConfigurationProviders(); // [5]struts.xml
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);
}
}


Dispatcher可以用来初始化一系列的配置文件,并且是按序加载。

2.6 包装一下request

request = prepare.wrapRequest(request);


wrapRequest方法的源码如下:

public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
HttpServletRequest request = oldRequest;
try {
// Wrap request first, just in case it is multipart/form-data
// 为了防止这个请求是一个multipart/form-data(上传文件)类型的请求
// parameters might not be accessible through before encoding (ww-1278)
// 因为这种类型请求的参数如果不经过处理可能获取不到
request = dispatcher.wrapRequest(request, servletContext);
} catch (IOException e) {
throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e);
}
return request;
}


为了防止这个请求是一个multipart/form-data(上传文件)类型的请求,将它包装一下。因为这种类型请求的参数如果不经过处理可能获取不到。继续进入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();
// 如果请求类型不为空并且是multipart/form-data类型的请求,那么就包装一下
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;
}


2.7 查找该请求相关的信息actionMapping

prepare.findActionMapping(request, response, true),源码如下:

public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
if (mapping == null || forceLookup) {
try {
// 这里就开始询问ActionMaper是否存在与该请求对应的业务控制类,详见下面的第三大步
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;
}


发现这个ActionMapping其实是通过ActionMapper的getMapping方法来获得的

ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager);


发现这个ActionMapper是一个接口,这里我启动我的项目debug发现是调用了它的实现类:DefaultActionMapper的getMapping方法,实现细节如下:

tips:在eclipse中,按Ctrl+T可以查看当前类的子类或者实现类

public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
ActionMapping mapping = new ActionMapping();
// 通过request工具类获取当前请求的uri
String uri = RequestUtils.getUri(request);

int indexOfSemicolon = uri.indexOf(";");
uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;
// 去掉后缀.action...
uri = dropExtension(uri, mapping);
if (uri == null) {
return null;
}
// 解析出configManager里面设置的actionName和namespace并放入ActionMapping
parseNameAndNamespace(uri, mapping, configManager);
handleSpecialParameters(request, mapping);
return parseActionName(mapping);
}


在getMaping方法中,获得当前请求中的uri以及配置文件中配置的action的name和namespace,看一下这个parseNameAndNamespace方法的实现细节:

protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) {
String namespace, name;
int lastSlash = uri.lastIndexOf("/");
...
// 将解析出来的name和namespace放入mapping
mapping.setNamespace(namespace);
mapping.setName(cleanupActionName(name));
}


从倒数两行代码可以看出了该方法的最终目的:将解析出来的name和namespace放入mapping,交给调用者来根据这个actionMapping判断请求是否有对应的业务控制类

2.8 没有找到请求对应的业务控制类所进行的操作

说明并没有为这个请求配置相应的业务控制类Action,就说明这个请求可能是一个静态的资源请求,于是就有了如下代码:

if (mapping == null) {
boolean handled = execute.executeStaticResourceRequest(request, response);
// 这里会根据上一步的返回值来确定是否执行
if (!handled) {
chain.doFilter(request, response);
}
}


查看executeStaticResourceRequest方法的具体实现:

public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// there is no action in this request, should we look for a static resource?
// 老外还挺逗:这个请求没有对应的action来处理,我们应不应该看看它是不是请求一个静态资源?
String resourcePath = RequestUtils.getServletPath(request);

if ("".equals(resourcePath) && null != request.getPathInfo()) {
resourcePath = request.getPathInfo();
}

StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class);
// 如果这个请求请求的资源在这个项目的资源路径下,返回true
if (staticResourceLoader.canHandle(resourcePath)) {
staticResourceLoader.findStaticResource(resourcePath, request, response);
// The framework did its job here
// 属于struts2管辖范围的静态资源,由struts2来处理
return true;

} else {
// this is a normal request, let it pass through
// 一个普通的请求,放行
return false;
}
}


简单点说就是:这个静态资源在项目中存在就返回,不存在就放行,交给其他的过滤器处理。

2.9 如果找到了这个请求对应的业务控制类Action

那就调用ExecuteOperations的executeAction方法去执行这个Action,源码如下:

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


它又调用了Dispatcher的serviceAction方法,源码如下:

public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
ActionMapping mapping) throws ServletException {

Map<String, Object> extraContext = createContextMap(request, response, mapping, context);

// 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);
// 获取ActionMapping中的namespace,name,method
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();

Configuration config = configurationManager.getConfiguration();
// 创建ActionProxy代理
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);

request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());

// if the ActionMapping says to go straight to a result, do it!
// 如果mapping中有结果集,那么就去执行结果集
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
// 之前已经有值栈了,就把它放入当前request域里面
if (!nullStack) {
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
}
} catch (ConfigurationException e) {
logConfigurationException(request, e);
sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
if (handleException || devMode) {
sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
} else {
throw new ServletException(e);
}
} finally {
UtilTimerStack.pop(timerKey);
}
}


这一步主要做了三件事:创建Action的代理,封装结果集Result,设置值栈。

实际上是通过调用DefaultActionProxyFactory的createActionProxy方法来创建的Action的代理

public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
// 创建ActionInvocation负责迭代拦截器和执行Action
ActionInvocation inv = new DefaultActionInvocation(extraContext, true);
// 放入容器中
container.inject(inv);
// 调用这个重载的createActionProxy方法
return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}


在方法返回的时候调用重载的createActionProxy方法,进入源码看一下:

public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {

DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
container.inject(proxy);
// 这里,proxy开始准备
proxy.prepare();
return proxy;
}


看这里:proxy.prepare();,proxy开始准备,继续跟源码:

protected void prepare() {
String profileKey = "create DefaultActionProxy: ";
try {
UtilTimerStack.push(profileKey);
config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);

if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
}
if (config == null) {
throw new ConfigurationException(getErrorMessage());
}

resolveMethod();

if (!config.isAllowedMethod(method)) {
throw new ConfigurationException("Invalid method: " + method + " for action " + actionName);
}
// 关键:开始初始化invocation
invocation.init(this);

} finally {
UtilTimerStack.pop(profileKey);
}
}


准备的关键代码就是初始化invocation,点进去一看又是一个接口,Ctrl+T,选中DefaultActionInvocation这个实现类,查看它的init方法:

public void init(ActionProxy proxy) {
this.proxy = proxy;
Map<String, Object> contextMap = createContextMap();

// Setting this so that other classes, like object factories, can use the ActionProxy and other
// contextual information to operate
ActionContext actionContext = ActionContext.getContext();

if (actionContext != null) {
actionContext.setActionInvocation(this);
}
// 创建Action(关键)
createAction(contextMap);

if (pushAction) {
stack.push(action);
contextMap.put("action", action);
}

invocationContext = new ActionContext(contextMap);
invocationContext.setName(proxy.getActionName());

// get a new List so we don't get problems with the iterator if someone changes the list
// 看到了吧,在这里获得了与action相关的拦截器,共19个
List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
// 迭代(就是执行所有的拦截器)
interceptors = interceptorList.iterator();
}


终于找到了创建Action的代码了,继续跟进源码:

protected void createAction(Map<String, Object> contextMap) {
// load action
String timerKey = "actionCreate: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);
// 可以看到在这里通过工厂类来创建Action
action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
} catch (InstantiationException e) {
...
}


经过这一步,终于通过了DefaultActionInvocation创建了Action。

然后我们回到上一步,成功创建Action之后就获取与之相关的拦截器列表,并用一个list集合装起来,依次迭代它们。

// get a new List so we don't get problems with the iterator if someone changes the list
List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
interceptors = interceptorList.iterator();


拦截器执行完毕后再执行Action,再封装结果集,再出拦截器,给客户端响应。

2.10 最终Action执行完毕一定要clear掉

防止内存泄漏(内存泄漏是指分配出去的内存不再使用,但是无法回收),当然在clear的时候还是要根据前面提到的计数器来判断是否清除。

struts1和struts2的区别

struts1的业务控制类必须继承ActionSupport,struts2可以不用继承

struts1是单例的,存在线程安全问题,struts2是多例的,不存在线程安全问题

struts1的业务控制类需要依赖servletAPI,struts2不需要

struts1对于页面请求的参数是通过一个ActionForm表单来收集的,struts2直接通过拦截器注入

struts1的业务流程是固定的(可以参考我的另一篇博客struts1原理),struts2可以通过拦截器改变这个流程

struts1是通过servlet来匹配所有的请求,struts2是通过filter来匹配所有的请求
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: