SpringMVC配置文件解析(五)


前言

我们接着讲解SpringMVC处理静态资源的方法,这一篇要讲解的是

<mvc:default-servlet-handler/>

配置处理静态资源的原理

mvc:default-servlet-handler的解析

不多说废话了,直接来到DefaultServletHandlerBeanDefinitionParser的parse方法

@Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        Object source = parserContext.extractSource(element);

        String defaultServletName = element.getAttribute("default-servlet-name");
        RootBeanDefinition defaultServletHandlerDef = new RootBeanDefinition(DefaultServletHttpRequestHandler.class);
        defaultServletHandlerDef.setSource(source);
        defaultServletHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        if (StringUtils.hasText(defaultServletName)) {
            defaultServletHandlerDef.getPropertyValues().add("defaultServletName", defaultServletName);
        }
        String defaultServletHandlerName = parserContext.getReaderContext().generateBeanName(defaultServletHandlerDef);
        parserContext.getRegistry().registerBeanDefinition(defaultServletHandlerName, defaultServletHandlerDef);
        parserContext.registerComponent(new BeanComponentDefinition(defaultServletHandlerDef, defaultServletHandlerName));

        Map<String, String> urlMap = new ManagedMap<String, String>();
        urlMap.put("/**", defaultServletHandlerName);

        RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
        handlerMappingDef.setSource(source);
        handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        handlerMappingDef.getPropertyValues().add("urlMap", urlMap);

        String handlerMappingBeanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
        parserContext.getRegistry().registerBeanDefinition(handlerMappingBeanName, handlerMappingDef);
        parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, handlerMappingBeanName));

        // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
        MvcNamespaceUtils.registerDefaultComponents(parserContext, source);

        return null;
    }

1.注册了一个DefaultServletHttpRequestHandler实例,beanName为defaultServletHandlerName。

2.注册了一个SimpleUrlHandlerMapping实例,并且注册了属性urlMap。

