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")来加载驱动不好吗?(即其好处是什么)
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值