SpringMVC系列第15篇:全注解的方式&原理解析

star2017 1年前 ⋅ 336 阅读

大家好,我是路人,这是SpringMVC系列第15篇。

前面写的14篇springmvc文章中都用到了配置文件,比如web.xml,springmvc的配置文件等等,使用起来比较繁琐,本文将把所有配置文件抛弃掉,采用全注解的方式使用springmvc,且会带大家了解其原理。

1、本文内容

  • 全注解方式使用springmvc
  • 全注解方式原理解析

2、全注解方式使用springmvc

2.1、新建maven web项目

项目中不需要web.xml配置文件,maven配置如下

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>com.javacode2018</groupId>
  6. <artifactId>chat12-annotation</artifactId>
  7. <version>1.0-SNAPSHOT</version>
  8. <packaging>war</packaging>
  9. <name>chat12-annotation Maven Webapp</name>
  10. <description>springmvc全注解方式</description>
  11. <url>http://www.itsoku.com</url>
  12. <properties>
  13. <maven.compiler.source>8</maven.compiler.source>
  14. <maven.compiler.target>8</maven.compiler.target>
  15. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  16. </properties>
  17. <dependencies>
  18. <!-- 添加springmvc依赖 -->
  19. <dependency>
  20. <groupId>org.springframework</groupId>
  21. <artifactId>spring-webmvc</artifactId>
  22. <version>5.3.6</version>
  23. </dependency>
  24. <!-- 添加jackson配置 -->
  25. <dependency>
  26. <groupId>com.fasterxml.jackson.core</groupId>
  27. <artifactId>jackson-core</artifactId>
  28. <version>2.11.4</version>
  29. </dependency>
  30. <dependency>
  31. <groupId>com.fasterxml.jackson.core</groupId>
  32. <artifactId>jackson-databind</artifactId>
  33. <version>2.11.4</version>
  34. </dependency>
  35. <!-- 添加servlet 依赖 -->
  36. <dependency>
  37. <groupId>javax.servlet</groupId>
  38. <artifactId>javax.servlet-api</artifactId>
  39. <version>4.0.1</version>
  40. <scope>provided</scope>
  41. </dependency>
  42. <!-- 日志 -->
  43. <dependency>
  44. <groupId>ch.qos.logback</groupId>
  45. <artifactId>logback-classic</artifactId>
  46. <version>1.2.3</version>
  47. </dependency>
  48. <!--文件上传的jar包-->
  49. <dependency>
  50. <groupId>commons-fileupload</groupId>
  51. <artifactId>commons-fileupload</artifactId>
  52. <version>1.4</version>
  53. </dependency>
  54. </dependencies>
  55. <build>
  56. <finalName>chat12-annotation</finalName>
  57. <resources>
  58. <resource>
  59. <directory>src/main/resources</directory>
  60. <filtering>false</filtering>
  61. <includes>
  62. <include>**/*.*</include>
  63. </includes>
  64. </resource>
  65. </resources>
  66. <plugins>
  67. <plugin>
  68. <groupId>org.apache.maven.plugins</groupId>
  69. <artifactId>maven-war-plugin</artifactId>
  70. <version>2.2</version>
  71. <configuration>
  72. <failOnMissingWebXml>false</failOnMissingWebXml>
  73. </configuration>
  74. </plugin>
  75. </plugins>
  76. </build>
  77. </project>

注意:上面配置中多了一个插件的配置,由于maven在web项目打包的时候,发现项目中没有web.xml,会报错,所以需要加入下面配置,让插件忽略这个问题

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

在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置Servlet容器。 Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为
AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展了AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文。

我们来创建的 MvcInit类,需继承AbstractAnnotationConfigDispatcherServletInitializer ,项目启动的时候,servlet容器会自动加载这个类,这个类相当于 web.xml 的功能。

  1. package com.javacode2018.springmvc.chat12.config;
  2. import org.springframework.web.filter.CharacterEncodingFilter;
  3. import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
  4. import javax.servlet.Filter;
  5. /**
  6. * ①:1、创建Mvc初始化类,需要继承AbstractAnnotationConfigDispatcherServletInitializer类
  7. */
  8. public class MvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
  9. /**
  10. * springmvc容器的父容器spring配置类
  11. * 实际工作中我们的项目比较复杂,可以将controller层放在springmvc容器中
  12. * 其他层,如service层、dao层放在父容器了,bean管理起来更清晰一些
  13. * 也可以没有父容器,将所有bean都放在springmvc容器中
  14. *
  15. * @return
  16. */
  17. @Override
  18. protected Class<?>[] getRootConfigClasses() {
  19. return new Class[0];
  20. }
  21. /**
  22. * ②:2、设置springmvc容器的spring配置类
  23. *
  24. * @return
  25. */
  26. @Override
  27. protected Class<?>[] getServletConfigClasses() {
  28. return new Class[]{MvcConfig.class};
  29. }
  30. /**
  31. * ③:3、配置DispatcherServlet的url-pattern
  32. *
  33. * @return
  34. */
  35. @Override
  36. protected String[] getServletMappings() {
  37. return new String[]{"/"};
  38. }
  39. /**
  40. * ④:4、注册拦截器
  41. *
  42. * @return
  43. */
  44. @Override
  45. protected Filter[] getServletFilters() {
  46. //添加拦截器,解决乱码问题
  47. CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
  48. characterEncodingFilter.setEncoding("UTF-8");
  49. characterEncodingFilter.setForceRequestEncoding(true);
  50. characterEncodingFilter.setForceResponseEncoding(true);
  51. return new Filter[]{characterEncodingFilter};
  52. }
  53. }

