手写SpringBoot核心功能流程

        本文通过手写模拟实现一个简易版的Spring Boot 程序,让大家能以非常简单的方式知道Spring Boot大概的工作流程。

工程依赖

创建maven工程,并创建两个module

springboot模块:手写模拟springboot框架的源码实现

test模块:业务系统,用来写业务代码来测试我们所模拟出来的SpringBoot

springboot模块依赖:SpringBoot基于的Spring,依赖Spring同上也支持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>

test模块依赖:依赖springboot模块依赖即可:

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

然后再test模块中创建相关的controllerservice测试类:

/**
 * @author liupeng
 * @version 1.0
 * @description: TODO
 * @date 2024/5/7 17:05
 */
@RestController
public class TestController {
    @Autowired
    private TestService testService;

    @GetMapping("/test")
    public String test(@RequestParam("name") String name){
        return testService.sayHello(name);
    }
}
/**
 * @author liupeng
 * @version 1.0
 * @description: TODO
 * @date 2024/5/7 17:06
 */
@Component
public class TestService {

    public String sayHello(String name) {
        return "Hello " + name + "!";
    }
}

创建核心注解和核心执行类

springboot中,最重要的是一个注解和一个启动类上的:

SpringApplication:该类有个run方法,主启动main中进行调用
@SpringBootApplication:该注解是添加在主启动类上的

因此我们可以在springboot模块中模拟实现以上核心注解和核心类。

@LPSpringBootApplication 注解:

/**
 * @author liupeng
 * @version 1.0
 * @description: TODO
 * @date 2024/5/7 17:10
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface LPSpringBootApplication {
}

启动类:


/**
 * @author liupeng
 * @version 1.0
 * @description: TODO
 * @date 2024/5/7 17:11
 */
public class LPSpringApplication {
    public static void run(Class clazz) {

    }

   
}

然后在test模块中LPApplication类的进行使用


/**
 * @author liupeng
 * @version 1.0
 * @description: TODO
 * @date 2024/5/7 17:12
 */
@LPSpringBootApplication
public class LPApplication {
    public static void main(String[] args) {
        LPSpringApplication.run(LPApplication.class);
    }
}

填充完善run方法

        首先,我们希望run方法执行完后,能在浏览器中访问到UserController,那么run方法
中需要启动Tomcat,通过Tomcat接收到http请求。
        在SpringMVC中有一个Servlet非常核心,那就是DispatcherServlet,这个DispatcherServlet需要绑定一个Spring容器,因为DispatcherServlet接收到请求后,就会从所绑定的Spring容器中找到所匹配的Controller,并执行所匹配的方法。
因此,在run方法中,我们需要实现如下步骤:
        创建Spring容器
        创建Tomcat对象
        生成DispatcherServlet对象,并且创建出来的Spring容器进行绑定
        将DispatcherServlet添加到Tomcat中
        启动Tomcat

创建Spring容器

/**
 * @author liupeng
 * @version 1.0
 * @description: TODO
 * @date 2024/5/7 17:11
 */
public class LPSpringApplication {
    public static void run(Class clazz) {
        System.out.println("Hello World!");
        AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
        webApplicationContext.register(clazz);
        webApplicationContext.refresh();


    }

        我们创建AnnotationConfigWebApplicationContext 容器,并且将run方法传入的Class作为容器的配置类,比如上面的LPApplication.Class传入到了run方法中,于是LPApplication就变成容器的配置类,由于LPApplication 类上添加了@LPSpringBootApplication注解,同时该注解中存在@ComponentScan注解,于是该配置类会去扫描LPApplication所在的包路径,从而会将TestController TestService 类进行扫描并添加在容器内部,并在容器内部存在了这两个bean对象。

 Tomcat启动

使用Embed-Tomcat,模拟真正的springboot使用的内嵌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(8080);

        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();
        }

    }

然后在run方法中执行startTomcat方法便可启动Tomcat容器

