1.自定义ServletContainerInitializer

2.使用ServletContext注册web三大组件

3.Servlet3.0与SpringMVC整合

1.引入依赖

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.sdehua</groupId>
  5. <artifactId>springmvc-annotation-coffee</artifactId>
  6. <version>0.0.1-SNAPSHOT</version>
  7. <packaging>war</packaging>
  8. <name>springmvc-annotation-coffee</name>
  9. <url>http://maven.apache.org</url>
  10. <properties>
  11. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  12. </properties>
  13. <dependencies>
  14. <dependency>
  15. <groupId>junit</groupId>
  16. <artifactId>junit</artifactId>
  17. <version>3.8.1</version>
  18. <scope>test</scope>
  19. </dependency>
  20. <!-- spring mvc依赖 -->
  21. <dependency>
  22. <groupId>org.springframework</groupId>
  23. <artifactId>spring-webmvc</artifactId>
  24. <version>4.3.11.RELEASE</version>
  25. </dependency>
  26. <!-- servlet api依赖 -->
  27. <dependency>
  28. <groupId>javax.servlet</groupId>
  29. <artifactId>javax.servlet-api</artifactId>
  30. <!-- 使用Servlet 3.0以上的特性 -->
  31. <version>3.1.0</version>
  32. <!--
  33. 由于Tomcat服务器里面也有servlet api,即目标环境已经该jar包了,
  34. 所以我们在这儿将以上servlet apiscope设置成provided
  35. 这样的话,我们的项目在被打成war包时,就不会带上该jar包了,
  36. 否则就会引起jar包冲突
  37. -->
  38. <scope>provided</scope>
  39. </dependency>
  40. </dependencies>
  41. <build>
  42. <plugins>
  43. <!-- java编译插件 -->
  44. <plugin>
  45. <groupId>org.apache.maven.plugins</groupId>
  46. <artifactId>maven-compiler-plugin</artifactId>
  47. <version>3.5.1</version>
  48. <configuration>
  49. <source>1.8</source>
  50. <target>1.8</target>
  51. <encoding>UTF-8</encoding>
  52. </configuration>
  53. </plugin>
  54. <plugin>
  55. <groupId>org.apache.maven.plugins</groupId>
  56. <artifactId>maven-war-plugin</artifactId>
  57. <version>2.4</version>
  58. <configuration>
  59. <failOnMissingWebXml>false</failOnMissingWebXml>
  60. </configuration>
  61. </plugin>
  62. </plugins>
  63. </build>
  64. </project>

2.两种方式整合SpringMVC

1)编码方式

  1. // 我们可以编写一个类来实现WebApplicationInitializer接口哟,当然了,你也可以编写一个类来实现ServletContainerInitializer接口
  2. public class MyWebApplicationInitializer implements WebApplicationInitializer {
  3. @Override
  4. public void onStartup(ServletContext servletContext) {
  5. // 然后,我们来创建一个AnnotationConfigWebApplicationContext对象,它应该代表的是web的IOC容器
  6. AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
  7. // 加载我们的配置类
  8. context.register(AppConfig.class);
  9. // 在容器启动的时候,我们自己来创建一个DispatcherServlet对象,并将其注册在ServletContext中
  10. DispatcherServlet servlet = new DispatcherServlet(context);
  11. ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
  12. registration.setLoadOnStartup(1);
  13. // 这儿是来配置DispatcherServlet的映射信息的
  14. registration.addMapping("/app/*");
  15. }
  16. }

2)继承抽象类AbstractAnnotationConfigDispatcherServletInitializer

展开我们maven工程下的Maven Dependencies目录,发现我们导入了spring-web-4.3.11.RELEASE.jar这样一个jar包,如下图所示:
image.png
展开该jar包,发现它里面有一个META-INF/services/目录,而且在该目录下有一个名字叫javax.servlet.ServletContainerInitializer的文件,其内容如下所示:
image.png
Servlet容器在启动应用的时候,会扫描当前应用每一个jar包里面的META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的实现类,然后加载该实现类并运行它里面的方法。

那我们就来看一下spring-web-4.3.11.RELEASE.jar中META-INF/services/目录里面的javax.servlet.ServletContainerInitializer文件中到底指定的哪一个类,从上图我们可以知道其指定的是org.springframework.web.SpringServletContainerInitializer这个类。

我们不妨查看一下该类的源码,如下图所示,它实现的就是ServletContainerInitializer接口:
image.png
它里面也只有一个onStartup方法,所以我们重点来看该方法的具体实现。

Servlet容器在启动我们Spring应用之后,会传入一个我们感兴趣的类型的集合,然后在onStartup方法中拿到之后就会来挨个遍历,如果遍历出来的我们感兴趣的类型不是接口,也不是抽象类,但是WebApplicationInitializer接口旗下的,那么就会创建该类型的一个实例,并将其存储到名为initializers的LinkedList集合中。

也可以这样说,我们Spring的应用一启动就会加载感兴趣的WebApplicationInitializer接口旗下的所有组件,并且为这些WebApplicationInitializer组件创建对象,当然前提是这些组件即不是接口,也不是抽象类。

接下来,我们就来到咱们感兴趣的WebApplicationInitializer接口中,并查看该接口的继承树(快捷键Ctrl + T),发现它下面有三个抽象类,如下图所示:
image.png
因此,接下来,我们就得来好好研究一下以上这三个抽象类:

第一层抽象类,即AbstractContextLoaderInitializer

我们先来研究WebApplicationInitializer接口下面的第一层抽象类,即AbstractContextLoaderInitializer。不妨点进该抽象类里面去看一看,如下图所示,我们主要来看其onStartUp方法:
image.png
发现在该方法中调用了一个registerContextLoaderListener方法,见名思意,应该是来注册ContextLoaderListener的。

我们继续点进registerContextLoaderListener方法里面去看一看,发现它里面调用了一个createRootApplicationContext方法,该方法是来创建根容器的,而且该方法是一个抽象方法,需要子类自己去实现。然后,根据创建的根容器创建上下文加载监听器(即ContextLoaderListener),接着,向ServletContext中注册这个监听器。

