struts2 处理请求流程分析(结合源码)2

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表达式,就能找到对应的值了。

 

Java代码   收藏代码
  1. protected HttpServletRequest prepareDispatcherAndWrapRequest(  
  2.         HttpServletRequest request, HttpServletResponse response)  
  3.         throws ServletException {  
  4.     //获取dispatch 的单例类,是由LocalThread 保存的,保证线程的安全  
  5.     Dispatcher du = Dispatcher.getInstance();  
  6.     // Prepare and wrap the request if the cleanup filter hasn't already,  
  7.     // cleanup filter should be  
  8.     // configured first before struts2 dispatcher filter, hence when its  
  9.     // cleanup filter's turn,  
  10.     // static instance of Dispatcher should be null.  
  11.     if (du == null) {  
  12.         //如果为空的话,值保存进LocalThread 中  
  13.         Dispatcher.setInstance(dispatcher);  
  14.         // prepare the request no matter what - this ensures that the proper  
  15.         // character encoding  
  16.         // is used before invoking the mapper (see WW-9127)  
  17.         // request 编码设置和response 本地化设置  
  18.         dispatcher.prepare(request, response);  
  19.     } else {  
  20.         dispatcher = du;  
  21.     }  
  22.   
  23.     try {  
  24.         // Wrap request first, just in case it is multipart/form-data  
  25.         // parameters might not be accessible through before encoding  
  26.         // (ww-1278)  
  27.         //在这里就开始包装  
  28.         request = dispatcher.wrapRequest(request, getServletContext());  
  29.     } catch (IOException e) {  
  30.         String message = "Could not wrap servlet request with MultipartRequestWrapper!";  
  31.         LOG.error(message, e);  
  32.         throw new ServletException(message, e);  
  33.     }  
  34.     return request;  
  35. }  

 

 简单看下这个方法Dispatcher.getInstance();

Java代码   收藏代码
  1. private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>();  
  2. //other code  
  3. public static Dispatcher getInstance() {  
  4.         return instance.get();  
  5. }  

 

主要的包装在此方法进行request = dispatcher.wrapRequest(request, getServletContext());

Java代码   收藏代码
  1. public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {  
  2.     // don't wrap more than once 如果已经包装了,就不用再包装了  
  3.     if (request instanceof StrutsRequestWrapper) {  
  4.         return request;  
  5.     }  
  6.     String content_type = request.getContentType();  
  7.     //非表单提交的request 封装,主要是图片上传等   
  8.     if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {  
  9.         MultiPartRequest multi = getContainer().getInstance(MultiPartRequest.class);  
  10.         //如果是非表单提交则包装成MultiPartRequestWrapper  
  11.         request = new MultiPartRequestWrapper(multi, request, getSaveDir(servletContext));  
  12.     } else {  
  13.         //如果是普通表单提交,在此包装  
  14.         request = new StrutsRequestWrapper(request);  
  15.     }  
  16.     return request;  
  17. }  

 

2.2、mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());方法的分析

   这里的分析参照了:http://zddava.iteye.com/blog/215504

下面来看一下默认使用的ActionMapper实现DefaultActionMapper的#getMapping():

Java代码   收藏代码
  1. public ActionMapping getMapping(HttpServletRequest request,  
  2.         ConfigurationManager configManager) {  
  3.     ActionMapping mapping = new ActionMapping();// (1)  
  4.     String uri = getUri(request);// (2)  
  5.   
  6.     uri = dropExtension(uri);// (3)  
  7.     if (uri == null) {  
  8.         return null;  
  9.     }  
  10.   
  11.     parseNameAndNamespace(uri, mapping, configManager);// (4)  
  12.   
  13.     handleSpecialParameters(request, mapping);// (5)  
  14.   
  15.     if (mapping.getName() == null) {  
  16.         return null;  
  17.     }  
  18.   
  19.     if (allowDynamicMethodCalls) {// (6)  
  20.         String name = mapping.getName();  
  21.         int exclamation = name.lastIndexOf("!");  
  22.         if (exclamation != -1) {  
  23.             mapping.setName(name.substring(0, exclamation));  
  24.             mapping.setMethod(name.substring(exclamation + 1));  
  25.         }  
  26.     }  
  27.   
  28.     return mapping;  
  29. }  

 