效果如下

然后访问controller中的测试方法

我们再test的业务模块中只需要用到@LPSpringBootApplication注解和LPSpringApplication类即可。

实现多http(servlet)服务器的切换

上面我们模拟实现了简单的sprinboot,但在真正的springboot中是可以进行使用多种httpservlet)服务器的比如Tomcatundertowjetty等。

那么我们就需要进行动态切换

        如果项目中有Tomcat的依赖,那就启动Tomcat
        如果项目中有Jetty的依赖就启动Jetty
        如果两者都没有或者都没有则进行报错

我们可以定义一个webserver接口,在接口进行定义抽象的start方法,不同的服务器进行各种的自定义实现

public interface WebServer {

   public void start();
}
public class TomcatWebServer implements WebServer{
    @Override
    public void start() {
        System.out.println("TomcatServer  is starting...");
    }
public class JettyWebServer implements WebServer {
    @Override
    public void start() {
        System.out.println("JettyWebServer is starting...");
    }

}

然后再run方法中我们需要进行获取对应的webserver实现,然后进行启动start方法

public class LPSpringApplication {
    public static void run(Class clazz) {
        System.out.println("Hello World!");
        AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
        webApplicationContext.register(clazz);
        webApplicationContext.refresh();

//        TomcatWebServer.startTomcat(webApplicationContext);
        getWebServer(webApplicationContext).start();
    }

    public static WebServer getWebServer(WebApplicationContext applicationContext){
        Map<String, WebServer> beansOfType = applicationContext.getBeansOfType(WebServer.class);

        if (beansOfType.isEmpty()) {
            throw new NullPointerException();
        }

        if (beansOfType.size() > 1) {
            throw new IllegalStateException();
        }
        return beansOfType.values().stream().findFirst().get();
    }

}

模拟使用条件注解

首先我们需要实现Contion接口和一个自定义的条件注解@LPConditionalOnClass

public class LPCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(LPConditionalOnClass.class.getName());
        String className = (String) annotationAttributes.get("value");

        try {
            context.getClassLoader().loadClass(className);
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
}

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(LPCondition.class)
public @interface LPConditionalOnClass {
    String value();
}

LPCondition 的逻辑是拿到LPConditionalOnClass 注解的value值然后进行类加载器加载,加载成功则符合条件,反之亦然。

模拟实现自动配置类

创建了条件注解我们应该咋样使用呢?

我们需要使用自动配置的概念,我们只需要将满足条件的类进行注入到spring容器中即可

public interface AutoConfiguration {
}

@Configuration
public class WebServerAutoConfiguration implements AutoConfiguration {

    @Bean
    @LPConditionalOnClass("org.apache.catalina.startup.Tomcat")
    public TomcatWebServer tomcatWebServer(){
        return new TomcatWebServer();
    }

    @Bean
    @LPConditionalOnClass("org.eclipse.jetty.server.Server")
    public JettyWebServer jettyWebServer(){
        return new JettyWebServer();
    }
}

然后再容器中进行根据WebServer类型进行加载便可获取到对应的bean对象:


    public static WebServer getWebServer(WebApplicationContext applicationContext){
        Map<String, WebServer> beansOfType = applicationContext.getBeansOfType(WebServer.class);

        if (beansOfType.isEmpty()) {
            throw new NullPointerException();
        }

        if (beansOfType.size() > 1) {
            throw new IllegalStateException();
        }
        return beansOfType.values().stream().findFirst().get();
    }

        整体SpringBoot大概启动逻辑:

        创建一个AnnotationConfigWebApplicationContext容器
        解析LPApplication类,然后进行扫描
        通过getWebServer方法从Spring容器中获取WebServer类型的Bean
        调用WebServer对象的start方法


