2024-01-17  阅读(14)
原文作者:路人 原文地址: http://www.itsoku.com/course/6/226

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

202401172041418281.png

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

202401172041425582.png

2.8、测试效果

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

202401172041432003.png

连接1效果:

202401172041435854.png

连接2效果:

202401172041442215.png

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文件

202401172041446036.png

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

202401172041450697.png

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

4、总结

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

有问题欢迎留言。

5、案例代码

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

Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文