struct2源码解读之处理Action请求
我们前面讨论过了struct2的初始化,我们先来回顾下
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);
init.initLogging(config);
//解析配置文件,并返回一个封装了封装配置信息的Dispacher对象
Dispatcher dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);
//返回一个处理action请求的对象
prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
//返回一个执行action请求的对象
execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
//黑名单列表
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
} finally {
init.cleanup();
}
}
从上面可以看出,struct2的初始化其实就是封装配置文件信息到Disacher对象,然后根据这个对象实例化两个对象:一个是用来处理action请求的PrepareOperations对象;一个是用来执行action请求的
ExecuteOperations对象,当有request请求的时候,就会调用这两个对象的方法来处理和执行请求。我们知道,当客户端放送请求的时候,会执行过滤器的doFilter()方法。我们来看看,StrutsPrepareAndExecuteFilter的doFilter()方法。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//1.处理aciton请求
try {
//设置编码和本地化信息
prepare.setEncodingAndLocale(request, response);
//创建actionContext上下文
prepare.createActionContext(request, response);
//把dispacher添加到线程
prepare.assignDispatcherToThread();
//判断是否在黑名单内
if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
//在黑名单,跳过,转到下一个过滤器
chain.doFilter(request, response);
} else {
//处理request请求
request = prepare.wrapRequest(request);
ActionMapping mapping = prepare.findActionMapping(request, response, true);
//如果找不到这个action
if (mapping == null) {
//执行静态文件
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
//如果找不到这个静态文件,跳过,转到下一个过滤器
chain.doFilter(request, response);
}
} else {
//2.找到action就执行action请求
execute.executeAction(request, response, mapping);
}
}
} finally {
prepare.cleanupRequest(request);
}
}
初始化为我们准备了PrepareOperations对象和ExecuteOperations对象还有Dispacher,我们下面来看看struct2是如何利用这些对象处理action请求的。这里分两步,一步是处理aciton请求,包括解析url和找到action对应的方法;另一步是执行这个action的方法。
一、处理aciton请求(PrepareOperations对象)
处理aciton请求,都是调用PrepareOperations对象的方法来进行处理。
1.1.设置编码和国际化信息
prepare.setEncodingAndLocale(request, response);
在PrepareOperations.setEncodingAndLocale()方法中调用了dispatcher.prepare()方法,在这个方法中设置了编码信息和国际化信息。
public void prepare(HttpServletRequest request, HttpServletResponse response) {
String encoding = null;
/**
* @Inject(StrutsConstants.STRUTS_I18N_ENCODING)
* public void setDefaultEncoding(String val) {
* defaultEncoding = val;
* }
*编码为xml中STRUTS_I18N_ENCODING中设置的值,默认为utf-8
*/
if (defaultEncoding != null) {
encoding = defaultEncoding;
}
Locale locale = null;
/**
* @Inject(value=StrutsConstants.STRUTS_LOCALE, required=false)
* public void setDefaultLocale(String val) {
* defaultLocale = val;
* }
*国际化信息为STRUTS_LOCALE中设置的值,默认不设置
*/
if (defaultLocale != null) {
locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
}
if (encoding != null) {
try {
//设置编码
request.setCharacterEncoding(encoding);
} //异常信息
}
if (locale != null) {
//设置国际化信息
response.setLocale(locale);
}
if (paramsWorkaroundEnabled) {
request.getParameter("foo");
}
}
在初始化的时候,通过container.inject(this)对dispacher进行了依赖注入,defaultEncoding和defaultLocale都标有@inject注解,这两个值就在这个时候设置好了。前面解析配置文件的时候,把这些值以key-value封装到了propertyContainer对象中,因此依赖注入的时候,通过value=key找到这些值并进行赋值。
1.2.创建actionContext上下文
prepare.createActionContext(request, response);
actionContext,顾名思义,指的是action请求的环境
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
ActionContext ctx;
//计数器,初始值为1,一次请求+1,请求执行完后保存到request的CLEANUP_RECURSION_COUNTER变量中
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));
ctx = new ActionContext(stack.getContext());
}
//保存计数器的值
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
//把actionContext保存到线程变量ThreadLocal中
ActionContext.setContext(ctx);
return ctx;
}
actionContext是一个Theadloacl类型,因此每个请求都是线程安全的。通过get()和set()对这个变量进行存取值。在创建actionContext的过程中,实例化了一个valueStack值栈对象。通过 dispatcher.getContainer()获得Container,然后通过Container.getInstance()取出容器中的对象,这个ValueStackFactory在strcut2-default.xml中就已配置好了。
<bean type="com.opensymphony.xwork2.util.ValueStackFactory" name="struts" class="com.opensymphony.xwork2.ognl.OgnlValueStackFactory" />
查看OgnlValueStackFactory对象的createValueStack()方法
public ValueStack createValueStack() {
//实例化一个ValueStack对象
ValueStack stack = new OgnlValueStack(xworkConverter, compoundRootAccessor, textProvider, allowStaticMethodAccess);
//对valuestack依赖注入
container.inject(stack);
//把container以一个map的形式保存到valuestack的context 属性中
stack.getContext().put(ActionContext.CONTAINER, container);
return stack;
}
值栈的context其实就是一个map,除了把container放进这个map中
dispatcher.createContextMap(request, response, null, servletContext)
这句话也把request,response,servletContext,application,session,params也放进了context这个map中.由此看出,struct2把所有值都放进了值栈valueStack.context中,而context是actionContext的一个属性,因此这个actionContext就涵盖了struct2运行所需的值,也就搭起了struct2的运行环境。
1.3.添加到线程
prepare.assignDispatcherToThread();
这句话,其实是把dispacher设置到一个线程变量ThreadLoacl<dispacher>中
private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>();
public static void setInstance(Dispatcher instance) {
Dispatcher.instance.set(instance);
}
1.4.判断是否是黑名单
if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
//如果设置了黑名单而且在请求在黑名单内,跳过,执行下一个过滤器
chain.doFilter(request, response);
} else {
//如果没设置黑名单或者是不在黑名单内,处理aciton请求
}
这个excludedPatterns是一个list<Pattern>集合,是一个正则表达式经编译后的表现模式.在struct2初始化的时候,我们对这个值进行设定
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
而这个黑名单,来源于我们在strcuts.xml文件中对struts.action.excludePattern的配置
public List<Pattern> buildExcludedPatternsList( Dispatcher dispatcher ) {
//获得struts.action.excludePattern的值
return buildExcludedPatternsList(dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN));
}
dispatcher.getContainer()获得Container,然后通过Container.getInstance()取出容器中的struts.action.excludePattern,这个在我们解析*.xml的时候已封装好。然后把这个值进一步封装到list<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集合中
list.add(Pattern.compile(token.trim()));
}
return Collections.unmodifiableList(list);
} else {
return null;
}
}
如果设置了黑名单,把黑名单填加到一个list集合中后,PrepareOperations提供了一个isUrlExcluded()方法来对黑名单进行筛选
public boolean isUrlExcluded( HttpServletRequest request, List<Pattern> excludedPatterns ) { //如果设置了黑名单
if (excludedPatterns != null) {
//获得请求url
String uri = getUri(request);
//循环遍历
for ( Pattern pattern : excludedPatterns ) {
//pattern.matcher(uri)生成一个给定命名的Matcher对象,调用该对象的matches()方法进行匹配检测,只有整个目标字符串完全匹配时才返回真值
if (pattern.matcher(uri).matches()) {
return true;
}
}
}
return false;
}
1.5.处理request请求
如果通过了黑名单的匹配检测,则处理request请求
request = prepare.wrapRequest(request);
我们知道,普通的request请求和文件上传时的request请求时的request请求类型是不一样的,这一步就是对不同类型的request请求,进行封装
public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
// 默认为StrutsRequestWrapper类型
if (request instanceof StrutsRequestWrapper) {
return request;
}
//获得request请求的类型
String content_type = request.getContentType();
//如果request类型不为空且为multipart/form-data类型
if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
MultiPartRequest mpr = null;
//check for alternate implementations of MultiPartRequest
Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class);
if (multiNames != null) {
for (String multiName : multiNames) {
if (multiName.equals(multipartHandlerName)) {
mpr = getContainer().getInstance(MultiPartRequest.class, multiName);
}
}
}
if (mpr == null ) {
mpr = getContainer().getInstance(MultiPartRequest.class);
}
//把request封装成MultiPartRequestWrapper对象
request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext));
} else {
//其它的封装成StrutsRequestWrapper对象
request = new StrutsRequestWrapper(request);
}
return request;
}
1.6.处理action请求
ActionMapping mapping = prepare.findActionMapping(request, response, true);
当前面设置好编码和黑名单都检测通过后,就开始要处理action请求了,处理action请求,其实就是
把url信息封装到一个actionMapping对象。
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
//因为封装后的ActionMapping对象,最后会保存到request的STRUTS_ACTION_MAPPING_KEY变量中,因此在封装前要先判断
ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
if (mapping == null || forceLookup) {
try {
//获得ActionMapper对象,调用其getMapping()方法封装url信息
mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
if (mapping != null) {
//mapping值不为空时,设置到request的STRUTS_ACTION_MAPPING_KEY变量中
request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
}
} //异常信息
}
}
return mapping;
}
特别注意,ActionMapper是一个操作者,而ActionMapping是一个保存信息的对象,通过ActionMapper.getMapping()方法,获得一个ActionMapping对象
public ActionMapping getMapping(HttpServletRequest request,
ConfigurationManager configManager) {
ActionMapping mapping = new ActionMapping();
//获得uri,如url="http:\\localhost:8080\playwell\back\login.action",则uri为"playwell\back\login.action?id=1!method"
String uri = getUri(request);
//以分号分割
int indexOfSemicolon = uri.indexOf(";");
//如果有分号,全第一个分号前面的,否则的话,取原值
uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;
//删除后缀,extension默认为aciton和"",如果为空,找最后一个“.”,找不到这个点才返回原值,如果是action,判断uri是否endwith action,是的话,取“.”前面的值,然后mapping.setExtension();
uri = dropExtension(uri, mapping);
if (uri == null) {
return null;
}
/*
*
*设置命名空间和action名。这里对url作逻辑判断,分离出命名空间和action名
* mapping.setNamespace(namespace);
* mapping.setName(name);
*
*/
parseNameAndNamespace(uri, mapping, configManager);
//处理params
handleSpecialParameters(request, mapping);
if (mapping.getName() == null) {
return null;
}
//mapping.setMethod(),处理!method
parseActionName(mapping);
return mapping;
}
这一步主要是对url进行解析工作,把url的每一个字段分割出来,并封装到actionMapping对象中,大多都是字符串操作,在这就不过多叙述了,下面附上actionMapping的部分代码以加深大家对这一步操作的理解
public class ActionMapping {
private String name; //记录action名
private String namespace; //记录命名空间
private String method; //记录!method
private String extension; //记录后缀
private Map<String, Object> params;
private Result result;
}
二、执行action请求
解析完url,把url封装到actionMapping对象中后,就开始执行action请求。这里又为分actionMapping为空和actionMapping不为空两种情况。什么情况下actionMapping为空呢?我们看回actionMapping的封装过程
if (uri == null) {
return null;
}
if (mapping.getName() == null) {
return null;
}
当uri地址为空或者是actionname为空时返回一个空的actionMapping对象
2.1.找不到action
当返回一个空的actionMapping对象时,struct2会寻找静态资源,如果连静态文件都找不到,则会调过当前过滤器,去到下一个过滤器
if (mapping == null) {
//执行静态资源
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
//跳到下一个过滤器
chain.doFilter(request, response);
}
}
在这个过程中,struct2会加载哪些静态资源呢?
public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String resourcePath = RequestUtils.getServletPath(request);
if ("".equals(resourcePath) && null != request.getPathInfo()) {
resourcePath = request.getPathInfo();
}
//取得StaticContentLoader对象
StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class);
if (staticResourceLoader.canHandle(resourcePath)) {
staticResourceLoader.findStaticResource(resourcePath, request, response);
//加载静态资源,否则的话抛出404错误。response.sendError(HttpServletResponse.SC_NOT_FOUND);
return true;
} else {
// 给跳到下一个过滤器设置标志
return false;
}
}
canHandle里面表明,以/structs或者是/static开头的url的静态资源才会被加载。
public boolean canHandle(String resourcePath) {
return serveStatic && (resourcePath.startsWith("/struts") || resourcePath.startsWith("/static"));
}
2.2.找到action
找到action后就开始正常执行action请求。这个在下篇博文继续探讨。
三、总结
本篇博文探讨了strcut2是如何处理action请求的:利用PrepareOperations对象的方法,设置了编码信息和本地化信息,设置了action的上下文actionContext,并处理了黑名单信息,最后封装了url信息到 actionMapping对象,利用actionMapping对象的去执行相应的请求,如actionMapping为空时就尝试去加载静态资源,不为空就取执行相应的操作。
转载于:https://blog.51cto.com/yoyanda/1713317