        有了以上步骤,但是还差了一个关键步骤,就是Spring要能解析到WebServiceAutoConfiguration这个自动配置类,此时我们需要SpringBootrun方法中,能找到WebServiceAutoConfiguration这个配置类并添加到Spring容器中。
        为了让Spring解析到WebServiceAutoConfiguration这个自动配置类并将其添加到Spring容器中,Spring Bootrun方法中需要找到这个自动配置类。虽然LPApplication是Spring的配置类,并且我们可以将其传递给Spring Boot以添加到Spring容器中,但WebServiceAutoConfiguration的自动发现需要借助Spring Boot的机制,而不是通过手动配置。

        由于LPApplication是我们显式提供给Spring Boot的配置类,Spring Boot会将其添加到Spring容器中。然而,WebServiceAutoConfiguration需要Spring Boot自动发现,并且不能依赖常规的组件扫描,因为它的包路径不同于应用程序的扫描路径。LPApplication所在的扫描路径是"com.lp.test",而WebServiceAutoConfiguration"com.lp.springboot"中。

        Spring Boot是如何实现自动发现并将自动配置类添加到Spring容器中的呢?关键在于Spring BootSPI机制。Spring Boot实现了自己的Service Provider Interface (SPI)机制,通过spring.factories文件来自动配置应用程序所需的类。通过这个机制,Spring Boot能够自动发现WebServiceAutoConfiguration这样的自动配置类,而无需显式配置。

        因此,如果我们希望Spring Boot自动发现自定义的自动配置类,可以通过在spring.factories文件中列出相应的配置类来实现。该文件通常位于META-INF目录中,Spring Boot在启动时会读取它,并将指定的自动配置类添加到Spring容器中。

        这个过程使得Spring Boot能够灵活地扩展和自动配置,而无需开发人员显式指定所有配置类。这种自动化机制大大简化了配置过程,并提供了高度可扩展性。

        那么我们模拟就可以直接用JDK自带的SPI机制。

解析自动配置类

我们只需要在springboot模块的resource文件下创建如下文件

也就是上面的AutoConfiguration接口,然后通过SPI机制和Import机制将WebServerAutoConfiguration配置类型进行导入到spring容器中

public class LPDeferredImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        ServiceLoader<AutoConfiguration> load = ServiceLoader.load(AutoConfiguration.class);
        List<String> list = new ArrayList<>();
        for (AutoConfiguration autoConfiguration : load) {
            list.add(autoConfiguration.getClass().getName());
        }
        return list.toArray(new String[0]);
    }
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(LPDeferredImportSelector.class)
public @interface LPSpringBootApplication {
}

这样就完成了在从com.lp.springboot.AutoConfiguration文件中获取自动配置类的名称并且进行导入到spring容器中,然后spring容器就可以获取到对应的配置类信息。

然后在test模块中添加jetty的依赖同时保留Tomcat依赖

        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-server</artifactId>
            <version>9.4.43.v20210629</version>
        </dependency>

然后启动test的启动类

便会进行异常处理。

这样我们就可以在test业务工程进行自定义使用http(servlet)服务器的切换。

结语

我们通过模拟了springboot的主要启动流程:核心注解、核心启动类、创建spring容器、对内嵌Tomcat的启动和注入springDispatcherServlet容器、使用多servlet服务器的切换、条件注解、自动配置类的解析等关键功能节点完成了一个简单版本的Springboot,使用这样的方式让我们对Springboot项目能有个更加深刻的理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/606576.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

提升工作效率,用ONLYOFFICE打造高效团队协作环境

作为一名深耕技术领域已有六七年的开发者&#xff0c;同时又是断断续续进行技术创作将近六年的一个小小作者&#xff0c;我在工作和日常生活中&#xff0c;使用过各色各样的软件。 而在最近几年&#xff0c;一款名为ONLYOFFICE的开源办公套件逐渐走进并融入我的工作与生活&…

使用Vue连接Mqtt实现主题的订阅及消息发布

效果如下&#xff1a; 直接贴代码&#xff0c;本地创建一个html文件将以下内容贴入即可 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, …