至此,以上这个抽象类,我们算是大概地分析完了。

接下来,我们来研究一下它下面的子类,即AbstractDispatcherServletInitializer,从名字上我们应该能知道它就是一个DispatcherServlet(即Spring MVC的前端控制器)的初始化器。

第二层抽象类,即AbstractDispatcherServletInitializer

我们也不妨点进该抽象类里面去看一看,并且主要来看其onStartUp方法,发现会注册DispatcherServlet,如下所示:

  1. @Override
  2. public void onStartup(ServletContext servletContext) throws ServletException {
  3. // 调用父类(即AbstractContextLoaderInitializer)的onStartup方法,先把根容器创建出来
  4. super.onStartup(servletContext);
  5. // 往ServletContext中注册DispatcherServlet
  6. registerDispatcherServlet(servletContext);
  7. }

然后,继续点进registerDispatcherServlet方法里面去看一看,看看究竟是怎么向ServletContext中注册DispatcherServlet的,如下所示:

  1. protected void registerDispatcherServlet(ServletContext servletContext) {
  2. String servletName = getServletName();
  3. Assert.hasLength(servletName, "getServletName() must not return empty or null");
  4. // 调用createServletApplicationContext方法来创建一个web的IOC容器,而且该方法还是一个抽象方法,需要子类去实现
  5. WebApplicationContext servletAppContext = createServletApplicationContext();
  6. Assert.notNull(servletAppContext,
  7. "createServletApplicationContext() did not return an application " +
  8. "context for servlet [" + servletName + "]");
  9. // 调用createDispatcherServlet方法来创建一个DispatcherServlet
  10. FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
  11. dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
  12. // 将创建好的DispatcherServlet注册到ServletAppContext中
  13. ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
  14. Assert.notNull(registration,
  15. "Failed to register servlet with name '" + servletName + "'." +
  16. "Check if there is another servlet registered under the same name.");
  17. registration.setLoadOnStartup(1);
  18. // 配置DispatcherServlet的映射映射信息,其中getServletMappings方法是一个抽象方法,需要由子类自己来重写
  19. registration.addMapping(getServletMappings());
  20. registration.setAsyncSupported(isAsyncSupported());
  21. Filter[] filters = getServletFilters();
  22. if (!ObjectUtils.isEmpty(filters)) {
  23. for (Filter filter : filters) {
  24. registerServletFilter(servletContext, filter);
  25. }
  26. }
  27. customizeRegistration(registration);
  28. }

我们发现会先调用createServletApplicationContext方法来创建一个WebApplicationContext(即web的IOC容器),再调用createDispatcherServlet方法来创建一个DispatcherServlet,如下所示,在这new了一个DispatcherServlet:

  1. protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
  2. // 创建一个DispatcherServlet
  3. return new DispatcherServlet(servletAppContext);
  4. }

此外,我们还发现在registerDispatcherServlet方法中还会将创建好的DispatcherServlet注册到ServletAppContext中,很显然,这时会返回一个ServletRegistration.Dynamic对象,自然地就要来配置该DispatcherServlet的映射信息了,好家伙,在配置该DispatcherServlet的映射信息时,还调用了一个getServletMapppings方法,不过该方法是一个抽象方法,需要由子类自己来重写。

至此,我们就算分析完了AbstractDispatcherServletInitializer抽象类。

