Java类加载器原理与实践

news2025/8/6 15:58:46

文章目录

  • 一、Java程序启动并运行的过程
  • 二、类加载器
  • 三、Java8内置的类加载器
    • 1. AppClassLoader
    • 2. ExtClassLoader
    • 3. BootStrap ClassLoader
    • 4. 3个类加载器之间的关系
  • 四、双亲委派模型
  • 五、关键类java.lang.ClassLoader
    • 1. loadClass(..)
    • 2. denfineClass(..)
    • 3. findClass(..)
    • 4. findVBootstrapClassOrNull(..)
    • 5. getParent()
  • 六、实现自定义ClassLoader
  • 七、类加载器的特性
    • 1. 唯一性
    • 2. 传递性
    • 3. 可见性
  • 八、打破“双亲委派模型”
    • 1. Java SPI机制打破双亲委派模型
    • 2. 热加载/热部署打破双亲委派模型
    • 3. 自定义热部署/热加载示例
  • 九、数组类的本质
    • 1. 数组类的加载
    • 2. 与数组类相关联的类加载器

一、Java程序启动并运行的过程

JVM在启动时,首先会加载并初始化main方法所在的类,这个类被称为起始类initial class,接着JVM会调用并执行main方法,在执行main方法的过程中,可能会触发进一步的执行,继续加载其他的类并执行其他的方法,直到程序退出。
需要注意的是,在加载某个类或执行某个方法的时候,也可能会触发其他类的加载,可以看到Java程序从启动到运行,类的加载无处不在,起到了举足轻重的作用。而且,Java中的类加载都是在运行时动态完成的,这种动态加载的特性,也正是Java语言灵活性的根源。

在这里插入图片描述

二、类加载器

在Java中,所有的类加载都通过类加载器(ClassLoader)来完成,加载的过程大致如下:
首先使用Java代码或JVM触发一个加载动作,然后将类的全限定名传给类加载器,类加载器再通过类名获取到字节码的二进制流,这可以从本地硬盘读取类文件,也可以是从网络远程读取到类文件,甚至还可以在运行时动态生成字节码,最后再根据字节码二进制流创建加载对应的Class对象。

在这里插入图片描述

三、Java8内置的类加载器

为了更好的理解Java的类加载机制,先介绍几个Java8中内置的ClassLoader,先看一个代码片段,它的目的是打印出指定类的类加载器,从输出可以看到Java8中内置的三个ClassLoader,分别是AppClassLoader、ExtClassLoader、BootStrap ClassLoader。

package cn.memset.sample;

import com.sun.javafx.util.Logging;

import java.util.ArrayList;

/**
 * 打印 Java 8 中内置的 ClassLoader
 */
public class BuiltinClassLoaderTest {
    public static void main(String[] args) {
        System.out.println("类 BuiltinClassLoaderTest 的加载器是: "
                + BuiltinClassLoaderTest.class.getClassLoader());

        System.out.println("类 Logging 的加载器是: "
                + Logging.class.getClassLoader());

        System.out.println("类 ArrayList 的加载器是: "
                + ArrayList.class.getClassLoader());
    }
}

在这里插入图片描述

1. AppClassLoader

AppClassLoader即应用类加载器,是系统默认的类加载器,它负责加载当前Java应用classpath中的类,而classpath通常是通过java命令的参数 -cp 或 -classpath 来指定。可以通过属性"java.class.path"来获取具体的值。

在这里插入图片描述

2. ExtClassLoader

ExtClassLoader即扩展类加载器,负责加载扩展目录中的类,扩展目录通常是<JAVA_HOME>/lib/ext,可以通过属性"java.ext.dirs"来设置或获取具体的值。

在这里插入图片描述

3. BootStrap ClassLoader

BootStrap ClassLoader即启动类加载器,负责加载JDK中核心类库中的类。例如:Java8中<JAVA_HOME>/jre/lib中的rt.jar。
在JVM中,BootStrap ClassLoader通常是使用C/C++语言原生实现的,它不能表现为一个Java类,所以将它打印出来是null。

在这里插入图片描述