为什么职场关系越来越冷漠?

不知道从什么时候开始&#xff0c;我们的职场关系变得越来越冷漠了。 早上上班打卡的时候&#xff0c;一个个都低着头&#xff0c;眼神紧紧盯着手机&#xff0c;生怕错过什么重要的信息&#xff1b; 下班后大家一哄而散&#xff0c;各自抱着手机“享受”生活&#xff0c;谁也…

如何添加、编辑、调整WordPress菜单

我们最近在使用WordPress建站建设公司网站。我们是使用的hostease的主机产品建设的WordPress网站。在建设网站使用遇到了一些WordPress菜单使用方面的问题。好在hostease提供了不少帮助。 下面把WordPress菜单使用心得分享一下。 本文将详细介绍WordPress菜单的各种功能&#x…

Total Store Orderand(TSO) the x86 MemoryModel

一种广泛实现的内存一致性模型是总store顺序 (total store order, TSO)。 TSO 最早由 SPARC 引入&#xff0c;更重要的是&#xff0c;它似乎与广泛使用的 x86 架构的内存一致性模型相匹配。RISC-V 还支持 TSO 扩展 RVTSO&#xff0c;部分是为了帮助移植最初为 x86 或 SPARC 架…

1-3ARM_GD32点亮LED灯

简介&#xff1a; 最多可支持 112 个通用 I/O 引脚(GPIO)&#xff0c;分别为 PA0 ~ PA15&#xff0c;PB0 ~ PB15&#xff0c;PC0 ~ PC15&#xff0c;PD0 ~ PD15&#xff0c;PE0 ~ PE15&#xff0c;PF0 ~ PF15 和 PG0 ~ PG15&#xff0c;各片上设备用其来实现逻辑输入/输出功能。…

使用DBeaver连接postgreSql提示缺少驱动

重新安装电脑之后用dbeaver链接数据库的时候&#xff0c;链接PG库一直提示缺少驱动&#xff0c;当选择下载驱动的时候又非常非常慢经常失败&#xff0c;尝试了一下更改源然后下载库驱动就非常快了&#xff0c;当然也包括dbeaver的自动更新。 方法&#xff1a;点击菜单栏【窗口…

霸榜!近期不容错过的3个AI开源项目,来了

在人工智能领域的迅速发展下&#xff0c;各种AI开源项目如雨后春笋般涌现&#xff0c;今天就来为大家介绍近期三个热门的AI开源项目&#xff0c;它们不仅技术前沿&#xff0c;而且非常实用&#xff0c;对于技术爱好者和业界专家来说&#xff0c;绝对不容错过。 一键创作漫画和视…

基于无监督学习算法的滑坡易发性评价的实施(k聚类、谱聚类、Hier聚类)

基于无监督学习算法的滑坡易发性评价的实施 1. k均值聚类2. 谱聚类3. Hier聚类4. 基于上述聚类方法的易发性实施本研究中的数据集和代码可从以下链接下载: 数据集实施代码1. k均值聚类 K-Means 聚类是一种矢量量化方法,最初来自信号处理,旨在将 N 个观测值划分为 K 个聚类,…

生信分析进阶2 - 利用GC含量的Loess回归矫正reads数量

在NGS数据比对后&#xff0c;需要矫正GC偏好引起的reads数量误差可用loess回归算法&#xff0c;使用R语言对封装的loess算法实现。 在NIPT中&#xff0c;GC矫正对检测结果准确性非常重要&#xff0c;具体研究参考以下文章。 Noninvasive Prenatal Diagnosis of Fetal Trisomy…

向量数据库:PGVector

一、PGVector 介绍 PGVector 是一个基于 PostgreSQL 的扩展插件&#xff0c;为用户提供了一套强大的向量存储和查询的功能&#xff1a; 精确和近似最近邻搜索单精度&#xff08;Single-precision&#xff09;、半精度&#xff08;Half-precision&#xff09;、二进制&#xff…

