SpringBoot源码解析: 从手写一个简易版SpringBoot开始

news2024/12/30 0:46:50

IDE:IntelliJ IDEA 2019.2.4 x64
操作系统:win10 x64 位 家庭版
Maven版本:apache-maven-3.6.3


文章目录

  • 真正的SpringBoot
  • 手写一个简易版的springboot
    • 一. 准备
      • 1.1 准备自定义的springboot模块
      • 1.2 准备用户模块User
    • 二. 运行测试
      • 2.1 第一次运行测试
      • 2.2 第二次运行测试
      • 2.3 第三次运行测试
    • 三. 改进优化
      • 3.1 不足①
      • 3.2 不足②
    • 四. 思考:为什么真正的SpringBoot中默认启动的是Tomcat容器?


在这里插入图片描述


真正的SpringBoot

看一段正常的springboot启动类

代码如下所示

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class ServiceCmnApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceCmnApplication.class, args);
    }
}

手写一个简易版的springboot

一. 准备

首先准备两个模块

在这里插入图片描述

1.1 准备自定义的springboot模块

① FcSpringApplication类代码如下所示

package org.springboot;

import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class FcSpringApplication {

    //自定义run方法
    public static void run(Class clazz) {

        //1.启动tomcat
        startTomcat();
    }

    //对Tomcat进行属性配置
    private static void startTomcat() {
        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);


        try {
            tomcat.start();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }

    }

②自定义注解FcSpringBootApplication代码如下所示

package org.springboot;

public @interface FcSpringBootApplication {
}

1.2 准备用户模块User

①启动类UserApplication代码如下

import org.springboot.FcSpringApplication;
import org.springboot.FcSpringBootApplication;

@FcSpringBootApplication
public class UserApplication {

    public static void main(String[] args) {
        FcSpringApplication.run(UserApplication.class);

    }
}

这里先抛出一个问题

为何要将User模块的启动类作为值传入FcSpringApplication.run()方法中?

在传值之前,有个很重要的前提–需要启动Tomcat服务器

②UserController类代码如下

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {


    @GetMapping("/test")
    public String test(){
        return "success";
    }
}

二. 运行测试

2.1 第一次运行测试

启动User模块,可以发现

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

它报错了!!!

🤔为什么会报错?

因为自定义springboot模块没有实现“根据指定请求路径去寻找controller中对应的请求方法并返回结果”的功能,而这一功能正好是springmvc技术。故而我们只需在自定义springboot模块里的run()中整合springmvc技术即可

2.2 第二次运行测试

整合springmvc代码如下所示

tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(webApplicationContext));
context.addServletMappingDecoded("/*", "dispatcher");

复制上述代码到run方法中

在这里插入图片描述

代码爆红了

通过查阅追踪SpringMvc中new DispatcherServlet()中的源码可知

public DispatcherServlet(WebApplicationContext webApplicationContext) {
    super(webApplicationContext);
    this.setDispatchOptionsRequest(true);
}

👉发现

它需要一个WebApplicationContext类型的对象,而这个WebApplicationContext类型是一个接口,它继承于ApplicationContext接口,而这个ApplicationContext接口实际上就是Spring容器

在这里插入图片描述

从这就不难猜到,DispatcherServlet需要从一个spring容器中去找请求路径相映射的处理方法并返回结果

👉结论

tomcat需要从传入的spring容器中去找UserController所对应的bean,进而找到对应请求路径映射的方法test

优化后整体代码示例如下

import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class FcSpringApplication {

    public static void run(Class clazz) {
        //启动Tomcat之前,需要创建spring容器(找到UserController这个Bean)
        AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext(); //通过注解形式配置spring容器
            

        //启动tomcat
        startTomcat(webApplicationContext);
    }

    //对Tomcat进行属性配置
    private static void startTomcat(WebApplicationContext webApplicationContext) {
        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(webApplicationContext));
        context.addServletMappingDecoded("/*", "dispatcher");

        try {
            tomcat.start();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }

    }
}

2.3 第三次运行测试

①运行User模块

在这里插入图片描述
在这里插入图片描述

它还是显示这个报错界面!!!

👉分析

上述界面表明Tomcat服务器已经接收到了请求,但它找不到对应相映射的请求方法并返回结果

🤔疑问

DispatcherServlet明明已经整合进了Tomcat里,为何tomcat还是找不到?

👉原因

虽然启动Tomcat服务器之前已经构建好了一个Spring容器,但是这个spring容器此时是一个空的容器,它里面没有包含"/test"相映射的请求方法的bean
[UserController],没有bean,自然无从查起。

