struts2源码初读(二)预处理

下面开始浏览struts2请求处理部分源码,最核心的方法doFilter


/**
* Dispatcher
*/
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
/**
* 实例化HttpServletRequest和HttpServletResponse
*/
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;

try {
/**
* 处理编码与本地化
*/
prepare.setEncodingAndLocale(request, response);
/**
* 创建action上下文
*/
prepare.createActionContext(request, response);
/**
* 关联当前的dispatcher与当前线程
*
*/
prepare.assignDispatcherToThread();
/**
* 判断请求的URL模式是不是规定的模式
*/
if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
/**
* 封装request对象
*/
request = prepare.wrapRequest(request);
/**
* xwork的ConfigurationManager读取struts.xml
* 返回值来自Dispatcher的Container
*/
ActionMapping mapping = prepare.findActionMapping(request, response, true);
/**
* 根据请求的URL,如果ActionMapping找不到,执行静态资源请求
* 如果找到就转发给action处理
*/
if (mapping == null) {
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
/**
* 在执行这个方法之前对http请求做预处理,为业务处理准备数据和运行环境
* 执行这个方法后把控制权交给xwork
* struts核心设计就是解耦合,消除核心程序对外部运行环境的依赖
* Web容器和MVC分离
*/
execute.executeAction(request, response, mapping);
}
}
} finally {
/**
* 清理本次请求相关内容
*/
prepare.cleanupRequest(request);
}
}

每一个请求过来就会调用一次doFilter方法,下面来看一下doFilter方法中都做了哪些操作。在实例化HttpServletRequest和HttpServletResponse后,设置请求的编码和国际化


/**
* PrepareOperations
*/
public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
dispatcher.prepare(request, response);
}

/**
* Dispacher
*/
public void prepare(HttpServletRequest request, HttpServletResponse response) {
String encoding = null;
if (defaultEncoding != null) {
encoding = defaultEncoding;
}

Locale locale = null;
if (defaultLocale != null) {
locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
}

if (encoding != null) {
try {
request.setCharacterEncoding(encoding);
} catch (Exception e) {
LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);
}
}

if (locale != null) {
response.setLocale(locale);
}

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


struts2用注解的方式在初始化的时候对属性赋值,类似与spring的依赖注入

在StrutsConstants中

public static final String STRUTS_LOCALE = "struts.locale";
public static final String STRUTS_I18N_ENCODING = "struts.i18n.encoding";


在default.properties中

# struts.locale=en_US
struts.i18n.encoding=UTF-8



@Inject(StrutsConstants.STRUTS_I18N_ENCODING)
public void setDefaultEncoding(String val) {
defaultEncoding = val;
}

@Inject(value=StrutsConstants.STRUTS_LOCALE, required=false)
public void setDefaultLocale(String val) {
defaultLocale = val;
}


所以在初始化的时候defaultEncoding赋值为UTF-8,stauts.locale被注掉了,同时注解设置了required=false,对不存在的属性,初始化注入的时候被忽略了,故defaultLocale为null

下面来看一下是如何创建action上下文,又做了哪些操作。我们知道每一个请求都会创建一个action上下文,不同的action不会共享action上下文,这是通过本地线程变量实现的。

public static ActionContext getContext() {
return (ActionContext) actionContext.get();
}
static ThreadLocal actionContext = new ThreadLocal();


下面来看看ActionContext中有哪些属性,除了一些静态常量外就只有Map<String, Object> context一个属性。

/**
* PrepareOperations
*/
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));
ctx = new ActionContext(stack.getContext());
}
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
ActionContext.setContext(ctx);
return ctx;
}


createActionContext中最重要的是dispatcher.createContextMap(request, response, null, servletContext),该方法就是将容器对象封装成普通java对象.这个方法是struts2核心设计,前面说过struts的核心设计就是解耦合,消除核心程序对外部运行环境的依赖,即Web容器和MVC分离,在创建action上下文的时候把web容器相关的数据Request,Session,
Applicatioin...封装成普通的java对象Map使得xwork不依赖与web容器,从而解耦和。

/**
* Dispatcher
*/
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);

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

if (mapping != null) {
extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
}
return extraContext;
}


createContextMap代码很清晰,先把容器对象封装成Map,在把这些Map作为value添加到contextMap中,创建之后再通过ActionContext.setContext(ctx)把context加到ThreadLocal中。

包装request的方法wrapRequest,在wrapRequest方法中又调用

/**
* Dispatcher
*/
public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
/**
* 判断有没有做过封装,确保只做一次封装
*/
if (request instanceof StrutsRequestWrapper) {
return request;
}

String content_type = request.getContentType();
/**
* 封装Request对象,判断content_type是不是multipart/form-data
* 如果是返回MultiPartRequestWrapper对象处理文件上传
* 如果不是返回StrutsRequestWrapper对象处理普通请求
*/
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 = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext));
} else {
request = new StrutsRequestWrapper(request);
}

return request;
}


