当前位置:   article > 正文

基于注解搭建Spring环境_abstractannotationconfigdispatcherservletinitializ

abstractannotationconfigdispatcherservletinitializer

一、创建Maven Project或Maven Module

配置webapp(SpringMVC)

选中这个之后出现这个页面(设置web资源的目录,也就是我们webapp所在的位置,它默认的位置并不正确,所有我们要修改一下),修改为 src\main\webapp(没有这个文件夹就新建)

因为我们现在搭建的注解版,所以就不需要web.xml(点击-号把默认的删除)

最终的项目目录:

二、pom.xml的依赖配置

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.codedot</groupId>
  7. <artifactId>SpringAnno</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <packaging>war</packaging>
  10. <properties>
  11. <!-- JDK版本 -->
  12. <java.version>1.8</java.version>
  13. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  14. <!-- Spring 5.1.6 要求JDK必须是1.8以上-->
  15. <spring.version>5.1.16.RELEASE</spring.version>
  16. </properties>
  17. <dependencies>
  18. <!-- 核心容器 之 spring-core -->
  19. <dependency>
  20. <groupId>org.springframework</groupId>
  21. <artifactId>spring-core</artifactId>
  22. <version>${spring.version}</version>
  23. </dependency>
  24. <!-- 核心容器 之 spring-beans -->
  25. <dependency>
  26. <groupId>org.springframework</groupId>
  27. <artifactId>spring-beans</artifactId>
  28. <version>${spring.version}</version>
  29. </dependency>
  30. <!-- 核心容器 之 spring-context -->
  31. <dependency>
  32. <groupId>org.springframework</groupId>
  33. <artifactId>spring-context</artifactId>
  34. <version>${spring.version}</version>
  35. </dependency>
  36. <!-- 核心容器 之 spring-context-support -->
  37. <dependency>
  38. <groupId>org.springframework</groupId>
  39. <artifactId>spring-context-support</artifactId>
  40. <version>${spring.version}</version>
  41. </dependency>
  42. <!-- 核心容器 之 spring-expression -->
  43. <dependency>
  44. <groupId>org.springframework</groupId>
  45. <artifactId>spring-expression</artifactId>
  46. <version>${spring.version}</version>
  47. </dependency>
  48. <!-- Spring Web -->
  49. <dependency>
  50. <groupId>org.springframework</groupId>
  51. <artifactId>spring-web</artifactId>
  52. <version>${spring.version}</version>
  53. </dependency>
  54. <dependency>
  55. <groupId>org.springframework</groupId>
  56. <artifactId>spring-webmvc</artifactId>
  57. <version>${spring.version}</version>
  58. </dependency>
  59. <!-- 否则 Error:(9,7) java: 错误: 无法访问ServletException-->
  60. <dependency>
  61. <groupId>javax.servlet</groupId>
  62. <artifactId>javax.servlet-api</artifactId>
  63. <version>3.0.1</version>
  64. <scope>provided</scope>
  65. </dependency>
  66. <!-- Spring Test -->
  67. <dependency>
  68. <groupId>org.springframework</groupId>
  69. <artifactId>spring-test</artifactId>
  70. <version>${spring.version}</version>
  71. </dependency>
  72. <!-- Junit -->
  73. <dependency>
  74. <groupId>junit</groupId>
  75. <artifactId>junit</artifactId>
  76. <version>4.12</version><!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上,否则用不了-->
  77. <scope>test</scope>
  78. </dependency>
  79. </dependencies>
  80. <build>
  81. <plugins>
  82. <plugin>
  83. <groupId>org.apache.maven.plugins</groupId>
  84. <artifactId>maven-compiler-plugin</artifactId>
  85. <version>2.3</version>
  86. <configuration>
  87. <source>1.8</source>
  88. <target>1.8</target>
  89. <encoding>${project.build.sourceEncoding}</encoding>
  90. </configuration>
  91. </plugin>
  92. <!-- 没有web.xml文件的情况下构建WAR, 否则打包时会报错 -->
  93. <plugin>
  94. <groupId>org.apache.maven.plugins</groupId>
  95. <artifactId>maven-war-plugin</artifactId>
  96. <version>2.6</version>
  97. <configuration>
  98. <!--如果想在没有web.xml文件的情况下构建WAR,请设置为false。-->
  99. <failOnMissingWebXml>false</failOnMissingWebXml>
  100. </configuration>
  101. </plugin>
  102. </plugins>
  103. </build>
  104. </project>

