在web.xml文件中配置DispatcherServlet前端控制器,默认会读取/WEB-INF/${servlet-name}-servlet.xml文件,我们也可以通过contextConfigLocation属性(DispatcherServlet父类FrameworkServlet的一个属性)来显式指定配置文件的路径,如配置成classpath:webmvc.xml,这将会读取根路径下的webmvc.xml文件,我们需要在resources目录中创建一个webmvc文件。
也可以配置此Servlet的<load-on-startup />,从而让tomcat一启动就创建DispatcherServlet实例(非必须,可选),配置如下:
<servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:webmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
我们在spring mvc配置文件需要配置三大组件:HandlerMapping处理器映射器、HandlerAdapter处理器适配器、ViewResolver视图解析器,配置示例如下:
<context:component-scan base-package="com.kou.controller"></context:component-scan> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"></property> <property name="suffix" value=".jsp"></property> </bean>
在上面的配置文件中,配置了RequestMappingHandlerMapping处理器映射器、RequestMappingHandlerAdapter处理器适配器、InternalResourceViewResolver视图解析器,还用<context:component-scan />元素指定了自动扫描的包。
如果要处理静态资源的请求,则需要配置
<mvc:default-servlet-handler />
这将启用spring-mvc-xxx.jar包中的DefaultServletHttpRequestHandler处理器,它会根据当前服务器环境去找对应的默认的servlet来处理静态资源的请求。
<mvc:annotation-driven />用法:
这个元素可以自动注册RequestMappingHandlerMapping和RequestMappingHandlerAdapter,也就是说,如果在spring mvc配置文件中配置了<mvc:annotation-driven />,就无需在<bean />元素中配置这两个组件了。
此外,在spring mvc配置文件中配置了<mvc:annotation-driven />的话,就可以支持以下注解的使用了:
1、@controller、@RestController
标注某个类,表示该类是Handler处理器类。其中,@RestController=@Controller + @ResponseBody。
2、@ResponseBody
用来修饰处理器类或是处理器方法,需要HttpMessageConverter接口来对处理器方法的返回值进行转换,处理器方法返回值类型不同,使用到的具体实现类也不一样,这也是策略模式了。
如果处理器方法返回值是String类型,则要用StringHttpMessageConverter实现类,该类默认的charSet是ISO-8859-1,需要配置其supportedMediaTypes属性值为text/html;charset=UTF-8,这样才能转为UTF-8编码。
如果处理器方法返回的是集合类型或者实体类类型,则具体使用哪个实现类要看应用中引用的处理json的jar包是什么。如果没有引用任何处理json的jar包,这个时候会报错的No converter found for return value of type: class java.util.ArrayList。如果引用了Jackson包,则会用MappingJackson2HttpMessageConverter实现类,该类默认的charSet是UTF-8,最终将向前端返回json。
3、@RequestMapping
既可以标注Handler处理器类中的方法,也可以标注Handler处理器类。标注Handler处理器类的话,该类中的所有方法都会用到在@RequestMapping中配置的属性。@RequestMapping有很多属性很常用:
method = {RequestMethod.POST}表示该方法只能匹配POST请求,默认可以匹配任意方式的请求。
value = {"/test1"}表示该方法匹配请求路径是/test1的请求,value属性必配。如果此时同时用@RequestMapping来标注Handler处理器类的话,假如配置value属性为value = "/delete",则配置的方法就匹配路为/delete/test1的请求。
value属性支持Ant风格的url配置,?匹配任意一个字符,*匹配任意个字符,**匹配任意层路径,如/delete/**/test1可以匹配/delete/aaa/test1、/delete/aaa/bbb/test1。
params = {"userName=kou", "age"}表示该方法匹配的请求的请求参数必须包含userName和age,且userName参数的值必须为kou。
4、@RequestBody
用于修饰处理器方法形参,解析请求体参数并赋给方法形参,用于contentType值为application/json且请求参数是json字符串的post请求,需要配合HttpMessageConverter使用。
5、@RequestParam
修饰处理器方法的形参,会先得到前端请求参数的值然后再赋给指定的形参:
@Controller
@RequestMapping(value = "/hello")
public class HelloController {
@Resource
private HelloService helloService;
@RequestMapping(value = { "/dd" })
public void getAll(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value = "username") String userName) {
System.out.println(userName);
}
}
如果请求url是/hello/dd,则会匹配到getAll()方法,@RequestParam会先执行request.getParameter("username")得到username参数的值,然后赋值给形参userName,总的来说就相当于执行userName=request.getParameter("username"),此时我们就可以在方法体中获得userName的值。如果username对应的参数值为null,则userName也会为null。如果设置了@RequestParam的话,默认要求请求必须有username参数,如果我们要求请求url不带这个参数的话,只需设置@RequestParam的required属性值为false。
值得注意的是,@RequestParam不仅可以从get请求、一般post请求中获取请求参数的值,也可以从multipart/form-data的post请求中得到请求参数的值。
6、@PathVariable
修饰处理器方法的形参,将请求url中占位符的值绑定到形参中,用例:
@Controller @RequestMapping(value = "/hello") public class HelloController { @Resource private HelloService helloService; @RequestMapping(value = { "/dd/{Id}" }) public void getAll(HttpServletRequest request, HttpServletResponse response, @PathVariable(value = "Id") String id) { System.out.println(id); } }
如果请求的url是/hello/dd/10,则此时将匹配到getAll()方法,@PathVariable从url中截取出ID对应的字符串,并赋值给参数id。@PathVariable的value属性的值必须要和RequestMapping的value属性的花括号的值保持一致。
7、@RequestHeader
获取请求报头中的指定的属性值并绑定到指定形参中。
8、@CookieValue
获取请求中指定的Cookie值并绑定到指定形参中。
spring mvc中请求的处理流程是什么?这个在面试中遇见过不止一次,有必要整清楚。
1、用户发送请求到前端控制器DispatcherServlet
2、DispatcherServlet根据处理器映射器HandlerMapping找到处理器
3、DispatcherServlet根据处理器找到处理器适配器HandlerAdapter
4、通过处理器适配器执行处理器方法得到ModelAndView。在执行处理器方法之前先按拦截器的顺序执行其preHandle方法,在执行处理器方法之后按照拦截器的逆序,执行其postHandle方法。
5、视图解析器解析ModelAndView返回具体View
6、DispatcherServlet渲染View并返回给用户
父子容器问题
首先需要指明的是,父容器是spring容器,Root WebApplicationContext,又称为根容器,子容器是DispatcherServlet初始化时创建的容器,WebApplicationContext for namespace ${servlet-name}-servlet。子容器可以访问父容器中的bean,父容器不能访问子容器中的bean。例如,Controller实例能访问Service实例,但是Service实例却不能访问Controller实例。
父容器创建:
在web.xml中配置了ContextLoaderListener,ContextLoaderListener实现了ServletContext接口,在Servlet容器启动后,调用监听器的初始化方法,方法内部调用了ContextLoader的initWebApplicationContext()方法。initWebApplicationContext()方法先是调用createWebApplicationContext()方法创建一个容器,设置容器的父容器为null,然后调用configureAndRefreshWebApplicationContext()方法,configureAndRefreshWebApplicationContext()方法会给容器设置id,设置配置文件位置,然后调用AbstractApplicationContext的refresh()方法,实例化各bean。
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
子容器创建:
DispatcherServlet在实例化之后,紧接着就初始化。DispatcherServlet继承了FrameworkServlet,FrameworkServlet又继承了HttpServletBean,HttpServletBean又继承了HttpServlet,HttpServlet又继承了GenericServlet,GenericServlet实现了Servlet接口。HttpServletBean重写了源自Servlet接口的init()方法,内部调用initServletBean()方法。FrameworkServlet重写了initServletBean()方法,内部调用initWebApplicationContext()方法,initWebApplicationContext()方法内部调用createWebApplicationContext()方法创建一个新的容器,并设置父容器为根容器,再调用configureAndRefreshWebApplicationContext()方法设置id、配置文件位置和实例化各bean。
获取bean的操作最终会调用AbstractBeanFactory的doGetBean()方法,从中可以看到如果从当前容器中获取不到bean,就会去父容器中获取。
在代码中有以下获取容器的方法:可以在上面的initWebApplicationContext()中找到
WebApplicationContextUtils工具类的getWebApplicationContext(ServletContext sc)静态方法,可获取根容器。
RequestUtils工具类的findWebApplicationContext(HttpServletRequest request)静态方法,可获取子容器。
ContextLoader的getCurrentWebApplicationContext()静态方法,可获取根容器。