接下来看一看prepare.findActionMapping(request, response, true)方法,在PrepareOperations的findActionMapping方法中又调用了dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager())
这里是调用ActionMapper的实现类DefaultActionMapper的getMapping方法,分析getMapping之前先看一下ActionMapping类的属性

/**
* action名
*/
private String name;
/**
* action的命名空间
*/
private String namespace;
/**
* action的执行方法
*/
private String method;
/**
* url后缀名
*/
private String extension;
/**
* 参数
*/
private Map<String, Object> params;
/**
* 返回的结果
*/
private Result result;



/**
* DefaultActionMapper
*/
public ActionMapping getMapping(HttpServletRequest request,
ConfigurationManager configManager) {
ActionMapping mapping = new ActionMapping();
/**
* 获取请求中的url
*/
String uri = getUri(request);
/**
* 去掉url中的参数
* eg:test/test.cation;id=1-->test/test.cation
*/
int indexOfSemicolon = uri.indexOf(";");
uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;
/**
* 去掉url中的后缀名test/test.cation-->test/test
*/
uri = dropExtension(uri, mapping);
if (uri == null) {
return null;
}
/**
* 解析Action的名称和命名空间
*/
parseNameAndNamespace(uri, mapping, configManager);
/**
* 去掉请求中的重复项
*/
handleSpecialParameters(request, mapping);

if (mapping.getName() == null) {
return null;
}
/**
* 处理test!mehtod格式的请求
*/
parseActionName(mapping);

return mapping;
}



/**
* DefaultActionMapper
*/
protected String getUri(HttpServletRequest request) {
/**
* 判断请求是否来自于一个jsp的include
* 如果通过属性"javax.servlet.include.servlet_path"取得url
*/
String uri = (String) request
.getAttribute("javax.servlet.include.servlet_path");
if (uri != null) {
return uri;
}

uri = RequestUtils.getServletPath(request);
if (uri != null && !"".equals(uri)) {
return uri;
}

uri = request.getRequestURI();
/**
* 去掉contextPath的路径
*/
return uri.substring(request.getContextPath().length());
}


下面看一下去除url后缀名的方法,先说一下extensions的赋值
在StrutsConstants中

String STRUTS_ACTION_EXTENSION = "struts.action.extension";


在default.properties中

struts.action.extension=action,,



@Inject(StrutsConstants.STRUTS_ACTION_EXTENSION)
public void setExtensions(String extensions) {
if (extensions != null && !"".equals(extensions)) {
List<String> list = new ArrayList<String>();
String[] tokens = extensions.split(",");
for (String token : tokens) {
list.add(token);
}
if (extensions.endsWith(",")) {
list.add("");
}
this.extensions = Collections.unmodifiableList(list);
} else {
this.extensions = null;
}
}

通过注解在初始化的时候赋值,所以extensions值为[action, ]


/**
* DefaultActionMapper
*/
protected String dropExtension(String name, ActionMapping mapping) {
if (extensions == null) {
return name;
}
for (String ext : extensions) {
if ("".equals(ext)) {
/**
* 如果name中不包含.
* 或name中最后一个点后还有/直接返回name
*/
int index = name.lastIndexOf('.');
if (index == -1 || name.indexOf('/', index) >= 0) {
return name;
}
} else {
String extension = "." + ext;
/**
* 如果name结尾匹配定义后缀,则去掉name的匹配部分
*/
if (name.endsWith(extension)) {
name = name.substring(0, name.length() - extension.length());
mapping.setExtension(ext);
return name;
}
}
}
return null;
}


parseNameAndNamespace解析action的名称和命名空间

/**
* DefaultActionMapper
*/
protected void parseNameAndNamespace(String uri, ActionMapping mapping,
ConfigurationManager configManager) {
String namespace, name;
int lastSlash = uri.lastIndexOf("/");
/**
* 如果处理过的url中不包含/或/在url的开头,那么namespace为""
*/
if (lastSlash == -1) {
namespace = "";
name = uri;
} else if (lastSlash == 0) {
namespace = "/";
name = uri.substring(lastSlash + 1);
} else if (alwaysSelectFullNamespace) {
/**
* alwaysSelectFullNamespace默认是false
* 判断是否把最后一个/前的字符全作为命名空间
*/
namespace = uri.substring(0, lastSlash);
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;
/**
* 匹配最长的命名空间
* eg:url test1/test2/test
* 如果配置文件有两个namespace test1/test2和test1
* 会匹配最长的那个test1/test2即贪婪匹配
*/
for (Object cfg : config.getPackageConfigs().values()) {
String ns = ((PackageConfig) cfg).getNamespace();
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 && name != null) {
int pos = name.lastIndexOf('/');
if (pos > -1 && pos < name.length() - 1) {
name = name.substring(pos + 1);
}
}

mapping.setNamespace(namespace);
mapping.setName(name);
}

根据请求的URL,如果ActionMapping找不到,执行静态资源请求,如果找到就转发给action处理,struts的预处理到此就结束了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值