2、过滤器中的doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 方法
2.1、request = prepareDispatcherAndWrapRequest(request, response);分析
我们知道JSTL默认是从page,request,session,application这四个Scope逐次查找相应的EL表达式所对应的对象的值。那么如果要使用JSTL来读取Action中的变量,就需要把Action中的变量,放到request域中才行Struts2,都使用另外一种整合方式:对HttpServletRequest进行装饰(StrutsRequestWrapper )这个类会在Struts2初始化的时候,替换HttpServletRequest,运行于整个Struts2的运行过程中,当我们试图调用request.getAttribute()的时候,就会执行上面的这个方法。(这是一个典型的装饰器模式)在执行上面的方法时,会首先调用HttpServletRequest中原本的request.getAttribute(),如果没有找到,它会继续到ValueStack中去查找,而action在ValueStack中,所以action中的变量通过OGNL表达式,就能找到对应的值了。
- protected HttpServletRequest prepareDispatcherAndWrapRequest(
- HttpServletRequest request, HttpServletResponse response)
- throws ServletException {
- //获取dispatch 的单例类,是由LocalThread 保存的,保证线程的安全
- Dispatcher du = Dispatcher.getInstance();
- // Prepare and wrap the request if the cleanup filter hasn't already,
- // cleanup filter should be
- // configured first before struts2 dispatcher filter, hence when its
- // cleanup filter's turn,
- // static instance of Dispatcher should be null.
- if (du == null) {
- //如果为空的话,值保存进LocalThread 中
- Dispatcher.setInstance(dispatcher);
- // prepare the request no matter what - this ensures that the proper
- // character encoding
- // is used before invoking the mapper (see WW-9127)
- // request 编码设置和response 本地化设置
- dispatcher.prepare(request, response);
- } else {
- dispatcher = du;
- }
- 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, getServletContext());
- } catch (IOException e) {
- String message = "Could not wrap servlet request with MultipartRequestWrapper!";
- LOG.error(message, e);
- throw new ServletException(message, e);
- }
- return request;
- }
简单看下这个方法Dispatcher.getInstance();
- private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>();
- //other code
- public static Dispatcher getInstance() {
- return instance.get();
- }
主要的包装在此方法进行request = dispatcher.wrapRequest(request, getServletContext());
- 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();
- //非表单提交的request 封装,主要是图片上传等
- if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
- MultiPartRequest multi = getContainer().getInstance(MultiPartRequest.class);
- //如果是非表单提交则包装成MultiPartRequestWrapper
- request = new MultiPartRequestWrapper(multi, request, getSaveDir(servletContext));
- } else {
- //如果是普通表单提交,在此包装
- request = new StrutsRequestWrapper(request);
- }
- return request;
- }
2.2、mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());方法的分析
这里的分析参照了:http://zddava.iteye.com/blog/215504
下面来看一下默认使用的ActionMapper实现DefaultActionMapper的#getMapping():
- public ActionMapping getMapping(HttpServletRequest request,
- ConfigurationManager configManager) {
- ActionMapping mapping = new ActionMapping();// (1)
- String uri = getUri(request);// (2)
- uri = dropExtension(uri);// (3)
- if (uri == null) {
- return null;
- }
- parseNameAndNamespace(uri, mapping, configManager);// (4)
- handleSpecialParameters(request, mapping);// (5)
- if (mapping.getName() == null) {
- return null;
- }
- if (allowDynamicMethodCalls) {// (6)
- String name = mapping.getName();
- int exclamation = name.lastIndexOf("!");
- if (exclamation != -1) {
- mapping.setName(name.substring(0, exclamation));
- mapping.setMethod(name.substring(exclamation + 1));
- }
- }
- return mapping;
- }
主要有6处需要重点说明:
(1) 关于ActionMapping类,它内部封装了如下5个字段:
- private String name;// Action名
- private String namespace;// Action名称空间
- private String method;// 执行方法
- private Map params;// 可以通过set方法设置的参数
- private Result result;// 返回的结果
这些在配置文件中都是可设置的,确定了ActionMapping类的各个字段的值,就可以对请求的Action进行调用了。
(2) String uri = getUri(request);
这个步骤用于获取客户端发送的请求的URI,源代码如下:
- String getUri(HttpServletRequest request) {
- // handle http dispatcher includes.
- 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();
- return uri.substring(request.getContextPath().length());
- }
这个方法首先判断请求是否来自于一个jsp的include,如果是,那么请求的"javax.servlet.include.servlet_path"属性可以获得include的页面uri,否则通过一般的方法获得请求的uri,最后返回去掉ContextPath的请求路径,比如http://127.0.0.1:8087/test/jsp/index.jsp?param=1,返回的为/jsp/index.jsp。去掉了ContextPath和查询字符串等。
(3) uri = dropExtension(uri); 负责去掉Action的"扩展名"(默认为"action"),源代码如下:
- String dropExtension(String name) {
- //extensions 为struts2 的后缀名,可有多个,默认为action
- // List extensions = new ArrayList() {{ add("action");}};
- if (extensions == null) {
- return name;
- }
- Iterator it = extensions.iterator();
- //分别遍历后去掉后缀名
- while (it.hasNext()) {
- String extension = "." + (String) it.next();
- if (name.endsWith(extension)) {
- name = name.substring(0, name.length() - extension.length());
- return name;
- }
- }
- return null;
- }
注意,这个步骤对于不是以特地扩展名结尾的请求会返回一个null的uri,进而#getMapping()也会返回null,FilterDispatcher的#doFilter()就会把这次请求当作一个普通请求对待了。
(4) parseNameAndNamespace(uri, mapping, configManager);
此方法用于解析Action的名称和命名空间,并赋给ActionMapping对象。源代码如下:
- void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) {
- String namespace, name;
- // 例如 http://127.0.0.1:8087/teststruts/namespace/name.action?param=1
- // dropExtension()后,获得uri为/namespace/name
- int lastSlash = uri.lastIndexOf("/");
- 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);// 获得字符串 namespace
- name = uri.substring(lastSlash + 1);// 获得字符串 name
- } else {
- // 例如 http://127.0.0.1:8087/teststruts/namespace1/namespace2/actionname.action?param=1
- // dropExtension()后,获得uri为/namespace1/namespace2/actionname
- Configuration config = configManager.getConfiguration();
- String prefix = uri.substring(0, lastSlash);// 获得 /namespace1/namespace2
- namespace = "";
- // 如果配置文件中有一个包的namespace是 /namespace1/namespace2,那么namespace为/namespace1/namespace2,name为actionname
- // 如果配置文件中有一个包的namespace是 /namespace1,那么namespace为/namespace1,name为/namespace2/actionname
- for (Iterator i = config.getPackageConfigs().values().iterator(); i
- .hasNext();) {
- String ns = ((PackageConfig) i.next()).getNamespace();
- if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {
- if (ns.length() > namespace.length()) {
- namespace = ns;
- }
- }
- }
- name = uri.substring(namespace.length() + 1);
- }
- if (!allowSlashesInActionNames && name != null) {// allowSlashesInActionNames代表是否允许"/"出现在Action的名称中,默认为false
- int pos = name.lastIndexOf('/');
- if (pos > -1 && pos < name.length() - 1) {
- name = name.substring(pos + 1);
- }
- }// 以 name = /namespace2/actionname 为例,经过这个if块后,name = actionname
- mapping.setNamespace(namespace);
- mapping.setName(name);
- }
(5) handleSpecialParameters(request, mapping); 此方法用于处理Struts框架定义的四种特殊的prefix:
下边是struts2的javadoc里提供的例子:
Method prefix:调用baz的另外一个方法"anotherMethod"而不是"execute"
- <a:form action="baz">
- <a:textfield label="Enter your name" name="person.name"/>
- <a:submit value="Create person"/>
- <a:submit name="method:anotherMethod" value="Cancel"/>
- </a:form>
Action prefix:调用anotherAction的"execute"
- <a:form action="baz">
- <a:textfield label="Enter your name" name="person.name"/>
- <a:submit value="Create person"/>
- <a:submit name="action:anotherAction" value="Cancel"/>
- </a:form>
Redirect prefix:将请求重定向,下例中为定向到google
- <a:form action="baz">
- <a:textfield label="Enter your name" name="person.name"/>
- <a:submit value="Create person"/>
- <a:submit name="redirect:www.google.com" value="Cancel"/>
- </a:form>
Redirect-action prefix:重定向action,下例中为定向到dashboard.action
- <a:form action="baz">
- <a:textfield label="Enter your name" name="person.name"/>
- <a:submit value="Create person"/>
- <a:submit name="redirect-action:dashboard" value="Cancel"/>
- </a:form>
handleSpecialParameters的源代码如下:
- public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) {
- Set<String> uniqueParameters = new HashSet<String>();
- Map parameterMap = request.getParameterMap();
- for (Iterator iterator = parameterMap.keySet().iterator(); iterator.hasNext();) {
- String key = (String) iterator.next();
- if (key.endsWith(".x") || key.endsWith(".y")) {// 去掉图片按钮的位置信息,具体情况我也不是很了解
- key = key.substring(0, key.length() - 2);
- }
- // 处理四种特殊的prefix:Method prefix,Action prefix,Redirect prefix,Redirect-action prefix
- if (!uniqueParameters.contains(key)) {
- ParameterAction parameterAction = (ParameterAction) prefixTrie.get(key);
- if (parameterAction != null) {// 当发现某种特殊的predix时
- parameterAction.execute(key, mapping);// 调用它的execute方法,在DefaultActionMapper的构造函数中定义
- uniqueParameters.add(key);// 下边已经break了为什么还要把key加入到排重的Set里呢??
- break;
- }
- }
- }
- }
(6) 处理调用的不是execute方法的情况:
Struts框架也可以处理"name!method"形式的action调用,碰到这种情况,在此处将name和method分别解析出来然后赋给ActionMapping对象。