阶段一模块四

本文介绍了如何自定义简易版SpringBoot,实现SpringBoot MVC、内嵌Tomcat的启动、DispatcherServlet的注册以及组件扫描功能。作业要求包括创建SpringApplication启动类、配置AppConfig、实现WebApplicationInitializer接口、创建ServletContainerInitializer以及编写Controller。通过这些步骤,实现了基于Servlet 3.0规范的Java配置方式启动和管理DispatcherServlet。

作业要求

自定义简易版 SpringBoot,实现 SpringBoot MVC 及内嵌 Tomcat 启动、DispatcherServlet 注册和组件扫描功能。

  • 程序通过 main 方法启动,可以自动启动 Tomcat 服务器
  • 可以自动创建和加载 DispatcherServlet 组件到 ServletContext
  • 可以自动通过 @ComponentScan 扫描 Controller 等组件
  • Controller 组件可以处理浏览器请求,返回响应结果

作业提示

当实现了 Servlet 3.0 规范的容器(比如 Tomcat 7 及以上版本)启动时,会通过 SPI 扩展机制自动扫描所有 jar 包里 META-INF/services/javax.servlet.ServletContainerInitializer 文件中指定的全路径类(该类需实现 ServletContainerInitializer 接口),并实例化该类,并回调类中的 onStartup 方法。

传统 SpringMVC 框架 web.xml 的配置内容

