SpringMVC初始化与配置简介

本文详细阐述了SpringMVC容器的9个生命周期阶段,以及WebApplicationInitializer和WebMvcConfigurer在配置上的应用,介绍了如何通过编程替代XML配置,实现SpringMVC的灵活定制。
摘要由CSDN通过智能技术生成

SpringMVC 容器的生命周期(9 个阶段)
阶段 1:Servlet 容器初始化
阶段 2:创建父容器
阶段 3:创建 springmvc 容器
阶段 4:Servlet 容器中注册 DispatcherServlet
阶段 5:启动父容器:ContextLoaderListener
阶段 6:启动 springmvc 容器:DispatcherServlet#init()
阶段 7:springmvc 容器启动过程中处理@WebMVC
阶段 8:组装 DispatcherServlet 中各种 SpringMVC 需要的组件
阶段 9:销毁 2 个容器

参看:SpringMVC这篇文章吃透了,最少最少涨5000 - 知乎

一.初始化:AbstractAnnotationConfigDispatcherServletInitializer

代码如下,这个类需要继承 AbstractAnnotationConfigDispatcherServletInitializer,会有 web 容器来调用,这个类中有 4 个方法需要实现,干了 4 件事情
getRootConfigClasses():获取父容器的配置类
getServletConfigClasses():获取 springmvc 容器的配置类,这个配置类相当于 springmvc xml 配置文件的功能
getServletMappings():获取 DispatcherServlet 能够处理的 url,相当于 web.xml 华国 servlet 指定的 url-pattern
getServletFilters():定义所有的 Filter

/**
 * ①:1、创建Mvc初始化类,需要继承AbstractAnnotationConfigDispatcherServletInitializer类
 */
public class WebInitializer 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};
    }
}

看一下类的继承关系

下面讲WebApplicationInitializer

WebApplicationInitializer简介

servlet3.0 中新增了一个接口:ServletContainerInitializer,这个接口功能特别的牛逼,有了它之后,web.xml 配置文件可要可不要了。

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

这个接口的实现类,如果满足下面 2 个条件,Servlet 容器启动的过程中会自动实例化这些类,然后调用他们的 onStartUp 方法,然后我们就可以在这些类的 onStartUp 方法中干活了,在 web.xml 干的所有事情,都可以在这个方法中干,特别强大:

这个类必须实现
ServletContainerInitializer 接口,且非抽象类
这个类的全类名必须要放在
META-INF/services/javax.servlet.ServletContainerInitializer这个文件中

下面重点来了,springmvc 提供了一个类SpringServletContainerInitializer,满足了上面个条件。

spring-web-5.3.6.jar!\META-INF\services\javax.servlet.ServletContainerInitializer

****************************************************20240125补充  start************************************************

@HandlesTypes 注解是 Java Servlet 规范的一部分,具体是在 Servlet 3.0 中引入的。它用于指示 Servlet 容器(比如 Tomcat 或 Jetty)在 Web 应用程序初始化过程中自动发现和处理某种类型的类。

在你的例子中,@HandlesTypes({WebApplicationInitializer.class}) 表示 Servlet 容器应该发现并处理实现了 WebApplicationInitializer 接口的类。WebApplicationInitializer 接口是 Servlet 3.0 规范的一部分,它允许你以编程方式配置 Servlet 容器,而不是使用部署描述符(比如 web.xml)。

当你使用这个注解时,Servlet 容器会扫描类路径以找到实现了 WebApplicationInitializer 接口的类,然后在 Web 应用程序初始化期间调用它们的 onStartup 方法。

这里是一个简单的示例,展示了如何使用 WebApplicationInitializer

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

public class MyInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // 在这里进行初始化逻辑
    }
}

通过使用 @HandlesTypes({WebApplicationInitializer.class}),你告诉 Servlet 容器在 Web 应用程序启动时发现和处理类似于 MyInitializer 的类。这是一种与传统的 web.xml 文件相比的配置替代方案。

****************************************************20240125补充  end************************************************

