一文搞懂:SpringMVC全注解方式及原理

路人甲Java

共 22784字,需浏览 46分钟

 ·

2021-09-10 23:55

文末可以领取所有系列高清 pdf。

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

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

目录

  • 1、本文内容

  • 2、全注解方式使用 springmvc

    • 2.1、新建 maven web 项目

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

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

    • 2.4、创建自定义拦截器

    • 2.5、创建全局异常处理类

    • 2.6、测试功能

    • 2.7、项目整体结构

    • 2.8、测试效果

  • 3、原理:ServletContainerInitializer 接口

    • 3.1、ServletContainerInitializer 源码

    • 3.2、ServletContainerInitializer 使用

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

  • 3、springmvc 全注解的原理

  • 4、总结

  • 5、案例代码

  • 6、SpringMVC 系列

  • 7、更多好文章

  • 8、【路人甲 Java】所有系列高清 PDF

1、本文内容

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

2、全注解方式使用 springmvc

2.1、新建 maven web 项目

项目中不需要 web.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.javacode2018</groupId>
    <artifactId>chat12-annotation</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>chat12-annotation Maven Webapp</name>
    <description>springmvc全注解方式</description>
    <url>http://www.itsoku.com</url>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- 添加springmvc依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!-- 添加jackson配置 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.11.4</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.11.4</version>
        </dependency>

        <!-- 添加servlet 依赖 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>

        <!-- 日志 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

        <!--文件上传的jar包-->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>chat12-annotation</finalName>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**/*.*</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</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 的功能。

package com.javacode2018.springmvc.chat12.config;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import javax.servlet.Filter;

/**
 * ①:1、创建Mvc初始化类,需要继承AbstractAnnotationConfigDispatcherServletInitializer类
 */

public class MvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     * springmvc容器的父容器spring配置类
     * 实际工作中我们的项目比较复杂,可以将controller层放在springmvc容器中
     * 其他层,如service层、dao层放在父容器了,bean管理起来更清晰一些
     * 也可以没有父容器,将所有bean都放在springmvc容器中
     *
     * @return
     */

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    /**
     * ②:2、设置springmvc容器的spring配置类
     *
     * @return
     */

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{MvcConfig.class};
    }

    /**
     * ③:3、配置DispatcherServlet的url-pattern
     *
     * @return
     */

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * ④:4、注册拦截器
     *
     * @return
     */

    @Override
    protected Filter[] getServletFilters() {
        //添加拦截器,解决乱码问题
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceRequestEncoding(true);
        characterEncodingFilter.setForceResponseEncoding(true);
        return new Filter[]{characterEncodingFilter};
    }
}

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

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

  1. 需要继承WebMvcConfigurer接口,这个接口中提供了很多方法,预留给开发者用来配置 springmvc 中的各种组件,springmvc 容器启动的过程中,会自动调用这些方法
  2. 标注有@Configuration 注解,表示这是一个配置类
  3. 标注有@ComponentScan 注解,用来扫描组件,将 bean 注册到 springmvc 容器
  4. 需要标注@EnableWebMvc 注解,用来起来 springmvc 注解配置功能,有了这个注解,springmvc 容器才会自动调用 WebMvcConfigurer 接口中的方法
  5. WebMvcConfigurer 接口中提供了一系列方法,用来配置视图解析器、静态资源处理器、拦截器
  6. 在这个类中我们配置了(② 视图解析器、③ 拦截器、④ 静态资源处理器、⑤ 文件上传解析器)
package com.javacode2018.springmvc.chat12.config;

import com.javacode2018.springmvc.chat12.interceptor.MyInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/**
 * 1.开启springmvc注解配置
 * 2、配置视图解析器
 * 3、配置截器
 * 4、配置静态资源访问
 * 5、配置文件上传解析器
 * 6、配置全局异常处理器
 */

@Configuration
@ComponentScan("com.javacode2018.springmvc.chat12")
@EnableWebMvc //1:使用EnableWebMvc开启springmvc注解方式配置
public class MvcConfig implements WebMvcConfigurer {