为urlMap添加了一个元素,key为”/**”,value为defaultServletHandlerName。

注意,这里没有指定SimpleUrlHandlerMapping的order,所以是默认的Integer.MAX,也就是优先级最低的。

3.注册默认的Mapping 以及Adapter等。

上一篇已经说过mvc:resources注册的SimpleUrlHandlerMapping实例的order为Integer.MAX-1,也就是说当这两种处理静态资源的方式都使用了的时候,会优先使用mvc:resources注册的。

那我们接下来就看看使用mvc:default-servlet-handler到底是如何处理静态资源请求的吧。

处理静态资源

首先getHandler得到的是上述defaultServletHandlerName对应的Bean,DefaultServletHttpRequestHandler实例。

而ha.supports(handler);得到的HandlerAdpater肯定是

HttpRequestHandlerAdapter,这个之前都讲过。

那么走到

ha.handle(processedRequest, response, mappedHandler.getHandler());

之后的具体执行也是

  @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        ((HttpRequestHandler) handler).handleRequest(request, response);
        return null;
    }

其实都和mvc:resources的步骤一样,就不细说了,只是之类的handler不一样,这时的Handler是

DefaultServletHttpRequestHandler。所有的处理都在他的handleRequest方法中。

handleRequest(request, response)

    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
        if (rd == null) {
            throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" +
                    this.defaultServletName +"'");
        }
        rd.forward(request, response);
    }

看到这段代码,是不是又回到了刚开始学习Servlet的时候呢,真的有好久好久没看到forward(requset,response);方法了,是时候来回顾一下了。

我们先看看这段代码做了什么。

1.用defaultServletName得到相应的RequestDispatcher实现类。

2.调用RequestDispatcher的forward来处理请求。

  • 首先我们来看看是如何得到RequestDispatcher实现类的。

我们这里的Web容器是Tomcat,对应的defaultServletName是default。至于这个defaullt的配置,在../lib/conf/web.xml

  <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

我们在这清楚的看到他是初始化时就要启动的一个servlet,而且知道了对应的类是org.apache.catalina.servlets.DefaultServlet。

接下来就要看这个方法的具体执行了。

RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);

这就需要回到我们的Tomcat源码中讲解了,Tomcat中serlvetContext的实现是ApplciationContext和ApplicationContextFacade,虽然我们给到用户的是后者,但是后者就是对前者的包装,实际的方法的实现都在ApplciationContext中,我们来看看这个类中的getNamedDispatcher方法

getNamedDispatcher(String name)

    @Override
    public RequestDispatcher getNamedDispatcher(String name) {

        // Validate the name argument
        if (name == null)
            return (null);

        // Create and return a corresponding request dispatcher
        Wrapper wrapper = (Wrapper) context.findChild(name);
        if (wrapper == null)
            return (null);

        return new ApplicationDispatcher(wrapper, null, null, null, null, name);

    }

在我们已经分析过Tomcat源码后,对这个方法就不难理解了。

1.通过name获得对应Wrapper实例(对Servlet实现类的封装,这里既是对DefaultServlet的封装),所有的Servlet类都被当做Web容器的子容器。

2.返回一个ApplicationDispatcher实例,该实例封装了Wrapper实例。

所以最终得到的是一个ApplicationDispatcher实例。

那么接下来就是调用这个实例的forward方法了。

 @Override
    public void forward(ServletRequest request, ServletResponse response)
        throws ServletException, IOException
    {
        if (Globals.IS_SECURITY_ENABLED) {
            try {
                PrivilegedForward dp = new PrivilegedForward(request,response);
                AccessController.doPrivileged(dp);
            } catch (PrivilegedActionException pe) {
                Exception e = pe.getException();
                if (e instanceof ServletException)
                    throw (ServletException) e;
                throw (IOException) e;
            }
        } else {
            doForward(request,response);
        }
    }

我们主要分析doForward方法

  private void doForward(ServletRequest request, ServletResponse response)
        throws ServletException, IOException
    {

        // Reset any output that has been buffered, but keep headers/cookies
        if (response.isCommitted()) {
            throw new IllegalStateException
                (sm.getString("applicationDispatcher.forward.ise"));
        }
        try {
            response.resetBuffer();
        } catch (IllegalStateException e) {
            throw e;
        }

        // Set up to handle the specified request and response
        State state = new State(request, response, false);

        if (WRAP_SAME_OBJECT) {
            // Check SRV.8.2 / SRV.14.2.5.1 compliance
            checkSameObjects(request, response);
        }

        wrapResponse(state);
        // Handle an HTTP named dispatcher forward
        if ((servletPath == null) && (pathInfo == null)) {

            ApplicationHttpRequest wrequest =
                (ApplicationHttpRequest) wrapRequest(state);
            HttpServletRequest hrequest = state.hrequest;
            wrequest.setRequestURI(hrequest.getRequestURI());
            wrequest.setContextPath(hrequest.getContextPath());
            wrequest.setServletPath(hrequest.getServletPath());
            wrequest.setPathInfo(hrequest.getPathInfo());
            wrequest.setQueryString(hrequest.getQueryString());

            processRequest(request,response,state);
        }

        // Handle an HTTP path-based forward
        else {

            ApplicationHttpRequest wrequest =
                (ApplicationHttpRequest) wrapRequest(state);
            String contextPath = context.getPath();
            HttpServletRequest hrequest = state.hrequest;
            if (hrequest.getAttribute(
                    RequestDispatcher.FORWARD_REQUEST_URI) == null) {
                wrequest.setAttribute(RequestDispatcher.FORWARD_REQUEST_URI,
                                      hrequest.getRequestURI());
                wrequest.setAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH,
                                      hrequest.getContextPath());
                wrequest.setAttribute(RequestDispatcher.FORWARD_SERVLET_PATH,
                                      hrequest.getServletPath());
                wrequest.setAttribute(RequestDispatcher.FORWARD_PATH_INFO,
                                      hrequest.getPathInfo());
                wrequest.setAttribute(RequestDispatcher.FORWARD_QUERY_STRING,
                                      hrequest.getQueryString());
            }

            wrequest.setContextPath(contextPath);
            wrequest.setRequestURI(requestURI);
            wrequest.setServletPath(servletPath);
            wrequest.setPathInfo(pathInfo);
            if (queryString != null) {
                wrequest.setQueryString(queryString);
                wrequest.setQueryParams(queryString);
            }

            processRequest(request,response,state);
        }

        if (request.isAsyncStarted()) {
            // An async request was started during the forward, don't close the
            // response as it may be written to during the async handling
            return;
        }



        if  (response instanceof ResponseFacade) {
            ((ResponseFacade) response).finish();
        } else {


            // Close anyway
            try {
                PrintWriter writer = response.getWriter();
                writer.close();
            } catch (IllegalStateException e) {
                try {
                    ServletOutputStream stream = response.getOutputStream();
                    stream.close();
                } catch (IllegalStateException f) {
                    // Ignore
                } catch (IOException f) {
                    // Ignore
                }
            } catch (IOException e) {
                // Ignore
            }
        }

    }

这个方法很长,主要的实现其实就是处理请求,有兴趣的可以看看源码。所以最后就是使用DefaultServlet来处理静态资源的请求,具体处理过程有兴趣的可以看看源码。

其实上面对RequestDispatcher实例已经其forward方法的分析看下来,都没有出现我们在学习forward方法提到的这个方法有请求转发的贡功能,一直都是用这个Servlet实例来处理请求呀,并没有出现第二个Servlet呀?

这是因为我们是使用getNamedDispatcher来得到RequestDispatcher实例的,就是通过名字来找到对应的Servlet,然后使用这个servlet来处理请求,确实没有转发给第二个Servlet处理。

而我们常说的具有请求转发功能的RequestDispatcher是由getRequestDispatcher来得到的。

通常表现为:

    //获取请求转发器对象,该转发器的指向通过getRequestDisPatcher()的参数设置
   RequestDispatcher requestDispatcher =request.getRequestDispatcher("资源的URL");
    //调用forward()方法,转发请求      
   requestDispatcher.forward(request,response);    

我们发现这里是调用Request对象的getRequestDispatcher方法,而实际上内部调用的还是ApplicationContext的getRequestDispatcher方法。

   @Override
    public RequestDispatcher getRequestDispatcher(final String path) {

        // Validate the path argument
        if (path == null) {
            return (null);
        }
        if (!path.startsWith("/")) {
            throw new IllegalArgumentException(
                    sm.getString("applicationContext.requestDispatcher.iae", path));
        }

        // Need to separate the query string and the uri. This is required for
        // the ApplicationDispatcher constructor. Mapping also requires the uri
        // without the query string.
        String uri;
        String queryString;
        int pos = path.indexOf('?');
        if (pos >= 0) {
            uri = path.substring(0, pos);
            queryString = path.substring(pos + 1);
        } else {
            uri = path;
            queryString = null;
        }

        String normalizedPath = RequestUtil.normalize(uri);
        if (normalizedPath == null) {
            return (null);
        }

        if (getContext().getDispatchersUseEncodedPaths()) {
            // Decode
            String decodedPath;
            try {
                decodedPath = URLDecoder.decode(normalizedPath, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                // Impossible
                return null;
            }

            // Security check to catch attempts to encode /../ sequences
            normalizedPath = RequestUtil.normalize(decodedPath);
            if (!decodedPath.equals(normalizedPath)) {
                getContext().getLogger().warn(
                        sm.getString("applicationContext.illegalDispatchPath", path),
                        new IllegalArgumentException());
                return null;
            }

            // URI needs to include the context path
            uri = URLEncoder.DEFAULT.encode(getContextPath(), "UTF-8") + uri;
        } else {
            // uri is passed to the constructor for ApplicationDispatcher and is
            // ultimately used as the value for getRequestURI() which returns
            // encoded values. Therefore, since the value passed in for path
            // was decoded, encode uri here.
            uri = URLEncoder.DEFAULT.encode(getContextPath() + uri, "UTF-8");
        }

        pos = normalizedPath.length();

        // Use the thread local URI and mapping data
        DispatchData dd = dispatchData.get();
        if (dd == null) {
            dd = new DispatchData();
            dispatchData.set(dd);
        }

        MessageBytes uriMB = dd.uriMB;
        uriMB.recycle();

        // Use the thread local mapping data
        MappingData mappingData = dd.mappingData;

        // Map the URI
        CharChunk uriCC = uriMB.getCharChunk();
        try {
            uriCC.append(context.getPath(), 0, context.getPath().length());
            /*
             * Ignore any trailing path params (separated by ';') for mapping
             * purposes
             */
            int semicolon = normalizedPath.indexOf(';');
            if (pos >= 0 && semicolon > pos) {
                semicolon = -1;
            }
            uriCC.append(normalizedPath, 0, semicolon > 0 ? semicolon : pos);
            context.getMapper().map(uriMB, mappingData);
            if (mappingData.wrapper == null) {
                return (null);
            }
            /*
             * Append any trailing path params (separated by ';') that were
             * ignored for mapping purposes, so that they're reflected in the
             * RequestDispatcher's requestURI
             */
            if (semicolon > 0) {
                uriCC.append(normalizedPath, semicolon, pos - semicolon);
            }
        } catch (Exception e) {
            // Should never happen
            log(sm.getString("applicationContext.mapping.error"), e);
            return (null);
        }

        Wrapper wrapper = (Wrapper) mappingData.wrapper;
        String wrapperPath = mappingData.wrapperPath.toString();
        String pathInfo = mappingData.pathInfo.toString();

        mappingData.recycle();

        // Construct a RequestDispatcher to process this request
        return new ApplicationDispatcher(wrapper, uri, wrapperPath, pathInfo,
                queryString, null);
    }