所以 SpringServletContainerInitializer 的 onStart 方法会 servlet 容器自动被调用

这个类的源码,大家先看一下,这个类干的事情:

类上有@HandlesTypes(
WebApplicationInitializer.class) 这个注解,注解的值为
WebApplicationInitializer.class,所以 onStartup 方法的第一个参数是WebApplicationInitializer类型的集合,这个集合由 web 容器自动扫描获取,然后传入进来
实例化 WebApplicationInitializer 集合
对 WebApplicationInitializer 集合进行排序
循环调用 WebApplicationInitializer 的 onStartup 方法

下面重点要看WebApplicationInitializer接口了。

1.WebApplicationInitializer的定义


从起初的Spring配置文件,到后来的Spring支持注解到后来的SpringBoot,Spring框架在一步步的使用注解的方式来去除Spring的配置的发展过程。WebApplicationInitializer就是取代web.xml配置的一个接口。
 

public interface WebApplicationInitializer {
    void onStartup(ServletContext var1) throws ServletException;
}

通过覆盖接口提供的onStartup方法我们可以往Servlet容器里面添加我们需要的servletlistener等,并且在Servlet容器启动的过程中就会加载这个接口的实现类,从而起到和web.xml相同的中作用,从而可以替代以前在web.xml中所做的配置。

2.实现原理

在与WebApplicationInitializer类同路径下有个SpringServletContainerInitializer类。这个类的作用就是发现所有实现了WebApplicationInitializer的类。