接下来,我们来研究一下它下面的子类,即AbstractAnnotationConfigDispatcherServletInitializer,从名字上我们应该能知道它就是一个注解方式配置的DispatcherServlet初始化器,它是我们研究的重点。
[

](https://blog.csdn.net/yerenyuan_pku/article/details/114915111)

第三层抽象类,即AbstractAnnotationConfigDispatcherServletInitializer

点进该抽象类里面去看,可以看到它重写了AbstractContextLoaderInitializer抽象父类里面的createRootApplicationContext方法,如下所示,而且我们知道该方法是来创建根容器的:

  1. @Override
  2. protected WebApplicationContext createRootApplicationContext() {
  3. // 传入一个配置类(用户自定义)
  4. Class<?>[] configClasses = getRootConfigClasses();
  5. if (!ObjectUtils.isEmpty(configClasses)) {
  6. // 创建一个根容器
  7. AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
  8. // 注册获取到的配置类,相当于注册配置类里面的组件
  9. rootAppContext.register(configClasses);
  10. return rootAppContext;
  11. }
  12. else {
  13. return null;
  14. }
  15. }

那是怎样创建根容器的呢?首先获取到一个配置类,调用的可是getRootConfigClasses方法,调用该方法能传入一个配置类(其实就是我们自己写的),而且该方法还是一个抽象方法,需要由子类自己来重写。继续,要知道我们以前写的可是xml配置文件,获取到了之后,会new一个AnnotationConfigWebApplicationContext,这就相当于创建了一个根容器,然后将获取到的配置类注册进去,相当于是注册配置类里面的组件,最终返回创建的根容器。

此外,该抽象类里面还有一个createServletApplicationContext方法,如下所示,它是来创建web的IOC容器的,其实这个方法就是重写的AbstractDispatcherServletInitializer抽象类里面的createServletApplicationContext方法:

  1. @Override
  2. protected WebApplicationContext createServletApplicationContext() {
  3. // 创建一个web容器
  4. AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
  5. // 获取一个配置类
  6. Class<?>[] configClasses = getServletConfigClasses();
  7. if (!ObjectUtils.isEmpty(configClasses)) {
  8. // 注册获取到的配置类,相当于注册配置类里面的组件
  9. servletAppContext.register(configClasses);
  10. }
  11. return servletAppContext;
  12. }

可以看到,在以上方法中首先会创建一个web的IOC容器(即AnnotationConfigWebApplicationContext对象),然后再获取一个配置类,调用的是getServletConfigClasses方法,我们不妨点进该方法里面去看一下,如下所示,发现它是一个抽象方法,需要由子类自己来重写:

  1. protected abstract Class<?>[] getServletConfigClasses();

获取到配置类之后,最终会将其注册进去。

至此,我们就算分析完了AbstractAnnotationConfigDispatcherServletInitializer抽象类。

3.概括

如果我们想以注解方式(也可以说是以配置类的方式)来整合Spring MVC,即再也不要在web.xml文件中进行配置,那么我们只需要自己来继承AbstractAnnotationConfigDispatcherServletInitializer这个抽象类就行了。继承它之后,它里面会给我们预留一些抽象方法,例如getServletConfigClasses、getRootConfigClasses以及getServletMappings等抽象方法,我们只须重写这些抽象方法即可,这样就能指定DispatcherServlet的配置信息了,随即,DispatcherServlet就会被自动地注册到ServletContext对象中。
image.png
查看官方文档可以看到Spring官方也推荐使用父子容器的概念,分为根容器和web容器:

  • web容器:也即子容器,只来扫描controller控制层组件(一般不包含核心业务逻辑,只有数据校验和视图渲染等工作)与视图解析器等等

  • 根容器:扫描业务逻辑核心组件,包括不同的数据源等等

继续往下看1.1.1. Context Hierarchy这一小节中的内容,发现跟我们分析的一样,要想以注解方式(也可以说是以配置类的方式)来整合Spring MVC,那么只需要我们自己来继承AbstractAnnotationConfigDispatcherServletInitializer这个抽象类就行了,这样,web容器启动的时候就能处理我们实现的这个类的内容了。
image.png

4.案例实战

首先,我们来编写一个类,例如MyWebAppInitializer,来继承AbstractAnnotationConfigDispatcherServletInitializer这个抽象类,一开始我们写成下面这样:

  1. package com.sdehua;
  2. import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
  3. public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
  4. /*
  5. * getRootConfigClasses方法:它是来获取根容器的配置类的,
  6. * 该配置类就类似于我们以前经常写的Spring的配置文件,
  7. * 而且我们以前是利用监听器的方式来读取Spring的配置文件的哟~,
  8. * 还记得吗?然后,就能创建出一个父容器了
  9. */
  10. @Override
  11. protected Class<?>[] getRootConfigClasses() {
  12. return null;
  13. }
  14. /*
  15. * getServletConfigClasses方法:它是来获取web容器的配置类的,
  16. * 该配置类就类似于我们以前经常写的Spring MVC的配置文件,
  17. * 而且我们以前是利用前端控制器来加载Spring MVC的配置文件的哟~,
  18. * 你还记得吗?然后,就能创建出一个子容器了
  19. */
  20. @Override
  21. protected Class<?>[] getServletConfigClasses() {
  22. return null;
  23. }
  24. /*
  25. * getServletMappings方法:它是来获取DispatcherServlet的映射信息的。
  26. * 该方法需要返回一个String[],
  27. *
  28. * 如果我们返回的是这样一个new String[]{"/"},
  29. * 那么DispatcherServlet就会来拦截所有请求,包括静态资源,比如xxx.js文件、
  30. * xxx.png等,但是不包括*.jsp,也即不会拦截所有的jsp页面。
  31. *
  32. * 如果我们返回的是这样一个new String[]{"/*"}
  33. * 那么DispatcherServlet就是真正来拦截所有请求了,包括*.jsp,
  34. * 也就是说就连jsp页面都拦截,所以,我们切忌不可写成这样(即/*)。
  35. * 否则的话,jsp页面一旦被Spring MVC拦截,
  36. * 最终极有可能我们就看不到jsp页面了,
  37. * 因为jsp页面是由Tomcat服务器中的jsp引擎来解析的。
  38. */
  39. @Override
  40. protected String[] getServletMappings() {
  41. return new String[] {"/"};
  42. }
  43. }

由于我们还需要在getRootConfigClasses和getServletConfigClasses这俩方法中指定两个配置类的位置,所以我们来创建上两个配置类,分别如下:

  • 根容器的配置类,例如RootConfig ```java package com.sdehua.config;

public class RootConfig {

}

  1. - web容器的配置类,例如AppConfig
  2. ```java
  3. package com.sdehua.config;
  4. public class AppConfig {
  5. }