4. 3个类加载器之间的关系

  1. 从JVM角度来看,只有2种类加载器,一种是BootStrap ClassLoader,它通常是JVM中的一部分,使用C/C++语言原生实现的,而另一种则是用户定义的ClassLoader,包括了JDK中内置的ExtClassLoader、AppClassLoader以及用户自行实现的ClassLoader。用户定义的ClassLoader都使用Java语言实现,并且要求继承抽象类java.lang.ClassLoader。
  2. BootStrap ClassLoader、ExtClassLoader、AppClassLoader三者之间并非继承关系,而是组合关系, AppClassLoader显示拥有一个parent加载器ExtCLassLoader,而ExtClassLoader的parent加载器则隐式指向BootStrap ClassLoader。

在这里插入图片描述

四、双亲委派模型

类加载的“双亲委派模型”展示了类加载器之间的协作方式。

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

如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个加载请求,委派给“父”类加载器去完成,而且每一个层次的类加载器都是如此。因此所有的加载请求,最终都应该被传送到最顶层的启动类加载器中。只有当“父”加载器反馈自己无法完成这个加载请求时,“子”加载器才会尝试继续加载,如果到最后也无法加载指定的类,那么就抛出异常ClassnotFoundException。
在这里插入图片描述

这样做的好处是,越顶层的类加载器,对其可见的类总是被优先加载。例如:java.lang.String存放在rt.jar中,即使自行定义了一个同名的类,并且将其放到了classpath中,但最终都是委派给处于最顶层的启动类加载器进行加载,即最终会加载rt.jar中的String类,而不是classpath中自行定义的String类,这样可以保证Java类型体系的稳定性。
在这里插入图片描述

反之,对于类app.ClassA,只有当上层的类加载器都找不到对应的类时,才会被用户自定义的类加载器加载。
在这里插入图片描述

双亲委派模型的实现源码:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
	  // 加锁,保证线程安全
      synchronized (getClassLoadingLock(name)) {
          // 首先,检查这个类是否被加载过。同一个类不能被重复加载。
          Class<?> c = findLoadedClass(name);
          if (c == null) {
              long t0 = System.nanoTime();
              try {
              	  // 如果parent不为空,那么委派给parent类加载器来尝试加载
                  if (parent != null) {
                      c = parent.loadClass(name, false);
                  } else {
                  	 // 如果parent为空,那么委派给BootStrap ClassLoader来尝试加载
                      c = findBootstrapClassOrNull(name);
                  }
              } catch (ClassNotFoundException e) {
                  // ClassNotFoundException thrown if class not found
                  // from the non-null parent class loader
              }

              if (c == null) {        
                  long t1 = System.nanoTime();
                   // 如果parent类加载器未能加载成功,那么就通过findclass方法进行加载
                  // 用户自定义的类加载器通常需要覆盖findclass方法
                  c = findClass(name);

                  // this is the defining class loader; record the stats
                  PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                  PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                  PerfCounter.getFindClasses().increment();
              }
          }
          // 类的解析
          if (resolve) {
              resolveClass(c);
          }
          return c;
      }
  }

五、关键类java.lang.ClassLoader

ClassLoader类是Java类加载机制中的核心类。在JVM规范中,规定了所有用户自行定义的类加载器都必须继承类ClassLoader。因此如果要自定义一个类加载器,首先需要彻底弄明白类ClassLoader中的关键方法。
ClassLoader类中的关键方法:

  • loadClass(…)
  • denfineClass(…)
  • findClass(…)
  • findVBootstrapClassOrNull(…)
  • getParent()

1. loadClass(…)

在这里插入图片描述

2. denfineClass(…)

在这里插入图片描述

3. findClass(…)

在这里插入图片描述

4. findVBootstrapClassOrNull(…)

在这里插入图片描述

5. getParent()

在这里插入图片描述

六、实现自定义ClassLoader

实现自定义ClassLoader,它遵循“双亲委派模型”,从任意指定的某个目录中读取字节码类文件,然后创建加载对应的类。

package cn.memset.sample.classloaders;

import java.io.*;

/**
 * 从任意指定的某个目录中读取字节码类文件,然后创建加载对应的类
 * 自定义的类加载器必须继承抽象类ClassLoader
 */
public class MyCommonClassLoader extends ClassLoader {

	// 静态初始化块
    static {
        // 表明当前的ClassLoader可并行加载不同的类
        registerAsParallelCapable();
    }

