springmvc基本配置及相关源码解读

来源:互联网 发布:彩票平台源码 编辑:程序博客网 时间:2024/04/20 17:24

希望通过阅读源码来熟悉spring框架。

spring版本为4.1.0

WEB.XML:

<web-app>

         <servlet>

                   <servlet-name>springmvc</servlet-name>

                   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

                   <init-param>

                            <param-name>contextConfigLocation</param-name>

                            <param-value>classpath:springmvc.xml</param-value>

                   </init-param>

                   <load-on-startup>1</load-on-startup>

         </servlet> 

         <servlet-mapping>

                   <servlet-name>springmvc</servlet-name>

                   <url-pattern>/*</url-pattern>

         </servlet-mapping>

</web-app>

 

 

contextConfigLocation属性:

 

         当然还有一点就是contextConfigLocation的初始化参数的问题,如果你不加这个参数,那么默认的配置文件名为/WEB-INF/[servlet-name]-servlet.xml。

 

org.springframework.web.servlet.FrameworkServlet这个类中有写关于contextConfigLocation的注释。如下:

<p>The default namespace is"'servlet-name'-servlet", e.g. "test-servlet" for a

 *servlet-name "test" (leading to a"/WEB-INF/test-servlet.xml" default location

 *with XmlWebApplicationContext). The namespace can also be set explicitly via

 *the "namespace" servlet init-param.

 

源码:

org.springframework.context.support.AbstractRefreshableConfigApplicationContext(line:98):


org.springframework.web.context.support.XmlWebApplicationContext(line:136):



org.springframework.web.context.support.XmlWebApplicationContext(line:68):


org.springframework.web.servlet.FrameworkServlet(line:339):

 

org.springframework.web.servlet.FrameworkServlet(line:643):

 

解析:加载时调用getConfigLocation方法获取配置文件的地址,但是由于我们未配置,所以this.configLocations==null,所以调用getDefaultConfigLocation()。XmlWebApplicationContext是

AbstractRefreshableConfigApplicationContext的子类,覆盖了getDefaultConfigLocation()方法,所以实际调用的是上图的getDefaultConfigLocation方法。getDefaultConfigLocation里面getNamespace()并加上前(/WEB-INF/)后(.xml)缀返回作为默认配置文件地址。

而namespace熟悉,在开始configureAndRefreshWebApplicationContext方法中已经设置为,servletName+”-servlet”。

所以默认的地址为/WEB-INF/[servletName]-servlet.xml。

 

关于url-pattern的配置:

         /*就是截取所有的请求。所以在springmvc里会有一个问题,他会把你的.jsp请求也拦截,还有就是静态文件也拦截,这就尴尬了~

 

这里讲几种配置方式:

         1):加后缀,比如所有的.do结尾的请求全被拦截,这样子.jsp和其他静态资源文件就不会被拦截。

 

         3)/模式,上网查怎么让springmvc获取静态资源的时候,很多地方是让你在web.xml里面url-patter里面配置为/,其实个人觉得这个和第一种方式是差不多。

 

         2)/*模式,这种模式下所有请求被拦截。那么也就是当你访问.jsp的时候也会被拦截。

这时就要考虑怎么让jsp访问到,并能够解析~

 

        

下面就围绕这3种方式去解析,并会附上相关源码,请各位看官不要捉急。

1:后缀型(*.do)

这种方式是最简单的,你要访问springmvc里面的controller,在配置里面路径全部配置为已.do结尾的路径,然后在web.xml里面的url-pattern值设为*.do。这样就只会拦截以.do结尾的路径。而对于静态文件的地址就不会拦截。这种方式就不加源码说明了,太简单了。

 

2:/模式:

         在前面其实是已经说了,这种模式其实是和后缀型差不多的,只不过他的后缀是/。所以只会拦截以/结尾的路径罢了。其实也是很简单的。这里需要指出的是,你的配置文件配置路径一点要注意是以/结尾,不然的话会请求不到。这里以SimpleUrlHandlerMapping为例,

配置文件:

         <beanid="myHandlerMapping"class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

                   <propertyname="urlMap">

                            <map>

                                     <entrykey="/user/" value-ref="userController"></entry>

                            </map>

                   </property>

         </bean>

 

3:/*模式:

         这种模式就相对比较麻烦了,因为它会拦截所有的请求。这时就会出现一个问题。比如我们请求/user/,匹配到userController这个handler,然后调用其中的handlerAdapter方法,得到对应的以.jsp为后缀的视图,然后转发,这个请求再次被DispatcherServlet截取到,再次匹配适合的handler结果没匹配到,然后就在发送个404错误给页面。

上面都是文字,看起来肯定不爽,下面配上相应的源码,很开心吧~~:

 

源码:

 

请求后匹配handler:

org.springframework.web.servlet.DispatcherServlet:(line:916)

 

遍历所有的handlerMapping,看哪个与路径匹配。就返回,没有则返回null。

org.springframework.web.servlet.DispatcherServlet:(line:1098)

 

//怎么获取handler就到这里,这里就不深入了,里面无非就是获取你的请求路径,和配置文件里面配置的handler匹配罢了。

 

接下来是匹配handlerAdapter

org.springframework.web.servlet.DispatcherServlet:(line:922)

 

与前面的方法差不多,先遍历所有的handlerAdapter,然后看他支不支持这个handler

org.springframework.web.servlet.DispatcherServlet:(line:1138)


 

所谓支不支持,其实就是调用supports方法,在上面源代码可以看到。

以SimpleControllerHandlerAdapter为例

org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter(line:42):

 

在支持方法里面其实就是看你是不是实现了某个接口,因为在后面,他要将你强制转为这个接口对象,然后调用方法。如下

org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter(line:47):

 

如果所有的handlerAdapter都不匹配,那么就会抛出错误,请从下往上看第三张图。

 

假设都已经匹配完。

 

那么就会调用其相关接口实现的方法,以SimpleConrollrHandlerAdapter为例,调用handleRequest方法,返回ModelAndView对象。

 

接下来就是视图解析器的工作了。在配置文件中配置,这里以


为例。

遍历所有的viewResolvers来解析。

org.springframework.web.servlet.DispatcherServlet:(line:1263)

Handler里面实现的adapter方法返回可以这样写,newModalAndView(“login”),

以我前面的配置为例,则是转发至/user/login.jsp。

如果是,new ModalAndView(“redirect:login”)

则是重定向,如果redirect改为forward则还是转发。

 

org.springframework.web.servlet.view.UrlBasedViewResolver(line:428):

 

下面代码是对上面的说法的证明。

如果viewName是以redirect:开头的,那么则创建RedirectView返回。

如果是以forward:开头,那么则创建InternalResourceView对象返回。

如果什么都不带,则调用父类的createView方法。最终还是创建InteralResourceView对象。

(上面的内容是以前面的配置文件里面将viewResolver配置为InteralResourceView 为基础的,不同情况不同分析,各位不要搞混了~)

 

以forward为例:

则在viewName加上前后缀之后转发。

 

转发之后又要判断是否被拦截,又重复前面的工作。

 

(好,大体上把springmvc的流程讲了一遍,由于我这里没有配置到interceptor所以也没讲到哪里调用。这里先略过,如果大家觉得需要,可以在以后的文章中提到。)

 

下面是第二轮请求:

转发的路径为/user/login.jsp,当前模式是/*,所有的路径都拦截,当然也包括这个。所以再一次进入DispatcherServlet的doDispatch方法中。

但是很遗憾没找到.

org.springframework.web.servlet.DispatcherServlet:(line:915)


MappedHandler==null,所以调用noHandlerFound方法,然后就直接返回了,页面报404错误。

怎么才能访问到jsp呢?

上网查下,怎么样在springmvc中调用静态文件。

(当然jsp不属于静态资源文件,需要通过相关的类将它转化为servlet,这里提到了静态文件,是因为可以从怎么解决访问静态文件的方法中获取怎么去解决访问jsp的问题)

这里讲一个最简单的方式,引入mvc命名空间。

并在配置文件中加入这么一句话         <mvc:default-servlet-handler/>。

上网查一下肯定也有很多人写了关于<mvc:default-servlet-handler/>具体作用的技术贴。这里我为大家详细讲解下<mvc:default-servlet-handler/>的作用。

 

深入底层,那么肯定要从spring怎么去解析xml配置开始咯,我截取些比较关键的代码为大家说明。

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader(line:178)

 

这段代码已经开始在解析xml文件了,首先看你的标签是不是默认命名空间的默认的则调用parseDefaultElement解析,不是默认的则调用parseCustomElement解析。<mvc:default-servlet-handler/>是mvc命名空间下的标签,所以是调用parseCustomElement解析。然后获取对应的命名空间的handler进行解析。

org.springframework.web.servlet.config.MvcNamespaceHandler

 

以default-servlet-handler为例:

org.springframework.web.servlet.config.DefaultServletHandlerBeanDefinitionParser

 

这段解析default-servlet-handle标签的作用是:

创建一个DefaultServletHttpRequestHandler,用来匹配/**也就是所有的路径。

然后再创建一个SimpleUrlHandlerMapping,并将匹配的urlMap加入。

看似只有这样,其实最后一句话又注册了3个类。

org.springframework.web.servlet.config.MvcNamespaceUtils(line:54)

BeanNameUrlHandlerMapping:用来匹配id或者name或者alias以/为开头的bean。

org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping

 

HttpRequestHandlerAdapter和SimpleControllerHandlerAdapter分别支持实现HttpRequestHandler和Controller的类。

 

由上面的解析,可以了解到,其实default-servlet-handle不过是帮我们注册几个类罢了。

这时需要注意一个问题,就是:

像我这样配置是不合适的,因为default-servlet-handler标签会先被解析,那么比如我访问的是/user/,其实我是想访问userController,但是由于default-servlet-handler帮我们注册的DefaultServletHttpRequestHandler是匹配/**,所以也能匹配到。

在前面贴出来过的代码中我们可以看到,一旦匹配到合适的handler,就会立即返回,而不会做下一次匹配。


所以被返回的是DefaultServletHttpRequestHandler,这就跟我们的意图不符了,所以在使用default-servlet-handler时,最好是把default-servlet-handler标签的位置放到所有的handlerMapping的最后。

 

这个时候我们不经要发问,DefaultServletHttpRequestHandler起到了什么作用呢?

其实DefaultServletHttpRequestHandler的工作很简单,只是做一个转发工作而已。

org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler(line:114)

转发给谁呢,转发给tomcat提供的名字叫default的一个默认的servlet。

这里要说明的是不同服务器叫的名字不一样

代码中也有体现。

org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler(line:54)


 

Default会直接读取你的文件在页面显示。

那么我这里也配置下,然后访问jsp,看看结果。


我的配置就是/*,然后加上default-servlet-handle标签,这里就不贴出了。

能访问到对于的jsp,当然这肯定不是我们所希望的,因为我们希望的是解析后的jsp。

其实这也很简单,为什么呢?我们可以试想下为什么tomcat可以解析jsp呢?是不是因为它把*.jsp的请求截取到了,然后转发到一个servlet里面然后再生产相应的servlet呢?

那么肯定也有一个跟default这个servlet一样的默认去解析jsp的一个servlet。

 

果然,在tomcat的web.xml里面找到

我们再查看jsp对应的servlet为

JspServlet这个类。其实我们并不需要知道对应的是哪个类,只要知道servletname为jsp,再联系下前面的DefaultServletHttpRequestHandler这个类,我们就可以找到怎么去解析jsp的方法了。只要在写一个Handler将请求*.jsp的请求转发给servletname为jsp的servlet就可以了。

定义一个JspRequestHandler:

public class JspRequestHandler implementsHttpRequestHandler,ServletContextAware{

         privateServletContext servletContext=null;

        

        

         publicServletContext getServletContext() {

                   returnservletContext;

         }

 

 

         publicvoid setServletContext(ServletContext servletContext) {

                   this.servletContext= servletContext;

         }

 

 

         publicvoid handleRequest(HttpServletRequest request, HttpServletResponse response)

                            throwsServletException, IOException {

 

                   RequestDispatcherrd = this.servletContext.getNamedDispatcher("jsp");

                   if(rd == null) {

                            thrownew IllegalStateException("A RequestDispatcher could not be located forthe default servlet jsp");

                   }

                   rd.forward(request,response);

         }

 

}

代码和DefaultServletHttpRequestHandler,只要将default改为jsp就好了。

在配置文件里注册下。

还需要在handlerMapping里面配置下,如


这样就会把请求都拦截,然后调用我的jspHandler,然后转发给jsp。

访问下jsp页面看看效果:

果然可以。

 

问题又来了,你拦截了所有的,那么静态文件肯定也被你拦截了,这肯定不行,那么其实我们可以再jspHandler里面判断下,如果是jsp结尾的就转发给jsp,其他的就转发给default。这也是一个解决方法。

那么如果这样解决的话,那么mvc:default-servlet-handler这个标签确实是没有什么用了,可以去掉。

 

当然这样写可能更能满足你的要求,就是只拦截.jsp结尾的请求

 

刚开始的时候本来有个想法,是这样的,就是注册个interceptor,如果是.jsp结尾的就直接拦截住,不再转发了,先不管它不转发后会干嘛,先看这个思想可不可行呢?

肯定是不可行的。因为先匹配handler在调用interceptor之前,如果没有匹配到handler就直接调用noHandlerFound方法返回了。

org.springframework.web.servlet.DispatcherServlet(line:922)

 

ApplyPreHandler方法会遍历拦截器,依次调用,但是在这之前,没有获取到合适的handler,那么就已经return了。

 

 

上面就是大致的springmvc的一些最基本的配置,以及为什么这么配置,还讲了3中urlpattern。

其实我感觉最主要的是配合了源码做的一些解析,希望大家能从中获益,也不枉我写了N个小时的帖子(加上自己的一些测试,确实是N个小时啊)~~

0 0
原创粉丝点击