以上这两个配置类最终需要形成父子容器的效果。还要一点需要重点来说明,即AppConfig配置类只来扫描所有的控制器(即Controller),以及和网站功能有关的那些逻辑组件;RootConfig配置类只来扫描所有的业务逻辑核心组件,包括dao层组件、不同的数据源等等,即只扫描和业务逻辑相关的组件。
[

](https://blog.csdn.net/yerenyuan_pku/article/details/114968293)
接下来,我们来完善以上两个配置类。首先,先来完善RootConfig配置类,我们可以使用@ComponentScan注解来指定扫描com.meimeixia包以及子包下的所有组件,而且为了形成父子容器,还必须得排除掉一些组件,那排除掉哪些组件呢?很显然,应该排除掉controller控制层组件,即Controller。所以,我们得通过@ComponentScan注解的excludeFilters()方法按照注解的方式来排除掉所有标注了@Controller注解的组件。

  1. package com.sdehua.config;
  2. import org.springframework.context.annotation.ComponentScan;
  3. import org.springframework.context.annotation.ComponentScan.Filter;
  4. import org.springframework.context.annotation.FilterType;
  5. import org.springframework.stereotype.Controller;
  6. //该配置类相当于Spring的配置文件
  7. //Spring容器不扫描Controller,它是一个父容器
  8. @ComponentScan(value="com.sdehua",excludeFilters={
  9. @Filter(type=FilterType.ANNOTATION, classes={Controller.class})
  10. })
  11. public class RootConfig {
  12. }

然后,再来完善AppConfig配置类,我们同样使用@ComponentScan注解来指定扫描com.meimeixia包以及子包下的所有组件,但是呢,与上面正好相反,这儿只扫描controller控制层组件,即Controller,如此一来就能与上面形成互补配置了。OK,那我们就通过@ComponentScan注解的includeFilters()方法按照注解的方式来指定只扫描controller控制层组件。

  1. package com.sdehua.config;
  2. import org.springframework.context.annotation.ComponentScan;
  3. import org.springframework.context.annotation.ComponentScan.Filter;
  4. import org.springframework.context.annotation.FilterType;
  5. import org.springframework.stereotype.Controller;
  6. @ComponentScan(value="com.sdehua",includeFilters={
  7. @Filter(type=FilterType.ANNOTATION, classes={Controller.class})
  8. },useDefaultFilters=false)
  9. public class AppConfig {
  10. }

尤其要注意这一点,在以上配置类中通过@ComponentScan注解的includeFilters()方法来指定只扫描controller控制层组件时,需要禁用掉默认的过滤规则,即必须得加上useDefaultFilters=false这样一个配置。千万记得必须要禁用掉默认的过滤规则哟,否则扫描就不会生效了。

但是,在RootConfig配置类中通过@ComponentScan注解的excludeFilters()方法来指定排除哪些组件时,是不需要对useDefaultFilters进行设置的,因为其默认值就是true,表示默认情况下标注了@Component、@Repository、@Service以及@Controller这些注解的组件都会被扫描,即扫描所有。

接下来,我们就要分别来编写一个controller控制层组件和service业务层组件来进行测试了。首先,编写一个service业务层组件,例如HelloService,如下所示:

  1. package com.sdehua.service;
  2. import org.springframework.stereotype.Service;
  3. @Service
  4. public class HelloService {
  5. public String sayHello(String name){
  6. return "Hello,"+name;
  7. }
  8. }

然后,编写一个controller控制层组件,例如HelloController,并且我们还可以在该HelloController中注入HelloService组件,来调用其方法。

  1. package com.sdehua.controller;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.ResponseBody;
  6. import com.sdehua.service.HelloService;
  7. @Controller
  8. public class HelloController {
  9. @Autowired
  10. HelloService helloService;
  11. @ResponseBody
  12. @RequestMapping("/hello")
  13. public String hello() {
  14. String hello = helloService.sayHello("tomcat...");
  15. return hello;
  16. }
  17. }

从以上HelloController的代码中,我们可以看到它里面的hello方法是来处理hello请求的,而且通过@ResponseBody注解会直接将返回的结果(即字符串)写到浏览器页面中。

现在,我们能不能启动咱们的项目进行测试了呢?还不可以,因为我们还没有在我们自己编写的MyWebAppInitializer类中指定两个配置类的位置。OK,那我们来分别指定一下,如下所示:

  1. package com.sdehua;
  2. import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
  3. import com.sdehua.config.AppConfig;
  4. import com.sdehua.config.RootConfig;
  5. public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
  6. /*
  7. * getRootConfigClasses方法:它是来获取根容器的配置类的,
  8. * 该配置类就类似于我们以前经常写的Spring的配置文件,
  9. * 而且我们以前是利用监听器的方式来读取Spring的配置文件的哟~,
  10. * 还记得吗?然后,就能创建出一个父容器了
  11. */
  12. @Override
  13. protected Class<?>[] getRootConfigClasses() {
  14. return new Class<?>[] {RootConfig.class};
  15. }
  16. /*
  17. * getServletConfigClasses方法:它是来获取web容器的配置类的,
  18. * 该配置类就类似于我们以前经常写的Spring MVC的配置文件,
  19. * 而且我们以前是利用前端控制器来加载Spring MVC的配置文件的哟~,
  20. * 你还记得吗?然后,就能创建出一个子容器了
  21. */
  22. @Override
  23. protected Class<?>[] getServletConfigClasses() {
  24. return new Class<?>[] {AppConfig.class};
  25. }
  26. /*
  27. * getServletMappings方法:它是来获取DispatcherServlet的映射信息的。
  28. * 该方法需要返回一个String[],
  29. *
  30. * 如果我们返回的是这样一个new String[]{"/"},
  31. * 那么DispatcherServlet就会来拦截所有请求,包括静态资源,比如xxx.js文件、
  32. * xxx.png等,但是不包括*.jsp,也即不会拦截所有的jsp页面。
  33. *
  34. * 如果我们返回的是这样一个new String[]{"/*"}
  35. * 那么DispatcherServlet就是真正来拦截所有请求了,包括*.jsp,
  36. * 也就是说就连jsp页面都拦截,所以,我们切忌不可写成这样(即/*)。
  37. * 否则的话,jsp页面一旦被Spring MVC拦截,
  38. * 最终极有可能我们就看不到jsp页面了,
  39. * 因为jsp页面是由Tomcat服务器中的jsp引擎来解析的。
  40. */
  41. @Override
  42. protected String[] getServletMappings() {
  43. return new String[] {"/"};
  44. }
  45. }

这就相当于分别来指定Spring配置文件和Spring MVC配置文件的位置。

最后,我们就可以启动项目来进行测试了。项目启动成功之后,我们在浏览器地址栏中输入http://localhost:8080/springmvc-annotation-coffee/hello进行访问:
image.png
这说明我们controller控制层组件和service业务层组件都起作用了。

至此,使用注解的方式(即配置类的方式)来整合Spring MVC,彻底讲完了。

5.总结

我们知道web容器(即Tomcat服务器)在启动应用的时候,会扫描当前应用每一个jar包里面的META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的实现类,然后再运行该实现类中的方法。

恰好在spring-web-4.3.11.RELEASE.jar中的META-INF/services/目录里面有一个javax.servlet.ServletContainerInitializer文件,并且在该文件中指定的实现类就是org.springframework.web.SpringServletContainerInitializer,打开该实现类,发现它上面标注了@HandlesTypes(WebApplicationInitializer.class)这样一个注解。

