手写SpringBoot核心功能流程

news2024/11/17 0:43:45

        本文通过手写模拟实现一个简易版的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.coloradmin.cn/o/1656241.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【刷题篇】双指针(二)

文章目录 1、有效三角形的个数2、查找总价格为目标值的两个商品3、三数之和4、四数之和 1、有效三角形的个数 给定一个包含非负整数的数组 nums &#xff0c;返回其中可以组成三角形三条边的三元组个数。 class Solution { public:int triangleNumber(vector<int>& n…

《MySQL对数据库中表的结构的操作》

文章目录 一、建表二、查看表结构所有能查看到数据库&#xff0c;表的操作痕迹的本质都是服务器保存下来了这些操作记录。 三、修改表1.改表名字2.添加表记录3.添加表的更多字段4.修改表的字段5. 删除表的字段 四、修改表的数据1.删除表的数据2.修改表的数据3.向表中添加数据 总…

【go项目01_学习记录02】

学习记录 1 新建项目2 自动重载安装air安装完成&#xff0c;查看版本&#xff1a;air -v使用air: air 3 设置标头4 如何知道 http 包有哪些接口呢&#xff1f;5 Web 数据响应6 小结 1 新建项目 windows系统&#xff0c;cmd&#xff1a; cd %GOPATH%/src //%GOPATH%表示取环境变…

【模型的量化 神经网络量化】对称量化与非对称量化 无符号数量化 有符号数量化

文章目录 量化方法1.非对称量化1.1 无符号量化&#xff08;uint8)1.2 有符号量化&#xff08;int8)1.3 代码1.4总结 2 对称量化2.1 无符号量化&#xff08;uint8)2.2 有符号量化&#xff08;uint8)2.3总结2.4 程序代码 减少模型大小&#xff1a;通过将模型的权重和激活值从浮点…

从抖音阳哥的经验看,选品师项目是否值得你投入?

在抖音这个短视频平台上&#xff0c;无数的创作者分享着他们的知识和经验&#xff0c;其中阳哥作为一个备受关注的博主&#xff0c;他的每一次分享都能引起广大网友的热烈讨论。最近&#xff0c;阳哥分享了一个名为“选品师”的项目&#xff0c;让许多对电商和抖音运营感兴趣的…

SpringCloud2024最新版链路追踪教程micrometer+zipkin

本文基于B站尚硅谷2024版springcloud教学视频&#xff0c;主要用于自己学习记录以及分享技术&#xff0c;侵权私删 自己本机环境信息&#xff1a; jdk&#xff1a;17.0.10springboot&#xff1a;3.2.0springcloud&#xff1a;2023.0.0 micrometer 之前行业内使用的分布式链路…

【yolov8 项目打包】pyinstaller 打包pyQt5 界面为exe

创建一篇博客文章&#xff0c;介绍如何使用PyInstaller将PyQt5界面打包为exe文件&#xff0c;并且处理与YOLOv8模型相关的文件&#xff0c;可以按照以下结构进行&#xff1a; 标题&#xff1a;使用PyInstaller将PyQt5界面与YOLOv8模型打包为Windows可执行文件 引言 在机器学习…

自建WSUS更新服务器完成内网的安全补丁更新

一、适用场景 1、企业内部网络无法访问外网&#xff0c;所以搭建WSUS服务器&#xff0c;可以让内网环境进行更新补丁。 2、校园内部的电脑实训室一般不用外网资源&#xff0c;偶尔开启外网使用时&#xff0c;电脑实训室集体自动更新占用外网资源量大&#xff0c;所以搭建WSUS服…

Mendix创客访谈录|助力工业领域,Mendix与IIOT相融合

本期创客 汤登揆 太平洋电信股份有限公司 AI 技术支持工程师 大家好&#xff0c;我是汤登揆&#xff0c;帝国理工大学&#xff0c;生态算法专业&#xff0c;主要关注于产品结构分析和产品应用落地。 目前任职于太平洋电信股份有限公司&#xff0c;主要专注于AI大模型的应用落地…

路由的基本