遂优化代码,如下所示

①在自定义SpringBoot模块中run方法里添加如下代码

public static void run(Class clazz) {
    //启动Tomcat之前,需要创建spring容器(找到UserController这个Bean)
    AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext(); //通过注解形式配置spring容器

    //将传入的类注册为spring容器的配置类
    webApplicationContext.register(clazz);
    
    //刷新spring容器
    webApplicationContext.refresh();
    //一旦刷新spring容器,便开始解析容器 --> 根据@ComponentScan中的包路径下去扫描,就会扫描到UserController这个bean,如果不写路径,默认扫描传入类的所在的包路径

    //启动tomcat
    startTomcat(webApplicationContext);
}

👉释义

上述代码是将传入的类作为配置类在Spring容器中注册,当执行 webApplicationContext.register(clazz);webApplicationContext.refresh()时,就会刷新并解析spring容器,根据传入的配置类上方的“@ComponentScan(“com.fc.user”) ”注解,便会去扫描【com.fc.user】该包路径下的bean【UserController】,进而找到与"/test"相映射的请求方法,最终将返回方法的结果给前端页面

这里便回答了1.2中抛出的问题!!!

②在User模块中启动类上加上@ComponentScan(“com.fc.user”) – 扫描该包下所有的bean

import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@ComponentScan("com.fc,user")
public class UserController {


    @GetMapping("/test")
    public String test(){
        return "success";
    }
}

运行

在这里插入图片描述
在这里插入图片描述

总结

大致的执行流程就是用户在浏览器上发出的请求被tomcat服务器所接收,tomcat服务器便将请求交由Dispatcherservlet,Dispatcherservlet便会根据“/test"去Spring容器中去找UserController这个bean,在这个bean中去寻找“/test"相映射的执行方法,最后返回”success“给用户。


三. 改进优化

上述代码虽然实现了SpringBoot框架的基本功能,但也有很多不足

3.1 不足①

User模块中的启动类上不必再写上@ComponentScan,在自定义注解@FcSpringBootApplication中可以集成它

👉对比

  1. 且看如下真正的SpringBoot里@SpringBoot注解写法

在这里插入图片描述

  1. 自己写的User模块启动类
import org.springboot.FcSpringApplication;
import org.springboot.FcSpringBootApplication;

@FcSpringBootApplication
@ComponentScan("com.fc.user")
public class UserApplication {

    public static void main(String[] args) {
        FcSpringApplication.run(UserApplication.class);

    }
}

改进优化代码如下所示

①User模块中的启动类上不再写上注解@ComponentScan()

@FcSpringBootApplication
public class UserApplication {

    public static void main(String[] args) {
        FcSpringApplication.run(UserApplication.class);

    }
}

②在自定义注解@FcSpringBootApplication中可以集成@ComponentScan()

import org.springframework.context.annotation.ComponentScan;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ComponentScan
public @interface FcSpringBootApplication {

}

👉释义

Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME) 是两个常用的注解属性。

  1. @Target(ElementType.TYPE): 这个属性表示该注解可以用于类、接口(包括注解类型本身)、枚举声明以及注解类型的成员变量和方法,但不包括类的成员方法。换句话说,这个注解只能用于代码级别的注解,不能用于元注解(即注解的注解)。
  2. @Retention(RetentionPolicy.RUNTIME): 这个属性表示该注解在运行时仍然有效。RetentionPolicy 是一个枚举类型,它有四个值:SOURCE(注解只在源码中保留,编译时会被丢弃),CLASS(注解在源码和字节码中都保留,运行时可以通过反射获取到),RUNTIME(注解在源码和字节码中都保留,运行时可以通过反射获取到,且可以通过动态代理等方式使用),PROTECTED(注解只在源码中保留,通过反射可以访问,但是不能通过动态代理等方式使用)。

👉运行

在这里插入图片描述

在这里插入图片描述

👉注意

如果@ComponentScan中没有写明扫描路径,Spring会默认解析扫描传入run方法中的那个类【配置类】所在的包路径,尽管我们在日常开发中习惯将启动类作为配置类传入run()中,但配置类不一定是启动类,因为我们完全可以再定义一个类作为配置类传入run()中,效果等同

不信?请看如下所示

案例:在User模块中自定义配置类MyApplocation并传入run()中,演示其效果

①定义自定义配置类MyApplocation

import org.springboot.FcSpringBootApplication;

