❤️SpringMVC成神之路之十五、全注解的方式 & 原理解析

小明的学习圈子2023-12-24后端SpringMVC成神之路

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

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,会报错,所以需要加入下面配置,让插件忽略这个问题

image-20231231153521669

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、项目整体结构

image-20231231154548625

2.8、测试效果

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

image-20231231154941808

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> c里面我们感兴趣的类,然后做一些初始化的工作

3、springmvc全注解的原理

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

image-20231231155116869

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

image-20231231155142075

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

4、总结

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

有问题欢迎留言。

5、案例代码

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

来源:http://www.itsoku.com/course/6/226

Last Updated 2024/10/26 12:50:05