精华内容
下载资源
问答
  • SpringBoot忽略某些自动配置 最近业务要求对接数据,需要连接第三方的数据库。但是他们的都是内网,只能通过前置机上部署我们的项目,由于 我们与第三方对接的处理都写在单独的服务里,如果来一个第三方,单独...

    SpringBoot启动时忽略某些自动配置类

    最近业务要求对接数据,需要连接第三方的数据库。但是他们的都是内网,只能通过前置机上部署我们的项目,由于
    我们与第三方对接的处理都写在单独的服务里,如果来一个第三方,单独再写一个服务会很麻烦,不好管理,所以需要把
    所有处理都写在已有的服务里,可是有个问题就是:这次需要把该服务放到指定的前置机上,本来是使用多数据源连接第
    三方数据库,并且连接的有自己的 mysql、Redis,还会注册到 Eureka 上,直接放到前置机上,会报 Redis 和 找不到
    Eureka 的错误,所以需要忽略 Spring 的这两个自动配置类

    解决方法

    在启动类上加上 :

    	@SpringBootApplication(exclude = {EurekaClientAutoConfiguration.class, RedisAutoConfiguration.class})
    

    问题:每次放到前置机上启动都需要更改代码,再重新打包,比较麻烦
    这时,点到 @SpringBootApplication 注解里看到

    在这里插入图片描述

    这里说明,真正生效的注解是 “EnableAutoConfiguration” ,所以点到 EnableAutoConfiguration 里,首先看
    Spring 对该类的说明,里面有一句话,解决了我的问题:
    在这里插入图片描述

    可以看到,Spring 启动时忽略,有两种方式:

    1、在 @SpringBootApplication 注解上指定 exclude
    2、在配置文件里指定 spring.autoconfigure.exclude

    本来项目在不同环境上使用的就是不同的配置文件,使用第二种方式就可以省去部署的时候需要改代码的步骤
    至此,在配置文件加上以下配置,解决问题。

    spring:
      autoconfigure:
        exclude:
          - org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration
          - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
    
    展开全文
  • 文章目录加载器加载加载器作用加载器分类双亲委派模型双亲委派模式的实现 加载器 加载 加载器作用 对于任意一个,必须由加载它的加载器和这个本身一起共同确立其在Java虚拟机中的唯一...

    类加载过程(生命周期)

    JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。其中加载、检验、准备、初始化和卸载这个五个阶段的顺序是固定的,而解析则未必。为了支持动态绑定,解析这个过程可以发生在初始化阶段之后。
    在这里插入图片描述

    加载

    由于Class文件并不一定由java源码编译而来,从其他途径产生的Class文件在字节码语言层面上,语义也是可以表达出来的。但是可能会载入有害字节流导致系统崩溃,如果不检查会对虚拟机造成损伤。

    加载过程主要完成三件事情

    1. 通过类的全限定名来获取定义此类的二进制字节流
    2. 将这个类字节流代表的静态存储结构转为方法区的运行时数据结构
    3. 在堆中生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口。

    这个过程主要就是类加载器完成。

    校验

    此阶段主要确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。

    1. 文件格式验证:基于字节流验证。验证字节流是否符合Class文件格式规范,并且能被当前版本的虚拟机处理。
    2. 元数据验证:基于方法区的存储结构验证。对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。
    3. 字节码验证:基于方法区的存储结构验证。通过数据流和控制流分析,确定程序语义是合法的符合逻辑的。如果一个类的字节码没有通过字节码验证,那一定是有问题的;但通过了也不能说明没有问题。
    4. 符号引用验证:基于方法区的存储结构验证。对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。

    准备

    为类变量分配内存,并将其初始化为默认值。(此时为默认值,在初始化的时候才会给变量赋值)即在方法区中分配这些变量所使用的内存空间。例如:

    public static int value = 123;
    

    此时在准备阶段过后的初始值为0而不是123;将value赋值为123的putstatic指令是程序被编译后,存放于类构造器<client>方法之中.特例:

    public static final int value = 123;
    

    此时value的值在准备阶段过后就是123。

    解析

    把常量池内的符号引用转换为直接引用。

    • 符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
    • 直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。

    主要有以下四种:

    1. 类或接口的解析
    2. 字段解析
    3. 类方法解析
    4. 接口方法解析

    初始化

    初始化阶段是执行类构造器<client>方法的过程。<client>方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证<client>方法执行之前,父类的<client>方法已经执行完毕。如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成<client>()方法。

    java中,对于初始化阶段,有且只有以下五种情况才会对要求类立刻“初始化”(加载,验证,准备,自然需要在此之前开始):

    1. 使用new关键字实例化对象、访问或者设置一个类的静态字段(被final修饰、编译器优化时已经放入常量池的例外)、调用类方法,都会初始化该静态字段或者静态方法所在的类。
    2. 初始化类的时候,如果其父类没有被初始化过,则要先触发其父类初始化。
    3. 使用java.lang.reflect包的方法进行反射调用的时候,如果类没有被初始化,则要先初始化。
    4. 虚拟机启动时,用户会先初始化要执行的主类(含有main)
    5. jdk 1.7后,如果java.lang.invoke.MethodHandle的实例最后对应的解析结果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄,并且这个方法所在类没有初始化,则先初始化。

    类加载器

    类加载器作用

    • 对于任意一个类,必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。也就是说,比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类必定不相等。
    • 这里的相等包括代表类的Class 对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等各种情况。

    类加载器分类

    1. 启动类加载器(Bootstrap Class Loader):这个类加载器负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机的内存中。启动类加载器无法被Java程序直接引用,但可间接引用。在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理,直接使用null代替即可。
    2. 扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。由于扩展类加载器是由Java代码实现的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。
    3. 应用程序类加载器(Application Class Loader):这个类加载器由sun.misc.Launcher$AppClassLoader来实现。由于应用程序类加载器是ClassLoader类中的getSystem-ClassLoader()方法的返回值,所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径(ClassPath)上所有的类库,在程序中可以直接使用,为程序中的默认加载器。

    双亲委派模型

    在这里插入图片描述

    上图中类加载器之间的层次关系称为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当有自己的父类加载器。

    *为什么要提出双亲委派模型?

    其实就是为了让基础类得以正确地统一地加载。
    从上面的图可以看出,如果你也定义了一个 java.lang.Object类,通过双亲委派模式是会把这个请求委托给启动类加载器,它扫描\lib目录就找到了 jdk 定义的 java.lang.Object 类来加载,所以压根不会加载你写的 java.lang.Object类,这就可以避免一些程序不小心或者有意的覆盖基础类

    所以它的优势可以归结为:
    1.确保类只被加载一次,避免重复加载
    2.避免核心类被任意或恶意篡改

    在这里插入图片描述

    工作过程:
    如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都如此,因此所有的加载请求应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时即它的搜索范围内没有找到所需要的类,子加载器才会尝试自己去加载。

    双亲委派模式的实现

    双亲委派模型的代码实现集中在java.lang.ClassLoader的loadClass()方法当中。

    • 首先检查类是否被加载,没有则调用父类加载器的loadClass()方法;
    • 若父类加载器为空,则默认使用启动类加载器作为父加载器;
    • 若父类加载失败,抛出ClassNotFoundException 异常后,再调用自己的findClass() 方法。

    loadClass源代码如下:

    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        //1.首先检查类是否被加载
        Class c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                   //2.没有则调用父类加载器的loadClass()方法;
                    c = parent.loadClass(name, false);
                } else {
                   //3.若父类加载器为空,则默认使用启动类加载器作为父加载器;
                    c = findBootstrapClass0(name);
                }
            } catch (ClassNotFoundException e) {
                //4.若父类加载失败,抛出ClassNotFoundException 异常后再调用自己的findClass() 方法。
                c = findClass(name);
            }
        }
        if (resolve) {
        	// 链接指定的类
            resolveClass(c);
        }
        return c;
    }
    
    展开全文
  • 打造java启动器步骤三

    千次阅读 2021-03-06 00:36:46
    一、Java程序的启动过程对于普通用户来说,Java让人不习惯的是程序的启动过程;即使对于富有经验的开发者,为了用默认的装载器启动Java程序,不得不编写大量批命令、脚本文件,不得不在命令行环境下进行大量的复制/...

    一、Java程序的启动过程

    对于普通用户来说,Java让人不习惯的是程序的启动过程;即使对于富有经验的开发者,为了用默认的装载器启动Java程序,不得不编写大量批命令、脚本文件,不得不在命令行环境下进行大量的复制/粘贴操作,也很容易出现误操作。

    用惯了Windows方便快捷的GUI,人们早就习惯了通过双击运行程序的方式。对于Java程序,要实现这个本机启动功能就必须编写定制的启动器。用定制启动器启动Java程序不仅方便了终用户,而且使软件作品看起来更专业。本文就以Windows平台为例,介绍如何构造Java定制启动器。

    Java程序可以由任何本机运行的程序调用执行。所谓Java启动器,就是一个专门用来启动Java程序的本机执行程序。常见的启动器是Sun在Java Runtime Environment的/bin目录中提供的启动器,就Windows平台而言,它们是java.exe和javaw.exe.前者运行时打开两个窗口:一个是接收System.out/err和启动器输出的控制台窗口,另一个是Java程序本身的窗口;javaw运行时不打开控制台窗口。在J2SE/EE平台中,虚拟机以动态库的形式实现,也放在/bin目录下。动态库的名字在Windows中是java.dll,在Unix中是java.so.所谓"装入虚拟机",就是指装入这个动态库。

    提供给VM的参数可以通过两种方式,或者是在启动器的命令行参数中,或者通过定义相应的环境变量。只有一个参数例外--要启动的类的名称只能在启动器的命令行参数中。虽然方式的多样姓为人们各取所需带来了方便,但不可否认地,它也正是许多混乱的根源。使用定制启动器能够完全避免这方面的问题。

    当VM结束启动类的main()方法的运行,启动器调用destroy()方法释放各种资源并退出。应当注意的是,VM一旦开始运行,我们就不能再卸载它。对于Java启动器来说,能否关闭VM无关紧要,因为启动器会随着Java程序的退出而退出;然而,对于嵌入了VM的本机应用,例如浏览器,这意味着有一块内存被永久姓地占用,不能再收回。

    二、Windows平台的启动器

    搞清楚了Java程序的启动过程,我们就可以开始编写启动器的代码。下面这个启动器用C++写成,适合于所有Windows平台。

    首先,就象大多数Windows程序一样,启动器需要一个WinMain()入口。与Windows这一特定平台相关的问题,除了必要的类型转换(例如对CreateJavaVM()的转换)之外,另外一个要注意的地方就是装入VM的DLL文件。装入DLL文件可靠的办法是显式地调用LoadLibrary()。装入JVM之后,就可以利用内核调用GetProcAddress()获得CreateJavaVM()的函数指针,然后调用该指针启动VM.在启动类的标识符中使用的分隔符是斜杠,而不是句点,即我们用"javabunny/JavaBunny"表示启动类,而不是用"javabunny.JavaBunny"的形式。这是因为,FindClass()是一个虚拟机调用,而虚拟机内部用斜杠作为分隔符。随便说明一下,这个例子把启动类的名字(和其他一些配置选项)直接写进了代码之中(称为"硬编码"),对于提供给终用户使用的产品,这种做法有其优点;但对于开发环境来说,这些值拿出来放在某个配置文件中。

    Java程序启动后执行的个方法称为启动方法,通常是main()。本例通过JNI调用GetStaticMethodID()获得启动方法的ID.GetStaticMethodID()要求方法的名字("main")和方法的类型描述符("([Ljava/lang/String;)V")。这个类型描述符表示方法的参数是一个字符串的数组,返回值类型是void.有关类型描述符的更详细的说明,请参见JVM相关资料。注意,从这里可以看出,在使用定制启动器时,Java程序的启动方法不必一定是static void的main方法,可以用任何方法作为Java程序中个执行的方法,甚至包括实例方法或构造函数。

    示例程序中一个需要注意的地方是jvm->DestroyJavaVM()调用。从表面看起来,这个语句似乎是程序执行后进行清理工作的方法,可有可无。其实不然,如果Java程序是多线程的,在调用这个方法时程序仍旧在运行。例如,对于一个运行着的Swing程序,如果它的main方法结束,DestroyJavaVM()的执行将被阻塞,直至所有非守护线程都执行完毕,所以这行代码是必不可少的。如果省略这行代码,则当主线程执行完毕,即使其他线程(例如Swing GUI的事件循环)仍旧在运行,整个程序也会立即退出。

    三、配置和使用

    如前所述,这个启动器以硬编码的方式了启动类的名字,但是没有一个路径是硬编码的。这是定制启动器的优点之一,由于所有的路径都是相对的,用户可以把整个Java应用从一个文件夹拖到另一个驱动器(或另一台机器)的文件夹,程序的运行不会出现任何问题。本文的启动器定JRE总是在应用软件所在目录的一个子目录下,也就是说,JRE应当随同应用软件一起发布。这样做的好处是使得应用软件完全不依赖于用户的环境,确保了JRE与应用程序的兼容姓。即使用户系统中原来已经有JRE,增加一个额外的JRE也只不过稍微占用了一点磁盘空间,但却能有效地保应用软件的稳定姓。

    在某些场合,你可能需要将一些配置参数移出程序,例如放入一个配置文件,特别是在需要频繁改动启动方式的开发阶段。建议移出程序之外的配置选项包括:启动类,类的路径,某些VM参数,例如"-verbose".

    来源:

    :

    展开全文
  • 本文就基于启动类加载器、扩展类加载器、系统类加载器和自定义类加载器来为大家补充一下这方面的知识。 类加载器简介 Java程序被编译器编译之后成为字节码文件(.class文件),当程序需要某个类时,虚拟机便会将对应...

    对于每个开发人员来说,java.lang.ClassNotFoundExcetpion这个异常几乎都遇到过,而追求其该异常的来源的话,就免不了谈一谈Java的类加载器了。本文就基于启动类加载器、扩展类加载器、系统类加载器和自定义类加载器来为大家补充一下这方面的知识。

    类加载器简介

    Java程序被编译器编译之后成为字节码文件(.class文件),当程序需要某个类时,虚拟机便会将对应的class文件进行加载,创建出对应的Class对象。而这个将class文件加载到虚拟机内存的过程,便是类加载。
    在这里插入图片描述

    类加载器负责在运行时将Java类动态加载到JVM(Java虚拟机),是JRE(Java运行时环境)的一部分。由于类加载器的存在,JVM无需了解底层文件或文件系统即可运行Java程序。

    Java类不会一次全部加载到内存中,而是在应用程序需要时才会加载。此时,类加载器负责将类加载到内存中。

    类加载的过程

    类的生命周期通常包括:加载、链接、初始化、使用和卸载。上图中包含了类加载的三个阶段:加载阶段、链接阶段和初始化阶段。如果将这三个阶段再拆分细化包括:加载、验证、准备、解析和初始化。
    在这里插入图片描述

    关于这几个阶段的作用,已经有很多文章在写了,我们就简单概况一下:

    • 加载:通过一个类的完全限定查找类字节码文件,转化为方法区运行时的数据结构,创建一个代表该类的Class对象。
    • 验证:确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。
    • 准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值。不包含被final修饰的static变量,因为它在编译时已经分配了。
    • 解析:将常量池内的符号引用转换为直接引用的过程。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载。
    • 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。

    在上述类加载的过程中,虚拟机内部提供了三种类加载器:启动(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)。

    下面就讨论不同类型的内置类加载器是如何工作,以及介绍如何自定义类加载器。

    内置类加载器

    先从一个简单的例子来看一下如何使用不同的加载器来加载不同的类:

    public void printClassLoaders() {
    
        System.out.println("Classloader of this class:"
            + PrintClassLoader.class.getClassLoader());
    
        System.out.println("Classloader of Logging:"
            + Logging.class.getClassLoader());
    
        System.out.println("Classloader of ArrayList:"
            + ArrayList.class.getClassLoader());
    }
    

    执行上述程序,打印如下内容:

    Classloader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2
    Classloader of Logging:sun.misc.Launcher$ExtClassLoader@2f0e140b
    Classloader of ArrayList:null
    

    上述三行输出分别对应三种不同的类加载器:系统(System)类加载器、扩展(Extension)类加载器和启动(Bootstrap)类加载器(显示为null)。

    系统程序类加载器加载包含示例方法的类,也就是将我们自己的文件加载到类路径中。扩展类加载器加载Logging类,也就是加载作为标准核心Java类扩展的类。启动类加载器加载ArrayList类,是所有其他类的父级。

    对于ArrayList的类加载器,输出为null。这是因为启动类加载器是用本机代码实现而不是Java,因此它不会显示为Java类。启动类加载器在操作在不同的JVM中会有所不同。

    上述三种类加载器,外加自定义类加载器,它们直接的关系可用下图表示:
    在这里插入图片描述

    现在来具体看一下这些类加载器。

    Bootstrap类加载器

    Java类由java.lang.ClassLoader的实例加载。但是,类加载器本身就是类。那么,谁来加载java.lang.ClassLoader?对,就是启动类加载器。

    启动类加载器主要负责加载JDK内部类,通常是rt.jar和$JAVA_HOME/jre/lib目录中的其他核心库。此外,Bootstrap类加载器还充当所有其他ClassLoader实例的父类。

    该启动程序类加载器是Java虚拟机的一部分,用本机代码编写(比如,C++),不同的平台的实现可能有所不同。

    出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类。

    Extension类加载器

    扩展类加载器是启动类加载器的子类,Java语言编写,由sun.misc.Launcher$ExtClassLoader实现,父类加载器为启动类加载器,负责加载标准核心Java类的扩展。

    扩展类加载器从JDK扩展目录(通常是$JAVA_HOME/lib/ext目录)或java.ext.dirs系统属性中指定的任何其他目录进行自动加载。

    系统类加载器

    系统类加载器负责将所有应用程序级类加载到JVM中。它加载在类路径环境变量,-classpath或-cp命令行选项中找到的文件。它是扩展类加载器的子类。

    系统类加载器,也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader,负责加载系统类路径-classpath或-D java.class.path指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。

    类加载器是如何工作的

    类加载器是Java运行时环境的一部分。当JVM请求一个类时,类加载器将尝试定位该类,并使用完全限定名将类定义装入运行时。

    在这里插入图片描述

    java.lang.ClassLoader.loadClass()方法负责将类定义加载到运行时,它尝试通过全限定名来加载类。如果未加载到该类,则它将请求委派给父类加载器。依次向上重复该过程。

    最终,如果父类加载器找不到指定类,则子类将调用java.net.URLClassLoader.findClass()方法在文件系统本身中查找类。

    如果最后一个子类加载器也无法加载该类,则它将抛出java.lang.NoClassDefFoundError或java.lang.ClassNotFoundException。抛出ClassNotFoundException时的输出示例:

    java.lang.ClassNotFoundException: com.baeldung.classloader.SampleClassLoader    
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)    
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)    
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)    
        at java.lang.Class.forName0(Native Method)    
        at java.lang.Class.forName(Class.java:348)
    

    上述过程,通常我们称作双亲委派机制。双亲委派机制要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,请注意双亲委派机制中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码。

    除外,类加载器还具有三个重要功能:委派模型、类的唯一性和可见性。

    委派模型

    类加载器遵循委派模型,在该模型中,根据请求查找类或资源,ClassLoader实例会将对类或资源的搜索委托给父类加载器。

    假设我们有一个将应用程序类加载到JVM中的请求。系统类加载器首先将该类的加载委托给其父扩展类加载器,而父扩展类加载器又将其委托给引导类加载器。

    仅当启动类加载器和扩展类加载器都未能成功加载类时,系统类加载器才会尝试加载类本身。

    类的唯一性

    作为委托模型的结果,很容易确保类的唯一性,因为始终尝试向上委托。如果父类加载器无法找到该类,则只有当前实例自己会尝试进行查找和加载。

    可见性

    此外,子类加载器对其父类加载器加载的类可见。例如,系统类加载器加载的类对扩展和Bootstrap类加载器加载的类具有可见性,反之亦然。

    比如,通过系统类加载器加载类A,而通过扩展类加载器加载类B,则对系统类加载器加载的其他类而言,A和B类都是可见的。但对扩展类加载器加载的其他类而言,类B是唯一可见的类。

    自定义类加载器

    在大多数情况下,如果文件已经在文件系统中,则内置的类加载器就足够了。但是,在需要从本地硬盘驱动器或网络中加载类的情况下,可能需要使用自定义类加载器。下面介绍自定义类加载器的使用。

    自定义类加载器示例

    自定义类加载器不仅对在运行时加载类有帮助,还有一些特殊的场景:

    • 帮助修改现有的字节码,例如weaving agents;
    • 动态创建适合用户需求的类。例如在JDBC中,通过动态类加载完成不同驱动程序实现之间的切换。
    • 在为具有相同名称和程序包的类加载不同的字节码时,实现类版本控制机制。这可以通过URL类加载器(通过URL加载jar)或自定义类加载器来完成。

    举一个更具体的例子,比如,浏览器使用自定义类加载器从网站加载可执行内容。浏览器可以使用单独的类加载器从不同的网页加载applet。用于运行applet的applet查看器包含一个ClassLoader,该类加载器可访问远程服务器上的网站,而无需查看本地文件系统。

    然后通过HTTP加载原始字节码文件,并将其转换为JVM中的类。即使这些applet具有相同的名称,但如果由不同的类加载器加载,它们也被视为不同的组件。

    现在,我们了解了为什么自定义类加载器是相关的,让我们实现ClassLoader的子类来扩展和总结JVM如何加载类的。

    创建自定义类加载器

    自定义类加载器通常通过继承java.lang.ClassLoader类,重写findClass()方法:

    public class CustomClassLoader extends ClassLoader {
    
        @Override
        public Class findClass(String name) throws ClassNotFoundException {
            byte[] b = loadClassFromFile(name);
            return defineClass(name, b, 0, b.length);
        }
    
        private byte[] loadClassFromFile(String fileName)  {
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
                    fileName.replace('.', File.separatorChar) + ".class");
            byte[] buffer;
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            int nextValue = 0;
            try {
                while ( (nextValue = inputStream.read()) != -1 ) {
                    byteStream.write(nextValue);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            buffer = byteStream.toByteArray();
            return buffer;
        }
    }
    

    在上面的示例中,我们定义了一个自定义类加载器,该类加载器扩展了默认类加载器并从指定文件加载字节数组。如果没有太复杂的需求,可以直接继承URLClassLoader类,重写loadClass方法,具体可参考AppClassLoader和ExtClassLoader。

    了解java.lang.ClassLoader

    下面来看看java.lang.ClassLoader类中的一些基本方法,以更清楚地了解其工作方式。

    loadClass方法

    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    

    此方法负责加载给定名称参数的类。name参数为类的全限定名。

    Java虚拟机调用loadClass()方法来解析类引用,并将resolve设置为true。但是,不一定总是要解析一个类。如果只需要确定该类是否存在,则将resolve参数设置为false。

    此方法用作类加载器的入口。我们可以尝试从java.lang.ClassLoader的源代码中了解loadClass()方法的内部工作:

    protected Class<?> loadClass(String name, boolean resolve)
      throws ClassNotFoundException {
        
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                    try {
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                    }
    
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        c = findClass(name);
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    

    该方法的默认实现按以下顺序搜索类:

    • 调用findLoadedClass(String)方法以查看是否已加载该类。
    • 在父类加载器上调用loadClass(String)方法。
    • 调用findClass(String)方法以查找类。

    defineClass方法

    protected final Class<?> defineClass(
      String name, byte[] b, int off, int len) throws ClassFormatError
    

    此方法负责将字节数组转换为类的实例。如果数据不包含有效的类,则会抛出ClassFormatError。 另外,由于此方法被标记为final,因此我们无法覆盖此方法。

    findClass方法

    protected Class<?> findClass(
      String name) throws ClassNotFoundException
    

    此方法查找以标准名称作为参数的类。我们需要在遵循委派模型加载类的自定义类加载器实现中重写此方法。

    另外,如果父类加载器找不到请求的类,则loadClass()会调用此方法。如果没有任何类加载器的父类找到该类,则默认实现会抛出ClassNotFoundException异常。

    getParent方法

    public final ClassLoader getParent()
    

    此方法返回父类加载器以进行委派。某些实现使用null来表示启动类加载器。

    getResource方法

    public URL getResource(String name)
    

    此方法尝试查找具有给定名称的资源。它将首先委托给资源的父类加载器,如果父级为null,则搜索虚拟机内置的类加载器的路径。如果失败,则该方法将调用findResource(String)来查找资源。

    指定为输入的资源名称可以相对于类路径,也可以是相对于绝对路径。

    它返回用于读取资源的URL对象;如果找不到资源或调用者没有足够的特权来返回资源,则返回null。

    需要注意的是,Java是从类路径中加载资源。

    最后,Java中的资源加载被认为是与位置无关的,因为只要设置了环境来查找资源,代码在何处运行都无关紧要。

    上下文类加载器

    通常,上下文类加载器为J2SE中引入的类加载委托方案提供了一种替代方法。JVM中的类加载器遵循分层模型,因此每个类加载器都有一个单独的父类,而启动类加载器除外。但是,有时当JVM核心类需要动态加载应用程序开发人员提供的类或资源时,可能会遇到问题。

    例如,在JNDI中,核心功能由rt.jar中的引导程序类实现。但是这些JNDI类可能会加载由独立供应商实现的JNDI提供程序(部署在应用程序类路径中)。这种情况要求启动类加载器(父类加载器)加载对应程序加载器(子类加载器)可见的类。

    线程上下文类加载器(context class loader)是从JDK 1.2开始引入的。Java.lang.Thread中的方法 getContextClassLoader()和setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java应用运行的初始线程的上下文类加载器是系统类加载器,在线程中运行的代码可以通过此类加载器来加载类和资源。

    线程上下文类加载器从根本解决了一般应用不能违背双亲委派模式的问题,使得java类加载体系显得更灵活。上面所提到的问题正是线程上下文类加载器的拿手好菜。如果不做任何的设置,Java应用的线程上下文类加载器默认就是系统类加载器。因此,在SPI接口的代码中使用线程上下文类加载器,就可以成功的加载到SPI实现的类。

    小结

    类加载器对于执行Java程序是必不可少的。我们先学习了不同类型的类加载器,即Bootstrap类加载器、扩展类加载器和系统类加载器。Bootstrap类加载器充当所有类加载器的父级,负责加载JDK内部类。扩展类加载器和系统类加载器分别从Java扩展目录和类路径加载类。然后,我们学习了类加载器的工作原理、特性,以及如何创建自定义类加载器。

    展开全文
  • ❤️ Android 应用是如何启动的?❤️

    千次阅读 多人点赞 2021-09-30 17:51:59
    作为多年的 Android 开发,写了不少应用,但是一个App到底是怎么启动起来的?你要说桌面点一下就启动了,那也对。但是它的启动过程呢?带着这样的疑问,咱们来一步步学习。 Android 启动过程 一般在任何平台上,...
  • springboot启动流程原理以及bean加载创建
  • ClassLoader.getSystemClassLoader() 系统加载器,即Application ClassLoader,用于加载ClassPath下的,采用双亲委派模型。Thread.currentThread().getContextClassLoader() 线程上下文加载器,用于解决基础...
  • 面试必问的 JVM 加载机制,你懂了吗?

    千次阅读 多人点赞 2021-08-14 14:49:36
    JVM 加载机制高频面试题
  • /** 汽车实体* 里面有属性和方法*/public class Car {String brand; //汽车品牌String modelNumber; //汽车型号String displacement; //汽车排量String speed; //汽车速度String kilometre;//汽车行驶公里//无参...
  • 1.启动页 2.Application启动优化 3.关闭APP优化 4.启动时间分析 5.常用启动分析工具
  • 天正cad启动失败

    千次阅读 2020-12-20 12:57:00
     二-六层平面图 门头立面 造型柱节点图 轴立面图 柱立面大样图 立即下载 等级: 文件 14MB 格式 zip 建筑功能:餐饮 图纸深度:施工图 设计时间:2015 负荷等级:二级,三级 防雷等级:第二 火灾保护分级:...
  • 方式一 :@PostConstruct对要求无,普通的java bean即可例如:/**** 执行完构造方法之后就会执行该方法*/@PostConstructpublic void init() {System.out.println("初始化字典");refresh2();}执行时机实例化之后...
  • 两个设备的显卡驱动都需要更新,其中NVIDIA NVS可以通过驱动精灵之的安装。 Intel的集成显卡,需要自己去intel的官网下载驱动,然后更新驱动,如果直接从驱动精灵安装会报错如下: 无法为此电脑验证正在安装的驱动...
  • 实际部署java应用时,在sun jdk1.6的环境下,为使应用能够高效率长时间稳定运行,需要配置特定的虚拟机参数。每到此时都会想有没有一个基准配置用来供选择,使用基准配置就可以使...默认,服务端模式,启动慢,...
  • 启动子的选择和预测

    千次阅读 2020-12-19 15:50:34
    启动子是位于结构基因5'端上游的DNA序列,能活化RNA聚合酶,使之与模板DNA准确的结合并具有转录起始的特性。常规启动子以二型为主,核心序列包含TATA box和CAAT box,这两部分组成的序列含有基础转录活性,也就是...
  • 金蝶服务器数据库未启动怎么办 内容精选换一换支持。云服务器备份提供数据库备份,兼容性请参考表1。针对不兼容的应用数据库,建议在备份前,暂停所有数据的写操作,再进行备份;如果无法暂停写操作,则可以将应用...
  • 请问如何用Java编写一个汽车Car

    千次阅读 2021-02-28 16:55:56
    输出汽车已启动,并输出汽车的各个属性 System.out.println("汽车已启动,汽车颜色为"+color+",车门数为"+door+",车速为"+speed); } public void speedUp(float speed){ //加速 System.out.println("汽车加速到"+...
  • 启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,然后再根据启动的参数,启动对应的进程组件,这个启动方式就是冷启动启动:当启动应用时,后台已有该应用的进程(例...
  • 安装天正后cad无法启动 - 卡饭网

    千次阅读 2020-12-20 12:56:56
    PE 安装完系统后不能启动怎么办PE 安装完系统后不能启动怎么办 operating system not found 的中文意思是没有找到可用的系统,一般出现这种情况有两种原因: 一、在你的BIOS中,第一启动没有设置成硬盘,而是其他的...
  • Python 的定义及的继承

    千次阅读 2021-01-29 05:24:45
    一,的定义:*面向对象的基础(实现的过程当中实际上就是对现实事物的进一步抽象)*一种类型(的类型)*的实例称之为对象(例如 ‘瓶子’是一类型的东西, ‘桌子上的那个瓶子’,比较具体 是一个对象)*一种...
  • 深入理解JVM虚拟机读书笔记——的加载机制

    千次阅读 多人点赞 2021-08-25 17:59:28
    Java虚拟机把描述的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的加载机制。 1. 加载的时机 一个类型从被加载到虚拟机...
  • Java 抽象与接口 编程练习

    千次阅读 2021-02-28 18:21:42
    具体要求如下:(1)使用接口或者抽象实现基类Employer(体会接口和抽象的不同),包含姓名、部门和工资三个属性,显示工资的方法showSalary()和显示奖金的抽象方法showBonus();提示:因每位职工奖金不同,...
  • RuoYi(若依) 微服务分离版 启动及常见问题总结

    千次阅读 热门讨论 2021-04-25 08:27:43
    152 以上版本) Mysql >= 5.7.0 (推荐5.7版本) Redis >= 3.0 Maven >= 3.0 Node >= 10 nacos >= 1.4.1 (安装的目录勿带中文) 仔细核对上面的环境要求,改好什么后,出现问题,先重启nacos,再把那几个启动类重新启动...
  • 不知道是不是翻译问题,在查阅官方文档的过程中,遇到了一些表述含义接近的名词,个人认为这是对同一个单词的... 有“暖启动”、“热启动”和“冷启动”三种启动模式,本文详述。用户可指定S7-CPU上电时的启动类型。 .
  • 基于Pytorch实现的声音分类

    千次阅读 热门讨论 2021-08-20 20:11:13
    编写一个 CustomDataset,用读取上一步生成的数据列表。 class CustomDataset(Dataset): def __init__(self, data_list_path, model='train', spec_len=128): super(CustomDataset, self).__init__() with open...
  • JVM加载器详解

    千次阅读 2021-01-11 23:12:03
    在上一篇中,通过下面这幅图大致了JVM整体的内部运行结构图,在JVM的结构中,加载子系统作为连接外部class文件与真正将class文件加载到运行时数据区,承担着重要的作用 加载器是什么?有什么作用? 1、负责从...
  • 而UEFI+GPT系统引导文件所在的ESP分区在操作系统中为不可见 3.BIOS+MBR启动要求的活动的主分区不是唯一固定的,可以任意设定某一分区为活动的主分区,然后MBR就可以通过分区表指引操作系统从此分区启动,也就是说,...
  • 加载子系统

    多人点赞 热门讨论 2021-08-05 20:12:36
    加载器分类2.1 自定义与核心类库的加载器2.2 虚拟机自带的加载器2.3 用户自定义加载器3. ClassLoader的常用方法及获取方法3.1 ClassLoader3.2 ClassLoader继承关系3.3 获取ClassLoader的途径4. 双亲委派...
  • 深入理解Java虚拟机之加载机制篇

    千次阅读 2021-10-24 21:02:54
    ​ 虚拟机把描述的数据从 Class 文件加载到内存中,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,就是虚拟机的加载机制。 ​ 在Java语言里面,类型的加载、连接和初始化过程都...
  • Android开机启动广播

    千次阅读 2021-11-10 13:50:55
    Android开机启动广播 理论概述 核心代码 第一章 理论概述 第01节 基础说明 1、开机启动的过程当中, 定义开机启动广播。 2、接收到开机启动广播之后, 可以开启 Service A. 低版本实现 Android8.0 之前的实现 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 571,589
精华内容 228,635
关键字:

启动类要求