因此,web容器在启动应用的时候,便会来扫描并加载org.springframework.web.SpringServletContainerInitializer实现类,而且会传入我们感兴趣的类型(即WebApplicationInitializer接口)的所有后代类型,最终再运行其onStartup方法。

  1. package org.springframework.web;
  2. import java.lang.reflect.Modifier;
  3. import java.util.LinkedList;
  4. import java.util.List;
  5. import java.util.ServiceLoader;
  6. import java.util.Set;
  7. import javax.servlet.ServletContainerInitializer;
  8. import javax.servlet.ServletContext;
  9. import javax.servlet.ServletException;
  10. import javax.servlet.annotation.HandlesTypes;
  11. import org.springframework.core.annotation.AnnotationAwareOrderComparator;
  12. @HandlesTypes(WebApplicationInitializer.class)
  13. public class SpringServletContainerInitializer implements ServletContainerInitializer {
  14. @Override
  15. public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
  16. throws ServletException {
  17. List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
  18. if (webAppInitializerClasses != null) {
  19. for (Class<?> waiClass : webAppInitializerClasses) {
  20. // Be defensive: Some servlet containers provide us with invalid classes,
  21. // no matter what @HandlesTypes says...
  22. if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
  23. WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
  24. try {
  25. initializers.add((WebApplicationInitializer) waiClass.newInstance());
  26. }
  27. catch (Throwable ex) {
  28. throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
  29. }
  30. }
  31. }
  32. }
  33. if (initializers.isEmpty()) {
  34. servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
  35. return;
  36. }
  37. servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
  38. AnnotationAwareOrderComparator.sort(initializers);
  39. for (WebApplicationInitializer initializer : initializers) {
  40. initializer.onStartup(servletContext);
  41. }
  42. }
  43. }

从以上onStartup方法中,我们可以看到它会遍历感兴趣的类型(即WebApplicationInitializer接口)的所有后代类型,然后利用反射技术创建WebApplicationInitializer类型的对象,而我们自定义的MyWebAppInitializer就是WebApplicationInitializer这种类型的。而且创建完之后,都会存储到名为initializers的LinkedList集合中。接着,又会遍历该集合,并调用每一个WebApplicationInitializer对象的onStartup方法。

遍历到每一个WebApplicationInitializer对象之后,调用其onStartup方法,实际上就是先调用其(例如我们自定义的MyWebAppInitializer)最高父类的onStartup方法,创建根容器;然后再调用其次高父类的onStartup方法,创建web容器以及DispatcherServlet;接着,根据其重写的getServletMappings方法来为DispatcherServlet配置映射信息,差不多就是这样了。

4.定制与接管Spring MVC

试着回顾一下我们以前整合Spring MVC的开发,是不是应该有一个Spring MVC的配置文件?例如mvc.xml,在该配置文件中我们会写非常多的配置!下面列举一下该配置文件中的一些常用配置,比如经常会写上这样的配置:

  1. <mvc:default-servlet-handler/>

该配置的作用就是将Spring MVC处理不了的请求交给Tomcat服务器,它是专门来针对静态资源的。试想,如果Spring MVC拦截了所有请求,必然地,静态资源也被一起拦截了,那么静态资源就无法访问到了,而写上该配置之后,静态资源就可以被访问到了。

还有,我们还写过这样的配置:

  1. <mvc:annotation-driven />

一般而言,以上配置经常会跟配置成对出现,该配置更多的作用是来开启Spring MVC的高级功能。

还有,我们还配置过拦截器,就像下面这样:

  1. <mvc:interceptors>
  2. ...
  3. </mvc:interceptors>

此外,我们还有可能配置视图映射,就像下面这样:

  1. <mvc:view-controller path=""/>

也就是说,我们以前会在Spring MVC的配置文件中配置非常多的东西,但是现在没有该配置文件了,那么我们该怎么做到上述的这些事情呢?其实非常简单,只要查看Spring MVC的官方文档就知道了,找到1.11.1. Enable MVC Configuration这一小节,映入眼帘的就是一个@EnableWebMvc注解,如下图所示:
image.png
这告诉我们首先要做的第一件事就是使用@EnableWebMvc注解,它的作用就相当于来启动Spring MVC的自定义配置。

现在,我们就要开始定制Spring MVC,分为两步。

第一步,首先你得写一个配置类,然后将@EnableWebMvc注解标注在该配置类上。我们就以上一讲中的AppConfig配置类为例,将@EnableWebMvc注解标注在该配置类上,如下所示:

  1. package com.sdehua.config;
  2. import org.springframework.context.annotation.ComponentScan;
  3. import org.springframework.context.annotation.ComponentScan.Filter;
  4. import org.springframework.context.annotation.FilterType;
  5. import org.springframework.stereotype.Controller;
  6. import org.springframework.web.servlet.config.annotation.EnableWebMvc;
  7. @ComponentScan(value="com.sdehua",includeFilters={
  8. @Filter(type=FilterType.ANNOTATION, classes={Controller.class})
  9. },useDefaultFilters=false)
  10. @EnableWebMvc
  11. public class AppConfig {
  12. }

@EnableWebMvc注解的作用就是来开启Spring MVC的定制配置功能。我们查看Spring MVC官方文档中的1.11.1. Enable MVC Configuration这一小节的内容,发现在配置类上标注了@EnableWebMvc注解之后,相当于我们以前在xml配置文件中加上了这样一个配置,它是来开启Spring MVC的一些高级功能的:
image.png
第二步,配置组件。要配置的组件还是挺多的,比如视图解析器、视图映射、静态资源映射以及拦截器等等,直接参考Spring MVC的官方文档。