这个方法也是巨长,我们简单说一下他做了什么:

1.通过用户(程序员)指定的path,我们先查看path是否带有参数(即是否有? ,当使用get方式请求时,参数会用?a=b&c=d形式呈现),如果有参数,则将参数放入到queryString属性中。

2.通过调用context.getMapper().map(uriMB, mappingData);得到我们要转发的Servlet(Servlet被封装成Warpper,Warpper封装在了MappingData中),具体怎么找的,我们在讲解Tomcat的时候说过。

3.将我们得到warpper实例,rui,queryString等注入到ApplicationDispatcher实例中,然后调用该实例的forward方法处理请求,就实现了将请求转发到另一个Servlet中处理的效果。

既然我们已经讲了在服务器端进行转发的forward方法,那我们刚好讲下在客户机上跳转的sendRedirect方法,一般以如下形式使用。

sendRedirect

//请求重定向到另外的资源
    response.sendRedirect("资源的URL");

我们使用的Web容器是Tomcat,所以我们就找到org.apache.catalina.connector.Response的sendRedirect方法

  @Override
    public void sendRedirect(String location) throws IOException {
        sendRedirect(location, SC_FOUND);
    }
 /**
     * Internal method that allows a redirect to be sent with a status other
     * than {@link HttpServletResponse#SC_FOUND} (302). No attempt is made to
     * validate the status code.
     */
    public void sendRedirect(String location, int status) throws IOException {
        if (isCommitted()) {
            throw new IllegalStateException(sm.getString("coyoteResponse.sendRedirect.ise"));
        }

        // Ignore any call from an included servlet
        if (included) {
            return;
        }

        // Clear any data content that has been buffered
        resetBuffer(true);

        // Generate a temporary redirect to the specified location
        try {
            String locationUri;
            // Relative redirects require HTTP/1.1
            if (getRequest().getCoyoteRequest().getSupportsRelativeRedirects() &&
                    getContext().getUseRelativeRedirects()) {
                locationUri = location;
            } else {
                locationUri = toAbsolute(location);
            }
            setStatus(status);
            setHeader("Location", locationUri);
            if (getContext().getSendRedirectBody()) {
                PrintWriter writer = getWriter();
                writer.print(sm.getString("coyoteResponse.sendRedirect.note",
                        RequestUtil.filter(locationUri)));
                flushBuffer();
            }
        } catch (IllegalArgumentException e) {
            log.warn(sm.getString("response.sendRedirectFail", location), e);
            setStatus(SC_NOT_FOUND);
        }

        // Cause the response to be finished (from the application perspective)
        setSuspended(true);
    }