@FcSpringBootApplication
public class MyApplication {
}

在这里插入图片描述

②将MyApplocation传入run()中

import com.fc.user.controller.MyApplication;
import org.springboot.FcSpringApplication;
import org.springboot.FcSpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@FcSpringBootApplication
public class UserApplication {

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

👉运行

在这里插入图片描述

在这里插入图片描述

3.2 不足②

自定义SpringBoot模块中只能运行Tomcat服务器,不能运行Jetty服务器,扩展性较差;而Spring Boot支持三种内嵌式服务器:Tomcat、Jetty和Undertow。其中,Tomcat是Spring Boot中默认的内嵌服务器,但是开发者可以在application.properties文件中更改它。如果想使用其他服务器,可以在pom.xml文件中添加相应的依赖项,然后在application.properties文件中配置它。

🚩优化版1.0

可通过定义Bean的方式来决定Tomcat或jetty的使用

代码示例如下

1.在自定义SpringBoot模块中使用 webServer.start()以启动服务器

import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import java.util.Map;

public class FcSpringApplication {

    public static void run(Class clazz){

        //启动Tomcat之前,需要创建spring容器(找到UserController这个Bean)
        AnnotationConfigWebApplicationContext annotationConfigWebApplicationContext = new AnnotationConfigWebApplicationContext(); //通过注解形式配置spring容器
        //将传入的类注册为spring容器的配置类
        annotationConfigWebApplicationContext.register(clazz);
        //刷新spring容器
        annotationConfigWebApplicationContext.refresh();
        //一旦刷新spring容器,便开始解析容器 --> 根据@ComponentScan中的包路径下去扫描,如果不写路径,默认扫描传入类的所在的包路径


        //启动Tomcat
		//startTomcat(annotationConfigWebApplicationContext);
        //以上写法写死了嵌定Tomcat容器,过于单元,但是真正的springboot中内嵌支持不止一种容器,有Tomcat,jetty和Undertow
        WebServer webServer = getWebServer(annotationConfigWebApplicationContext);
        webServer.start();

    }

    private static WebServer getWebServer(WebApplicationContext webApplicationContext) {
        //在spring容器中去找容器对应的bean,有且只有一个容器【Tomcat/Jetty】可以使用
        //key为beanName,value为Bean对象
        Map<String, WebServer> webServers = webApplicationContext.getBeansOfType(WebServer.class);

        if (webServers.isEmpty()){
            throw new NullPointerException();
        }
        if (webServers.size()>1){
            throw new IllegalStateException();
        }

        return webServers.values().stream().findFirst().get();
    }

   

2.定义接口WebServer,接口WebServer的实现类TomcatWebServer与JettyWebServer

//接口WebServer
import org.springframework.web.context.WebApplicationContext;

public interface WebServer {

    void start(WebApplicationContext webApplicationContext);
}


//接口WebServer的实现类TomcatWebServer
import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class TomcatWebServer implements WebServer {

    @Override
    //对Tomcat进行属性配置
    public void start(WebApplicationContext webApplicationContext) {
        System.out.println("开启Tomcat");
        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(webApplicationContext));
        context.addServletMappingDecoded("/*", "dispatcher");

        try {
            tomcat.start();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }

    }
}

//接口WebServer的实现类JettyWebServer
import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;
import org.eclipse.jetty.util.Jetty;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class JettyWebServer implements WebServer {

    @Override
    public void start(WebApplicationContext webApplicationContext) {
        System.out.println("启动Jetty");
        
        
    }
}

3.在User模块中启动类上定义TomcatWebserver的bean

import com.fc.user.controller.MyApplication;
import org.springboot.FcSpringApplication;
import org.springboot.FcSpringBootApplication;
import org.springboot.TomcatWebServer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;

@FcSpringBootApplication
public class UserApplication {

    @Bean
    public TomcatWebServer tomcatWebServer(){
        return new TomcatWebServer();
    }

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

👉运行

在这里插入图片描述

在这里插入图片描述

这样,就可以通过定义Bean的形式来切换Tomcat或jetty的使用!!!

🚩优化版2.0

可通过pom文件定义依赖的方式来决定Tomcat或jetty的使用,且默认使用Tomcat

代码示例如下

1.在自定义SpringBoot模块中定义一个SpringBoot自动装配的配置类

@Configuration
public class WebServerAutoConfiguration {

    @Bean
    //如果pom文件中存在jetty的依赖,便产生这个bean
    @Conditional(JettyCondition.class)
    public JettyWebServer jettyWebServer(){
        return new JettyWebServer();
    }

    @Bean
    @Conditional(TomcatCondition.class) //只有满足类中的条件,才能产生bean
    public TomcatWebServer tomcatWebServer(){
        return new TomcatWebServer();
    }
}

2.定义JettyCondition类与TomcatCondition类,以设置产生bean的条件

//定义TomcatCondition类
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class TomcatCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        try {
            context.getClassLoader().loadClass("org.apache.catalina.startup.Tomcat");
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
}

//--------------------------------------------------------------------------------
//定义JettyCondition类
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class JettyCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //如何判定pom文件中添加了jetty的依赖?如果jetty类存在,说明添加了jetty的依赖
        try {
            context.getClassLoader().loadClass("org.eclipse.jetty.util.Jetty");
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

👉运行

在这里插入图片描述

🤔为什么会触发空指针异常?

👉原因

因为上述定义的WebServerAutoConfiguration类没有被传入run方法中的容器配置类所扫描到,尽管上述类中定义的两个bean都满足条件并产生了。但由于没有被扫描到,两个bean没有被装载到spring容器中,故触发了空指针异常。

👉解决方案

在所传入run()方法中的容器配置类上导入WebServerAutoConfiguration类,使其可以被扫描到

代码示例如下

@FcSpringBootApplication
//导入WebServerAutoConfiguration类,使其可以被扫描到
@Import(WebServerAutoConfiguration.class)
public class UserApplication {

/*
    @Bean
    public TomcatWebServer tomcatWebServer(){
        return new TomcatWebServer();
    }
*/

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

👉再次运行

报错了!!!

在这里插入图片描述

🤔why?

👉原因

请看如下两个模块的maven依赖关系图

在这里插入图片描述

👉解决方案

将自定义springboot模块pom文件中jetty的依赖属性option设为true

设置如下所示

 <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-server</artifactId>
      <version>9.4.48.v20220622</version>
	<!--    当在父子关系中使用此依赖项时,它不会传递到子项目中。-->
      <optional>true</optional>
</dependency>

在这里插入图片描述

👉运行测试

在这里插入图片描述

这与真正的SpringBoot框架“如果不指定服务器,默认调用Tomcat服务器”的特性相一致

如果想切换jetty服务器,则须在User模块中使用exclusions标签排除tomcat容器的依赖,然后引入jetty的依赖,最后记得刷新maven,重新运行User模块即可。

代码示例如下

 <artifactId>user</artifactId>

    <dependencies>

        <dependency>
            <groupId>com.fc</groupId>
            <artifactId>springboot</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
            <!--  排除tomcat依赖          -->
            <exclusions>
                <exclusion>
                    <groupId>org.apache.tomcat.embed</groupId>
                    <artifactId>tomcat-embed-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--  引入jetty的依赖    -->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-server</artifactId>
            <version>9.4.48.v20220622</version>
        </dependency>

    </dependencies>

👉运行测试

在这里插入图片描述

再看此时如下两个模块的maven依赖关系

在这里插入图片描述

上述代码成功排除了tomcat的依赖,而只包含jetty的依赖!!!


四. 思考:为什么真正的SpringBoot中默认启动的是Tomcat容器?

打开一个基于SpringBoot的web应用项目的pom文件,如下所示

在这里插入图片描述

众所周知,spring-boot-starter-web是Spring Boot提供的一个依赖,它包含了开发Web应用所需的基本依赖库。使用该依赖可以快速搭建一个基于Spring Boot的Web应用,且无需手动配置大量的依赖库。

该依赖主要包括以下组件:

  • spring-boot-starter-tomcat: 内嵌的Tomcat服务器,用于运行Spring Boot Web应用。
  • spring-boot-starter-web: Spring MVC核心组件,提供了创建Web应用所需的基本功能,如控制器、视图解析器等。
  • spring-boot-starter-data-jpa: Spring Data JPA支持,简化了数据访问层的开发。
  • spring-boot-starter-security: Spring Security支持,提供了安全的Web应用开发能力。
  • spring-boot-starter-test: Spring Test框架支持,简化了单元测试和集成测试的开发。

使用Ctrl + 鼠标左键点击追踪进入该文件,可以发现该文件只引入Tomcat容器的依赖,而无jetty的依赖

在这里插入图片描述

在这里插入图片描述

接着点进spring-boot-starter-tomcat的依赖里,如下所示

在这里插入图片描述

可以看到spring-boot-starter-tomcat包含了开发Web应用所需Tomcat的基本依赖库

该依赖主要包括以下组件:

  • spring-boot-starter-web: Spring MVC核心组件,提供了创建Web应用所需的基本功能,如控制器、视图解析器等。
  • spring-boot-starter-data-jpa: Spring Data JPA支持,简化了数据访问层的开发。
  • spring-boot-starter-security: Spring Security支持,提供了安全的Web应用开发能力。
  • spring-boot-starter-test: Spring Test框架支持,简化了单元测试和集成测试的开发。

综上,因为springboot工程中只嵌入了一个Tomcat,而无jetty;如果想要使用别的容器,例如jetty,可以使用exclusions标签排除tomcat的依赖,并引入jetty的依赖,刷新maven重启项目即可。


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

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

相关文章

如何快速上传批量剪辑的素材?附素材水印、字幕去除教程

想要快速上传网络素材&#xff0c;大家可以使用超级编导批量剪辑软件。 首先进入超级编导官网&#xff0c;免费下载超级编导超级编导软件&#xff0c;可以在官网中添加产品顾问&#xff0c;获取试用账号&#xff0c;登录即可开始使用。 超级编导支持批量上传URL链接到后台&…

Manifest merger failed

编译报错&#xff1a;Manifest merger failed with multiple errors 定位编译错误&#xff1a;java.lang.RuntimeException: Manifest merger failed with multiple errors 近日&#xff0c;项目中需要引入一个module。在成功导入后&#xff0c;添加依赖到主模块上&#xff0c…

【RocketMQ】(六)顺序消息实现原理

全局有序 在RocketMQ中&#xff0c;如果使消息全局有序&#xff0c;可以为Topic设置一个消息队列&#xff0c;使用一个生产者单线程发送数据&#xff0c;消费者端也使用单线程进行消费&#xff0c;从而保证消息的全局有序&#xff0c;但是这种方式效率低&#xff0c;一般不使用…

955. 删列造序 II;1838. 最高频元素的频数;1392. 最长快乐前缀

955. 删列造序 II 核心思想&#xff1a;我们可以按照一行一行排列strs&#xff0c;删除索引序列就代表删除某一列&#xff0c;那么我们如何判断一列是否应该删除呢&#xff0c;我们可以从反方向思考&#xff0c;应该保留那些列呢&#xff1f;从第一列开始&#xff0c;如果它不…

WebGIS面试题(浙江中海达)

1、Cesium中有几种拾取坐标的方式&#xff0c;分别介绍 Cesium是一个用于创建3D地球和地理空间应用的JavaScript库。在Cesium中&#xff0c;你可以使用不同的方式来拾取坐标&#xff0c;以便与地球或地图上的对象进行交互。以下是Cesium中几种常见的拾取坐标的方式&#xff1a…

jdk21(最新版) download 配置(linux window mac)

download 直达链接 jdk21,17 # wget https://download.oracle.com/java/20/latest/jdk-21_linux-x64_bin.deb # 选择你需要的包类似格式替换包的名称就可以实现终端下载jdk下载登录/oracle账号 下载jdk有可能存在要求登录帐号的情况 # 好心人的帐号 账号&#xff1a; 599…

C语言内存函数的使用、剖析及模拟实现

目录 一、内存拷贝函数——memcpy 1.函数声明&#xff1a; 注意&#xff1a; 2.函数使用用例&#xff1a; 3.memcpy函数的模拟实现&#xff1a; 二、内存拷贝函数2——memmove 1.函数声明&#xff1a; 2.memmove函数的模拟实现 三、内存比较函数——memcmp 1.函数声明…

01_docker镜像管理:80分钟一口气学完docker+k8s!带你掌握docker+k8s所有核心知识点,全程干货,无废话!

docker镜像的实际使用学习 开发过程中&#xff0c;需要安装很多三方工具&#xff0c;比如etcd、kafka、mysql、nginx等等 1、下载安装Docker工具。 2、获取该软件的Docker镜像&#xff08;基本上&#xff0c;都能搜索到核实的镜像&#xff09;&#xff0c;下载镜像nginx镜像…

浏览器基本原理

1、浏览器内部组成 我们看到浏览器主要包括&#xff1a; 1个浏览器主进程&#xff1a; 主要负责界面显示&#xff0c;用户交互&#xff0c;子进程管理多个渲染进程&#xff1a;一般浏览器会为每个Tab标签窗口创建一个渲染进程&#xff0c;主要负责将html&#xff0c;css&#…

经典网络(一) AlexNet逐层解析 | 代码、可视化、参数查看!

文章目录 1 回顾2 AlexNet的重要性3 AlexNet解析3.1 结构3.1.1 CONV13.1.2 Max Pool13.1.3 NORM13.1.4 CONV23.1.5 Max Pool23.1.6 CONV3 CONV43.1.7 CONV53.1.8 Max Pool33.1.9 FC1 FC2 FC3 3.2 AlexNet使用到的技巧3.3 可视化3.4 代码实现模拟3.4.1 查看每一层输入输出3.4.2 …

UE 虚幻引擎 利用LOD,Nanite技术优化场景性能

目录 0 引言1 LOD1.1 LOD定义1.2 UE5中的LOD技术1.3 HLOD&#xff08;Hierarchical Level of Detail&#xff09; 2 Nanite2.1 UE5的Nanite技术2.2 Nanite介绍2.2.1 Nanite的优势2.2.2 Nanite网格体与传统静态网格体的不同2.2.3 Nanite支持的类型2.2.4 在地形中使用Nanite 0 引…

KT142C语音芯片flash型用户如何更新固件的说明_V2

目录 一、简介 2.1 让芯片进入PC模式 2.2 双击提供的exe程序即可 一、简介 正常的情况下&#xff0c;用户肯定是不需要更新固件的&#xff0c;因为芯片出厂默认就烧录了对应的程序固件&#xff0c;但是有客户可能需要小修小改&#xff0c;或者订制一下某些功能&#xff0c…

寻找环形链表的入环点

之前我们在判断一个链表是否为环&#xff0c; 是运用快慢指针的方法&#xff0c;且只能是慢指针走一步&#xff0c;快指针两步&#xff1b; 那么如何求带环链表的入环点的 思路一&#xff1a;数学方法&#xff08;找出带环链表各个特点量的关系&#xff09; 代码&#xff1a;…

Linux设备驱动之Camera驱动

Linux设备驱动之Camera驱动 Camera&#xff0c;相机&#xff0c;平常手机使用较多&#xff0c;但是手机的相机怎么进行拍照的&#xff0c;硬件和软件&#xff0c;都是如何配合拍摄到图像的&#xff0c;下面大家一起来了解一下。 基础知识 在介绍具体Camera框架前&#xff0c…

图像复原与重建,解决噪声的几种空间域复原方法(数字图像处理概念 P4)

文章目录 图像复原模型噪声模型只存在噪声的空间域复原 图像复原模型 噪声模型 只存在噪声的空间域复原

字节一面:你能手撕节流防抖吗?

前言 最近博主在字节面试中遇到这样一个面试题&#xff0c;这个问题也是前端面试的高频问题&#xff0c;节流防抖是前端性能优化一个很重要的手段&#xff0c;所以作为一个前端工程师必须要深入掌握这个知识点&#xff0c;博主在这给大家细细道来。 &#x1f680; 作者简介&…

01 TextRNN FastText TextCNN-04-训练要点,实验过程

TextRNN & FastText & TextCNN-03-模型总览&#xff0c;后 训练要点 RNN训练 得出来的y&#xff08;m&#xff09;&#xff08;预测标签&#xff09;是每一个分类的概率&#xff0c;比如是一个五分类&#xff0c;化成5个格子&#xff0c;每一个格子是概率&#xff0c…

java生成PDF的Util

java使用itext生成pdf-CSDN博客 接上文 支持表格绘制表格 支持表格中的文本 字体加粗、字体上色、单元格背景上色&#xff0c; 支持拼接文本 支持单行文本 多种背景颜色、字体上色 支持自定义水印 废话不说先上效果图 工具类代码 package com.zxw.文件.PDF.util;import …

建立一张表: 表里面有多个字段,每一个字段对应一种数据类

首先mysql -uroot -p 进入MySQL 选择一个数据库并使用 在该数据库内创建表格 create table homework_tb( id int(11) comment 编号, company_name char(6) comment 公司名称, introduce varchar(100) comment 介绍, content1 tinytext comment 内容1, co…

ad18学习笔记十一:显示和隐藏网络、铺铜

如何显示和隐藏网络&#xff1f; Altium Designer--如何快速查看PCB网络布线_ad原理图查看某一网络的走线_辉_0527的博客-CSDN博客 AD19(Altium Designer)如何显示和隐藏网络 如何显示和隐藏铺铜&#xff1f; Altium Designer 20在PCB中显示或隐藏每层铺铜-百度经验 AD打开与…