2.3、创建配置springmvc配置类,代替springmvc配置文件

下面这个类相当于springmvc配置文件的功能,springmvc需要的各种组件可以在这个里面配置,大家注意啦,这个类的特点

  1. 需要继承WebMvcConfigurer接口,这个接口中提供了很多方法,预留给开发者用来配置springmvc中的各种组件,springmvc容器启动的过程中,会自动调用这些方法
  2. 标注有@Configuration注解,表示这是一个配置类
  3. 标注有@ComponentScan注解,用来扫描组件,将bean注册到springmvc容器
  4. 需要标注@EnableWebMvc注解,用来起来springmvc注解配置功能,有了这个注解,springmvc容器才会自动调用WebMvcConfigurer接口中的方法
  5. WebMvcConfigurer接口中提供了一系列方法,用来配置视图解析器、静态资源处理器、拦截器
  6. 在这个类中我们配置了(②视图解析器、③拦截器、④静态资源处理器、⑤文件上传解析器)
  1. package com.javacode2018.springmvc.chat12.config;
  2. import com.javacode2018.springmvc.chat12.interceptor.MyInterceptor;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.ComponentScan;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.core.Ordered;
  8. import org.springframework.web.multipart.commons.CommonsMultipartResolver;
  9. import org.springframework.web.servlet.config.annotation.*;
  10. import org.springframework.web.servlet.view.InternalResourceViewResolver;
  11. /**
  12. * 1.开启springmvc注解配置
  13. * 2、配置视图解析器
  14. * 3、配置截器
  15. * 4、配置静态资源访问
  16. * 5、配置文件上传解析器
  17. * 6、配置全局异常处理器
  18. */
  19. @Configuration
  20. @ComponentScan("com.javacode2018.springmvc.chat12")
  21. @EnableWebMvc //1:使用EnableWebMvc开启springmvc注解方式配置
  22. public class MvcConfig implements WebMvcConfigurer {
  23. /**
  24. * ②:2、添加视图解析器(可以添加多个)
  25. *
  26. * @param registry
  27. */
  28. @Override
  29. public void configureViewResolvers(ViewResolverRegistry registry) {
  30. InternalResourceViewResolver resolver = new InternalResourceViewResolver();
  31. resolver.setPrefix("/WEB-INF/view/");
  32. resolver.setSuffix(".jsp");
  33. resolver.setOrder(Ordered.LOWEST_PRECEDENCE);
  34. registry.viewResolver(resolver);
  35. }
  36. @Autowired
  37. private MyInterceptor myInterceptor;
  38. /**
  39. * ③:3、添加拦截器(可以添加多个)
  40. *
  41. * @param registry
  42. */
  43. @Override
  44. public void addInterceptors(InterceptorRegistry registry) {
  45. registry.addInterceptor(this.myInterceptor).addPathPatterns("/**");
  46. }
  47. /**
  48. * ④:4、配置静态资源访问处理器
  49. *
  50. * @param registry
  51. */
  52. @Override
  53. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  54. registry.addResourceHandler("/static/**").addResourceLocations("/static/");
  55. }
  56. /**
  57. * ⑤:5、配置文件上传解析器
  58. *
  59. * @return
  60. */
  61. @Bean
  62. public CommonsMultipartResolver multipartResolver() {
  63. CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
  64. //maxUploadSizePerFile:单个文件大小限制(byte)
  65. //maxUploadSize:整个请求大小限制(byte)
  66. commonsMultipartResolver.setMaxUploadSizePerFile(10 * 1024 * 1024);
  67. commonsMultipartResolver.setMaxUploadSize(100 * 1024 * 1024);
  68. return commonsMultipartResolver;
  69. }
  70. }