我们查看一下Spring MVC官方文档中1.11.2. MVC Config API这一小节的内容,发现只须让Java配置类实现WebMvcConfigurer接口,就可以来定制配置。不妨让AppConfig配置类来实现该接口,如下所示:

  1. package com.sdehua.config;
  2. import java.util.List;
  3. import org.springframework.context.annotation.ComponentScan;
  4. import org.springframework.context.annotation.ComponentScan.Filter;
  5. import org.springframework.context.annotation.FilterType;
  6. import org.springframework.format.FormatterRegistry;
  7. import org.springframework.http.converter.HttpMessageConverter;
  8. import org.springframework.stereotype.Controller;
  9. import org.springframework.validation.MessageCodesResolver;
  10. import org.springframework.validation.Validator;
  11. import org.springframework.web.method.support.HandlerMethodArgumentResolver;
  12. import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
  13. import org.springframework.web.servlet.HandlerExceptionResolver;
  14. import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
  15. import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
  16. import org.springframework.web.servlet.config.annotation.CorsRegistry;
  17. import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
  18. import org.springframework.web.servlet.config.annotation.EnableWebMvc;
  19. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  20. import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
  21. import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
  22. import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
  23. import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
  24. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  25. @ComponentScan(value="com.sdehua",includeFilters={
  26. @Filter(type=FilterType.ANNOTATION, classes={Controller.class})
  27. },useDefaultFilters=false)
  28. @EnableWebMvc
  29. public class AppConfig implements WebMvcConfigurer{
  30. @Override
  31. public void addArgumentResolvers(List<HandlerMethodArgumentResolver> arg0) {
  32. // TODO Auto-generated method stub
  33. }
  34. @Override
  35. public void addCorsMappings(CorsRegistry arg0) {
  36. // TODO Auto-generated method stub
  37. }
  38. @Override
  39. public void addFormatters(FormatterRegistry arg0) {
  40. // TODO Auto-generated method stub
  41. }
  42. @Override
  43. public void addInterceptors(InterceptorRegistry arg0) {
  44. // TODO Auto-generated method stub
  45. }
  46. @Override
  47. public void addResourceHandlers(ResourceHandlerRegistry arg0) {
  48. // TODO Auto-generated method stub
  49. }
  50. @Override
  51. public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> arg0) {
  52. // TODO Auto-generated method stub
  53. }
  54. @Override
  55. public void addViewControllers(ViewControllerRegistry arg0) {
  56. // TODO Auto-generated method stub
  57. }
  58. @Override
  59. public void configureAsyncSupport(AsyncSupportConfigurer arg0) {
  60. // TODO Auto-generated method stub
  61. }
  62. @Override
  63. public void configureContentNegotiation(ContentNegotiationConfigurer arg0) {
  64. // TODO Auto-generated method stub
  65. }
  66. @Override
  67. public void configureDefaultServletHandling(DefaultServletHandlerConfigurer arg0) {
  68. // TODO Auto-generated method stub
  69. }
  70. @Override
  71. public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> arg0) {
  72. // TODO Auto-generated method stub
  73. }
  74. @Override
  75. public void configureMessageConverters(List<HttpMessageConverter<?>> arg0) {
  76. // TODO Auto-generated method stub
  77. }
  78. @Override
  79. public void configurePathMatch(PathMatchConfigurer arg0) {
  80. // TODO Auto-generated method stub
  81. }
  82. @Override
  83. public void configureViewResolvers(ViewResolverRegistry arg0) {
  84. // TODO Auto-generated method stub
  85. }
  86. @Override
  87. public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> arg0) {
  88. // TODO Auto-generated method stub
  89. }
  90. @Override
  91. public void extendMessageConverters(List<HttpMessageConverter<?>> arg0) {
  92. // TODO Auto-generated method stub
  93. }
  94. @Override
  95. public MessageCodesResolver getMessageCodesResolver() {
  96. // TODO Auto-generated method stub
  97. return null;
  98. }
  99. @Override
  100. public Validator getValidator() {
  101. // TODO Auto-generated method stub
  102. return null;
  103. }
  104. }

发现这个WebMvcConfigurer接口里面定义了好多的方法!如下图所示:
image.png
实现该接口之后,我们就得来实现其里面的每一个方法了,这就是来定制Spring MVC。

来看一下其中的configurePathMatch方法,该方法的作用就是来配置路径映射规则的:

  1. @Override
  2. public void configurePathMatch(PathMatchConfigurer configurer) {
  3. // TODO Auto-generated method stub
  4. }

再来看一下其中的configureAsyncSupport方法,它的作用是来配置是否开启异步支持的:

  1. @Override
  2. public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
  3. // TODO Auto-generated method stub
  4. }

再再来看一下其中的configureDefaultServletHandling方法,它的作用是来配置是否开启静态资源的。我们不妨实现一下该方法,如下所示:

  1. @Override
  2. public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
  3. // TODO Auto-generated method stub
  4. configurer.enable()
  5. }

实现以上方法之后,效果就相当于我们以前在xml配置文件中写上这样一个配置。

继续往下看吧,来看一下其中的addFormatters方法,它的作用是可以来添加一些自定义的类型转换器以及格式化器:

  1. @Override
  2. public void addFormatters(FormatterRegistry registry) {
  3. // TODO Auto-generated method stub
  4. }

最后,看一下其中的addInterceptors方法,顾名思义,它是来添加拦截器的:

  1. @Override
  2. public void addInterceptors(InterceptorRegistry registry) {
  3. // TODO Auto-generated method stub
  4. }

后面还有非常多的方法,这时,你会发现配置类只要实现了WebMvcConfigurer接口,那么你就得一个一个来实现其中的方法了,麻烦吗?!

于是,我们就要看看WebMvcConfigurer接口的源码了,如下图所示,我们不妨查看一下该接口的继承树(快捷键Ctrl + T),发现它下面有一个叫WebMvcConfigurerAdapter的适配器:
image.png
我们点进去看一下它的源码,发现它是一个实现了WebMvcConfigurer接口的抽象类,如下图所示:
image.png
该抽象类把WebMvcConfigurer接口中的方法都实现了,只不过每一个方法里面都是空的而已,所以,我们的配置类继承WebMvcConfigurerAdapter抽象类会比较好一点。这是因为如果我们的配置类实现了WebMvcConfigurer接口,那么其中的每一个方法我们都得来具体实现,但是呢,大多数情况下我们并不需要实现这么多方法!

于是,就要修改一下AppConfig配置类,让其继承WebMvcConfigurerAdapter抽象类,如下所示:

  1. package com.sdehua.config;
  2. import org.springframework.context.annotation.ComponentScan;
  3. import org.springframework.context.annotation.ComponentScan.Filter;
  4. import org.springframework.context.annotation.FilterType;
  5. import org.springframework.stereotype.Controller;
  6. import org.springframework.web.servlet.config.annotation.EnableWebMvc;
  7. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  8. @ComponentScan(value="com.sdehua",includeFilters={
  9. @Filter(type=FilterType.ANNOTATION, classes={Controller.class})
  10. },useDefaultFilters=false)
  11. @EnableWebMvc
  12. public class AppConfig extends WebMvcConfigurerAdapter{
  13. }

