Spring MVC 教程-全注解的方式&原理解析

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

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

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、总结

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

有问题欢迎留言。

  • 31
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值