2.4、创建自定义拦截器

上面的MvcConfig配置类中,我们定义了一个拦截器MyInterceptor myInterceptor;,这个类的代码如下

  1. package com.javacode2018.springmvc.chat12.interceptor;
  2. import org.springframework.stereotype.Component;
  3. import org.springframework.web.servlet.HandlerInterceptor;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. @Component
  7. public class MyInterceptor implements HandlerInterceptor {
  8. @Override
  9. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  10. System.out.println("这是MyInterceptor拦截器");
  11. return true;
  12. }
  13. }

2.5、创建全局异常处理类

异常处理,我们也给整上,添加一个类,当出错的时候,跳转到错误页面。

  1. package com.javacode2018.springmvc.chat12.config;
  2. import org.springframework.web.bind.annotation.ControllerAdvice;
  3. import org.springframework.web.bind.annotation.ExceptionHandler;
  4. import org.springframework.web.servlet.ModelAndView;
  5. /**
  6. * 异常处理
  7. */
  8. @ControllerAdvice
  9. public class GlobalExceptionHandler {
  10. @ExceptionHandler
  11. public ModelAndView doException(Exception e) {
  12. ModelAndView modelAndView = new ModelAndView();
  13. modelAndView.setViewName("error");
  14. modelAndView.addObject("ex", e);
  15. return modelAndView;
  16. }
  17. }

2.6、测试功能

添加一个controller及几个jsp页面,测效果

  1. @Controller
  2. public class IndexController {
  3. /**
  4. * 首页
  5. *
  6. * @return
  7. */
  8. @RequestMapping("/")
  9. public String index() {
  10. return "index";
  11. }
  12. /**
  13. * 测试异常情况
  14. *
  15. * @return
  16. */
  17. @RequestMapping("/testError")
  18. public String testError() {
  19. System.out.println(10 / 0);
  20. return "success";
  21. }
  22. }

webapp/WEB-INF/view中创建3个页面

index.jsp:

  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <html>
  3. <head>
  4. <title>Title</title>
  5. </head>
  6. <body>
  7. <h2>全注解的方式配置springmvc</h2><br/>
  8. <a target="_blank" href="${pageContext.request.contextPath}/static/imgs/1.jpg">测试访问静态资源</a><br/>
  9. <a target="_blank" href="${pageContext.request.contextPath}/testError">测试触发全局异常处理</a>
  10. </body>
  11. </html>

