SpringMVC(8)——注解配置SpringMVC、SpringMVC汇总、<mvc:annotation-driven>作用

本文详细介绍了如何使用注解配置SpringMVC,包括创建初始化类、配置类来替代web.xml和SpringMVC核心配置文件。此外,文章讨论了SpringMVC的常用组件,DispatcherServlet的初始化和调用过程,以及<mvc:annotation-driven/>标签的作用,包括其在处理注解驱动开发中的关键功能。最后,文章深入解析了Spring解析该标签的机制以及与<context:component-scan/>和<context:annotation-config/>的区别。

目录

注解配置SpringMVC

(前置工作)创建新的模块springMVC-annotation

1.创建初始化类,代替web.xml

2.设置配置类用来代替SpringMVC核心配置文件SpringMVC.config

SpringMVC汇总

SpringMVC常用组件

DispatcherServlet初始化过程

DispatcherServlet调用各个组件的过程

DispatcherServlet调用各个组件处理请求的过程

SpringMVC执行流程

标签的作用

再细分析

1.Spring解析标签

2.Spring怎么知道处理mvc开头的标签就调用MvcNamespaceHandler中注册的解析器的呢

3.与有什么区别

4.与的区别


注解配置SpringMVC

说明:使用配置类和注解代替web.xml和SpringMVC配置文件的功能,后面的SSM框架整合之后,Spring的配置也不用写

(前置工作)创建新的模块springMVC-annotation

  1. pom.xml,刷新Maven
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.atguigu.com</groupId>
        <artifactId>springMVC-annotation</artifactId>
        <version>1.0-SNAPSHOT</version>
        <!--修改Idea默认模块Language Level从5修改成6,否则接口方法会报错-->
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>6</source>
                        <target>6</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
        <!--工程打包方式,默认是jar,如果这是一个web工程,打包方式为war
                当前是一个web工程,因此我们需要为这个Maven工程添加web模块,
                模块里面有webapp及webapp里面的web.xml-->
        <packaging>war</packaging>
    
        <!--添加依赖群,当导报成war包时,这些依赖都会被放到webapp目录的
        WEB-INF目录中的lib目录下-->
        <dependencies>
            <!--添加单个依赖SpringMVC,下面同理.由于 Maven 的传递性,
            我们不必将所有需要的包全部配置依赖,而是配置最顶端的依赖,其他靠传递性导入,
            意味着只要导入了这个SpringMVC依赖,那么
            SpringMVC依赖的依赖也会导入进来,其中就有spring的基础框架jar包:
            aop、beans、context、core、expression,还有自身web框架jar包
            传递性对于其他依赖同理-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.3.1</version>
            </dependency>
            <!-- 日志,用于日志输出 -->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.2.3</version>
            </dependency>
            <!-- ServletAPI,之前没导入是因为Tomcat自带了Servlet和JSP的jar包,
            因此只要配置了tomcat就能使用Servlet,java不自带Servlet的-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
                <!--依赖范围,表示我们什么地方可以使用这个jar包,
                这里设置成provided就是该依赖已被服务器提供,因此编译时
                这个依赖都会被放到webapp目录的WEB-INF目录中的lib目录下-->
                <scope>provided</scope>
            </dependency>
            <!-- Spring5和Thymeleaf整合包,是视图技术,控制页面显示内容 -->
            <dependency>
                <groupId>org.thymeleaf</groupId>
                <artifactId>thymeleaf-spring5</artifactId>
                <version>3.0.12.RELEASE</version>
            </dependency>
            <!--JackSon依赖,用于SpringMVC处理JSON-->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.12.1</version>
            </dependency>
            <!--上传文件需要的Jar包-->
            <dependency>
                <groupId>commons-fileupload</groupId>
                <artifactId>commons-fileupload</artifactId>
                <version>1.3.1</version>
            </dependency>
        </dependencies>
    </project>
  2. 完善web模块,但是不需要web.xml
  3. 配置Tomcat服务器

1.创建初始化类,代替web.xml

说明:

  • 在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类, 如果找到的话就用它来配置Servlet容器(我们使用的Tomcat服务器)。
  • Spring提供了这个接口的实现,名为 SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer接口的类并将配置的任务交给它们来完成。
  • Spring3.2(我们现在是用的是Spring5.3.1)引入了一个便利的WebApplicationInitializer基础实现,名为AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展(继承)了 AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文。
  • 也就是说Servlet3.2版本之后,容器会这样去找初始化类代替web.xml:
    实现javax.servlet.ServletContainerInitializer接口的类SpringServletContainerInitializer-->实现WebApplicationInitializer接口的类AbstractAnnotationConfigDispatcherServletInitializer-->继承了AbstractAnnotationConfigDispatcherServletInitializer的类,这个拓展类就是我们用来代替web.xml配置Servlet上下文
  1. 在 src\main\java\com\atguigu\mvc\config\WebInit.java 创建一个web初始化类,该类是用来代替Web.xml的web初始化类,配置前端控制器,springMVC\spring的配置文件路径,还有过滤器
    package com.atguigu.mvc.config;
    
    import org.springframework.web.filter.CharacterEncodingFilter;
    import org.springframework.web.filter.HiddenHttpMethodFilter;
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    
    import javax.servlet.Filter;
    
    /**
     * 该类是用来代替Web.xml的web初始化类
     * 除了配置前端控制器,springMVC\spring的配置文件路径,还有过滤器
     */
    public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
        /**
         * 指定根配置,也就是Spring的配置类
         * @return 返回值是一个数组,也就是可以有多个配置类
         */
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[]{SpringConfig.class};
        }
    
        /**
         * 指定ServletC配置,也就是SpringMVC的配置类
         * @return 返回值是一个数组,也就是可以有多个配置类
         */
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{WebConfig.class};
        }
    
        /**
         * 指定DispatcherServlet前端控制器的映射规则,也就是什么请求会被前端控制器拦截
         * @return 返回值是一个数组,也就是可以有多个请求映射路径,规则和Web.xml一样,
         * "/"表示除了JSP以外的请求都处理
         */
        @Override
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    
        /**
         * 分别注册CharacterEncodingFilter、HiddenHttpMethodFilter过滤器
         * @return 返回值表示多个过滤器,注意过滤器的顺序
         */
        @Override
        protected Filter[] getServletFilters() {
            //设置编码过滤器要支持UTF-8
            CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
            characterEncodingFilter.setEncoding("UTF-8");//设置字符编码为UTF-8
            characterEncodingFilter.setForceResponseEncoding(true);//设置响应编码强制遵循如上设置的编码
            //不设置接收请求为true是因为底层代码在request并没有设置,因此肯定为true,也就是接收请求编码的一定是强制用如上所设置的编码
    
            //设置隐藏请求方法过滤器,启用除了GET和POST以外的请求
            HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
            return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter};//注意过滤器顺序放置乱码
        }
    }
    
  2. 在 src\main\java\com\atguigu\mvc\config\WebConfig.java 创建一个(SpringMVC)配置类待用
    package com.atguigu.mvc.config;
    
    import org.springframework.beans.factory.annotation.Configurable;
    
    /**
     * 该类用来代替SpringMVC核心配置文件SpringMVC.config
     */
    @Configurable//标识该类是一个(SpringMVC)配置类
    public class WebConfig {
    }
    
  3. 在 src\main\java\com\atguigu\mvc\config\SpringConfig.java 创建一个(Spring)配置类待用
    package com.atguigu.mvc.config;
    
    import org.springframework.beans.factory.annotation.Configurable;
    
    /**
     * 因为并没有使用SSM的整合,所以Spring的配置就不写了
     */
    @Configurable//标识该类是一个(Spring)配置类
    public class SpringConfig {
    }
    

2.设置配置类用来代替SpringMVC核心配置文件SpringMVC.config

说明:不演示文件上传是否实现

  1. 在SpringMVC配置类 src\main\java\com\atguigu\mvc\config\WebConfig.java 中添加如下配置实现扫描组件、配置视图解析器、视图控制器view-controller、默认Servlet default-servlet-handler(解决静态资源的)、MVC注解驱动、文件上传解析器、异常处理、拦截器
    package com.atguigu.mvc.config;
    
    import com.atguigu.mvc.interceptor.TestInterceptor;
    import org.springframework.beans.factory.annotation.Configurable;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.web.context.ContextLoader;
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.multipart.commons.CommonsMultipartResolver;
    import org.springframework.web.servlet.HandlerExceptionResolver;
    import org.springframework.web.servlet.ViewResolver;
    import org.springframework.web.servlet.config.annotation.*;
    import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
    import org.thymeleaf.spring5.SpringTemplateEngine;
    import org.thymeleaf.spring5.view.ThymeleafViewResolver;
    import org.thymeleaf.templatemode.TemplateMode;
    import org.thymeleaf.templateresolver.ITemplateResolver;
    import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
    
    import java.util.List;
    import java.util.Properties;
    
    /**
     * 该类用来代替SpringMVC核心配置文件SpringMVC.config
     * 实现扫描组件、配置视图解析器、视图控制器view-controller、
     * 默认Servlet default-servlet-handler(解决静态资源的)、
     * MVC注解驱动、文件上传解析器、异常处理、拦截器
     */
    @Configurable//标识该类是一个(SpringMVC)配置类
    @ComponentScan("com.atguigu.mvc")//扫描组件,设置成员参数value来指定包路径
    @EnableWebMvc//开启MVC注解驱动
    public class WebConfig implements WebMvcConfigurer {//WebMvcConfigurer接口里面有很多我们需要的配置SpringMVC的功能
        /*
        设置视图解析器,从最里面对象开始设置,视图解析器是一个Bean,所以不需要实现任何的接口和继承任何的类
        */
    
        /**
         * 1.配置生成模板解析器
         * @return 返回值是ITemplateResolver接口的实现类SpringResourceTemplateResolver
         */
        @Bean//这个注解表示该方法的返回值就是IOC容器中的一个Bean
        public ITemplateResolver templateResolver() {
            //如果是web工程,就需要用WebApplicationContext接口的实现类
            WebApplicationContext webApplicationContext =
                    ContextLoader.getCurrentWebApplicationContext();
            //ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得(这里没有细说所以不太明白)
            ServletContextTemplateResolver templateResolver = new
                    ServletContextTemplateResolver(
                    webApplicationContext.getServletContext());
            //设置视图位置,视图前缀和后缀
            templateResolver.setPrefix("/WEB-INF/templates/");
            templateResolver.setSuffix(".html");
            templateResolver.setCharacterEncoding("UTF-8");
            //设置模板模型
            templateResolver.setTemplateMode(TemplateMode.HTML);
            return templateResolver;
        }
    
        /**
         * 2.生成模板引擎并为模板引擎注入模板解析器
         * @param templateResolver 已经配置到IOC容器的模板解析器.这个形参的值是spring在IOC容器中找到同类型的Bean,
         *                         然后通过类型注入进去的,是spring自动装配功能
         * @return
         */
        @Bean
        public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
            SpringTemplateEngine templateEngine = new SpringTemplateEngine();
            templateEngine.setTemplateResolver(templateResolver);
            return templateEngine;
        }
    
        /**
         * 3.生成视图解析器并未解析器注入模板引擎
         * @param templateEngine 已经配置到IOC容器的模板引擎.这个形参的值是spring在IOC容器中找到同类型的Bean,
         *                       然后通过类型注入进去的,是spring自动装配功能
         * @return
         */
        @Bean
        public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
            ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
            viewResolver.setCharacterEncoding("UTF-8");
            viewResolver.setTemplateEngine(templateEngine);
            return viewResolver;
        }
    
        /*
        通过快捷键"ctr+o"重写方法如下方法,配置默认的Servlet处理静态资源
        */
    
        /**
         * 下面的方法相当于<mvc:default-servlet-handler/>
         * @param configurer 用于开启或者关闭默认Servlet处理,默认是关闭的
         */
        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
            configurer.enable();//开启默认Servlet处理
        }
    
        /*
        添加拦截器
         */
    
        /**
         * 下面方法相当于<mvc:interceptors>...
         * @param registry 是一个可以调用方法存入并设置多个拦截器的对象
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //创建自定义的拦截器的对象
            TestInterceptor interceptor = new TestInterceptor();
            //添加一个拦截器并设置该拦截器的支持什么请求路径(这里设置的是支持所有请求)
            registry.addInterceptor(interceptor).addPathPatterns("/**");
        }
    
        /*
        设置视图控制器
        */
    
        /**
         *
         * @param registry 可以调用方法设置多个路径对应的视图名称
         */
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            //添加一个请求路径对应的视图名称(在没有参数需要操作时)
            registry.addViewController("/").setViewName("index");
        }
    
        /*
        文件上传
         */
    
        /**
         * 这个方法相当于 <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
         * @return
         */
        @Bean
        public CommonsMultipartResolver multipartResolver(){
            CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
            return commonsMultipartResolver;
        }
    
        /*
        异常处理
         */
    
        /**
         * 这个方法相当于自定义异常处理器交给SpringMVC异常解析器,也可以使用Bean,因为它本身就是一个Bean
         * @param resolvers 多个处理器处理器的集合
         */
        @Override
        public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
            //创建自定义异常处理器
            SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
            //创建一个Properties对象,里面是一个键值对集合,用于存放异常及对应的视图
            Properties properties = new Properties();
            //这个键值对Key为出现的异常类型,Value为遇到异常应该跳转到什么视图,都是字符串
            properties.setProperty("java.lang.ArithmeticException","error");
            //然后把properties放入到自定义异常的处理器中
            simpleMappingExceptionResolver.setExceptionMappings(properties);
            //把异常信息放到请求域request中的key为exception中
            simpleMappingExceptionResolver.setExceptionAttribute("exception");
            //将自定义异常处理器存放到异常处理器的集合中
            resolvers.add(simpleMappingExceptionResolver);
        }
    }
    
  2. 创建一个首页视图 src\main\webapp\WEB-INF\templates\index.html,设置向服务器发送模拟异常的请求 
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
    <h1>首页</h1>
    <a th:href="@{/error}">模拟异常错误</a>
    </body>
    </html>
  3. 创建一个错误展示视图 src\main\webapp\WEB-INF\templates\error.html ,设置向服务器展示错误信息
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>错误</title>
    </head>
    <body>
    <h1>错误</h1>
    错误信息:<a th:text="${exception}"></a>
    </body>
    </html>
  4. 将 1.jpg 图片存入到 src\main\webapp\static\photo 目录,一会用于测试访问静态资源
  5. 创建拦截器 src\main\java\com\atguigu\mvc\interceptor\TestInterceptor.java ,一会测试拦截器执行
    package com.atguigu.mvc.interceptor;
    
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @Component//将过滤器注册到SpringMVC作为其中一个组件
    public class TestInterceptor implements HandlerInterceptor {
    /*
        通过Ctr+o 重写里面接口方法,如果出现重写注解@Override没有出现,
        手写上去会报错,就要在Pom.xml中修改Idea默认模块Language Level从5修改成6
    */
    
        /**
         * 该方法会在请求控制器的方法执行器前执行
         * @param request
         * @param response
         * @param handler
         * @return 返回值表示的是拦截器执行完拦截器preHandle方法后是否放行
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("我在请求控制器的方法执行前执行,FirstInterceptor-->preHandle");
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("我在请求控制器的方法执行后执行,FirstInterceptor-->postHandle");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("我在视图渲染后执行,FirstInterceptor-->afterCompletion");
        }
    }
    
    
  6. 创建请求控制器 src\main\java\com\atguigu\mvc\controller\TestController.java ,用于模拟处理请求时出现异常
    package com.atguigu.mvc.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class TestController {
        @RequestMapping("/error")
        public String TestException(){
            int i = 1/0;
            return "index";
        }
    }
    
  7. 重启服务器,一个个测试效果
  8. 测试组件扫描
  9. 测试视图解析器与视图控制器
  10. 默认Servlet处理静态资源
  11. 异常处理
  12. 异常处理和拦截器
  13. 不演示文件上传了

SpringMVC汇总

SpringMVC常用组件

  • DispatcherServlet:前端控制器,不需要工程师开发,由框架提供,
    作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求(例如下面介绍的一些比较常用的组件都是前端控制器介绍的,后面查看源码的时候会细说)
  • HandlerMapping:处理器(处理器就是控制器)映射器,不需要工程师开发,由框架提供
    作用:根据请求的url、method等信息查找Handler,就是处理器映射器先找请求控制器再找请求控制器方法
  • Handler:处理器,就是我们写的被@Controller标识的类(请求控制器,异常控制器等......)及类里面被@RequestMapping标识的方法,需要工程师开发
    作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理
  • HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供
    作用:先通过处理器映射器找到要执行哪个控制器的哪个方法,再通过HandlerAdapter执行处理器(控制器)的方法,最终返回一个ModelAndView对象
  • ViewResolver:视图解析器,不需要工程师开发,由框架提供
    作用:对视图名称进行视图解析,来对页面(html、jpg)进行不同的渲染,得到相应的视图,例如不同的视图名称解析出来的视图都是不一样的:ThymeleafView(视图名称没有任何前缀解析出来就是这个,前提是配置的视图解析器是ThymeLeaf)、InternalResourceView(视图名称前缀有"forward:" 转发,如果是用SpringMVC默认的视图解析器InternalResourceViewResolver)、 RedirectView(视图名称前缀有"redirect:" 重定向)
  • View:视图,是视图解析器根据视图名称和模型数据解析出来的,然后展示给用户
    作用:将模型数据通过页面展示给用户

DispatcherServlet初始化过程

  • DispatcherServlet 本质上是一个 Servlet,所以天然的遵循 Servlet 的生命周期。所以宏观上是 Servlet 生命周期来进行调度。
  • 通过源码分析DispathcerServlet初始化过程及为什么要饿汉式执行初始化操作,就是因为Dispathcer在初始化过程十分复杂,为了不影响第一次访问的使用
    public abstract class GenericServlet 
        implements Servlet, ServletConfig, java.io.Serializable
    {
    
    ...
    
        public void init(ServletConfig config) throws ServletException {//该方法是重写了Servlet的init方法
    	this.config = config;//将成员变量给属性赋值
    	this.init();//执行本类的重载方法
        }
    
        public void init() throws ServletException {//该方法没有任何的内容
    
        }
    
    ...
    
    }
    
    
    -->
    
    public abstract class HttpServlet extends GenericServlet{这个类并没有重写init方法
    
    }
    
    -->
    
    public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
    
    ...
    	@Override
    	public final void init() throws ServletException {//这个类重写了init方法是重写了GenericServlet类的
    
    		// 通过 PropertyValues 包装属性,BeanWrapper 包装bean ,ResourceLoader 加载资源文件,initServletBean执行,但是此方法是空的
    		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    		if (!pvs.isEmpty()) {
    			try {
    				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
    				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
    				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
    				initBeanWrapper(bw);
    				bw.setPropertyValues(pvs, true);
    			}
    			catch (BeansException ex) {
    				if (logger.isErrorEnabled()) {
    					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
    				}
    				throw ex;
    			}
    		}
    
    		// Let subclasses do whatever initialization they like.
    		initServletBean();//查看该方法
    	}
    
    	protected void initServletBean() throws ServletException {//没有内容,寻找子类
    	}
    
    ...
    
    }
    
    -->
    
    public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    
    ...
    
        、、
    	@Override
    	protected final void initServletBean() throws ServletException {
    		getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
    		if (logger.isInfoEnabled()) {
    			logger.info("Initializing Servlet '" + getServletName() + "'");//打印日志
    		}
    		long startTime = System.currentTimeMillis();
    
    		try {
    			this.webApplicationContext = initWebApplicationContext();//初始化WebApplicationContext操作,也就是对SpringMVC的IOC容器进行初始化(java工程是ApplicitonContext,这里是web模块)
    			initFrameworkServlet();
    		}
    		catch (ServletException | RuntimeException ex) {
    			logger.error("Context initialization failed", ex);
    			throw ex;
    		}
    
    		if (logger.isDebugEnabled()) {
    			String value = this.enableLoggingRequestDetails ?
    					"shown which may lead to unsafe logging of potentially sensitive data" :
    					"masked to prevent unsafe logging of potentially sensitive data";
    			logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
    					"': request parameters and headers will be " + value);
    		}
    
    		if (logger.isInfoEnabled()) {
    			logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
    		}
    	}
    
    ...
    
    	protected WebApplicationContext initWebApplicationContext() {
    		WebApplicationContext rootContext =
    				WebApplicationContextUtils.getWebApplicationContext(getServletContext());//通过工具类获取WebApplicationContext
    		WebApplicationContext wac = null;//设置WebApplicationContext对象为空
    
    		if (this.webApplicationContext != null) {//这个方法第一次执行肯定是空的,所以不会被执行
    			// A context instance was injected at construction time -> use it
    			wac = this.webApplicationContext;
    			if (wac instanceof ConfigurableWebApplicationContext) {
    				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
    				if (!cwac.isActive()) {
    					// The context has not yet been refreshed -> provide services such as
    					// setting the parent context, setting the application context id, etc
    					if (cwac.getParent() == null) {
    						// The context instance was injected without an explicit parent -> set
    						// the root application context (if any; may be null) as the parent
    						cwac.setParent(rootContext);
    					}
    					configureAndRefreshWebApplicationContext(cwac);
    				}
    			}
    		}
    		if (wac == null) {
    			// No context instance was injected at construction time -> see if one
    			// has been registered in the servlet context. If one exists, it is assumed
    			// that the parent context (if any) has already been set and that the
    			// user has performed any initialization such as setting the context id
    			wac = findWebApplicationContext();
    		}
    		if (wac == null) {//这个肯定为空
    			// No context instance is defined for this servlet -> create a local one
    			wac = createWebApplicationContext(rootContext);////然后就去执行创建WebApplicationContext,进入方法
    		}
    
    		if (!this.refreshEventReceived) {
    			// Either the context is not a ConfigurableApplicationContext with refresh
    			// support or the context injected at construction time had already been
    			// refreshed -> trigger initial onRefresh manually here.
    			synchronized (this.onRefreshMonitor) {
    				onRefresh(wac);//触发刷新事件,进入方法
    			}
    		}
    
    		if (this.publishContext) {
    			// Publish the context as a servlet context attribute.
    			String attrName = getServletContextAttributeName();//获取ServletContextAttribute的名字
    			getServletContext().setAttribute(attrName, wac);//然后在域对象ServletContext中共享数据,就是将容器存到域对象中
    		}
    
    		return wac;
    	}
    
    ...
    
    	protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
    		return createWebApplicationContext((ApplicationContext) parent);//继续深入,这是一个创建webApplicationContext的过程
    	}
    
    ...
    
    	protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    		Class<?> contextClass = getContextClass();
    		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    			throw new ApplicationContextException(
    					"Fatal initialization error in servlet with name '" + getServletName() +
    					"': custom WebApplicationContext class [" + contextClass.getName() +
    					"] is not of type ConfigurableWebApplicationContext");
    		}
    		ConfigurableWebApplicationContext wac =
    				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);//通过反射创建一个ConfigurableWebApplicationContext对象,是一个容器对象
    		wac.setEnvironment(getEnvironment());
    		wac.setParent(parent);//在DispathcerServlet初始化时,会设置SpringMVC的容器是Spring容器的子类,也就是完成SSM的SS整合,Spring和SpringMVC各自各自的配置文件及IOC容器。如果只是单纯SpringMVC,那么springMVC容器就是最高级的容器。子容器是可以访问父容器的,也就是springMVC组件(Bean)可以访问spring的组件(Bean),但是父容器不能访问子容器的(Bean)
    		String configLocation = getContextConfigLocation();
    		if (configLocation != null) {
    			wac.setConfigLocation(configLocation);
    		}
    		configureAndRefreshWebApplicationContext(wac);
    
    		return wac;//创建好webApplicitonContext之后返回给调用她的方法
    	}
    
    ...
    
    	protected void onRefresh(ApplicationContext context) {
    		// For subclasses: do nothing by default.  子类执行
    	}
    
    ...
    
    }
    
    -->
    
    public class DispatcherServlet extends FrameworkServlet {
    
    ...
    
    	@Override
    	protected void onRefresh(ApplicationContext context) {
    		initStrategies(context);//初始化策略,进入方法
    	}
    
    ...
    
    	protected void initStrategies(ApplicationContext context) {//这么多组件随着DispathcerServlet初始化而初始化,所以不应该放到第一次访问时候才对Dispathcer进行初始化(懒人模式)
    		initMultipartResolver(context);//初始化文件上传信息
    		initLocaleResolver(context);
    		initThemeResolver(context);
    		initHandlerMappings(context);//初始化处理器映射器映射器
    		initHandlerAdapters(context);//初始化处理器适配器
    		initHandlerExceptionResolvers(context);//初始化异常处理器
    		initRequestToViewNameTranslator(context);
    		initViewResolvers(context);//初始化视图解析器
    		initFlashMapManager(context);
    	}
    
    
    ...
    
    }
    

DispatcherServlet调用各个组件的过程

  • 通过源码分析DispathcerServlet调用各个组件的过程
    public interface Servlet {
    
    ...
    
        public void service(ServletRequest req, ServletResponse res)
    	throws ServletException, IOException;//是一个抽象方法,等待实现中,在GenericServlet一样是一个抽象方法,知道HttpServlet
    
    ...
    }
    
    -->
    public abstract class HttpServlet extends GenericServlet{
    
    ...
    
        @Override
        public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException
        {
            HttpServletRequest  request;
            HttpServletResponse response;
            
            if (!(req instanceof HttpServletRequest &&
                    res instanceof HttpServletResponse)) {
                throw new ServletException("non-HTTP request or response");
            }
    
            request = (HttpServletRequest) req;//强转成更小的类型
            response = (HttpServletResponse) res;//与上同理
    
            service(request, response);//进入该方法
        }
    
    ...
    
        protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
        {
            String method = req.getMethod();//获取请求方式
    
            if (method.equals(METHOD_GET)) {看看请求方式是不是get
                long lastModified = getLastModified(req);
                if (lastModified == -1) {
                    // servlet doesn't support if-modified-since, no reason
                    // to go through further expensive logic
                    doGet(req, resp);//调用Servlet的doGet的方法
                } else {
                    long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                    if (ifModifiedSince < lastModified) {
                        // If the servlet mod time is later, call doGet()
                        // Round down to the nearest second for a proper compare
                        // A ifModifiedSince of -1 will always be less
                        maybeSetLastModified(resp, lastModified);
                        doGet(req, resp);
                    } else {
                        resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                    }
                }
    
            } else if (method.equals(METHOD_HEAD)) {//同理
                long lastModified = getLastModified(req);
                maybeSetLastModified(resp, lastModified);
                doHead(req, resp);
    
            } else if (method.equals(METHOD_POST)) {//同理
                doPost(req, resp);
                
            } else if (method.equals(METHOD_PUT)) {//同理
                doPut(req, resp);
                
            } else if (method.equals(METHOD_DELETE)) {//同理
                doDelete(req, resp);
                
            } else if (method.equals(METHOD_OPTIONS)) {//同理
                doOptions(req,resp);
                
            } else if (method.equals(METHOD_TRACE)) {//同理
                doTrace(req,resp);
                
            } else {
                //
                // Note that this means NO servlet supports whatever
                // method was requested, anywhere on this server.
                //
    
                String errMsg = lStrings.getString("http.method_not_implemented");
                Object[] errArgs = new Object[1];
                errArgs[0] = method;
                errMsg = MessageFormat.format(errMsg, errArgs);
                
                resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
            }
        }
    
    ...
    
    }
    
    -->
    
    public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {//HttpServlet子类HttpServletBean 没有重写父类HttpServlet的protected void service(HttpServletRequest req, HttpServletResponse resp)方法,但是HttpServletBean的子类FrameworkServlet重写了HttpServlet的protected void service(HttpServletRequest req, HttpServletResponse resp)方法
    
    ...
    
    }
    
    -->
    
    public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    
    ...
    
    	@Override
    	protected void service(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    
    		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());//获取请求方式
    		if (httpMethod == HttpMethod.PATCH || httpMethod == null) {//如果请求方式为PATCH或者请求方式为空就...
    			processRequest(request, response);//就执行请求,进入该方法
    		}
    		else {
    			super.service(request, response);//如果有请求就执行HttpServt继承回来的service方法,这个service方法根据不同请求方式执行不同的do方法,这些do方法有些是被FrameworkServlet重写了
    		}
    //无论有无请求方式或者请求方式不同,最终执行的都是processRequest(request, response)方法,因此我们还是要进入该方法查看
    	}
    
    ...
    
    	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    
    		long startTime = System.currentTimeMillis();//获取时间错
    		Throwable failureCause = null;
    
    		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    		LocaleContext localeContext = buildLocaleContext(request);
    
    		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    
    		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    
    		initContextHolders(request, localeContext, requestAttributes);
    
    		try {
    			doService(request, response);//执行Service,也就是处理请求响应请求的服务,但是FramworkServlet类里这个方法是一个待实现的抽象方法
    		}
    		catch (ServletException | IOException ex) {
    			failureCause = ex;
    			throw ex;
    		}
    		catch (Throwable ex) {
    			failureCause = ex;
    			throw new NestedServletException("Request processing failed", ex);
    		}
    
    		finally {
    			resetContextHolders(request, previousLocaleContext, previousAttributes);
    			if (requestAttributes != null) {
    				requestAttributes.requestCompleted();
    			}
    			logResult(request, response, failureCause, asyncManager);
    			publishRequestHandledEvent(request, response, startTime, failureCause);
    		}
    	}
    
    ...
    
    	protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
    			throws Exception;//待实现的抽象方法
    
    ...
    
    }
    
    -->
    
    public class DispatcherServlet extends FrameworkServlet {
    
    ...
    
    	@Override
    	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		logRequest(request);
    
    		// Keep a snapshot of the request attributes in case of an include,
    		// to be able to restore the original attributes after the include.
    		Map<String, Object> attributesSnapshot = null;
    		if (WebUtils.isIncludeRequest(request)) {
    			attributesSnapshot = new HashMap<>();
    			Enumeration<?> attrNames = request.getAttributeNames();
    			while (attrNames.hasMoreElements()) {
    				String attrName = (String) attrNames.nextElement();
    				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
    					attributesSnapshot.put(attrName, request.getAttribute(attrName));
    				}
    			}
    		}
    
    		// Make framework objects available to handlers and view objects.
    		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
    
    		if (this.flashMapManager != null) {
    			FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
    			if (inputFlashMap != null) {
    				request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
    			}
    			request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
    			request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    		}
    
    		RequestPath requestPath = null;
    		if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
    			requestPath = ServletRequestPathUtils.parseAndCache(request);
    		}
    
    		try {
    			doDispatch(request, response);//开始执行doDispatch方法,这个方法就是用来执行拦截器方法、异常方法、请求控制的方法等......
    		}
    		finally {
    			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    				// Restore the original attribute snapshot, in case of an include.
    				if (attributesSnapshot != null) {
    					restoreAttributesAfterInclude(request, attributesSnapshot);
    				}
    			}
    			if (requestPath != null) {
    				ServletRequestPathUtils.clearParsedRequestPath(request);
    			}
    		}
    	}
    
    ...
    
    }

DispatcherServlet调用各个组件处理请求的过程

  • 通过源码分析DispatcherServlet调用各个组件处理请求的过程

    	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		HttpServletRequest processedRequest = request;//将请求赋予给了这个成员变量
    		HandlerExecutionChain mappedHandler = null;//设置控制执行链,之前说过他里面包含请求控制器方法存放所有拦截器的集合及拦截器集合的索引
    		boolean multipartRequestParsed = false;
    
    		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
    		try {
    			ModelAndView mv = null;//创建一个ModelAndView对象
    			Exception dispatchException = null;
    
    			try {
    				processedRequest = checkMultipart(request);
    				multipartRequestParsed = (processedRequest != request);
    
    				// Determine handler for the current request.
    				mappedHandler = getHandler(processedRequest);//之前也说过,当这个方法执行完后,也就是根据请求生成的mappedHandler对象,对象里面存有对应请求的请求控制器的方法和拦截器集合及拦截器集合的索引
    				if (mappedHandler == null) {
    					noHandlerFound(processedRequest, response);
    					return;
    				}
    
    				// Determine handler adapter for the current request.
    				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());通过这个方法获取处理器适配器,一会用处理器适配器执行请求控制器的方法
    
    				// Process last-modified header, if supported by the handler.
    				String method = request.getMethod();
    				boolean isGet = "GET".equals(method);
    				if (isGet || "HEAD".equals(method)) {
    					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    						return;
    					}
    				}
    
    				if (!mappedHandler.applyPreHandle(processedRequest, response)) {//调用拦截器的PerHandle方法,之前已经演示过了
    					return;
    				}
    
    				// Actually invoke the handler.
    				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//通过处理器适配器执行请求控制器的方法并返回一个ModelAndView对象,里面有视图名称和模型数据.其中就完成了很多任务,比如说收齐请求参数(将String转换类型)、请求头、请求体等为请求控制器的方法的形参赋值,
    
    				if (asyncManager.isConcurrentHandlingStarted()) {
    					return;
    				}
    
    				applyDefaultViewName(processedRequest, mv);
    				mappedHandler.applyPostHandle(processedRequest, response, mv);//执行拦截器的postHandle方法,之前也演示过了
    			}
    			catch (Exception ex) {
    				dispatchException = ex;
    			}
    			catch (Throwable err) {
    				// As of 4.3, we're processing Errors thrown from handler methods as well,
    				// making them available for @ExceptionHandler methods and other scenarios.
    				dispatchException = new NestedServletException("Handler dispatch failed", err);
    			}
    			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);//进入该方法,该方法处理异常也执行拦截器集合各个拦截器的afterCompletion方法
    		}
    		catch (Exception ex) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    		}
    		catch (Throwable err) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler,
    					new NestedServletException("Handler processing failed", err));
    		}
    		finally {
    			if (asyncManager.isConcurrentHandlingStarted()) {
    				// Instead of postHandle and afterCompletion
    				if (mappedHandler != null) {
    					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    				}
    			}
    			else {
    				// Clean up any resources used by a multipart request.
    				if (multipartRequestParsed) {
    					cleanupMultipart(processedRequest);
    				}
    			}
    		}
    	}
    
    ...
    
    	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
    			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
    			@Nullable Exception exception) throws Exception {
    
    		boolean errorView = false;
    
    		if (exception != null) {//如果在执行方法时出现了异常,就会执行如下代码
    			if (exception instanceof ModelAndViewDefiningException) {
    				logger.debug("ModelAndViewDefiningException encountered", exception);
    				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
    			}
    			else {
    				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
    				mv = processHandlerException(request, response, handler, exception);
    				errorView = (mv != null);
    			}
    		}
    
    		// Did the handler return a view to render?
    		if (mv != null && !mv.wasCleared()) {
    			render(mv, request, response);//渲染视图,将模型数据放到请求域中,然后根据视图名称渲染视图
    			if (errorView) {
    				WebUtils.clearErrorRequestAttributes(request);
    			}
    		}
    		else {
    			if (logger.isTraceEnabled()) {
    				logger.trace("No view rendering, null ModelAndView returned.");
    			}
    		}
    
    		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    			// Concurrent handling started during a forward
    			return;
    		}
    
    		if (mappedHandler != null) {
    			// Exception (if any) is already handled..
    			mappedHandler.triggerAfterCompletion(request, response, null);//执行拦截器的afterCompleiton方法,之前也演示过了
    		}
    	}
    
    ...
    
    }
    

SpringMVC执行流程

  1. 用户向服务器发送请求,根据DispatcherServlet设置<url-pattern>的路径来将请求捕获
  2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),通过HandlerMapping组件判断请求URI对应的映射
    1. 如果没有对应的请求控制器方法与URI匹配
      1. 判断是否匹配了<mvc:default-servlet-handler>
        1. 如果匹配了将DispatcherServlet捕获的但不能处理的请求交给DefaultServlet处理,会根据URI(从web资源目录webapp开始找,一般都是静态资源js、html、css、jpg等......)
          1.  如果找到了就会将资源直接返回给客户端,通过客户端打开资源
          2. 如果找不到就会出现404错误
        2. 如果没有匹配就会出现404错误
    2. 如果有对应的请求控制器方法与URI匹配
      1. 根据URI,调用HandlerMapping获取该Handler配置的所有相关对象(例如Handler对象及Handler对象对应的拦截器等),最后以HandlerExecutionChain执行链对象的形式返回,这个HandlerExecutionChain对象有请求控制器方法、请求控制方法对应的拦截器的集合,请求控制器方法对应的拦截器的集合的索引
      2. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter(HandlerAdapter也是有不同的类型,如果是通过注解@RequestMapping来实现请求与请求控制其方法进行映射,那么她的实现类应该是RequestMappingHandlerAdapter)
      3. 如果成功获取到了合适的HandlerAdapter,就会先执行拦截器集合的各个拦截器的perHandler方法(正序方式从第一个拦截器开始执行)
      4. 提取Request的模型数据,通过HttpMessageConveter为Handler(具体点说就是请求控制器的方法)进行一些列操作(String类型转换成其他类型的类型转换操作、将字符串转换成格式化数字或格式化日期、数据验证等)的形参赋值。也就是将请求信息(如json、xml等数据)转换成java对象
      5. 开始执行执行请求控制器的方法,返回一个ModelAndView对象(DispatcherServlet的1061行代码)
      6. 通过HttpMessageConveter将java对象转换成指定的响应信息
      7. 执行拦截器集合的各个拦截器的PostHandler方法(倒序)
      8. 根据返回的ModelAndView(此时会判断是否存在异常
        1. 如果存在异常,则执行 HandlerExceptionResolver进行异常处理,选择一个适合的ViewResolver(ThymeLeafViewResolve、InternalResourceView、RedirectView)进行视图解析,根据Model和View来渲染视图
      9. 没有异常就会选择一个适合的ViewResolver(ThymeLeafViewResolve、InternalResourceView、RedirectView)进行视图解析,根据Model和View来渲染视图
      10. 渲染视图完毕执行拦截器集合的各个拦截器的afterCompletion方法(倒序)

<mvc:annotation-driven>标签的作用

  1. <mvc:annotation-driven /> 是一种简写形式,完全可以手动配置替代这种简写形式,简写形式可以让初学都快速应用默认配置方案。<mvc:annotation-driven /> 会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter 两个bean

  2. DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter 两个bean是spring MVC为@Controller分发请求所必须的,即解决了@Controller注解使用的前提配置

  3. 同时它还提供了:数据绑定支持,@NumberFormatannotation支持,@DateTimeFormat支持,@Valid支持,读写XML的支持(JAXB,读写JSON的支持(Jackson)。我们处理响应ajax请求时,就使用到了对json的支持(配置之后,在加入了jackson的core和mapper包之后,不写配置文件也能自动转换成json,之前在别的章节就演示过了)

  4. 还有当对action写JUnit单元测试时,要从spring IOC容器中取DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter 两个bean,来完成测试,取的时候要知道正是<mvc:annotation-driven />这一句注册的这两个bean

  • 注意:在spring mvc 3.1以上变更为:

  1. DefaultAnnotationHandlerMapping -> RequestMappingHandlerMapping ,提供了AbstractHandlerMethodMapping 实现类方便用户实现自定义

  2. AnnotationMethodHandlerAdapter -> RequestMappingHandlerAdapter ,提供了AbstractHandlerMethodAdapter 实现类方便用户实现自定义

  3. AnnotationMethodHandlerExceptionResolver -> ExceptionHandlerExceptionResolver,提供了AbstractHandlerMethodExceptionResolver 实现类方便用户实现自定义

  • 如果我们希望通过注解的方式来进行Spring MVC开发,我们都会在SpringMVC核心配置类中加入<mvc:annotation-driven/>标签来告诉Spring我们的目的,那么这个标签到底做了什么呢,我们先看看它的解析类,我们知道所有的自定义命名空间(像mvc,context等)下的标签解析都是由BeanDefinitionParser接口的子类(解析类)里面的其中一个解析器完成的(详细请看下面的“Spring怎么知道处理mvc开头的标签就调用MvcNamespaceHandler中注册的解析器的呢”)。而<mvc:annotation-driven/>标签是由org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser解析类处理的
  • 阅读类注释文档,我们发现这个类主要是用来向工厂中注册了如下类型Bean,他们功能分别是
    RequestMappingHandlerMapping //HandlerMapping接口的实现类,是处理@RequestMapping注解的,并将其注册到请求映射表中
     
    BeanNameUrlHandlerMapping  //HandlerMapping接口的实现类,将controller类的名字映射为请求url
     
    RequestMappingHandlerAdapter//处理@Controller注解的处理器,支持自定义方法参数和返回值
     
    HttpRequestHandlerAdapter//处理继承HttpRequestHandler的处理器
     
    SimpleControllerHandlerAdapter//处理继承自Controller接口的处理器,第3-5总的来说就是确认调用哪个controller的哪个方法来处理当前请求
     
    ExceptionHandlerExceptionResolver //第6-8都是处理异常的解析器
     
    ResponseStatusExceptionResolver 
     
    DefaultHandlerExceptionResolver 
  • 具体注册过程查看:AnnotationDrivenBeanDefinitionParser的parse方法与MvcNamespaceUtils的registerDefaultComponents方法,执行注册功能注册了很多的解析器对应一个个的命名空间的元素,最重要就是RequestMappingHandlerMapping和RequestMappingHandlerAdapter。第一个是HandlerMapping的实现类,它会处理@RequestMapping 注解,并将其注册到请求映射表中;第二个是HandlerAdapter的实现类,它是处理请求的适配器,说白了,就是确定调用哪个类的哪个方法,并且构造方法参数,返回值
    public BeanDefinition parse(Element element, ParserContext parserContext) {
            Object source = parserContext.extractSource(element);
     
            CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
            parserContext.pushContainingComponent(compDefinition);
     
            RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);
            //第一个在这 RequestMappingHandlerMapping
            RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
            handlerMappingDef.setSource(source);
            handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            handlerMappingDef.getPropertyValues().add("order", 0);
            handlerMappingDef.getPropertyValues().add("removeSemicolonContent", false);
            handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
            String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(handlerMappingDef);
            //第二个在这 RequestMappingHandlerAdapter
            RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
            handlerAdapterDef.setSource(source);
            handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
            handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
            handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
            if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
                Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignoreDefaultModelOnRedirect"));
                handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
            }
            if (argumentResolvers != null) {
                handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
            }
            if (returnValueHandlers != null) {
                handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
            }
            if (asyncTimeout != null) {
                handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
            }
            if (asyncExecutor != null) {
                handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
            }
            handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
            handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
            String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedName(handlerAdapterDef);
            //异常处理解析器
            RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
            exceptionHandlerExceptionResolver.setSource(source);
            exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
            exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
            exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
            String methodExceptionResolverName =
                    parserContext.getReaderContext().registerWithGeneratedName(exceptionHandlerExceptionResolver);
            //异常处理解析器
            RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
            responseStatusExceptionResolver.setSource(source);
            responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            responseStatusExceptionResolver.getPropertyValues().add("order", 1);
            String responseStatusExceptionResolverName =
                    parserContext.getReaderContext().registerWithGeneratedName(responseStatusExceptionResolver);
            //异常处理解析器
            RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
            defaultExceptionResolver.setSource(source);
            defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            defaultExceptionResolver.getPropertyValues().add("order", 2);
            String defaultExceptionResolverName =
                    parserContext.getReaderContext().registerWithGeneratedName(defaultExceptionResolver);
     
            parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName));
            parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, handlerAdapterName));
            parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
            parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
            parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
            parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
            //这里注册了BeanNameUrlHandlerMapping,SimpleControllerHandlerAdapter等
            // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
            MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
     
            parserContext.popAndRegisterContainingComponent();
     
            return null;
        }
    
    
    	public static void registerDefaultComponents(ParserContext context, @Nullable Object source) {
    		registerBeanNameUrlHandlerMapping(context, source);
    		registerHttpRequestHandlerAdapter(context, source);
    		registerSimpleControllerHandlerAdapter(context, source);
    		registerHandlerMappingIntrospector(context, source);
    		registerLocaleResolver(context, source);
    		registerThemeResolver(context, source);
    		registerViewNameTranslator(context, source);
    		registerFlashMapManager(context, source);
    	}
    

再细分析

1.Spring解析<mvc:annotation-driven/>标签

  1. Spring对于不同前缀表示的命名空间会有不同的解析器类,这个类要继承“org.springframework.beans.factory.xml.NamespaceHandlerSupport”。
    例如:对于前缀名为mvc的命名空间,它对应的是一个继承抽象类的“org.springframework.beans.factory.xml.NamespaceHandlerSupport”的子类“org.springframework.web.servlet.config.MvcNamespaceHandler”
  2. 针对单个命名空间的特定元素都会在实现了NamespaceHandlerSupport的解析器类里有单独的解析器。这个解析器在解析器类里的init()方法中。
    例如:对于针对annotation-driven,就有一个解析器AnnotationDrivenBeanDefinitionParser
  3. 解析器必须实现org.springframework.beans.factory.xml.BeanDefinitionParser接口,这个接口只有一个parse方法,它有两个参数,第一个参数org.w3c.dom.Element就是我们在xml文件中声明的<mvc:annotation-driven/>结点,拿到这个结点信息,就可以开始具体的业务了(后面解释)

2.Spring怎么知道处理mvc开头的标签就调用MvcNamespaceHandler中注册的解析器的呢

  1. 这需要有一个"mvc”<–>MvcNamespaceHandler这样一个映射关系,这个映射关系在“org\springframework\spring-webmvc\5.3.1\spring-webmvc-5.3.1.jar!\META-INF\spring.handlers”,参考图例SpringMVC核心配置文件与spring.handlers对比,这里定义了只要是“http\://www.springframework.org/schema/mvc”命名空间的标签,就使用org.springframework.web.servlet.config.MvcNamespaceHandler中的解析器
  2. 将SpringMVC核心配置文件与org\springframework\spring-webmvc\5.3.1\spring-webmvc-5.3.1.jar!\META-INF\spring.schemas对比,springMVC核心配置文件根元素上的xsi:schemaLocation里面的“https://www.springframework.org/schema/mvc/spring-mvc.xsd” 并不是真的到网上去下载这个文件,而是在spring.schemas上定义了指向org\springframework\web\servlet\config\spring-mvc.xsd这个文件
  3. 这个.xsd文件是用来描述元素的命名规则的,参考mvc的annotation-driven元素
  • 假设如果想要在Spring中,想使用自己的命名空间
  1. 首先需要一个xsd文件,来描述自定义元素的命名规则,并在Spring配置文件的<beans>头中引用它
  2. 然后命名空间的一个元素就需要一个实现类去需要实现一个BeanDefinitionParser接口,在接口的parse方法中,解析在Spring配置文件中出现的与该实现类对应的元素。(如果xsd声明可以有多个元素,需要实现多个实现类实现BeanDefinitionParser接口)
  3. 最后需要继承抽象类NamespaceHandlerSupport的解析器类,在它的init方法中,调用registerBeanDefinitionParser方法,传入待解析的xml元素与解析器作为方法参数进行元素与解析器的绑定
  4. 在META-INF目录下,创建spring.schemas、spring.handlers文件,建立最高级的映射关系以便Spring进行处理

3.<mvc:annotation-scan/><context:component-scan/>有什么区别

  1. <context:component-driven/>标签是告诉Spring 来扫描指定包下的类,并注册被@Component,@Controller,@Service,@Repository等注解标记的组件。
  2. 而<mvc:annotation-scan/>是告知Spring,我们启用注解驱动。然后Spring会自动为我们注册上面说到的几个Bean到工厂中,来处理我们的请求

4.<context:component-scan/><context:annotation-config/>的区别

  1. 当我们需要使用注解模式时,直接在Spring配置文件中定义这些Bean显得比较笨拙,例如:
    使用@Autowired注解,必须事先在Spring容器中声明AutowiredAnnotationBeanPostProcessor的Bean
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor "/>

    其次就是使用@@Required注解,就必须声明RequiredAnnotationBeanPostProcessor的Bean
    <bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>
  2. 简单的说,用什么注解,就需要声明对应的BeanPostProcessor。这样的声明太麻烦了,而Spring为我们提供了一种极为方便注册这些BeanPostProcessor的方式,即使用<context:annotation- config/>隐式地向 Spring容器注册AutowiredAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor以及PersistenceAnnotationBeanPostProcessor这4个BeanPostProcessor
  3. 在我们使用注解时一般都会配置扫描包路径选项,即<context:component-scan/>。该配置项其实也包含了自动注入上述processor的功能,因此当使用<context:component-scan/>后,即可将<context:annotation-config/>省去,但必须要配置全!以防万一,还是同时声明的好

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值