要使用spring的事务,需要引入:

  1. <!-- Spring 事务支持 -->
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-tx</artifactId>
  5. <version>${spring.version}</version>
  6. </dependency>

要使用spring的JdbcTemplate等功能,需要引入:

  1. <!-- Spring JDBC -->
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-jdbc</artifactId>
  5. <version>${spring.version}</version>
  6. </dependency>

三、搭建Spring和Spring MVC环境

继承AbstractAnnotationConfigDispatcherServletInitializer,创建启动器

  1. import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
  2. /**
  3. * @Description AbstractAnnotationConfigDispatcherServletInitializer 是在spring-webmvc包下的
  4. */
  5. public class SpringInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
  6. //Spring容器:等同于applicationContext.xml
  7. @Override
  8. protected Class<?>[] getRootConfigClasses() {
  9. return new Class[]{SpringConfig.class};
  10. }
  11. //SpringMVC容器 :等同于springmvc.xml
  12. @Override
  13. protected Class<?>[] getServletConfigClasses() {
  14. return new Class[]{SpringMVCConfig.class};
  15. }
  16. // SpringMVC 的DispatcherServlet拦截规则
  17. @Override
  18. protected String[] getServletMappings() {
  19. return new String[]{"/"};
  20. }
  21. }

这里需要特别强调的是使用的是 / , 而不是 /*。请求时可以通过DispatcherServlet转发到相应的的Controller中的,但是返回的内容,如返回的jsp还会再次被拦截,这样导致404错误,即访问不到jsp。所以如果以后发现总是有404错误的时候,别忘了检查一下 /的配置是否是/*。

Spring 的Servlet拦截器匹配规则,当映射为@RequestMapping("/user/add")时:

① 拦截*.do、*.htm, 例如:/user/add.do

这是最传统的方式,最简单也最实用。不会导致静态文件(jpg,js,css)被拦截。

② 拦截/,例如:/user/add

可以实现现在很流行的REST风格。很多互联网类型的应用很喜欢这种风格的URL。

弊端:会导致静态文件(jpg,js,css)被拦截后不能正常显示(有解决方法)。

③ 拦截/*,这是一个错误的方式,请求可以走到Action中,但转到jsp时再次被拦截,不能访问到jsp。

Spring容器配置

  1. import org.springframework.context.annotation.ComponentScan;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.context.annotation.FilterType;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.web.bind.annotation.RestController;
  6. /**
  7. * 这个是Spring容器,相当于ApplicationContext.xml,负责扫描相关的service和dao,排除controller的扫描
  8. * 数据源、事务等均在这里配置
  9. */
  10. @ComponentScan(value = {"com.codedot"},
  11. excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),
  12. @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {RestController.class})})
  13. @Configuration
  14. public class SpringConfig {
  15. }

Spring MVC容器配置

  1. import org.springframework.context.annotation.ComponentScan;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.context.annotation.FilterType;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.web.bind.annotation.RestController;
  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.WebMvcConfigurer;
  9. /**
  10. * SpringMVC容器,负责扫描controller,声明视图解析规则、静态资源处理规则、拦截器等.
  11. */
  12. @EnableWebMvc
  13. @ComponentScan(value = {"com.codedot.ctrl"},
  14. includeFilters={@ComponentScan.Filter(type= FilterType.ANNOTATION,value={Controller.class}),
  15. @ComponentScan.Filter(type= FilterType.ANNOTATION,value={RestController.class})},useDefaultFilters = false)
  16. @Configuration
  17. public class SpringMVCConfig implements WebMvcConfigurer{
  18. @Override
  19. public void configureViewResolvers(ViewResolverRegistry registry) {
  20. registry.jsp("/WEB-INF/views/", ".jsp");
  21. }
  22. }

在Spring5.0后,原来继承WebMvcConfigurerAdapter过期,通常情况下我们会采用下面两种代替方案:

  • 实现WebMvcConfigurer  
  • 继承WebMvcConfigurationSupport

在webapp/WEB-INF/views目录下创建index.jsp

  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <html>
  3. <head>
  4. <title>首页</title>
  5. </head>
  6. <body>
  7. Hello World!!!
  8. </body>
  9. </html>

新建Controller

  1. import org.springframework.stereotype.Controller;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. @Controller
  4. public class HelloController {
  5. @GetMapping("fromIndex")
  6. public String helloIndex(){
  7. return "index";
  8. }
  9. }

