`

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

 

	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对象。 

分享到:
评论

相关推荐

    Struts2源码分析

    总的来说,Struts2的源码分析可以帮助开发者深入理解其内部机制,包括请求处理流程、拦截器的运作方式以及视图组件的实现。这有助于提高应用的可维护性,优化性能,并使开发者能够更好地定制和扩展框架。对于熟悉...

    struts2框架源码

    1. **Action**:在Struts2中,Action类是业务逻辑处理的主要部分,它是请求处理的中心。Action类通常继承自`com.opensymphony.xwork2.ActionSupport`,并重写execute方法来执行特定的业务逻辑。 2. **Action ...

    struts2案例 struts2 struts2源码

    下面,我们将深入探讨Struts2的核心概念、源码分析以及如何利用它来创建实际的案例。 首先,Struts2的架构基于Action和Result的设计模式,Action是业务逻辑的载体,Result则是处理Action执行后展示结果的方式。这种...

    Struts2权威指南 加源码

    总的来说,《Struts2权威指南》结合源码阅读,可以帮助读者不仅掌握Struts2的基本使用,还能深入到框架底层,提高问题排查和优化能力。同时,对于Spring和Hibernate的集成部分,也能提升开发者在大型项目中的协作和...

    struts2入门学习源码

    Struts2的核心组件包括Action、Result、Interceptor等,它们协同工作,实现了请求处理、业务逻辑执行和视图展示。 1. **Action**:Action是Struts2的核心,它负责接收用户的请求并进行业务逻辑处理。开发者可以通过...

    struts2源码解析.pdf

    Struts2是一个流行的Java Web框架,它为开发者提供了一种结构化的MVC(Model-View-...通过分析源码,我们可以发现Struts2是如何优雅地处理请求、管理Action状态以及与其他Web组件交互的,从而提升我们的编程技能。

    struts2下的xwork源码

    通过分析以上关键组件的源码,我们可以理解Struts2的请求处理流程,以及如何扩展和定制自己的拦截器、Action和Result。这将有助于提高我们在实际项目中的问题定位和性能优化能力。在阅读源码时,建议结合实际应用...

    struts2,hibernate,spring整合源码,配置文件,jar包

    Struts2、Hibernate和Spring是Java开发中三大主流框架,...同时,Struts2和Hibernate的结合使得Web请求到数据库操作的流程更加顺畅。这种整合方式在现代Java Web开发中非常常见,是提升开发效率和项目质量的有效途径。

    struts2数据封装源码

    这个"struts2数据封装源码"很可能是为了演示如何在Struts2框架下处理用户输入数据并进行封装的过程。在Struts2中,数据封装是通过Action类和模型对象(通常称为POJOs,Plain Old Java Objects)来实现的,这使得业务...

    struts2源码

    在Struts2中,Action和Command的设计模式相结合,使得业务处理更加灵活。Action作为Web层的入口,负责接收HTTP请求,而Command对象则执行实际的业务逻辑。Validator则提供了数据验证功能,可以对用户输入进行校验,...

    Struts2权威指南源码(完整),不含JAR文件

    2. **请求处理流程**:跟踪一个HTTP请求从进入Struts2到返回响应的整个过程,重点关注ActionInvocation、Interceptor的执行顺序。 3. **拦截器实现**:深入研究默认拦截器的实现,如PrepareInterceptor、...

    spring3、struts2、mybatis结合的一个简单web实现

    Struts2处理请求,Spring管理业务逻辑和依赖,MyBatis处理数据持久化,而Tiles则负责视图的组装。这种架构模式在实际项目中非常常见,对于初学者来说,理解和掌握这些框架的整合是提高开发技能的关键步骤。通过分析...

    Struts2源码

    结合源码阅读和PPT讲解,你可以逐步掌握Struts2的核心概念,提升在实际项目中的应用能力。同时,也要关注Struts2与其他框架(如Spring、Hibernate)的整合,以及如何利用Struts2实现RESTful服务。通过深入学习,你将...

    Struts2 poi动态导入导出Excel源码示例

    综上所述,这个示例项目提供了Struts2与POI结合使用来实现Excel动态导入导出的完整流程,对于学习和理解这两种技术在实际项目中的应用具有很高的参考价值。通过阅读源码和运行示例,开发者可以更好地掌握这些技术,...

    struts2 相关教程和源码分析(集合)

    分析Struts2的源码可以帮助我们理解其内部工作流程,例如Filter Dispatcher如何分发请求,ActionInvocation如何执行Action,Interceptor链如何工作等。通过源码阅读,可以提高对框架的深度理解和定制能力。 7. **...

    struts2 权威指南 源码

    7. **异常处理**:Struts2提供了一套完整的异常处理机制,源码中可以看到如何自定义异常处理策略。 8. **Tiles框架集成**:Tiles是用于创建可重用的页面布局的框架,Struts2可以与之结合使用。源码中可能会有Tiles...

    struts-1.2.9源码

    通过对Struts 1.2.9源码的深入学习,开发者可以了解Web应用的典型开发流程,掌握如何有效地组织和管理复杂的业务逻辑,以及如何优雅地处理用户交互。虽然Struts 1已逐渐被Struts 2和Spring MVC等更新框架替代,但它...

    Struts2权威指南全部源码

    5. **请求处理流程**:跟随请求从Servlet容器到Struts2的流程,理解Struts2如何拦截和处理请求。 6. **异常处理**:研究Struts2如何处理异常,以及如何自定义异常处理策略。 7. **国际化与本地化**:分析Struts2...

    Struts2+IText动态导出PDF示例源码

    在这个"Struts2+IText动态导出PDF示例源码"项目中,开发者利用这两者结合,实现了在Web应用中动态生成PDF文件的功能。这在报表生成、合同制作、证书打印等场景中非常实用。 首先,Struts2作为控制器层框架,负责...

    博客网源码 结合struts2和JSP,hibernate

    博客网源码是一款基于Struts2和JSP技术,结合Hibernate ORM框架开发的网站系统。Struts2是一个强大的MVC框架,它提供了丰富的控制结构,用于处理用户请求并将其映射到相应的业务逻辑。JSP(JavaServer Pages)是Java...

Global site tag (gtag.js) - Google Analytics