接下来,我们可以来个性化定制Spring MVC了,因为只须复写WebMvcConfigurerAdapter抽象类中的某些方法就行了。这里,我们不妨先来定制一下视图解析器,要想达成这一目的,只须复写WebMvcConfigurerAdapter抽象类中的configureViewResolvers方法:

  1. package com.sdehua.config;
  2. import org.springframework.context.annotation.ComponentScan;
  3. import org.springframework.context.annotation.ComponentScan.Filter;
  4. import org.springframework.context.annotation.FilterType;
  5. import org.springframework.stereotype.Controller;
  6. import org.springframework.web.servlet.config.annotation.EnableWebMvc;
  7. import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
  8. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  9. @ComponentScan(value="com.sdehua",includeFilters={
  10. @Filter(type=FilterType.ANNOTATION, classes={Controller.class})
  11. },useDefaultFilters=false)
  12. @EnableWebMvc
  13. public class AppConfig extends WebMvcConfigurerAdapter{
  14. // 定制视图解析器
  15. @Override
  16. public void configureViewResolvers(ViewResolverRegistry registry) {
  17. // TODO Auto-generated method stub
  18. // super.configureViewResolvers(registry); 注释掉这行代码,因为其父类中的方法都是空的
  19. // 如果直接调用jsp方法,那么默认所有的页面都从/WEB-INF/目录下开始找,即找所有的jsp页面
  20. // registry.jsp();
  21. /*
  22. * 当然了,我们也可以自己来编写规则,比如指定一个前缀,即/WEB-INF/views/,再指定一个后缀,即.jsp,
  23. * 很显然,此时,所有的jsp页面都会存放在/WEB-INF/views/目录下,自然地,程序就会去/WEB-INF/views/目录下面查找jsp页面了
  24. */
  25. registry.jsp("/WEB-INF/views/", ".jsp");
  26. }
  27. }

为了达到测试的目的,我们在项目的webapp目录下新建一个WEB-INF/views目录,该目录是专门用于存放jsp页面的,然后再在WEB-INF/views目录新建一个jsp页面,例如success.jsp,其内容如下所示:

  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  4. <html>
  5. <head>
  6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  7. <title>Success</title>
  8. </head>
  9. <body>
  10. <h1>成功success!</h1>
  11. </body>
  12. </html>

接着,我们在HelloController中新增一个如下success方法,以便来处理success请求:

  1. package com.sdehua.controller;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.ResponseBody;
  6. import com.sdehua.service.HelloService;
  7. @Controller
  8. public class HelloController {
  9. @Autowired
  10. HelloService helloService;
  11. @ResponseBody
  12. @RequestMapping("/hello")
  13. public String hello() {
  14. String hello = helloService.sayHello("tomcat...");
  15. return hello;
  16. }
  17. // 处理success请求
  18. @RequestMapping("/success")
  19. public String success() {
  20. // 这儿直接返回"success",那么它就会跟我们视图解析器中指定的那个前后缀进行拼串,来到指定的页面
  21. return "success";
  22. }
  23. }

当客户端发送过来一个suc请求,那么HelloController中的以上success方法就会来处理这个请求。由于该方法直接返回了一个success字符串,所以该字符串就会跟我们视图解析器中指定的那个前后缀进行拼串,并最终来到所指定的页面。

说人话就是,只要客户端发送过来一个suc请求,那么服务端就会响应/WEB-INF/views/目录下的success.jsp页面给客户端。

OK,我们启动项目,启动成功之后,在浏览器地址栏中输入http://localhost:8080/springmvc-annotation-liayun/success进行访问,效果如下图所示:
image.png
这说明我们已经成功定制了视图解析器。

然后,我们来定制一下静态资源的访问。假设我们项目的webapp目录下有一些静态资源,比如有一张图片,名字就叫test.jpg,打开发现它是一张美女图片:
image.png
此时,我们在项目的webapp目录下新建一个jsp页面,例如index.jsp,很显然,该页面是项目的首页,随即我们在首页中使用一个5. WEB相关 - 图16标签来引入上面那张美女图片,即在页面中引入静态资源:

  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  4. <html>
  5. <head>
  6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  7. <title>Insert title here</title>
  8. </head>
  9. <body>
  10. <img alt="" src="test.jpg">
  11. </body>
  12. </html>

此时,在浏览器中访问项目的首页,你会发现上面那张美女图片压根就显示不出来,与此同时,Eclipse控制台会打印如下这样一个警告:
image.png
这是因为请求被Spring MVC拦截处理了,这样,它就得要找@RequestMapping注解中写的映射了,但是实际上呢,test.jpg是一个静态资源,它得交给Tomcat服务器去处理,因此,我们就得来定制静态资源的访问。

要想达成这一目的,我们只须复写WebMvcConfigurerAdapter抽象类中的configureDefaultServletHandling方法。

  1. package com.sdehua.config;
  2. import org.springframework.context.annotation.ComponentScan;
  3. import org.springframework.context.annotation.ComponentScan.Filter;
  4. import org.springframework.context.annotation.FilterType;
  5. import org.springframework.stereotype.Controller;
  6. import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
  7. import org.springframework.web.servlet.config.annotation.EnableWebMvc;
  8. import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
  9. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  10. @ComponentScan(value="com.sdehua",includeFilters={
  11. @Filter(type=FilterType.ANNOTATION, classes={Controller.class})
  12. },useDefaultFilters=false)
  13. @EnableWebMvc
  14. public class AppConfig extends WebMvcConfigurerAdapter{
  15. // 定制视图解析器
  16. @Override
  17. public void configureViewResolvers(ViewResolverRegistry registry) {
  18. // TODO Auto-generated method stub
  19. // super.configureViewResolvers(registry); 注释掉这行代码,因为其父类中的方法都是空的
  20. // 如果直接调用jsp方法,那么默认所有的页面都从/WEB-INF/目录下开始找,即找所有的jsp页面
  21. // registry.jsp();
  22. /*
  23. * 当然了,我们也可以自己来编写规则,比如指定一个前缀,即/WEB-INF/views/,再指定一个后缀,即.jsp,
  24. * 很显然,此时,所有的jsp页面都会存放在/WEB-INF/views/目录下,自然地,程序就会去/WEB-INF/views/目录下面查找jsp页面了
  25. */
  26. registry.jsp("/WEB-INF/views/", ".jsp");
  27. }
  28. // 定制静态资源的访问
  29. @Override
  30. public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
  31. configurer.enable();
  32. }
  33. }