    /**
     * 指定的字节码类文件所在的本地目录
     */
    private final String commonPath;

    /**
     * 构造函数。默认的parent ClassLoader是 AppClassLoader
     *
     * @param commonPath 字节码类文件所在的本地目录
     */
    public MyCommonClassLoader(String commonPath) {
        if (!commonPath.isEmpty()
                && commonPath.charAt(commonPath.length() - 1) != File.separatorChar) {
            commonPath += File.separator;
        }
        this.commonPath = commonPath;
    }

    /**
     * 构造函数。指定了一个 parent ClassLoader 。
     *
     * @param commonPath 字节码类文件所在的本地目录
     * @param parent     指定的parent ClassLoader
     */
    public MyCommonClassLoader(String commonPath, ClassLoader parent) {
        super(parent);
        if (!commonPath.isEmpty()
                && commonPath.charAt(commonPath.length() - 1) != File.separatorChar) {
            commonPath += File.separator;
        }
        this.commonPath = commonPath;
    }

    /**
     * 覆盖父类的 findClass(..) 方法。
     * 从指定的目录中查找字节码类文件,并创建加载对应的Class对象。
     *
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 读取字节码的二进制流
            byte[] b = loadClassFromFile(name);
            // 调用 defineClass(..) 方法创建 Class 对象
            Class<?> c = defineClass(name, b, 0, b.length);
            return c;
        } catch (IOException ex) {
            throw new ClassNotFoundException(name);
        }
    }

    private byte[] loadClassFromFile(String name) throws IOException {
        String fileName = name.replace('.', File.separatorChar) + ".class";
        String filePath = this.commonPath + fileName;

        try (InputStream inputStream = new FileInputStream(filePath);
             ByteArrayOutputStream byteStream = new ByteArrayOutputStream()
        ) {
            int nextValue;
            while ((nextValue = inputStream.read()) != -1) {
                byteStream.write(nextValue);
            }
            return byteStream.toByteArray();
        }
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // System.out.println("准备使用 MyCommonClassLoader 加载类:" + name);
        return super.loadClass(name, resolve);
    }
}

七、类加载器的特性

1. 唯一性

ClassLoader虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。它还是JVM中类命名空间的一部分,用来确定某个类的唯一性:假设某个类的全限定名是N,加载定义它的加载器是L,那么这个类的唯一性可以通过元组<N,L>来确定。通俗来说,即如果要比较两个类是否相等,除了要看类的全限定类名是否相等外,还需要看这两个类的类加载器是否是同一个。这里所说的相等,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,以及instanceof关键字所做的类型判定等情况。

在这里插入图片描述

package cn.memset.sample;

import cn.memset.sample.classloaders.MyCommonClassLoader;

/**
 * Java中类的唯一性(命名空间)
 */
public class ClassNamespaceTest {
    private final static String COMMON_PATH = "D:\\anyuanwai\\common-sdk";
    public static void main(String[] args) throws Exception {
        MyCommonClassLoader l1 = new MyCommonClassLoader(COMMON_PATH);
        MyCommonClassLoader l2 = new MyCommonClassLoader(COMMON_PATH);

        String className = "cn.memset.app.entities.Employee";
        Class<?> c1 = Class.forName(className, false, l1);
        Class<?> c2 = Class.forName(className, false, l2);
        Object o1 = c1.newInstance();

        System.out.println("c1类型是否等于c2类型?" + c1.equals(c2));
        System.out.println("对象o1是否是c2类型?" + (c2.isInstance(o1)));
    }
}

用两个不同的ClassLoader实例对象,来加载同一个类,得到的结果并不相等。
在这里插入图片描述

2. 传递性

**加粗样式**

测试类加载器的传递性,在之前自定义的类加载器MyCommonClassLoader中增加一行代码,打印将要被加载的类名

package cn.memset.sample.classloaders;

import java.io.*;

/**
 * 从任意指定的某个目录中读取字节码类文件,然后创建加载对应的类
 * 自定义的类加载器必须继承抽象类ClassLoader
 */
public class MyCommonClassLoader extends ClassLoader {

	...

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    	// 增加一行代码,打印将要被加载的类名
        System.out.println("准备使用 MyCommonClassLoader 加载类:" + name);
        return super.loadClass(name, resolve);
    }
}

CompanyService 实现了Service接口,并且编译后得到的类文件会被拷贝到"D:\anyuanwai\common-sdk"的目录下,

package cn.memset.service.api;

public interface Service {
    void start();

    void stop();
}
package cn.memset.app;

import cn.memset.app.entities.Employee;
import cn.memset.app.entities.Manager;
import cn.memset.service.api.Service;

public class CompanyService implements Service {
    @Override
    public void start() {
        System.out.println("start Service[" + this + ']');
        Employee employee = new Employee("张三");
        Manager manager = new Manager("李四");

        String employeeStr = employee.toString().toLowerCase();
        String managerStr = manager.toString().toLowerCase();

        System.out.println("公司员工: " + employeeStr);
        System.out.println("公司经理: " + managerStr);
    }

    @Override
    public void stop() {
        System.out.println("stop Service[" + this + ']');
    }
}
package cn.memset.sample;

import cn.memset.sample.classloaders.MyCommonClassLoader;
import cn.memset.service.api.Service;

/**
 * 类加载器“传递性”的示例代码
 */
public class ClassLoaderTransTest {
    private final static String COMMON_PATH = "D:\\anyuanwai\\common-sdk";

    public static void main(String[] args) throws Exception {
        ClassLoader myLoader = new MyCommonClassLoader(COMMON_PATH);
        Class<?> serviceCls = Class.forName(
                "cn.memset.app.CompanyService",
                false,
                myLoader);

        Service service = (Service) serviceCls.newInstance();
        service.start();
    }
}

运行测试代码可以看到CompanyService类是通过我们自定义的类加载器MyCommonClassLoader加载的,那么MyCommonClassLoader中所依赖的Employee、Manager 类,也同样会通过同一个类加载器实例来进行加载,甚至CompanyService类中所依赖的JDK核心类库中的类或接口。例如:String、StringBuilder也同样会通过同一个类加载器进行加载。当然String、StringBuilder最终会被委派给BootStrap ClassLoader进行加载。
在这里插入图片描述

类的传递性非常重要,通过它,我们可以从某个入口类开始,不断使用相同类加载器展开加载同一个模块或应用中的其他类。整体来说,是以某种递归的形式逐步加载所需的类。

在这里插入图片描述

3. 可见性

例如:如果“类A”是通过AppClassLoader加载的,而“类B”是通过ExtClassLoader加载的,那么“类B”对于“类A”是可见的。反过来“类A”对于“类B”则是不可见的。 拓展开来,对于AppClassLoader加载的其他类来说,“类A”和“类B”都是可见的。但是,对于ExtClassLoader加载的其他类,只有“类B”是可见的,而“类A”是不可见的。

八、打破“双亲委派模型”

在这里插入图片描述

双亲委派模型并非是强制性约束,它更多是推荐给开发者的一种类加载器的实现方式。在Java世界中,我们通常会遵循“双亲委派模型”,但有的时候,由于“双亲委派模型”自身的局限性,我们不得不主动去打破它。

1. Java SPI机制打破双亲委派模型

在这里插入图片描述

Java SPI机制中的核心类是ServiceLoader,它在java.util包中,很显然类ServiceLoader是BootStrap ClassLoader加载的,但ServiceLoader中需要去实例化部署在classpath中的第三方厂商的服务提供类Service Provider。根据类加载的“传递性”,类ServiceLoader所依赖的类都应该由BootStrap ClassLoader来进行加载,但很明显,BootStrap ClassLoader是无法加载那些位于classpath中的第三方厂商的Service Provider类。
为了解决这个问题,Java团队提供了一个不太优雅的设计,“线程上下文类加载器”。首先要说明的是,“线程上下文类加载器”并非是一个具体的类,而是类Thread中的一个成员属性,可以理解为Java设计团队找了一个地方(当前线程),把某个具体的ClassLoader存储起来,然后在需要的时候再取出来。通常我们可以通过Thread类中的setContextClassLoader()方法对其进行设置。如果创建线程时还未设置,它将从父线程中继承一个。如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用类加载器(APPClassLoader)。

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

有了“线程上下文类加载器”,我们就可以在Java SPI的ServiceLoader中使用“线程上下文类加载器” 去加载classpath中的第三方厂商类,这种行为实际上是打破了“双亲委派模型”中的层次结构。在BootStrap ClassLoader加载的类中,再反向调用APPClassLoader来加载第三方厂商类,逆转了类之间的可见性。

在这里插入图片描述

2. 热加载/热部署打破双亲委派模型

例如:Tomcat允许在不重启Tomcat进程的前提下,部署新的war包。其实,所有的热加载/热部署机制的原理都一样,都是基于Java的类加载器来实现的。类加载器的唯一性、传递性、可见性正是实现热加载/热部署的基础。
在这里插入图片描述

3. 自定义热部署/热加载示例

在这里插入图片描述

package cn.memset.sample;

import cn.memset.sample.classloaders.MyCommonClassLoader;
import cn.memset.service.api.Service;

import java.util.Scanner;

/**
 * 简单的热加载示例
 */
public class ReloadableApplication {
    /**
     * Service类型的全局变量
     */
    private static Service service;

    /**
     * 加载指定目录中的 Service
     */
    private static void loadService() throws Exception {
        // 首先创建一个全新的 ClassLoader 对象
        // 自定义的类加载器MyCompanyClassLoader
        ClassLoader myLoader = new MyCommonClassLoader(
                "D:\\anyuanwai\\common-sdk");
        // 调用 Class.forName 加载指定的 Service 类
        Class<?> serviceCls = Class.forName(
                "cn.memset.app.CompanyService",
                false,
                myLoader);

        if (service != null) {
            service.stop();
        }

        // 创建 Service 对象,然后运行它
        // 动态加载类CompanyService,实现了热加载
        service = (Service) serviceCls.newInstance();
        service.start();
    }

    public static void main(String[] args) throws Exception {
        loadService();

        Scanner scanner = new Scanner(System.in);
        while (true) {
            String command = scanner.nextLine();
            // 在控制台输入reload命令时,会使用loadService()方法完成热加载的动作
            // 在实际中,我们可以通过监听指定目录中的文件变化,进而自动触发热加载动作
            if ("reload".equalsIgnoreCase(command)) {
                // 重新加载服务
                // 在不停止当前进程的前提下,加载最新版本的类
                loadService();
            } else if ("exit".equalsIgnoreCase(command)) {
                // 停止服务
                if (service != null) {
                    service.stop();
                }
                break;
            } else {
                System.out.println(command);
            }
        }
    }
}

先编译项目,然后把编译得到的类文件拷贝到指定的目录中,接着启动ReloadableApplication

在这里插入图片描述

接着修改项目,加一行代码。编译后将最新版本的类文件拷贝到指定目录,然后在控制台输入reload命令,可以看到最新版本的CompanyService成功运行了。

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

九、数组类的本质

所有的数组示例都属于Object,每个数组示例都有对应的Class。
数组类继承了Object,同时也实现了接口Cloneable和Serializable

package cn.memset.sample;

/**
 * 数组类的本质
 */
public class ArrayTest {
    public static void main(String[] args) {
        int[] ia = new int[3];
        System.out.println("数组类: " + ia.getClass());
        System.out.println("数组类的父类: "
                + ia.getClass().getSuperclass());
        for (Class<?> c : ia.getClass().getInterfaces())
            System.out.println("数组类的父接口: " + c);
    }
}

在这里插入图片描述

1. 数组类的加载

数组类的加载是JVM直接创建的,有专门的JVM指令newarray

在这里插入图片描述

2. 与数组类相关联的类加载器

在这里插入图片描述

package cn.memset.sample;

import cn.memset.sample.classloaders.MyCommonClassLoader;
import com.sun.javafx.util.Logging;

import java.lang.reflect.Array;

/**
 * 数组类关联的类加载器
 */
public class ArrayClassLoaderTest {
    public static void main(String[] args) throws Exception {
        int[] ia = new int[0];
        System.out.println("int数组:" + ia.getClass().getClassLoader());

        String[] sa = new String[0];
        System.out.println("String数组:" + sa.getClass().getClassLoader());

        Logging[] la = new Logging[0];
        System.out.println("Logging数组:" + la.getClass().getClassLoader());

        ArrayClassLoaderTest[] aa = new ArrayClassLoaderTest[0];
        System.out.println("ArrayClassLoaderTest数组:" + aa.getClass().getClassLoader());

        ClassLoader myLoader = new MyCommonClassLoader("D:\\anyuanwai\\common-sdk");
        Class<?> serviceCls = Class.forName(
                "cn.memset.app.CompanyService",
                true,
                myLoader);

        Object[] oa = (Object[]) Array.newInstance(serviceCls, 0);
        System.out.println("CompanyService数组:" + oa.getClass().getClassLoader());
    }
}

其中int数组和String数组关联的类加载器打印出来是null,表示了与其关联的类加载器是BootStrap ClassLoader。

在这里插入图片描述

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

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

相关文章

k8s client-go源码解析之informer 一

Informer(一) 注意&#xff1a;本文内容为学习笔记&#xff0c;内容为个人见解&#xff0c;不保证准确性&#xff0c;但欢迎大家讨论何指教。 本篇为先导篇&#xff0c; 介绍informer的入口工厂函数。 informer目录结构 (仅展示部分目录&#xff0c;省略的目录相似) clien…

项目实战典型案例27——对生产环境以及生产数据的敬畏之心

对生产环境以及生产数据的敬畏之心一&#xff1a;背景介绍总结升华一&#xff1a;背景介绍 本篇博客是对项目开发中出现的对生产环境以及生产数据的敬畏之心行的总结并进行的改进。目的是将经历转变为自己的经验。通过博客的方式分享给大家&#xff0c;大家一起共同进步和提高…

SpringCloud之 Gateway路由网关

文章目录Gateway 路由网关一、部署网关&#x1f34d;①添加依赖&#x1f34d;②设置配置文件&#x1f34d;③创建启动类&#x1f34d;④路由功能配置&#x1f34d;⑤路由访问服务二、路由过滤器2.1 单个过滤器: 配置文件2.2 全局过滤器: 自定义类提示&#xff1a;以下是本篇文章…

【数据结构初阶】详解链表OJ题

目录一.删除链表中等于给定值的节点二.合并有序链表并返回三.链表的回文结构1.反转单链表2.返回非空链表的中间节点四.输出链表倒数第K个节点五.基于给定值x分割单链表六.返回两个链表的第一个中间节点一.删除链表中等于给定值的节点 我们先来看第一题(题目链接): 因为我们需…

王道《操作系统》学习(二)—— 进程管理(一)

2.1 进程的概念、组成、特征、组织 2.1.1 进程的概念 这里像QQ这个程序执行了多次&#xff0c;虽然名字一样&#xff0c;但是它们的PID不同。 2.1.2 进程的组成 &#xff08;1&#xff09;PCB &#xff08;2&#xff09;程序段 和 数据段 小例子&#xff1a;程序是如何执行的&…

Nacos实现配置中心

文章目录配置中心提供的基础功能Nacos实现配置中心1、在配置文件增加配置&#xff08;application.yml&#xff09;2、使用Value来引用配置使用配置中心&#xff0c;怎样的配置方式呢一、安装nacos二、启动服务发现1、引入依赖2、配置文件3、开启服务注册发现功能4、启动服务三…

quarkus 生产环境与k8s集成总结

quarkus 生产环境与k8s集成总结 大纲 基础准备quarkus2.13.7脚手架工程配置GraalVM-java11 安装配置配置maven3.8.7linux环境下云原生二进制文件打包环境搭建编译运行quarkus二进制文件quarkus二进制文件制作为docker镜像并运行使用k8s部署quarkus二进制文件 基础准备 生产…

手把手交叉编译mysql

1.下载mysql&#xff08;注意下载boost版本&#xff0c;这样会少一步编译&#xff09; 下载mysql的时候一定要看好交叉编译工具链的版本。因为mysql 8.0需要的工具链版本较高&#xff0c;所以有可能不支持 查看链接如下&#xff1a; MySQL :: MySQL 8.0 Reference Manual :: …

InstructGPT方法简读

InstructGPT方法简读 引言 仅仅通过增大模型规模和数据规模来训练更大的模型并不能使得大模型更好地理解用户意图。由于数据的噪声极大&#xff0c;并且现在的大多数大型语言模型均为基于深度学习的“黑箱模型”&#xff0c;几乎不具有可解释性和可控性&#xff0c;因此&…

「SAP ABAP」OPEN SQL(四)【FROM语句】

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

CIMCAI port ai shipping ai artificial intelligence smart port

上海人工智能独角兽中集集团高科技中集飞瞳&#xff0c;是全球应用落地最广&#xff0c;规模最大&#xff0c;最先进的的港航人工智能高科技企业&#xff0c;工业级成熟港航人工智能产品全球规模化落地应用&#xff0c;全球前三大船公司及港口码头应用落地。上海人工智能独角兽…

3.4 按键控制LED灯光敏传感器控制蜂鸣器

按键控制LED灯1.1 按键连接示意图1.2 代码设计1.21 设计思路我们要实现按键控制led&#xff0c;我们需要完成LED和按键驱动代码&#xff0c;但如果把这两部分代码都混在主函数里面&#xff0c;那么代码显得过于杂乱&#xff0c;不容易管理和移植&#xff0c;所以对于这种驱动代…

记一次反射型XSS

记一次反射型XSS1.反射型XSS1.1.前言1.2.测试过程1.3.实战演示1.3.1.输入框1.3.2.插入代码1.3.3.跳转链接2.总结1.反射型XSS 1.1.前言 关于这个反射型XSS&#xff0c;利用的方式除了钓鱼&#xff0c;可能更多的就是自娱自乐&#xff0c;那都说是自娱自乐了&#xff0c;并且对系…

Maxscale读写分离实施文档

Maxscale介绍 MaxScale是maridb开发的一个mysql数据中间件&#xff0c;其配置简单&#xff0c;能够实现读写分离&#xff0c;并且可以根据主从状态实现写库的自动切换。 使用Maxscale无需对业务代码进行修改&#xff0c;其自带的读写分离模块&#xff0c;能够解析SQL语句&…

DD-1/40 10-40mA型【接地继电器】

系列型号&#xff1a; DD-1/40接地继电器 DD-1/50接地继电器 DD-1/60接地继电器 一、 用途及工作原理 DD-1型接地继电器为瞬时动作的过电流继电器&#xff0c;用作小电流接地电力系统高电压三相交流发电机和电动机的接地零序过电流保护。继电器线圈接零序电流互感器(电缆式、母…

Vue动态粒子特效插件(背景线条吸附动画)

目录 效果图&#xff1a; 一、安装&#xff1a; 二、引入 main.js 文件&#xff1a; 三、使用&#xff1a; 四、属性说明&#xff1a; 效果图&#xff1a; 一、安装&#xff1a; npm install vue-particles --save 二、引入 main.js 文件&#xff1a; import VueParticles…

【C++】30h速成C++从入门到精通(多态)

多态的概念多态&#xff1a;通俗来说就是多种心态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。多态的定义及实现多态的构成条件多态是在不同继承关系的类对象&#xff0c;去调用同意函数&#xff0c;产生了不同的行为&#xff0…

C/C++每日一练(20230307)

目录 1. 国名排序 ★★ 2. 重复的DNA序列 ★★★ 3. 买卖股票的最佳时机 III ★★★ &#x1f31f; 每日一练刷题专栏 C/C 每日一练 ​专栏 Python 每日一练 ​专栏 1. 国名排序 小李在准备明天的广交会&#xff0c;明天有来自世界各国的客房跟他们谈生意&#xff0c…

结合基于规则和机器学习的方法构建强大的混合系统

经过这些年的发展&#xff0c;我们都确信ML即使不能表现得更好&#xff0c;至少也可以在几乎所有地方与前ML时代的解决方案相匹配。比如说一些规则约束&#xff0c;我们都会想到能否把它们替换为基于树的ml模型。但是世界并不总是黑白分明的&#xff0c;虽然机器学习在解决问题…

spring boot actuator 动态修改日志级别

1 日志级别 Spring Boot Actuator包括在运行时查看和配置应用程序日志级别的功能。您可以查看整个列表&#xff0c;也可以查看单个记录器的配置&#xff0c;该配置由显式配置的日志级别和日志框架给出的有效日志级别组成。这些级别可以是: TRACEDEBUGINFOWARNERRORFATALOFFnu…