配置tomcat部署项目,访问http://ip:port/上下文/fromIndex

注意:这里要特别注意上下文的设置。

四、原理解析

为什么ServletContainerInitializer取代web.xml实现零配置?

tomcat自动扫描web项目的WEB-INF/classes或WEB-INF/lib/*.jar的META-INF/services/javax.servlet.ServletContainerInitializer文件,调用其onStartup方法,参数webAppInitializerClasses为@HandlesTypes指定接口的所有实现类。因为spring-web.jar,所以开发过程中,只需定义一个类实现org.springframework.web.WebApplicationInitializer接口就可以了。

javax.servlet.ServletContainerInitializer接口

在基于注解的servlet开发中,ServletContainerInitializer接口用于代替web.xml。它只有一个方法:onStartup,可以在其中注册servlet、拦截器(Filter)、监听器(Listener)这三大组件。另外,ServletContainerInitializer还可以使用@HandlesTypes在onStartup方法的参数列表中注入感兴趣的类。servlet容器启动时,会扫描每个jar包的项目根目录下的/META-INF/services/javax.servlet.ServletContainerInitializer文件,执行这个文件中指定的ServletContainerInitializer接口的实现类的onStartup方法。

org.springframework.web包提供的ServletContainerInitializer实现类

org.springframework.web包的/META-INF/services/javax.servlet.ServletContainerInitializer文件指定了ServletContainerInitializer接口的实现类:SpringServletContainerInitializer。源码如下:

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

(1) 通过@HandlesType注解在onStartup方法的参数列表中注入感兴趣的类,即WebApplicationInitializer;

(2) 将WebApplicationInitializer的每个实现类,都新建一个实例,并放入initializers列表中;

(3) 遍历initializers列表,对每个WebApplicationInitializer实例执行其onStartup方法。

那么问题来了:WebApplicationInitializer有哪些实现类,是用来干什么的?

WebApplicationInitializer的实现类及其功能

WebApplicationInitializer的实现类有很多,重点看一下AbstractAnnotationConfigDispatcherServletInitializer

  1. package org.springframework.web.servlet.support;
  2. public abstract class AbstractAnnotationConfigDispatcherServletInitializer
  3. extends AbstractDispatcherServletInitializer {
  4. @Override
  5. @Nullable
  6. protected WebApplicationContext createRootApplicationContext() {
  7. Class<?>[] configClasses = getRootConfigClasses();
  8. if (!ObjectUtils.isEmpty(configClasses)) {
  9. AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
  10. context.register(configClasses);
  11. return context;
  12. }
  13. else {
  14. return null;
  15. }
  16. }
  17. @Override
  18. protected WebApplicationContext createServletApplicationContext() {
  19. AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
  20. Class<?>[] configClasses = getServletConfigClasses();
  21. if (!ObjectUtils.isEmpty(configClasses)) {
  22. context.register(configClasses);
  23. }
  24. return context;
  25. }
  26. @Nullable
  27. protected abstract Class<?>[] getRootConfigClasses();
  28. @Nullable
  29. protected abstract Class<?>[] getServletConfigClasses();
  30. }

这个类提供了两个方法的实现,以及两个抽象方法供子类继承:

(1) createRootApplicationContext:创建根容器;

(2) createServletApplicationContext:创建servlet容器;

(3) getRootConfigClasses:抽象类,用于注册根容器的配置类,相当于applicationContext.xml;

(4) getServletConfigClasses:抽象的类,用于注册servlet容器的配置类,相当于springmvc.xml;

另外,它还从AbstractDispatcherServletInitializer类继承了getServletMappings方法,用于注册servlet的映射。

因此,我们可以自定义一个WebApplicationInitializer的实现类,继承AbstractAnnotationConfigDispatcherServletInitializer;在servlet容器启动时,会创建spring根容器和servlet容器,代替web.xml配置文件。同时,我们可以看到,在基于注解的springmvc开发中,真正用于代替web.xml的是WebApplicationInitializer,而并不是ServletContainerInitializer,这与本文开头提到的基于注解的servlet开发有些区别。

根容器和Servlet容器

根容器用于管理@Service、@Repository等业务逻辑层和数据库交互层组件;

servlet容器用于管理@Controller、视图解析器、拦截器等跟页面处理有关的组件。

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号