主要有6处需要重点说明: 

(1) 关于ActionMapping类,它内部封装了如下5个字段:

Java代码   收藏代码
  1. private String name;// Action名  
  2. private String namespace;// Action名称空间  
  3. private String method;// 执行方法  
  4. private Map params;// 可以通过set方法设置的参数  
  5. private Result result;// 返回的结果  

 

这些在配置文件中都是可设置的,确定了ActionMapping类的各个字段的值,就可以对请求的Action进行调用了。 

(2) String uri = getUri(request); 

这个步骤用于获取客户端发送的请求的URI,源代码如下:

Java代码   收藏代码
  1. String getUri(HttpServletRequest request) {  
  2.     // handle http dispatcher includes.  
  3.     String uri = (String) request.getAttribute("javax.servlet.include.servlet_path");  
  4.     if (uri != null) {  
  5.         return uri;  
  6.     }  
  7.   
  8.     uri = RequestUtils.getServletPath(request);  
  9.     if (uri != null && !"".equals(uri)) {  
  10.         return uri;  
  11.     }  
  12.   
  13.     uri = request.getRequestURI();  
  14.     return uri.substring(request.getContextPath().length());  
  15. }  

 

     这个方法首先判断请求是否来自于一个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"),源代码如下:

 

Java代码   收藏代码
  1. String dropExtension(String name) {  
  2.     //extensions 为struts2 的后缀名,可有多个,默认为action  
  3.     // List extensions = new ArrayList() {{ add("action");}};  
  4.        if (extensions == null) {  
  5.            return name;  
  6.        }  
  7.        Iterator it = extensions.iterator();  
  8.        //分别遍历后去掉后缀名  
  9.        while (it.hasNext()) {  
  10.            String extension = "." + (String) it.next();  
  11.            if (name.endsWith(extension)) {  
  12.                name = name.substring(0, name.length() - extension.length());  
  13.                return name;  
  14.            }  
  15.        }  
  16.        return null;  
  17.    }  

 

注意,这个步骤对于不是以特地扩展名结尾的请求会返回一个null的uri,进而#getMapping()也会返回null,FilterDispatcher的#doFilter()就会把这次请求当作一个普通请求对待了。 

(4) parseNameAndNamespace(uri, mapping, configManager); 

此方法用于解析Action的名称和命名空间,并赋给ActionMapping对象。源代码如下:

Java代码   收藏代码
  1.    void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) {  
  2.        String namespace, name;  
  3. // 例如 http://127.0.0.1:8087/teststruts/namespace/name.action?param=1   
  4. // dropExtension()后,获得uri为/namespace/name   
  5.        int lastSlash = uri.lastIndexOf("/");  
  6.        if (lastSlash == -1) {  
  7.            namespace = "";  
  8.            name = uri;  
  9.        } else if (lastSlash == 0) {  
  10.            namespace = "/";  
  11.            name = uri.substring(lastSlash + 1);  
  12.        } else if (alwaysSelectFullNamespace) {// alwaysSelectFullNamespace默认为false,代表是否将最后一个"/"前的字符全作为名称空间。  
  13.            namespace = uri.substring(0, lastSlash);// 获得字符串 namespace  
  14.            name = uri.substring(lastSlash + 1);// 获得字符串 name  
  15.        } else {  
  16.     // 例如 http://127.0.0.1:8087/teststruts/namespace1/namespace2/actionname.action?param=1  
  17.                  // dropExtension()后,获得uri为/namespace1/namespace2/actionname   
  18.            Configuration config = configManager.getConfiguration();  
  19.            String prefix = uri.substring(0, lastSlash);// 获得 /namespace1/namespace2  
  20.            namespace = "";  
  21.            // 如果配置文件中有一个包的namespace是 /namespace1/namespace2,那么namespace为/namespace1/namespace2,name为actionname  
  22.            // 如果配置文件中有一个包的namespace是 /namespace1,那么namespace为/namespace1,name为/namespace2/actionname  
  23.            for (Iterator i = config.getPackageConfigs().values().iterator(); i  
  24.                    .hasNext();) {  
  25.                String ns = ((PackageConfig) i.next()).getNamespace();  
  26.                if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {  
  27.                    if (ns.length() > namespace.length()) {  
  28.                        namespace = ns;  
  29.                    }  
  30.                }  
  31.            }  
  32.   
  33.            name = uri.substring(namespace.length() + 1);  
  34.        }  
  35.   
  36.        if (!allowSlashesInActionNames && name != null) {// allowSlashesInActionNames代表是否允许"/"出现在Action的名称中,默认为false  
  37.            int pos = name.lastIndexOf('/');  
  38.            if (pos > -1 && pos < name.length() - 1) {  
  39.                name = name.substring(pos + 1);  
  40.            }  
  41.        }// 以 name = /namespace2/actionname 为例,经过这个if块后,name = actionname  
  42.   
  43.        mapping.setNamespace(namespace);  
  44.        mapping.setName(name);  
  45.    }  

 