【代码随想录——栈与队列】

1.栈和队列理论基础 栈和队列的原理大家应该很熟悉了&#xff0c;队列是先进先出&#xff0c;栈是先进后出。 2.用栈实现队列 type MyQueue struct {head []intheadSize intstore []intstoreSize int }func Constructor() MyQueue {return MyQueue{head : make([]int,100),h…

AI智剪新风尚:一键操作,批量视频剪辑轻松入门

随着科技的飞速进步&#xff0c;人工智能(AI)已逐渐渗透到我们生活的各个领域&#xff0c;其中&#xff0c;AI视频剪辑技术的出现&#xff0c;为视频制作带来了革命性的变革。如今&#xff0c;一键操作、批量处理的AI智剪正成为视频剪辑的新风尚&#xff0c;让剪辑工作变得前所…

品牌舆情监测工作要怎么做?

一个负面舆论的传播&#xff0c;可能在短时间内对企业品牌形象造成巨大损害&#xff0c;甚至引发舆情危机。因此&#xff0c;如何有效地进行品牌舆情监测&#xff0c;成为企业不可忽视的问题。伯乐网络传媒多年网络公关、舆情监测经验&#xff0c;今天就来给大家分享一下。 一、…

【半个月我拿下了软考证】软件设计师高频考点--系统化教学-网络安全

&#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;软件设计师考点暴击 ⭐&#x1f170;️进入狂砍分⭐ ⭐软件设计师高频考点文档&#xff0c; ⭐软件设计师高频考点专栏 ⭐软件设计师高频考点⭐ &#x1f3b6;&#xff08;A) 考点1&#xff0c;网络攻击 理解记忆 &#…

Kubernetes——基础认识

目录 一、简介 1.Kubernetes是什么 2.Kubernetes特性 2.1自我修复 2.2弹性伸缩 2.3自动部署和回滚 2.4服务发现和负载均衡 2.5机密和配置管理 2.6存储编排 2.7批量处理 二、Kubernetes架构与组件 1.Master 1.1Kube-ApiServer 1.2Kube-Scheduler调度器 1.3Kube-C…

机器学习(二) ----------K近邻算法(KNN)+特征预处理+交叉验证网格搜索

目录 1 核心思想 1.1样本相似性 1.2欧氏距离&#xff08;Euclidean Distance&#xff09; 1.3其他距离 1.3.1 曼哈顿距离&#xff08;Manhattan Distance&#xff09; 1.3.2 切比雪夫距离&#xff08;Chebyshev distance&#xff09; 1.3.3 闵式距离&#xff08;也称为闵…

OpenHarmony 4.0 实战开发——分布式任务调度浅析

1 概述 OpenHarmony 分布式任务调度是一种基于分布式软总线、分布式数据管理、分布式 Profile 等技术特性的任务调度方式。它通过构建一种统一的分布式服务管理机制&#xff0c;包括服务发现、同步、注册和调用等环节&#xff0c;实现了对跨设备的应用进行远程启动、远程调用、…

ChatPPT开启高效办公新时代,AI赋能PPT创作

目录 一、前言二、ChatPPT的几种用法1、通过在线生成2、通过插件生成演讲者模式最终成品遇到问题改进建议 三、ChatPPT其他功能 一、前言 想想以前啊&#xff0c;为了做个PPT&#xff0c;我得去网上找各种模板&#xff0c;有时候还得在某宝上花钱买。结果一做PPT&#xff0c;经…

拼多多投产比怎么逐步调高

提高拼多多的投产比&#xff08;ROI&#xff09;需要综合考虑多个因素&#xff0c;包括点击量、转化率、客单价以及点击花费。以下是一些有效的方法&#xff1a; 拼多多推广可以使用3an推客。3an推客&#xff08;CPS模式&#xff09;给商家提供的营销工具&#xff0c;由商家自…