博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringMVC配置文件解析(五)
阅读量:2351 次
发布时间:2019-05-10

本文共 20874 字,大约阅读时间需要 69 分钟。


前言

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

配置处理静态资源的原理

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
urlMap = new ManagedMap
(); 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

default
org.apache.catalina.servlets.DefaultServlet
debug
0
listings
false
1

我们在这清楚的看到他是初始化时就要启动的一个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的一小部分问题,后面的路还很长呀。

转载地址:http://ydmvb.baihongyu.com/

你可能感兴趣的文章
CSS hack 收集
查看>>
Markdown 语法
查看>>
前端工程师面试考察要点
查看>>
前端面试题——js闭包
查看>>
阿里实习生面试——电面1
查看>>
保留小数点后两位
查看>>
js使用栈来实现10进制转8进制 js取除数 余数
查看>>
myeclipse 红色叹号的原因
查看>>
前端那些事儿——中文乱码,网页中文乱码,网页乱码,块元素,内联元素
查看>>
XML与HTML区别,XML解析
查看>>
http请求(get 和 post 请求)与响应
查看>>
jsp、el、jstl——前端面试
查看>>
java IO流
查看>>
Column count doesn't match value count at row 1
查看>>
页面优化——js异步加载
查看>>
CSS3渐变
查看>>
CSS实现居中的7种方法
查看>>
Charles拦截不到请求
查看>>
gitlab/github 多账户下设置 ssh keys
查看>>
Mac版 charles安装与破解
查看>>