java类加载: SPI机制及反射原理

java类加载: SPI机制及反射原理

java SPI、反射原理:是干什么的?解决了什么问题?

SPI介绍(待更新):

SPI:(Service Provider Interface, 是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制,比如有个接口,想运行时动态的给它添加实现,则只需要给它添加个实现,比如经常遇到的就是java.sql.Driver接口,)
    主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对统一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。
    当服务提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的是实现的工具类是:java.util.ServiceLoader。
  SPI & API:(当接口属于调用方,即我们自己定义的代码时,我们就将其称为SPI,全称为Service Provider Interface,SPI的规则如下)
    1.概念上更依赖调用方
    2.组织上位于调用方所在的包中
    3.实现位于独立的包中(也可以认为在提供方中)
  SPI的用途:(做什么的)
    服务提供发现机制;
    完成指定模块的查找(加载类)、实例化

  SPI解决了什么问题:
    使用特定的接口实现时,不用修改现有的代码,只是通过一个简单的配置就可以达到效果。(如DriverManager的作用:传统做法需要使用Class.forName("**.Driver")来完成指定驱动实现类的加载,现在只需要在/resource/META-INF/service/全限定文件 中指定要使用的指定驱动实现类的类名即可完成类的加载)

  JDK SPI需要遵循的规范:
    需要一个目录:
      META/service
      放到ClassPath下面
    目录下面放置一个配置文件:
      文件名是要扩展的接口全名
      文件内部为要实现的接口实现类
      文件必须为UTF-8编码
    如何使用:
      ServiceLoad.load(xx.class) (xx.class为接口名称,如下:)
      ServiceLoad<HelloInterface> loads = ServiceLoad.load(HelloInterface.class)

    总结:
      线程上下文类加载器:当高层提供了统一接口让底层去实现,同时又要是在高层加载(或实例化)底层的类时,必须通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。
      当使用本类托管类加载,然而加载本类的ClassLoader未知时,为了隔离不同的调用者,可以取调用者各自的线程上下文类加载其代为托管。
      getContextClassLoader()和setContextClassLoader(ClassLoader cl)分别用来获取和设置线程上下文类加载器,如果没有通过setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java应用运行的初始线程的上下文类加载器是系统类加载器。
      loadInitialDrivers() -> ServiceLoader.load() -> getContextClassloader() -> load(Driver.class, getContextClassloader()) -> ServiceLoader(Driver.class, getContextClassloader()) -> reload() -> 

反射原理

  Class类型信息:(Java中使用类java.lang.Class来指向一个类型信息,获取一个Class对象的方法)
    类名.class (如Object.class、Integer.class等)
    getClass方法:(Object类有一个方法 public final native Class<?> getClass();如:Integer integer = new Integer(12); integer.getClass();)
    forName方法:()
  反射字段属性:Class中有关获取字段属性的方法有:
    public Field[] getFields(): 返回该类型的所有public修饰的属性,包括父类的
    public Field[] getFields(String name): 根据字段名称返回相应的字段
    ...
    (如:
    Class<People> cls = People.class;
    Field name = cls.getField("name");
    People people = new People();
    name.set(people, "hello");
    System.out.println(people.name);)
  反射方法:
    public Method[] getMethods(): 返回所有的public方法,包括父类中的
    public Method[] getMethods(String name, Class<?>...parameterTypes): 返回指定的方法
    ...
    (如:
    Class<People> cls = People.class;
    Method syaHello = cls.getMethod("sayHello");
    People people = new People();
    sayHello.invoke(people);)
  反射构造器:
    public Constructor<?> [] getConstructors(): 返回所有public 修饰的构造器
    public Constructor<?> [] getDeclaredConstructors(): 返回所有的构造器,无视访问修饰符
    ...
  反射与数组、泛型:
    public native Class<?> getComponentType();
    public static Object newInstance(Class<?> componentType, int length)
    ...

  反射是干什么的:
    能够在程序运行时动态的创建对象等操作;

  反射解决了什么问题:(使用场景: 如果一个程序对象是自己new的,程序相当于写死了给jvm去跑。假如一个服务器上突然遇到某个请求需要用到某个类,但是没有加载进jvm,那是不是要停下来自己写段代码,new一下,再启动服务器。 显然事情不应该是这个样子的)
    当程序在运行时,需要动态的加载一些类,这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载。

  <font color="#660000">反射原理:</font>
    java程序是怎么运行的:JVM启动后,代码就会被编译成.class文件,然后被类加载器加载进JVM内存中,在JVM内存中,方法区存放的是代码中所有的类的信息;堆中存放的是方法区中的类对应的Class对象,这样在代码执行的过程中,当遇到需要新建类对象的时候,就通过Class对象新建该类对应的对象。(Java栈、本地栈用来存放对象中的变量、运算符以及静态变量方法等数据)
    那么反射主要是负责程序运行时动态的加载类与创建对象,所以在java程序运行的基础上,通过classForName()等方法就可以动态加载类与创建对象了。

问题思考:
    为什么要使用SPI,Class.forName("com.mysql.jdbc.Driver")来加载驱动不好吗?(即其好处是什么)

相关拓展

  双亲委托模型:
    ClassLoader使用的是双亲委托模型来搜索类,每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图在亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下一次检查的,首先由最顶层的类加载器BootStrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader进行加载,如果也没加载到,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存中,最后返回这个类在内存中的Class实例对象。
  JVM在搜索类的时候,是如何判定两个class是相同的:
    不仅要判断两个类名是否相同,而且也要判断是否是同一个类加载器实例加载的。
  ClassLoader的体系架构:
    1.自底向上检查类是否已经加载
    2.自顶向下尝试加载类
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaAgent、Javassist、SPI机制Java反射Java开发中的四个概念,它们在功能和应用场景上有一些区别。 1. JavaAgent: - JavaAgent是Java虚拟机(JVM)提供的一个机制,允许在程序运行时对字节码进行修改和增强。 - 主要应用于性能监控、代码热替换、AOP(面向切面编程)等方面。 - 通过JavaAgent,开发者可以在应用程序运行期间动态地修改已加载的类或增加新的类,从而实现对程序行为的改变。 2. Javassist: - Javassist是一个开源的Java字节码操作库,提供了一组简单易用的API,用于在运行时修改字节码。 - 可以通过Javassist来实现类似于JavaAgent的功能,包括动态生成类、修改现有类的方法、字段等。 - Javassist提供了更高层次的抽象和更加易用的API,使得字节码操作更加简单和灵活。 3. SPI(Service Provider Interface)机制: - SPI是一种Java的扩展机制,用于在运行时动态地发现和加载实现某个接口或抽象类的服务提供者。 - 主要应用于插件化开发,允许开发者通过配置文件或其他方式定义服务提供者,使得程序在运行时可以动态地加载和使用这些服务。 - SPI机制的核心是通过Java反射机制来实现动态加载和调用。 4. Java反射: - Java反射Java语言提供的一种机制,允许在运行时动态地检查类、实例化对象和调用对象的方法或字段。 - 反射可以使得开发者在运行时获取类的信息并进行操作,而不需要在编译时明确知道类的具体细节。 - Java反射可以用于动态加载类、实现框架和工具、进行代码生成等。 综上所述,JavaAgent是JVM提供的机制Javassist是一个方便进行字节码操作的库,SPI机制是一种动态发现和加载服务提供者的机制Java反射Java语言提供的一种机制。它们各自在功能和应用场景上有所不同。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值