目录 一、VueRouter介绍 二、VueRouter的使用 三、注意 一、VueRouter介绍 VueRouter是Vue官方的一个路由插件&#xff0c;是一个第三方包。 作用&#xff1a;修改地址栏路径时&#xff0c;切换显示匹配的组件 官网:Vue Router (vuejs.org) 二、VueRouter的使用 注意&am…

ABUMN_公司内资产转移

ABUMN_公司内资产转移 一、功能介绍 使用事务码ABUMN进行公司内资产转移 二、程序代码 程序代码&#xff1a; *&---------------------------------------------------------------------* *& Report ZFIR218 *&---------------------------------------------…

如何确保UDP文件传输工具有最低稳定的传输速度?

在当前日新月异的数字时代背景下&#xff0c;文件传输工具已经成为我们日常生活与工作中不可或缺的一部分&#xff0c;尤其针对那些频繁涉及即时数据交互与多媒体流通的场景。 UDP协议&#xff0c;以其突出的高速传输与低延迟特性&#xff0c;脱颖而出成为众多用户的首选。不过…

通过自适应提示提升大语言模型的零样本推理能力

随着大模型&#xff08;LLMs&#xff09;的快速发展&#xff0c;它们在自然语言处理&#xff08;NLP&#xff09;任务上取得了前所未有的成就。特别是&#xff0c;LLMs展现出了强大的推理和规划能力&#xff0c;这得益于它们的少样本和零样本学习能力。然而&#xff0c;现有的方…

校验--ECC详细分析

ECC介绍 ECC 以下是针对瑞萨MCU的应用的ECC检测的详细分析。 当前公认安全有效的三大类公钥密钥体制分别为基于大数因子分解难题(RSA)、离散对数难题(DSA)和椭圆曲线离散对数&#xff08;ECC&#xff09;难题的密码体制。 保证RSA的安全性&#xff0c;则必须要增加密钥长度…

影响视频视觉质量的因素——各类视觉伪影

模糊效应&#xff08;Blurring Artifact&#xff09; 图像模糊&#xff08;blurring&#xff09;&#xff1a;平滑图像的细节和边缘产生的现象&#xff0c;模糊对于图像来说&#xff0c;是一个低通滤波器&#xff08;low-pass filter&#xff09;。一般而言&#xff0c;用户更…

Layer1 公链竞争破局者:Sui 生态的全面创新之路

随着 Sui 生态逐渐在全球范围内树立起声望&#xff0c;并通过与 Revolut 等前沿金融科技平台合作&#xff0c;推广区块链教育与应用&#xff0c;Sui 生态的未来发展方向已成为业界瞩目的焦点。如今&#xff0c;Sui 的总锁定价值已攀升至 5.93 亿美元&#xff0c;充分展示了其在…

python如何整体缩进

python自带编辑器的缩进和取消缩进快捷键&#xff1a; 整体缩进 Ctrl【 整体取消缩进 Ctrl】 pycharm编辑器的缩进和取消缩进快捷键&#xff1a; 整体缩进&#xff1a; tab 整体取消缩进&#xff1a; tabshift

【ZIP技巧】ZIP分卷压缩包如何解压?

经过压缩的文件仍然过大&#xff0c;大家可能都会选择“分卷压缩”来压缩ZIP文件&#xff0c;但是当我们将压缩包分卷之后&#xff0c;解压的时候该如何解压&#xff1f;今天我们分享两个ZIP分卷压缩包如何解压的方法给大家。 一、 我们可以直接点击第一个分卷压缩包&#xf…

USB系列四:USB数据传输类型(重要)

本章包括知识点如下&#xff0c;也是学习USB比较重要的一部分。 USB的块传输方式 USB的中断传输方式 USB的同步传输方式 USB的控制传输方式 USB3.0与USB2.0数据传输对比 USB总线技术协议具有极大的灵活性&#xff0c;可以针对不同的应用场合需求来采用最适合的…

分布式链路追踪工具Sky walking详解

1&#xff0c;为什么要使用分布式链路追踪工具 随着分布式系统和微服务架构的出现&#xff0c;且伴随着用户量的增加&#xff0c;项目的体量变得十分庞大&#xff0c;一次用户请求会经过多个系统&#xff0c;不同服务之间调用关系十分复杂&#xff0c;一旦一个系统出现错误都可…