在以上configureDefaultServletHandling方法中调用configurer.enable(),其实就相当于我们以前在xml配置文件中写上这样一个配置。

此时,我们重启项目,成功之后,再次来访问项目的首页,发现那张美女图片终于在浏览器页面中显示出来了,效果如下:
image.png
OK,静态资源就能访问了。

接着,定制拦截器,这还是稍微有点复杂的。

先编写一个拦截器,例如MyFirstInterceptor,要知道一个类要想成为拦截器,那么它必须得实现Spring MVC提供的HandlerInterceptor接口,如下所示:

  1. package com.sdehua.interceptor;
  2. import javax.servlet.http.HttpServletRequest;
  3. import javax.servlet.http.HttpServletResponse;
  4. import org.springframework.web.servlet.HandlerInterceptor;
  5. import org.springframework.web.servlet.ModelAndView;
  6. public class MyFirstInterceptor implements HandlerInterceptor{
  7. // 在页面响应以后执行
  8. @Override
  9. public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
  10. throws Exception {
  11. // TODO Auto-generated method stub
  12. System.out.println("*【afterCompletion】");
  13. }
  14. // 在目标方法运行正确以后执行
  15. @Override
  16. public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
  17. throws Exception {
  18. // TODO Auto-generated method stub
  19. System.out.println("*【postHandle】");
  20. }
  21. // 在目标方法运行之前执行
  22. @Override
  23. public boolean preHandle(HttpServletRequest request, HttpServletResponse arg1, Object arg2) throws Exception {
  24. // TODO Auto-generated method stub
  25. System.out.println("*【preHandle】");
  26. return true; // 返回true,表示放行(目标方法)
  27. }
  28. }

编写好以上拦截器之后,如果要是搁以前,那么我们就得在xml配置文件里面像下面这样配置该拦截器:

  1. <mvc:interceptors>
  2. <mvc:interceptor>
  3. <mvc:mapping path="/**"/>
  4. <bean class="com.meimeixia.controller.MyFirstInterceptor"/>
  5. </mvc:interceptor>
  6. </mvc:interceptors>

而现在我们只须复写WebMvcConfigurerAdapter抽象类中的addInterceptors方法就行了,就像下面这样:

  1. package com.sdehua.config;
  2. import org.springframework.context.annotation.ComponentScan;
  3. import org.springframework.context.annotation.ComponentScan.Filter;
  4. import org.springframework.context.annotation.FilterType;
  5. import org.springframework.stereotype.Controller;
  6. import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
  7. import org.springframework.web.servlet.config.annotation.EnableWebMvc;
  8. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  9. import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
  10. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  11. import com.sdehua.interceptor.MyFirstInterceptor;
  12. @ComponentScan(value="com.sdehua",includeFilters={
  13. @Filter(type=FilterType.ANNOTATION, classes={Controller.class})
  14. },useDefaultFilters=false)
  15. @EnableWebMvc
  16. public class AppConfig extends WebMvcConfigurerAdapter{
  17. // 定制视图解析器
  18. @Override
  19. public void configureViewResolvers(ViewResolverRegistry registry) {
  20. // super.configureViewResolvers(registry); 注释掉这行代码,因为其父类中的方法都是空的
  21. // 如果直接调用jsp方法,那么默认所有的页面都从/WEB-INF/目录下开始找,即找所有的jsp页面
  22. // registry.jsp();
  23. /*
  24. * 当然了,我们也可以自己来编写规则,比如指定一个前缀,即/WEB-INF/views/,再指定一个后缀,即.jsp,
  25. * 很显然,此时,所有的jsp页面都会存放在/WEB-INF/views/目录下,自然地,程序就会去/WEB-INF/views/目录下面查找jsp页面了
  26. */
  27. registry.jsp("/WEB-INF/views/", ".jsp");
  28. }
  29. // 定制静态资源的访问
  30. @Override
  31. public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
  32. configurer.enable();
  33. }
  34. // 定制拦截器
  35. @Override
  36. public void addInterceptors(InterceptorRegistry registry) {
  37. // super.addInterceptors(registry);
  38. /*
  39. * addInterceptor方法里面要传一个拦截器对象,该拦截器对象可以从容器中获取过来,也可以我们自己来new一个,
  40. * 很显然,这儿我们是new了一个我们自定义的拦截器对象。
  41. *
  42. * 虽然创建出了一个拦截器,但是最关键的一点还是指示拦截器要拦截哪些请求,因此还得继续使用addPathPatterns方法来配置一下,
  43. * 若在addPathPatterns方法中传入了"/**",则表示拦截器会拦截任意请求,而不管该请求是不是有任意多层路径
  44. */
  45. registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
  46. }
  47. }

OK,我们来看一下以上定制的拦截器能不能生效。重启项目,项目启动成功之后,在浏览器地址栏中输入http://localhost:8080/springmvc-annotation-liayun/suc进行访问,即访问suc请求,发现Eclipse控制台打印出了如下内容:
image.png
这说明定制的拦截器生效了。

那么,剩余其他的对Spring MVC的个性化定制,就是照葫芦画瓢,很简单,而且还可以参考Spring MVC的官方文档,比方说要定制类型转换器,那么可以参考Spring MVC官方文档中的1.11.3. Type Conversion这一小节中的内容,主要是参考Java代码:
image.png
所以,常看官方写的文档,在每一小节中,上面都会先用一段Java代码告诉你应该复写哪个方法,下面则会告诉你复写之后相当于我们以前在xml配置文件中写的什么样的配置。

最后,我们总结一下,如果我们想要个性化定制Spring MVC,那么只须编写一个配置类来继承WebMvcConfigurerAdapter抽象类就行了,当然,前提是该配置类上得有@EnableWebMvc注解。这就是个性化定制Spring MVC的规则。