spring mvc及父子容器问题

在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 />用法:

这个元素可以自动注册RequestMappingHandlerMappingRequestMappingHandlerAdapter,也就是说,如果在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()静态方法,可获取根容器。

原文地址:https://www.cnblogs.com/koushr/p/5873369.html