error.jsp,错误跳转的页面,会显示异常信息

  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <html>
  3. <head>
  4. <title>Title</title>
  5. </head>
  6. <body>
  7. <h2>出错啦,错误信息如下:</h2>
  8. <h3>${ex}</h3>
  9. </body>
  10. </html>

success.jsp

  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <html>
  3. <head>
  4. <title>Title</title>
  5. </head>
  6. <body>
  7. <h2>success</h2>
  8. </body>
  9. </html>

在搞一个图片放在webapp/static/imgs中,稍后测试静态资源访问的效果。

2.7、项目整体结构

2.8、测试效果

项目发布到tomcat,访问首页,首页上有2个连接,可以点击一下,分别用来测试静态资源是否可以访问,另外一个测试全局异常处理的效果。

连接1效果:

连接2效果:

3、原理:ServletContainerInitializer接口

刚才上面2.2章节中有提到过,重点在于Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置Servlet容器,servlet3.0赋予了web项目免去所有配置文件(web.xml)的能力。

所以重点就在于ServletContainerInitializer这个接口上,springmvc全注解方式就是依靠这个接口来实现的,掌握了这个接口的用法,springmvc全注解的原理大家基本上就搞懂了,对阅读springmvc源码也是非常有利的。

下面看来这个接口的用法。

3.1、ServletContainerInitializer源码

这个接口比较简单,只有一个onStartup方法,web容器启动的时候会自动调用这个方法,有2个参数,第1个参数稍后介绍,第2个参数ctx是servlet上下文,通过servlet上下文对象,我们可以在这个方法中实现web.xml的所有操作。

  1. public interface ServletContainerInitializer {
  2. public void onStartup(Set<Class<?>> c, ServletContext ctx)
  3. throws ServletException;
  4. }

3.2、ServletContainerInitializer使用

1、可以自定义一个实现ServletContainerInitializer接口,这个类必须在jar包的META-INF/services/javax.servlet.ServletContainerInitializer文件里面进行声明,这个文件的内容就是自定义类的全类名

2、Servlet容器启动会在所有jar和classes目录中扫描META-INF/services/javax.servlet.ServletContainerInitializer文件,然后找到这个文件中的具体的类,然后会自动实例化这个类,调用这个类的onStartup方法

3.2、onStartup的第1个参数,@HandlesTypes注解

提到onStartup方法的第一个参数,这里就需要介绍一下@HandlesTypes这个注解,先来看一下其源码,比较简单,就只有一个Calss数组类型的value属性。

  1. Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface HandlesTypes {
  4. Class<?>[] value();
  5. }

1、@HandlesTypes标签用在实现ServletContainerInitializer接口的类上面,比如:

  1. @HandlesTypes(WebApplicationInitializer.class)
  2. public class SpringServletContainerInitializer implements ServletContainerInitializer

2、servlet容器会扫描项目中的所有类(jar包和classes路径中),如果符合@HandlesTypes注解value值指定的类型,就会放在一个数组中,最终会传递给onStartup方法的第一个参数

3、当容器启动的时候,我们就可以通过拿到Set> c里面我们感兴趣的类,然后做一些初始化的工作

3、springmvc全注解的原理

了解了ServletContainerInitializer接口的原理,咱们来看springmvc,spring-web.jar中包含了META-INF/services/javax.servlet.ServletContainerInitializer文件

这个文件中指定的是org.springframework.web.SpringServletContainerInitializer这个类,重点来了,springmvc就是依靠这个类来实现注解功能的,大家可以去看看这个类的源码,在其onStartup方法中添加断点,可以看到完整清晰的启动过程。

后面会专门有一篇文章带大家阅读源码,一步步带大家了解springmvc容器的整个启动过程。

4、总结

建议大家自己去实战一下,光看是不行的,看可能觉得什么都会了,但是抛开文章自己去试试,又是一番景象,学技术一定要多动手。

有问题欢迎留言。

5、案例代码

  1. git地址:https://gitee.com/javacode2018/springmvc-series

最新资料

更多内容请访问:IT源点

相关文章推荐

全部评论: 0

    我有话说: