当前位置: 首页 > news >正文

【第三十九讲】Boot 启动流程

【第三十九讲】Boot 启动流程

  1. SpringApplication 构造分析
  2. SpringApplication run 分析

12大步骤、7大事件

文章目录

  • 【第三十九讲】Boot 启动流程
    • 阶段一:springApplication
      • 1.来源演示
      • 2.推断应用类型
      • 3.初始化器
      • 4. 监听事件
      • 5.主类推断
    • 阶段二:执行 run 方法
      • 1.得到 SpringApplicationRunListeners
      • 2.封装启动 args
      • 8.创建容器
      • 9.准备容器
      • 10.加载 bean 定义
      • 11. refresh 容器
      • 12执行 runner
    • 上面阶段
      • org.springframework.boot.Step3
      • Step4
      • Step5
      • Step6
      • Step7
    • 总结
        • 演示 - 启动过程
        • 收获💡

阶段一:springApplication

1.来源演示

当作外部处理

添加

@Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
 return new TomcatServletWebServerFactory();
}

既可
在这里插入图片描述

输出结果

在这里插入图片描述

来源于xml

public static void main(String[] args) {
    System.out.println("1.演示获取 Bean Definition 源");
    SpringApplication springApplication = new SpringApplication(A39_1.class);
    Set<String> set = new HashSet<>();
    set.add("classpath:b01.xml");
    springApplication.setSources(set);
    
    ConfigurableApplicationContext context = springApplication.run(args);
    for (String name : context.getBeanDefinitionNames()) {
        // 获取来源
        System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
    }

    context.close();
}

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

2.推断应用类型

在这里插入图片描述

因为加入的为web依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

推断出类型为 应用类型SERVLET

在这里插入图片描述

3.初始化器

System.out.println("3.演示 ApplicationContext 初始化器");
springApplication.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        if(applicationContext instanceof GenericApplicationContext ){
            GenericApplicationContext context = (GenericApplicationContext) applicationContext;
            context.registerBean("bean3",Bean3.class);
        }
    }
});

在这里插入图片描述

4. 监听事件

System.out.println("4.演示监听器与事件");
springApplication.addListeners(new ApplicationListener<ApplicationEvent>() {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("\t事件为" + event.getClass());
    }
});

5.主类推断

在这里插入图片描述

System.out.println("5.演示主类推断");
Method deduceMainApplicationClass = SpringApplication.class.getDeclaredMethod("deduceMainApplicationClass");
deduceMainApplicationClass.setAccessible(true);
System.out.println("\t 5-------" + deduceMainApplicationClass.invoke(springApplication));

在这里插入图片描述

阶段二:执行 run 方法

public class A39_3 {
    public static void main(String[] args) throws Exception{
        SpringApplication app = new SpringApplication();
        app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
                System.out.println("执行初始化器增强...");
            }
        });

        System.out.println("----------2.封装启动 args");
            DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);
        System.out.println("----------8.创建容器");
            GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);

        System.out.println("----------9.准备容器");
            for (ApplicationContextInitializer initializer : app.getInitializers()) {
                initializer.initialize(context);
            }
        System.out.println("----------10.加载 bean 定义");
            DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
            //1.配置类
            AnnotatedBeanDefinitionReader reader1 = new AnnotatedBeanDefinitionReader(beanFactory);
            reader1.register(Config.class);
            //2.xml
            XmlBeanDefinitionReader reader2 = new XmlBeanDefinitionReader(beanFactory);
            reader2.loadBeanDefinitions(new ClassPathResource("b03.xml"));
            //3.包扫描
            ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
            scanner.scan("com.example.spring01.com.a39.sub");
        System.out.println("----------11.refresh 容器");
            context.refresh();

            for (String name : context.getBeanDefinitionNames()) {
                System.out.println("name:"+ name + "来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());

            }
        System.out.println("----------12.执行 runner");
            for (CommandLineRunner runner : context.getBeansOfType(CommandLineRunner.class).values()) {
                runner.run(args);
            }

            for (ApplicationRunner runner : context.getBeansOfType(ApplicationRunner.class).values()) {
                runner.run(arguments);
            }
    }

1.得到 SpringApplicationRunListeners

名字取得不好,实际是事件发布器

  • 发布 application starting 事件

学到了什么
a. 如何读取 spring.factories 中的配置
b. run 方法内获取事件发布器 (得到 SpringApplicationRunListeners) 的过程, 对应步骤中
1.获取事件发布器
发布 application starting 事件1️⃣
发布 application environment 已准备事件2️⃣
发布 application context 已初始化事件3️⃣
发布 application prepared 事件4️⃣
发布 application started 事件5️⃣
发布 application ready 事件6️⃣
这其中有异常,发布 application failed 事件7️⃣

public class A39_2 {
    public static void main(String[] args) throws Exception {

        // 添加 app 监听
        SpringApplication app = new SpringApplication();
        app.addListeners( event -> System.out.println(event.getClass()));

        // 获取事件发送器实现类名
        List<String> names = SpringFactoriesLoader.loadFactoryNames(SpringApplicationRunListener.class, A39_2.class.getClassLoader());
        for (String name : names) {
            System.out.println("name---"  +  name);
            // 获取事件发布器的类
            Class<?> aClass = Class.forName(name);
            // 构造器
            Constructor<?> constructor = aClass.getConstructor(SpringApplication.class, String[].class);
            // 创建对象
            SpringApplicationRunListener publisher = (SpringApplicationRunListener) constructor.newInstance(app, args);
            // 参数
            GenericApplicationContext context = new GenericApplicationContext();

            //发布事件
            publisher.starting(); // Spring boot 开始启功
            publisher.environmentPrepared(new StandardEnvironment()); // 环境信息准备完毕
            publisher.contextPrepared(context); // 在spring 容器创建,并调用初始化器之后,发送此事件
            publisher.contextLoaded(context); // 所有 bean definition 加载完毕
            context.refresh();
            publisher.started(context); // spring 容器初始化完成(refresh 方法调用完毕)
            publisher.running(context); // spring boot 启动完毕

            publisher.failed(context,new Exception("出错了")); //spring boot 启动出错
        }
    }
}

在这里插入图片描述

2.封装启动 args

DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);

8.创建容器

  • 发布 application context 已初始化事件
GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);

--------
 private static GenericApplicationContext createApplicationContext(WebApplicationType type) {
        GenericApplicationContext context = null;
        switch (type) {
            case SERVLET:
                context =  new AnnotationConfigServletWebServerApplicationContext();
                return context;
            case REACTIVE:
                context =  new AnnotationConfigReactiveWebServerApplicationContext();
                return context;
            case NONE:
                context = new AnnotationConfigApplicationContext();
                break;
        }
        return context;
    }
    

9.准备容器

for (ApplicationContextInitializer initializer : app.getInitializers()) {
                initializer.initialize(context);
            }

10.加载 bean 定义

  • 发布 application prepared 事件

三种方式

  1. Config配置类
  2. xml 文件
  3. 包扫描
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
//1.
AnnotatedBeanDefinitionReader reader1 = new AnnotatedBeanDefinitionReader(beanFactory);
reader1.register(Config.class);
//2.
XmlBeanDefinitionReader reader2 = new XmlBeanDefinitionReader(beanFactory);
reader2.loadBeanDefinitions(new ClassPathResource("b03.xml"));
//3.
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
scanner.scan("com.example.spring01.com.a39.sub");
@Configuration
    static class Config {
        @Bean
        public Bean5 bean5() {
            return new Bean5();
        }

        @Bean
        public ServletWebServerFactory servletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }

        @Bean
        public CommandLineRunner commandLineRunner() {
            return new CommandLineRunner() {
                @Override
                public void run(String... args) throws Exception {
                    System.out.println("commandLineRunner()..." + Arrays.toString(args));
                }
            };
        }

        @Bean
        public ApplicationRunner applicationRunner() {
            return new ApplicationRunner() {
                @Override
                public void run(ApplicationArguments args) throws Exception {
                    System.out.println("applicationRunner()..." + Arrays.toString(args.getSourceArgs()));
                    System.out.println(args.getOptionNames());
                    System.out.println(args.getOptionValues("server.port"));
                    System.out.println(args.getNonOptionArgs());
                }
            };
        }
    }

b03.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bean4" class="com.example.spring01.com.a39.A39_3.Bean4"></bean>
</beans>

在这里插入图片描述

11. refresh 容器

* 发布 application started 事件

12执行 runner

* 发布 application ready 事件

* 这其中有异常,发布 application failed 事件

在config配置中加入

两个接口的参数不同

@Bean
public CommandLineRunner commandLineRunner() {
    return new CommandLineRunner() {
        @Override
        public void run(String... args) throws Exception {
            System.out.println("commandLineRunner()..." + Arrays.toString(args));
        }
    };
}

@Bean
public ApplicationRunner applicationRunner() {
    return new ApplicationRunner() {
        @Override
        public void run(ApplicationArguments args) throws Exception {
            System.out.println("applicationRunner()..." + Arrays.toString(args.getSourceArgs()));
            System.out.println(args.getOptionNames());
            System.out.println(args.getOptionValues("server.port"));
            System.out.println(args.getNonOptionArgs());
        }
    };
}

参数

  1. –server.port=8080 debug
    在这里插入图片描述

runner 执行

for (CommandLineRunner runner : context.getBeansOfType(CommandLineRunner.class).values()) {
    runner.run(args);
}

for (ApplicationRunner runner : context.getBeansOfType(ApplicationRunner.class).values()) {
    runner.run(arguments);
}

上面阶段

org.springframework.boot.Step3

public class Step3 {
    public static void main(String[] args) throws IOException {
        ApplicationEnvironment env = new ApplicationEnvironment(); // 系统环境变量, properties, yaml
        env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("step3.properties")));
        env.getPropertySources().addFirst(new SimpleCommandLinePropertySource(args));
        for (PropertySource<?> ps : env.getPropertySources()) {
            System.out.println(ps);
        }
//        System.out.println(env.getProperty("JAVA_HOME"));

        System.out.println(env.getProperty("server.port"));
    }
}

先从系统属性里面找

在这里插入图片描述

Step4

ConfigurationPropertySources——名字不一致进行统一
在这里插入图片描述

public class Step4 {

    public static void main(String[] args) throws IOException, NoSuchFieldException {
        ApplicationEnvironment env = new ApplicationEnvironment();
        env.getPropertySources().addLast(
            new ResourcePropertySource("step4", new ClassPathResource("step4.properties"))
        );
        // 加入后可以读取
        ConfigurationPropertySources.attach(env);
        for (PropertySource<?> ps : env.getPropertySources()) {
            System.out.println(ps);
        }

        System.out.println(env.getProperty("user.first-name"));
        System.out.println(env.getProperty("user.middle-name"));
        System.out.println(env.getProperty("user.last-name"));
    }
}

Step5

/*
    可以添加参数 --spring.application.json={\"server\":{\"port\":9090}} 测试 SpringApplicationJsonEnvironmentPostProcessor
 */
public class Step5 {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication();
        app.addListeners(new EnvironmentPostProcessorApplicationListener());

        /*List<String> names = SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, Step5.class.getClassLoader());
        for (String name : names) {
            System.out.println(name);
        }*/

        EventPublishingRunListener publisher = new EventPublishingRunListener(app, args);
        ApplicationEnvironment env = new ApplicationEnvironment();
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强前");
        for (PropertySource<?> ps : env.getPropertySources()) {
            System.out.println(ps);
        }
        publisher.environmentPrepared(new DefaultBootstrapContext(), env);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强后");
        for (PropertySource<?> ps : env.getPropertySources()) {
            System.out.println(ps);
        }

    }

    private static void test1() {
        SpringApplication app = new SpringApplication();
        ApplicationEnvironment env = new ApplicationEnvironment();

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强前");
        for (PropertySource<?> ps : env.getPropertySources()) {
            System.out.println(ps);
        }
        ConfigDataEnvironmentPostProcessor postProcessor1 = new ConfigDataEnvironmentPostProcessor(new DeferredLogs(), new DefaultBootstrapContext());
        postProcessor1.postProcessEnvironment(env, app);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强后");
        for (PropertySource<?> ps : env.getPropertySources()) {
            System.out.println(ps);
        }
        RandomValuePropertySourceEnvironmentPostProcessor postProcessor2 = new RandomValuePropertySourceEnvironmentPostProcessor(new DeferredLog());
        postProcessor2.postProcessEnvironment(env, app);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强后");
        for (PropertySource<?> ps : env.getPropertySources()) {
            System.out.println(ps);
        }
        System.out.println(env.getProperty("server.port"));
        System.out.println(env.getProperty("random.int"));
        System.out.println(env.getProperty("random.int"));
        System.out.println(env.getProperty("random.int"));
        System.out.println(env.getProperty("random.uuid"));
        System.out.println(env.getProperty("random.uuid"));
        System.out.println(env.getProperty("random.uuid"));
    }
}

Step6

public class Step6 {
    // 绑定 spring.main 前缀的 key value 至 SpringApplication, 请通过 debug 查看
    public static void main(String[] args) throws IOException {
        SpringApplication application = new SpringApplication();
        ApplicationEnvironment env = new ApplicationEnvironment();
        env.getPropertySources().addLast(new ResourcePropertySource("step4", new ClassPathResource("step4.properties")));
        env.getPropertySources().addLast(new ResourcePropertySource("step6", new ClassPathResource("step6.properties")));

//        User user = Binder.get(env).bind("user", User.class).get();
//        System.out.println(user);

//        User user = new User();
//        Binder.get(env).bind("user", Bindable.ofInstance(user));
//        System.out.println(user);

        System.out.println(application);
        Binder.get(env).bind("spring.main", Bindable.ofInstance(application));
        System.out.println(application);
    }

    static class User {
        private String firstName;
        private String middleName;
        private String lastName;
        public String getFirstName() {
            return firstName;
        }
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
        public String getMiddleName() {
            return middleName;
        }
        public void setMiddleName(String middleName) {
            this.middleName = middleName;
        }
        public String getLastName() {
            return lastName;
        }
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
        @Override
        public String toString() {
            return "User{" +
                   "firstName='" + firstName + '\'' +
                   ", middleName='" + middleName + '\'' +
                   ", lastName='" + lastName + '\'' +
                   '}';
        }
    }
}

Step7

public class Step7 {
    public static void main(String[] args) {
        ApplicationEnvironment env = new ApplicationEnvironment();
        SpringApplicationBannerPrinter printer = new SpringApplicationBannerPrinter(
                new DefaultResourceLoader(),
                new SpringBootBanner()
        );
        // 测试文字 banner
//        env.getPropertySources().addLast(new MapPropertySource("custom", Map.of("spring.banner.location","banner1.txt")));
        // 测试图片 banner
//        env.getPropertySources().addLast(new MapPropertySource("custom", Map.of("spring.banner.image.location","banner2.png")));
        // 版本号的获取
        System.out.println(SpringBootVersion.getVersion());
        printer.print(env, Step7.class, System.out);
    }
}

总结

SpringApplication.class

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    this.configureHeadlessProperty();
    // 1.创建事件发布器
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting();

    Collection exceptionReporters;
    try {
        // 2.封装参数 -- 选项参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //3-6
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        //7.Step7
        Banner printedBanner = this.printBanner(environment);
        //8.创建容器
        context = this.createApplicationContext();
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
        // 9-10
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 11
        this.refreshContext(context);
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }

        listeners.started(context);
        // 12
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, exceptionReporters, listeners);
        throw new IllegalStateException(var10);
    }

    try {
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    // 3.Step3    
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        // 4.Step4
        ConfigurationPropertySources.attach((Environment)environment);
    	// 5.Step5
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
    	// 6.Step6
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }

        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        this.postProcessApplicationContext(context);
        // 9.
        this.applyInitializers(context);
    	// 发布事件
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }

        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }

        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }

        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
		// 10. 加载各种bean
        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        this.load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }

阶段一:SpringApplication 构造

  1. 记录 BeanDefinition 源
  2. 推断应用类型
  3. 记录 ApplicationContext 初始化器
  4. 记录监听器
  5. 推断主启动类

阶段二:执行 run 方法

  1. 得到 SpringApplicationRunListeners,名字取得不好,实际是事件发布器

    • 发布 application starting 事件1️⃣
  2. 封装启动 args

  3. 准备 Environment 添加命令行参数(*)

  4. ConfigurationPropertySources 处理(*)

    • 发布 application environment 已准备事件2️⃣
  5. 通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理(*)

    • application.properties,由 StandardConfigDataLocationResolver 解析
    • spring.application.json
  6. 绑定 spring.main 到 SpringApplication 对象(*)

  7. 打印 banner(*)

  8. 创建容器

  9. 准备容器

    • 发布 application context 已初始化事件3️⃣
  10. 加载 bean 定义

    • 发布 application prepared 事件4️⃣
  11. refresh 容器

    • 发布 application started 事件5️⃣
  12. 执行 runner

    • 发布 application ready 事件6️⃣

    • 这其中有异常,发布 application failed 事件7️⃣

带 * 的有独立的示例

演示 - 启动过程

com.itheima.a39.A39_1 对应 SpringApplication 构造

com.itheima.a39.A39_2 对应第1步,并演示 7 个事件

com.itheima.a39.A39_3 对应第2、8到12步

org.springframework.boot.Step3

org.springframework.boot.Step4

org.springframework.boot.Step5

org.springframework.boot.Step6

org.springframework.boot.Step7

收获💡

  1. SpringApplication 构造方法中所做的操作
    • 可以有多种源用来加载 bean 定义
    • 应用类型推断
    • 添加容器初始化器
    • 添加监听器
    • 演示主类推断
  2. 如何读取 spring.factories 中的配置
  3. 从配置中获取重要的事件发布器:SpringApplicationRunListeners
  4. 容器的创建、初始化器增强、加载 bean 定义等
  5. CommandLineRunner、ApplicationRunner 的作用
  6. 环境对象
    1. 命令行 PropertySource
    2. ConfigurationPropertySources 规范环境键名称
    3. EnvironmentPostProcessor 后处理增强
      • 由 EventPublishingRunListener 通过监听事件2️⃣来调用
    4. 绑定 spring.main 前缀的 key value 至 SpringApplication
  7. Banner

相关文章:

  • ApkScan-PKID 查壳工具下载使用以及相关技术介绍
  • 从BNB遭黑客攻击(跨链桥BSC Token Hub遭到攻击),看公链中心化问题
  • 【多线程实践】一、为何使用多线程三种线程创建方式利弊分析
  • LIFELONG LEARNING WITH DYNAMICALLY EXPANDABLE NETWORKS论文阅读+代码解析
  • 计算机网络——集线器与交换机
  • 用通俗易懂的方式讲解:CatBoost 算法原理及案例
  • 系统架构演变和SpringCloud的定义:
  • 后端必会的前端vue基础知识
  • VGG16 - 咖啡豆识别
  • 2022.10.7 英语背诵
  • 『Android』什么是Service
  • 【Windows核心编程+第一个内核程序】爆肝120小时整理-80%程序员最欠缺的能力,一半以上研究生毕业了还不懂?理解各种深度技术的基本功
  • 【微信小程序】事件传参与数据同步
  • Android移动应用开发之Fragment
  • 分类评估方法-召回率、ROC与AUC
  • JavaScript 如何正确处理 Unicode 编码问题!
  • “寒冬”下的金三银四跳槽季来了,帮你客观分析一下局面
  • 10个确保微服务与容器安全的最佳实践
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • Js基础知识(一) - 变量
  • Kibana配置logstash,报表一体化
  • Nginx 通过 Lua + Redis 实现动态封禁 IP
  • ViewService——一种保证客户端与服务端同步的方法
  • VUE es6技巧写法(持续更新中~~~)
  • VuePress 静态网站生成
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 机器学习学习笔记一
  • 可能是历史上最全的CC0版权可以免费商用的图片网站
  • 前端知识点整理(待续)
  • 再次简单明了总结flex布局,一看就懂...
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • #100天计划# 2013年9月29日
  • #Ubuntu(修改root信息)
  • #vue3 实现前端下载excel文件模板功能
  • (07)Hive——窗口函数详解
  • (八)Spring源码解析:Spring MVC
  • (汇总)os模块以及shutil模块对文件的操作
  • (九)One-Wire总线-DS18B20
  • (力扣记录)1448. 统计二叉树中好节点的数目
  • (推荐)叮当——中文语音对话机器人
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (转)关于多人操作数据的处理策略
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
  • .NET MVC、 WebAPI、 WebService【ws】、NVVM、WCF、Remoting
  • .Net 路由处理厉害了
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter
  • /etc/shadow字段详解
  • /var/lib/dpkg/lock 锁定问题
  • @RestControllerAdvice异常统一处理类失效原因
  • @zabbix数据库历史与趋势数据占用优化(mysql存储查询)
  • [ vulhub漏洞复现篇 ] Celery <4.0 Redis未授权访问+Pickle反序列化利用
  • [AIGC] Java 和 Kotlin 的区别
  • [AIGC] 如何建立和优化你的工作流?
  • [Android]创建TabBar
  • [Angular 基础] - 表单:响应式表单