<web-app>
   <!-- 初始化Spring上下文 -->
   <listener>
       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
   </listener>
   <!-- 指定Spring的配置文件 -->
   <context-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>/WEB-INF/app-context.xml</param-value>
   </context-param>
   <!-- 初始化DispatcherServlet -->
   <servlet>
       <servlet-name>app</servlet-name>
       <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
       <init-param>
           <param-name>contextConfigLocation</param-name>
           <param-value></param-value>
       </init-param>
       <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
       <servlet-name>app</servlet-name>
       <url-pattern>/app/*</url-pattern>
   </servlet-mapping>
</web-app>

Spring 官方文档中给出了基于 Servlet 3.0 规范如何使用 Java 代码实现 web.xml 配置的 example

public class MyWebApplicationInitializer implements WebApplicationInitializer {

   @Override
   public void onStartup(ServletContext servletCxt) {

       // Load Spring web application configuration
       //通过注解的方式初始化Spring的上下文
       AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
       //注册spring的配置类(替代传统项目中xml的configuration)
       ac.register(AppConfig.class);
       ac.refresh();

       // Create and register the DispatcherServlet
       //基于java代码的方式初始化DispatcherServlet
       DispatcherServlet servlet = new DispatcherServlet(ac);
       ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
       registration.setLoadOnStartup(1);
       registration.addMapping("/app/*");
   }
}

以上知识点为作业实现基础,以此为基础完成简易版 SpringBoot,并创建 Controller,能够完成方法访问。

作业实现过程

  1. 创建 maven 工程,导入以下依赖:

    <properties>
    	<java.version>1.8</java.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.0.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>8.5.32</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.0.8.RELEASE</version>
        </dependency>
    </dependencies>
    
  2. 创建 SpringApplication 类,编写 run 方法(方法中要求完成 Tomcat 的创建及启动)

  3. 创建 Spring 的配置类 AppConfig 该类上要通过 @ComponentScan 来进行包扫描

  4. 创建 MyWebApplicationInitializer 实现 WebApplicationInitializer 接口,重写 onStartup 方法(WebApplicationInitializer 实现 web.xml 的配置)

提示
仿写上面 Spring 官方给出的例子,完成 AppConfig 的注册,并基于 Java 代码的方式初始化 DispatcherServlet

  1. 创建 MySpringServletContainerInitializer,实现 ServletContainerInitializer 接口,重写 onStartup 方法,方法中调用第 4 步中 MyWebApplicationInitializeronStartup 方法

提示
Servlet 3.0+ 容器启动时将自动扫描类路径以查找实现 SpringWebapplicationinitializer 接口的所有实现,将其放进一个 Set 集合中,提供给 ServletContainerInitializeronStartup 方法的第一个参数。

  1. 创建文件:META-INF/services/javax.servlet.ServletContainerInitializer,在该文件中配置 ServletContainerInitializer 的实现类 MySpringServletContainerInitializer

  2. 编写一个 Controller 测试类及目标方法,响应输出 “hello” 即可

  3. 编写一个启动类 MyRunBoot,通过执行 main 方法启动服务

提示
main 方法调用第 2 步中 SpringApplicationrun 方法启动

分析

这次作业看似十分复杂,实际上非常简单,并且已经将思路告诉了我们。实际上,这次作业就是分为两个内容:一是创建内嵌的 Tomcat 容器,二是实现 DispatcherServlet 加载到 ServletContext 中。但是在实现的过程中仍旧有一些地方需要我们注意。

代码

  1. 项目依赖 pom.xml

    <?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>cn.worstone</groupId>
        <artifactId>AutoConfigFramework</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <maven.compiler.source>11</maven.compiler.source>
            <maven.compiler.target>11</maven.compiler.target>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.3.3</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
                <version>5.3.3</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.3.3</version>
            </dependency>
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-core</artifactId>
                <version>8.5.32</version>
            </dependency>
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-jasper</artifactId>
                <version>8.5.32</version>
            </dependency>
        </dependencies>
    
    </project>
    

    这里有一个 tomcat-embed-jasper 依赖,这是因为内嵌的 Tomcat 容器启动的时候需要使用 JspServlet,如果不引入这个依赖会抛出 NoSuchClassException。但是这个依赖是否引入没有什么影响。

  2. SpringApplication

    package cn.worstone;
    
    import org.apache.catalina.LifecycleException;
    import org.apache.catalina.startup.Tomcat;
    
    import javax.servlet.ServletException;
    
    public class SpringApplication {
    
        public static void run() {
            // 创建 Tomcat 容器
            Tomcat tomcat = new Tomcat();
            // 此 Tomcat 端口是否自动部署
            tomcat.getHost().setAutoDeploy(false);
            // 设置 Tomcat 端口
            tomcat.setPort(8080);
            // 启动 Tomcat
            try {
                String baseDir = System.getProperty("user.dir");
                tomcat.addWebapp("/", baseDir);
                tomcat.start();
            } catch (LifecycleException e) {
                e.printStackTrace();
            } catch (ServletException e) {
                e.printStackTrace();
            }
            System.out.println("Tomcat Start...");
            // 开启异步服务, 接收请求
            tomcat.getServer().await();
        }
    
    }
    
    
  3. ApplicationConfiguration

    package cn.worstone.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan("cn.worstone")
    public class ApplicationConfiguration {
    }
    
  4. SpringWebApplicationInitializer

    package cn.worstone;
    
    import cn.worstone.config.ApplicationConfiguration;
    import org.springframework.web.WebApplicationInitializer;
    import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
    import org.springframework.web.servlet.DispatcherServlet;
    
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRegistration;
    
    public class SpringWebApplicationInitializer implements WebApplicationInitializer {
    
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            System.out.println("Load Spring web application configuration...");
            // Load Spring web application configuration
            // 通过注解的方式初始化 Spring 上下文
            AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
            // 注册 Spring 的配置类(替代传统项目中 XML 的 Configuration)
            ac.register(ApplicationConfiguration.class);
            ac.refresh();
    
            System.out.println("Create and register the DispatcherServlet...");
            // Create and register the DispatcherServlet
            // 基于 Java 代码的方式初始化 DispatcherServlet
            DispatcherServlet servlet = new DispatcherServlet(ac);
            ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet", servlet);
            registration.setLoadOnStartup(1);
            registration.addMapping("/*");
        }
    }
    
  5. SpringServletContainerInitializer

    package cn.worstone;
    
    import javax.servlet.ServletContainerInitializer;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import java.util.Set;
    
    // @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
    
        @Override
        public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
            System.out.println("SpringServletContainerInitializer...");
            // new SpringWebApplicationInitializer().onStartup(servletContext);?
        }
    }
    
  6. META-INF/services/javax.servlet.ServletContainerInitializer

    cn.worstone.SpringServletContainerInitializer
    
  7. HelloController

    package cn.worstone.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    
        @RequestMapping("hello")
        public String hello() {
            return "Hello World !";
        }
    }
    
  8. RunApplication

    package cn.worstone;
    
    public class RunApplication {
    
        public static void main(String[] args) {
            SpringApplication.run();
        }
    }
    

问题

  1. Tomcat 9.x 版本没有报错,但是无法启动
  2. registration.setLoadOnStartup(1);NullPointerException
  3. SpringServletContainerInitializer 中的 Set 集合为 null
  4. java.lang.ClassNotFoundException: org.apache.jasper.servlet.JspServlet
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值