手写SpringBoot框架

本文通过手写代码模拟SpringBoot的启动、条件注解、自动配置及整合Tomcat的过程,详细解释了如何从零开始构建一个简易SpringBoot框架。通过创建AnnotationConfigWebApplicationContext容器,注册配置类,启动内嵌Tomcat并配置DispatcherServlet,实现了项目启动并能正常接收HTTP请求的功能。
摘要由CSDN通过智能技术生成

前言

通过手写模拟一个简单Spring Boot框架,来熟悉Spring Boot大概是如何工作的。

分为以下4个方面

1、手写模拟SpringBoot启动过程

2、手写模拟SpringBoot条件注解功能

3、手写模拟SpringBoot自动配置功能

4、SpringBoot整合Tomcat底层源码分析

废话不多说,直接上代码

实战

依赖

建一个工程,两个Module:

  1. springboot模块,表示springboot框架的源码实现
  2. user包,表示用户业务系统,用来写业务代码来测试我们所模拟出来的SpringBoot

首先,SpringBoot是基于的Spring,所以要依赖Spring,然后我希望模拟出来的SpringBoot也支持Spring MVC的那一套功能,所以也要依赖Spring MVC,包括Tomcat等,所以在SpringBoot模块中要添加以下依赖:

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.18</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.3.18</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.18</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>9.0.60</version>
        </dependency>
    </dependencies>

在User模块下我们进行正常的开发就行了,比如先添加SpringBoot依赖:

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>springboot</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

然后定义相关的Controller和Service:

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("test")
    public String test(){
        return userService.test();
    }
}

因为模拟实现的是SpringBoot,而不是SpringMVC,所以直接在user包下定义了UserController和UserService,最终希望能运行MyApplication中的main方法,就直接启动了项目,并能在浏览器中正常的访问到UserController中的某个方法。

核心注解和核心类

在真正使用SpringBoot时,核心会用到SpringBoot一个类和注解:

  1. @SpringBootApplication,这个注解是加在应用启动类上的,也就是main方法所在的类
  2. SpringApplication,这个类中有个run()方法,用来启动SpringBoot应用的

所以我也来模拟实现他们。

一个@GuashuSpringBootApplication注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface GuashuSpringBootApplication {
}

一个用来实现启动逻辑的GuashuSpringApplication类。

public class GuashuSpringApplication {

    public static void run(Class clazz){

    }

}

注意run方法需要接收一个Class类型的参数,这个class是用来干嘛的,等会就知道了。

有了以上两者,我们就可以在MyApplication中来使用了,比如:

@GuashuSpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        GuashupringApplication.run(MyApplication.class);
    }
}

现在用来是有模有样了,但中看不中用,所以我们要来好好实现以下run方法中的逻辑了。

run方法

首先,我希望run方法一旦执行完,就能在浏览器中访问到UserController,那势必在run方法中要启动Tomcat,通过Tomcat就能接收到请求了。

Spring MVC的底层原理有一个Servlet非常核心,那就是DispatcherServlet,这个DispatcherServlet需要绑定一个Spring容器,因为DispatcherServlet接收到请求后,就会从所绑定的Spring容器中找到所匹配的Controller,并执行所匹配的方法。

所以,在run方法中,我们要实现的逻辑如下:

  1. 创建一个Spring容器
  2. 创建Tomcat对象
  3. 生成DispatcherServlet对象,并且和前面创建出来的Spring容器进行绑定
  4. 将DispatcherServlet添加到Tomcat中
  5. 启动Tomcat

创建Spring容器

这个步骤比较简单,代码如下:

public class GuashuSpringApplication {

    public static void run(Class clazz){
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(clazz);
        applicationContext.refresh();

        
    }
}

我创建的是一个AnnotationConfigWebApplicationContext容器,并且把run方法传入进来的class作为容器的配置类,比如在MyApplication的run方法中,我就是把MyApplication.class传入到了run方法中,最终MyApplication就是所创建出来的Spring容器的配置类,并且由于MyApplication类上有@GuashuSpringBootApplication注解,而@GuashuSpringBootApplication注解的定义上又存在@ComponentScan注解,所以AnnotationConfigWebApplicationContext容器在执行refresh时,就会解析MyApplication这个配置类,从而发现定义了@ComponentScan注解,也就知道了要进行扫描,只不过扫描路径为空,而AnnotationConfigWebApplicationContext容器会处理这种情况,如果扫描路径会空,则会将MyApplication所在的包路径做为扫描路径,从而就会扫描到UserService和UserController。

所以Spring容器创建完之后,容器内部就拥有了UserService和UserController这两个Bean。

启动Tomcat

我们用的是Embed-Tomcat,也就是内嵌的Tomcat,真正的SpringBoot中也用的是内嵌的Tomcat,而对于启动内嵌的Tomcat,也并不麻烦,代码如下:

public static void startTomcat(WebApplicationContext applicationContext){
    
    Tomcat tomcat = new Tomcat();
    
    Server server = tomcat.getServer();
    Service service = server.findService("Tomcat");
    
    Connector connector = new Connector();
    connector.setPort(8081);
    
    Engine engine = new StandardEngine();
    engine.setDefaultHost("localhost");
    
    Host host = new StandardHost();
    host.setName("localhost");
    
    String contextPath = "";
    Context context = new StandardContext();
    context.setPath(contextPath);
    context.addLifecycleListener(new Tomcat.FixContextListener());
    
    host.addChild(context);
    engine.addChild(host);
    
    service.setContainer(engine);
    service.addConnector(connector);
    
    tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));
    context.addServletMappingDecoded("/*", "dispatcher");
    
    try {
        tomcat.start();
    } catch (LifecycleException e) {
        e.printStackTrace();
    }
    
}

代码虽然看上去比较多,但是逻辑并不复杂,比如配置了Tomcat绑定的端口为8081,后面向当前Tomcat中添加了DispatcherServlet,并设置了一个Mapping关系,最后启动,其他代码则不用太过关心。

而且在构造DispatcherServlet对象时,传入了一个ApplicationContext对象,也就是一个Spring容器,就是前文说的,DispatcherServlet对象和一个Spring容器进行绑定。

接下来,只需要在run方法中,调用startTomcat即可:

public static void run(Class clazz){
    AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
    applicationContext.register(clazz);
    applicationContext.refresh();
    
    startTomcat(applicationContext);
    
}

实际上代码写到这,一个极度精简版的SpringBoot就写出来了,比如现在运行MyApplication,就能正常的启动项目,并能接收请求。

启动能看到Tomcat的启动日志:

然后在浏览器上访问:http://localhost:8081/test

也能正常的看到结果:

完成!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值