方法很好理解,就是重新封装了一个请求,给出了请求行的状态为302(302为重定向,我们还是需要多熟悉状态码滴)并且使用了Location这个请求头,将给定的url放入这个请求头属性中,当浏览器接受到请求头信息中的 Location: xxxx 后,就会自动跳转到 xxxx 指向的URL地址,这个跳转只有浏览器知道,所以使用了这个跳转这后,浏览器地址栏的url是会改变的,变成了我指定的url,而使用forward是不会改变的,因为他只是把请求转发给另一个servlet来处理。

include

既然都说了forward方法和sendRedirect方法了,那就再说下include方法吧。

使用方式:

request.getRequestDispatcher("jsp2.jsp").include(request,   response);  

功能是将RequestDispatcher对象封装的资源内容作为当前响应内容的一部分包含进来,从而实现可编程的服务器端包含功能。

这个方法的实现依旧是在ApplicationDispatcher中

@Override
    public void include(ServletRequest request, ServletResponse response)
        throws ServletException, IOException
    {
        if (Globals.IS_SECURITY_ENABLED) {
            try {
                PrivilegedInclude dp = new PrivilegedInclude(request,response);
                AccessController.doPrivileged(dp);
            } catch (PrivilegedActionException pe) {
                Exception e = pe.getException();

                if (e instanceof ServletException)
                    throw (ServletException) e;
                throw (IOException) e;
            }
        } else {
            doInclude(request, response);
        }
    }
      private void doInclude(ServletRequest request, ServletResponse response)
            throws ServletException, IOException {

        // Set up to handle the specified request and response
        State state = new State(request, response, true);

        if (WRAP_SAME_OBJECT) {
            // Check SRV.8.2 / SRV.14.2.5.1 compliance
            checkSameObjects(request, response);
        }

        // Create a wrapped response to use for this request
        wrapResponse(state);

        // Handle an HTTP named dispatcher include
        if (name != null) {

            ApplicationHttpRequest wrequest =
                (ApplicationHttpRequest) wrapRequest(state);
            wrequest.setAttribute(Globals.NAMED_DISPATCHER_ATTR, name);
            if (servletPath != null)
                wrequest.setServletPath(servletPath);
            wrequest.setAttribute(Globals.DISPATCHER_TYPE_ATTR,
                    DispatcherType.INCLUDE);
            wrequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
                    getCombinedPath());
            invoke(state.outerRequest, state.outerResponse, state);
        }

        // Handle an HTTP path based include
        else {

            ApplicationHttpRequest wrequest =
                (ApplicationHttpRequest) wrapRequest(state);
            String contextPath = context.getPath();
            if (requestURI != null)
                wrequest.setAttribute(RequestDispatcher.INCLUDE_REQUEST_URI,
                                      requestURI);
            if (contextPath != null)
                wrequest.setAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH,
                                      contextPath);
            if (servletPath != null)
                wrequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH,
                                      servletPath);
            if (pathInfo != null)
                wrequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO,
                                      pathInfo);
            if (queryString != null) {
                wrequest.setAttribute(RequestDispatcher.INCLUDE_QUERY_STRING,
                                      queryString);
                wrequest.setQueryParams(queryString);
            }

            wrequest.setAttribute(Globals.DISPATCHER_TYPE_ATTR,
                    DispatcherType.INCLUDE);
            wrequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
                    getCombinedPath());
            invoke(state.outerRequest, state.outerResponse, state);
        }

    }

咋一看doInclude方法好像跟forward里的doForward方法没差呀,确实很多地方都相同,但是他相较而言多了一个 invoke(state.outerRequest, state.outerResponse, state);方法

这个方法就是include的核心了,这个方法完成了是将RequestDispatcher对象封装的资源内容作为当前响应内容的一部分包含进来的功能,有兴趣的可以看看源码具体如何实现。

总结

到这里,对SpringMVC配置文件的讲解就先告一段落了,接下来就要讲解HandlerMethod到底是如何处理请求等一系列问题了,我们只是解决了SpringMVC的一小部分问题,后面的路还很长呀。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值