(5) handleSpecialParameters(request, mapping); 此方法用于处理Struts框架定义的四种特殊的prefix: 

下边是struts2的javadoc里提供的例子: 

Method prefix:调用baz的另外一个方法"anotherMethod"而不是"execute"

Html代码   收藏代码
  1. <a:form action="baz">  
  2.     <a:textfield label="Enter your name" name="person.name"/>  
  3.     <a:submit value="Create person"/>  
  4.     <a:submit name="method:anotherMethod" value="Cancel"/>  
  5. </a:form>  

 

Action prefix:调用anotherAction的"execute"

Html代码   收藏代码
  1. <a:form action="baz">  
  2.     <a:textfield label="Enter your name" name="person.name"/>  
  3.     <a:submit value="Create person"/>  
  4.     <a:submit name="action:anotherAction" value="Cancel"/>  
  5. </a:form>  

 

Redirect prefix:将请求重定向,下例中为定向到google

Html代码   收藏代码
  1. <a:form action="baz">  
  2.     <a:textfield label="Enter your name" name="person.name"/>  
  3.     <a:submit value="Create person"/>  
  4.     <a:submit name="redirect:www.google.com" value="Cancel"/>  
  5. </a:form>  

 

Redirect-action prefix:重定向action,下例中为定向到dashboard.action

Html代码   收藏代码
  1. <a:form action="baz">  
  2.     <a:textfield label="Enter your name" name="person.name"/>  
  3.     <a:submit value="Create person"/>  
  4.     <a:submit name="redirect-action:dashboard" value="Cancel"/>  
  5. </a:form>  

 

handleSpecialParameters的源代码如下:

Java代码   收藏代码
  1. public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) {  
  2.     Set<String> uniqueParameters = new HashSet<String>();  
  3.     Map parameterMap = request.getParameterMap();  
  4.     for (Iterator iterator = parameterMap.keySet().iterator(); iterator.hasNext();) {  
  5.         String key = (String) iterator.next();  
  6.           
  7.         if (key.endsWith(".x") || key.endsWith(".y")) {// 去掉图片按钮的位置信息,具体情况我也不是很了解  
  8.             key = key.substring(0, key.length() - 2);  
  9.         }  
  10.   
  11.         // 处理四种特殊的prefix:Method prefix,Action prefix,Redirect prefix,Redirect-action prefix  
  12.         if (!uniqueParameters.contains(key)) {  
  13.             ParameterAction parameterAction = (ParameterAction) prefixTrie.get(key);  
  14.             if (parameterAction != null) {// 当发现某种特殊的predix时  
  15.                 parameterAction.execute(key, mapping);// 调用它的execute方法,在DefaultActionMapper的构造函数中定义  
  16.                 uniqueParameters.add(key);// 下边已经break了为什么还要把key加入到排重的Set里呢??  
  17.                 break;  
  18.             }  
  19.         }  
  20.     }  
  21. }  

 (6) 处理调用的不是execute方法的情况: 
Struts框架也可以处理"name!method"形式的action调用,碰到这种情况,在此处将name和method分别解析出来然后赋给ActionMapping对象。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值