我们首先可以从Spring源码中找到SpringServletContainerInitializer实现类。

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

    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        LinkedList initializers = new LinkedList();
        Iterator var4;
        if(webAppInitializerClasses != null) {
            var4 = webAppInitializerClasses.iterator();

            while(var4.hasNext()) {
                Class initializer = (Class)var4.next();
                if(!initializer.isInterface() && !Modifier.isAbstract(initializer.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(initializer)) {
                    try {
                        initializers.add((WebApplicationInitializer)initializer.newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }

        if(initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort(initializers);
            var4 = initializers.iterator();

            while(var4.hasNext()) {
                WebApplicationInitializer initializer1 = (WebApplicationInitializer)var4.next();
                initializer1.onStartup(servletContext);
            }

        }
    }
}

这个类上面有@HandlesTypes({WebApplicationInitializer.class})这个注解,这个注解的作用是将其value中配置的一些类放入到ServletContainerInitializer

initializers.add((WebApplicationInitializer)initializer.newInstance());

最后通过循环去执行WebApplicationInitializer中的onStartup方法来实现里面的具体的具体的逻辑。

while(var4.hasNext()) {
	WebApplicationInitializer initializer1 = (WebApplicationInitializer)var4.next();
	initializer1.onStartup(servletContext);
}

那问题来了,Tomcat容器怎么知道先执行这个SpringServletContainerInitializer类?
这里涉及一个知识点SPI机制,SPI全称为 Service Provider Interface,是一种服务发现机制。SPI机制是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI机制为我们的程序提供拓展功能。

Tomcat启动过程中查找所有的ServletContainerInitializer实现类然后添加到StandardContext的initializers集合中,然后执行里面的onStartup方法。

Spring中SPI就是通过SpringServletContainerInitializer类来实现的

关于Web应用的启动过程在《一个基于注解配置的Web项目的启动流程分析》这篇文章写的很好。

3.利用SPI我们能做什么?


可以加一些自己的启动配置信息,把自己的Servlet打成jar包放到Tomcat服务器或者其他工程中执行。我们可以实现一个自己的SPI接口。

  • 定义一个MyWebAppInitializer
     
public interface MyWebAppInitializer {
    void loadOnStart(ServletContext var1) throws ServletException;
}
  • 定义一个MySpringServletContainerInitializer


实现ServletContainerInitializer接口
修改@HandlesTypes为我们自己定义的MyWebAppInitializer.class
实例化MyWebAppInitializer的实现类,并且调用接口的loadOnStart方法
 

@HandlesTypes(MyWebAppInitializer.class)
public class MySpringServletContainerInitializer implements ServletContainerInitializer{
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletcontext)
            throws ServletException {
        if (set != null) {
            for (Class<?> waiClass : set) {
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        MyWebAppInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        //创建MyWebAppInitializer实现类的对象,并调用loadOnStart方法
                        ((MyWebAppInitializer) waiClass.newInstance()).loadOnStart(servletcontext);
                    } catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }
    }
}
  • 写一个MyWebAppInitializer的实现类
public class MyWebApplicationInitializerTest implements MyWebAppInitializer{
    @Override
    public void loadOnStart(ServletContext servletContext){
        System.out.println("启动执行MyWebApplicationInitializerTest的loadOnStart方法");
        //注册一个为名字call的servlet
        ServletRegistration.Dynamic servletReg = servletContext.addServlet("call", CallServlet.class);
        servletReg.setLoadOnStartup(1);
        servletReg.addMapping("/call");
    }
}

其中里面的CallServlet.java如下

public class CallServlet extends HttpServlet {
    private static final long serialVersionUID = 3684613967452881093L;

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");
        resp.getWriter().write(name + ", if you like me, please call me!");
    }
}
  • 添加配置文件javax.servlet.ServletContainerInitializerr


添加位置为:src -> main -> resources-> META-INF-> services ->javax.servlet.ServletContainerInitializerr

内容:com.leo.spi.MySpringServletContainerInitializer

  • 启动项目测试

启动日志:

启动执行MyWebApplicationInitializerTest的loadOnStart方法

浏览器测试:http://localhost:8080/springmvc/call?name=leo825

上面也提到了,可以把这个SPI的方式打成jar包在其他项目或者直接在Tomcat容器这种运行。
本文的相关源码请参考:chapter-5-springmvc-zero-configuration
spi代码包路径:com.leo.spi

参考:

SpringMVC学习(五)——零配置实现SpringMVC_springmvc零配置-CSDN博客

spring-framework-learning-example: 关于spring框架学习的例子

Spring中对于WebApplicationInitializer的理解-CSDN博客

二.配置:WebMvcConfigurer简介

WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要创建一个配置类并实现WebMvcConfigurer 接口;

在Spring Boot 1.5版本都是靠重写WebMvcConfigurerAdapter的方法来添加自定义拦截器,消息转换器等。SpringBoot 2.0 后,该类被标记为@Deprecated(弃用)。官方推荐直接实现WebMvcConfigurer或者直接继承WebMvcConfigurationSupport,方式一实现WebMvcConfigurer接口(推荐),方式二继承WebMvcConfigurationSupport类,具体实现可看这篇文章。继承WebMvcConfigurationSupport后自动配置不生效的问题及如何配置拦截器

下面是一些WebMvcConfigurer接口中常用的方法:

  1. addViewControllers:用于注册简单的视图控制器。

  2. addInterceptors:用于注册拦截器,可以在请求处理之前或之后执行一些逻辑。

  3. addResourceHandlers:用于注册静态资源处理器,可以将静态资源映射到指定的URL路径。

  4. configureViewResolvers:用于配置视图解析器,可以将逻辑视图名称解析为实际的视图。

  5. configureContentNegotiation:用于配置内容协商策略,可以根据请求头中的Accept字段来返回不同的响应格式。

  6. configureDefaultServletHandling:用于配置静态资源的处理方式,可以将请求转发给默认的Servlet。

  7. addArgumentResolvers:用于注册自定义的方法参数解析器,可以将请求参数解析为控制器方法的参数。

  8. addReturnValueHandlers:用于注册自定义的返回值处理器,可以将控制器方法的返回值转换为响应体。

总之,WebMvcConfigurer接口提供了很多方法来定制Spring MVC的行为,可以满足不同的需求。

具体参考:

SpringBoot--WebMvcConfigurer详解-CSDN博客

WebMvcConfigurer详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值