    /**
     * ②:2、添加视图解析器(可以添加多个)
     *
     * @param registry
     */

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/view/");
        resolver.setSuffix(".jsp");
        resolver.setOrder(Ordered.LOWEST_PRECEDENCE);
        registry.viewResolver(resolver);
    }

    @Autowired
    private MyInterceptor myInterceptor;

    /**
     * ③:3、添加拦截器(可以添加多个)
     *
     * @param registry
     */

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this.myInterceptor).addPathPatterns("/**");
    }


    /**
     * ④:4、配置静态资源访问处理器
     *
     * @param registry
     */

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("/static/");
    }

    /**
     * ⑤:5、配置文件上传解析器
     *
     * @return
     */

    @Bean
    public CommonsMultipartResolver multipartResolver() {
        CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
        //maxUploadSizePerFile:单个文件大小限制(byte)
        //maxUploadSize:整个请求大小限制(byte)
        commonsMultipartResolver.setMaxUploadSizePerFile(10 * 1024 * 1024);
        commonsMultipartResolver.setMaxUploadSize(100 * 1024 * 1024);
        return commonsMultipartResolver;
    }
}

2.4、创建自定义拦截器

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

package com.javacode2018.springmvc.chat12.interceptor;


import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("这是MyInterceptor拦截器");
        return true;
    }
}

2.5、创建全局异常处理类

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

package com.javacode2018.springmvc.chat12.config;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

/**
 * 异常处理
 */

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler
    public ModelAndView doException(Exception e) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("ex", e);
        return modelAndView;
    }

}

2.6、测试功能

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

@Controller
public class IndexController {
    /**
     * 首页
     *
     * @return
     */

    @RequestMapping("/")
    public String index() {
        return "index";
    }

    /**
     * 测试异常情况
     *
     * @return
     */

    @RequestMapping("/testError")
    public String testError() {
        System.out.println(10 / 0);
        return "success";
    }
}

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

index.jsp:

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

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

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h2>出错啦,错误信息如下:</h2>
<h3>${ex}</h3>
</body>
</html>

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h2>success</h2>
</body>
</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 的所有操作。

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

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 属性。

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

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

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

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

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

3、springmvc 全注解的原理

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

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

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

4、总结

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

有问题欢迎留言。

5、案例代码

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

6、SpringMVC 系列

  1. SpringMVC 系列第 1 篇:helloword
  2. SpringMVC 系列第 2 篇:@Controller、@RequestMapping
  3. SpringMVC 系列第 3 篇:异常高效的一款接口测试利器
  4. SpringMVC 系列第 4 篇:controller 常见的接收参数的方式
  5. SpringMVC 系列第 5 篇:@RequestBody 大解密,说点你不知道的
  6. SpringMVC 系列第 6 篇:上传文件的 4 种方式,你都会么?
  7. SpringMVC 系列第 7 篇:SpringMVC 返回视图常见的 5 种方式,你会几种?
  8. SpringMVC 系列第 8 篇:返回 json & 通用返回值设计
  9. SpringMVC 系列第 9 篇:SpringMVC 返回 null 是什么意思?
  10. SpringMVC 系列第 10 篇:异步处理
  11. SpringMVC 系列第 11 篇:集成静态资源
  12. SpringMVC 系列第 12 篇:拦截器
  13. SpringMVC 系列第 13 篇:统一异常处理
  14. SpringMVC 系列第 14 篇:实战篇:通用返回值 & 异常处理设计

7、更多好文章

  1. Spring 高手系列(共 56 篇)
  2. Java 高并发系列(共 34 篇)
  3. MySql 高手系列(共 27 篇)
  4. Maven 高手系列(共 10 篇)
  5. Mybatis 系列(共 12 篇)
  6. 聊聊 db 和缓存一致性常见的实现方式
  7. 接口幂等性这么重要,它是什么?怎么实现?
  8. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!

8、【路人甲 Java】所有系列高清 PDF

领取方式,扫码发送:yyds

浏览 34
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报