广告
返回顶部
首页 > 资讯 > 精选 >如何理解Java中的SPI机制
  • 854
分享到

如何理解Java中的SPI机制

2023-06-15 22:06:47 854人浏览 安东尼
摘要

本篇内容介绍了“如何理解Java中的SPI机制”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!SPI的概念SPI在Java中的全称为Servi

本篇内容介绍了“如何理解Java中的SPI机制”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

SPI的概念

SPI在Java中的全称为Service Provider  Interface,是jdk内置的一种服务提供发现机制,是Java提供的一套用来被第三方实现或者扩展的api,它可以用来启用框架扩展和替换组件。

JAVA SPI = 基于接口的编程+策略模式+配置文件的动态加载机制

SPI的使用场景

Java是一种面向对象语言,虽然Java8开始支持函数式编程和Stream,但是总体来说,还是面向对象的语言。在使用Java进行面向对象开发时,一般会推荐使用基于接口的编程,程序的模块与模块之前不会直接进行实现类的硬编码。而在实际的开发过程中,往往一个接口会有多个实现类,各实现类要么实现的逻辑不同,要么使用的方式不同,还有的就是实现的技术不同。为了使调用方在调用接口的时候,明确的知道自己调用的是接口的哪个实现类,或者说为了实现在模块装配的时候不用在程序里动态指明,这就需要一种服务发现机制。Java中的SPI加载机制能够满足这样的需求,它能够自动寻找某个接口的实现类。

大量的框架使用了Java的SPI技术,如下:

(1)JDBC加载不同类型的数据库驱动 (2)日志门面接口实现类加载,SLF4J加载不同提供商的日志实现类 (3)spring中大量使用了SPI

对servlet3.0规范

对ServletContainerInitializer的实现

自动类型转换Type Conversion SPI(Converter SPI、FORMatter SPI)等

(4)dubbo里面有很多个组件,每个组件在框架中都是以接口的形成抽象出来!具体的实现又分很多种,在程序执行时根据用户的配置来按需取接口的实现

SPI的使用

当服务的提供者,提供了接口的一种实现后,需要在jar包的META-INF/services/目录下,创建一个以接口的名称(包名.接口名的形式)命名的文件,在文件中配置接口的实现类(完整的包名+类名)。

当外部程序通过java.util.ServiceLoader类装载这个接口时,就能够通过该Jar包的META/Services/目录里的配置文件找到具体的实现类名,装载实例化,完成注入。同时,SPI的规范规定了接口的实现类必须有一个无参构造方法。

SPI中查找接口的实现类是通过java.util.ServiceLoader,而在java.util.ServiceLoader类中有一行代码如下:

// 加载具体实现类信息的前缀,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目录下 private static final String PREFIX = "META-INF/services/";

这也就是说,我们必须将接口的配置文件写到Jar包的META/Services/目录下。

SPI实例

这里,给出一个简单的SPI使用实例,演示在Java程序中如何使用SPI动态加载接口的实现类。

注意:实例是基于Java8进行开发的。

1.创建Maven项目

idea中创建Maven项目spi-demo,如下:

如何理解Java中的SPI机制

2.编辑pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="Http://maven.apache.org/POM/4.0.0"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <artifactId>spi-demo</artifactId> <groupId>io.binghe.spi</groupId> <packaging>jar</packaging> <version>1.0.0-SNAPSHOT</version> <modelVersion>4.0.0</modelVersion>  <build>     <plugins>         <plugin>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-maven-plugin</artifactId>         </plugin>         <plugin>             <groupId>org.apache.maven.plugins</groupId>             <artifactId>maven-compiler-plugin</artifactId>             <version>3.6.0</version>             <configuration>                 <source>1.8</source>                 <target>1.8</target>             </configuration>         </plugin>     </plugins> </build>  </project>

3.创建类加载工具类

在io.binghe.spi.loader包下创建MyServiceLoader,MyServiceLoader类中直接调用JDK的ServiceLoader类加载Class。代码如下所示。

package io.binghe.spi.loader;   import java.util.ServiceLoader;    public class MyServiceLoader {            public static <S> ServiceLoader<S> loadAll(final Class<S> clazz) {         return ServiceLoader.load(clazz);     } }

4.创建接口

在io.binghe.spi.service包下创建接口MyService,作为测试接口,接口中只有一个方法,打印传入的字符串信息。代码如下所示:

package io.binghe.spi.service;    public interface MyService {            void print(String info); }

5.创建接口的实现类

(1)创建第一个实现类MyServiceA

在io.binghe.spi.service.impl包下创建MyServiceA类,实现MyService接口。代码如下所示:

package io.binghe.spi.service.impl; import io.binghe.spi.service.MyService;    public class MyServiceA implements MyService {     @Override     public void print(String info) {         System.out.println(MyServiceA.class.getName() + " print " + info);     } }

(2)创建第二个实现类MyServiceB

在io.binghe.spi.service.impl包下创建MyServiceB类,实现MyService接口。代码如下所示:

package io.binghe.spi.service.impl;   import io.binghe.spi.service.MyService;    public class MyServiceB implements MyService {     @Override     public void print(String info) {         System.out.println(MyServiceB.class.getName() + " print " + info);     } }

创建接口文件

在项目的src/main/resources目录下创建META/Services/目录,在目录中创建io.binghe.spi.service.MyService文件,注意:文件必须是接口MyService的全名,之后将实现MyService接口的类配置到文件中,如下所示:

io.binghe.spi.service.impl.MyServiceA io.binghe.spi.service.impl.MyServiceB

7.创建测试类

在项目的io.binghe.spi.main包下创建Main类,该类为测试程序的入口类,提供一个main()方法,在main()方法中调用ServiceLoader类加载MyService接口的实现类。并通过Java8的Stream将结果打印出来,如下所示:

package io.binghe.spi.main;   import io.binghe.spi.loader.MyServiceLoader; import io.binghe.spi.service.MyService;   import java.util.ServiceLoader; import java.util.stream.StreamSupport;    public class Main {       public static void main(String[] args){         ServiceLoader<MyService> loader = MyServiceLoader.loadAll(MyService.class);         StreamSupport.stream(loader.spliterator(), false).forEach(s -> s.print("Hello World"));     } }

8.测试实例

运行Main类中的main()方法,打印出的信息如下所示:

io.binghe.spi.service.impl.MyServiceA print Hello World io.binghe.spi.service.impl.MyServiceB print Hello World  Process finished with exit code 0

通过打印信息可以看出,通过Java SPI机制正确加载出接口的实现类,并调用接口的实现方法。

源码解析

这里,主要是对SPI的加载流程涉及到的java.util.ServiceLoader的源码的解析。

进入java.util.ServiceLoader的源码,可以看到ServiceLoader类实现了java.lang.Iterable接口,如下所示。

public final class ServiceLoader<S>  implements Iterable<S>

说明ServiceLoader类是可以遍历迭代的。

java.util.ServiceLoader类中定义了如下的成员变量:

// 加载具体实现类信息的前缀,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目录下 private static final String PREFIX = "META-INF/services/";  // 需要加载的接口 private final Class<S> service;  // 类加载器,用于加载以接口命名的文件中配置的接口的实现类 private final ClassLoader loader;  // 创建ServiceLoader时采用的访问控制上下文环境 private final AccessControlContext acc;  // 用来缓存已经加载的接口实现类,其中,Key是接口实现类的完整类名,Value为实现类对象 private LinkedHashMap<String,S> providers = new LinkedHashMap<>();  // 用于延迟加载实现类的迭代器 private LazyIterator lookupIterator;

可以看到ServiceLoader类中定义了加载前缀为“META-INF/services/”,所以,接口文件必须要在项目的src/main/resources目录下的META-INF/services/目录下创建。

从MyServiceLoader类调用ServiceLoader.load(clazz)方法进入源码,如下所示:

//根据类的Class对象加载指定的类,返回ServiceLoader对象 public static <S> ServiceLoader<S> load(Class<S> service) {  //获取当前线程的类加载器  ClassLoader cl = Thread.currentThread().getContextClassLoader();  //动态加载指定的类,将类加载到ServiceLoader中  return ServiceLoader.load(service, cl); }

方法中调用了ServiceLoader.load(service, cl)方法,继续跟踪代码,如下所示:

//通过ClassLoader加载指定类的Class,并将返回结果封装到ServiceLoader对象中 public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){  return new ServiceLoader<>(service, loader); }

可以看到ServiceLoader.load(service,  cl)方法中,调用了ServiceLoader类的构造方法,继续跟进代码,如下所示:

//构造ServiceLoader对象 private ServiceLoader(Class<S> svc, ClassLoader cl) {  //如果传入的Class对象为空,则判处空指针异常  service = Objects.requireNonNull(svc, "Service interface cannot be null");  //如果传入的ClassLoader为空,则通过ClassLoader.getSystemClassLoader()获取,否则直接使用传入的ClassLoader  loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;  acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;  reload();

继续跟reload()方法,如下所示。

//重新加载 public void reload() {  //清空保存加载的实现类的LinkedHashMap  providers.clear();  //构造延迟加载的迭代器  lookupIterator = new LazyIterator(service, loader); }

继续跟进懒加载迭代器的构造函数,如下所示。

private LazyIterator(Class<S> service, ClassLoader loader) {  this.service = service;  this.loader = loader; }

可以看到,会将需要加载的接口的Class对象和类加载器赋值给LazyIterator的成员变量。

当我们在程序中迭代获取对象实例时,首先在成员变量providers中查找是否有缓存的实例对象。如果存在则直接返回,否则调用lookupIterator延迟加载迭代器进行加载。

迭代器进行逻辑判断的代码如下所示:

//迭代ServiceLoader的方法 public Iterator<S> iterator() {  return new Iterator<S>() {   //获取保存实现类的LinkedHashMap<String,S>的迭代器   Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();   //判断是否有下一个元素   public boolean hasNext() {    //如果knownProviders存在元素,则直接返回true    if (knownProviders.hasNext())     return true;    //返回延迟加载器是否存在元素    return lookupIterator.hasNext();   }   //获取下一个元素   public S next() {    //如果knownProviders存在元素,则直接获取    if (knownProviders.hasNext())     return knownProviders.next().getValue();    //获取延迟迭代器lookupIterator中的元素    return lookupIterator.next();   }    public void remove() {    throw new UnsupportedOperationException();   }  }; }

LazyIterator加载类的流程如下代码所示

//判断是否拥有下一个实例 private boolean hasNextService() {  //如果拥有下一个实例,直接返回true  if (nextName != null) {   return true;  }  //如果实现类的全名为null  if (configs == null) {   try {    //获取全文件名,文件相对路径+文件名称(包名+接口名)    String fullName = PREFIX + service.getName();    //类加载器为空,则通过ClassLoader.getSystemResources()方法获取    if (loader == null)     configs = ClassLoader.getSystemResources(fullName);    else     //类加载器不为空,则直接通过类加载器获取     configs = loader.getResources(fullName);   } catch (IOException x) {    fail(service, "Error locating configuration files", x);   }  }  while ((pending == null) || !pending.hasNext()) {   //如果configs中没有更过的元素,则直接返回false   if (!configs.hasMoreElements()) {    return false;   }   //解析包结构   pending = parse(service, configs.nextElement());  }  nextName = pending.next();  return true; }  private S nextService() {  if (!hasNextService())   throw new NoSuchElementException();  String cn = nextName;  nextName = null;  Class<?> c = null;  try {   //加载类对象   c = Class.forName(cn, false, loader);  } catch (ClassNotFoundException x) {   fail(service,     "Provider " + cn + " not found");  }  if (!service.isAssignableFrom(c)) {   fail(service,     "Provider " + cn  + " not a subtype");  }  try {   //通过c.newInstance()生成对象实例   S p = service.cast(c.newInstance());   //将生成的对象实例保存到缓存中(LinkedHashMap<String,S>)   providers.put(cn, p);   return p;  } catch (Throwable x) {   fail(service,     "Provider " + cn + " could not be instantiated",     x);  }  throw new Error();          // This cannot happen }  public boolean hasNext() {  if (acc == null) {   return hasNextService();  } else {   PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {    public Boolean run() { return hasNextService(); }   };   return AccessController.doPrivileged(action, acc);  } }  public S next() {  if (acc == null) {   return nextService();  } else {   PrivilegedAction<S> action = new PrivilegedAction<S>() {    public S run() { return nextService(); }   };   return AccessController.doPrivileged(action, acc);  } }

最后,给出整个java.util.ServiceLoader的类,如下所示:

package java.util;  import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedAction;   public final class ServiceLoader<S>  implements Iterable<S> {     // 加载具体实现类信息的前缀,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目录下     private static final String PREFIX = "META-INF/services/";      // 需要加载的接口     private final Class<S> service;          // 类加载器,用于加载以接口命名的文件中配置的接口的实现类     private final ClassLoader loader;          // 创建ServiceLoader时采用的访问控制上下文环境     private final AccessControlContext acc;          // 用来缓存已经加载的接口实现类,其中,Key是接口实现类的完整类名,Value为实现类对象     private LinkedHashMap<String,S> providers = new LinkedHashMap<>();          // 用于延迟加载实现类的迭代器     private LazyIterator lookupIterator;          //重新加载     public void reload() {         //清空保存加载的实现类的LinkedHashMap         providers.clear();         //构造延迟加载的迭代器         lookupIterator = new LazyIterator(service, loader);     }          //构造ServiceLoader对象     private ServiceLoader(Class<S> svc, ClassLoader cl) {         //如果传入的Class对象为空,则判处空指针异常         service = Objects.requireNonNull(svc, "Service interface cannot be null");         //如果传入的ClassLoader为空,则通过ClassLoader.getSystemClassLoader()获取,否则直接使用传入的ClassLoader         loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;         acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;         reload();     }          private static void fail(Class<?> service, String msg, Throwable cause)         throws ServiceConfigurationError     {         throw new ServiceConfigurationError(service.getName() + ": " + msg,                                             cause);     }          private static void fail(Class<?> service, String msg)         throws ServiceConfigurationError     {         throw new ServiceConfigurationError(service.getName() + ": " + msg);     }          private static void fail(Class<?> service, URL u, int line, String msg)         throws ServiceConfigurationError     {         fail(service, u + ":" + line + ": " + msg);     }          // Parse a single line from the given configuration file, adding the name     // on the line to the names list.     //     private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,                           List<String> names)         throws IOException, ServiceConfigurationError     {         String ln = r.readLine();         if (ln == null) {             return -1;         }         int ci = ln.indexOf('#');         if (ci >= 0) ln = ln.substring(0, ci);         ln = ln.trim();         int n = ln.length();         if (n != 0) {             if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))                 fail(service, u, lc, "Illegal configuration-file syntax");             int cp = ln.codePointAt(0);             if (!Character.isJavaIdentifierStart(cp))                 fail(service, u, lc, "Illegal provider-class name: " + ln);             for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {                 cp = ln.codePointAt(i);                 if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))                     fail(service, u, lc, "Illegal provider-class name: " + ln);             }             if (!providers.containsKey(ln) && !names.contains(ln))                 names.add(ln);         }         return lc + 1;     }          private Iterator<String> parse(Class<?> service, URL u)         throws ServiceConfigurationError     {         InputStream in = null;         BufferedReader r = null;         ArrayList<String> names = new ArrayList<>();         try {             in = u.openStream();             r = new BufferedReader(new InputStreamReader(in, "utf-8"));             int lc = 1;             while ((lc = parseLine(service, u, r, lc, names)) >= 0);         } catch (IOException x) {             fail(service, "Error reading configuration file", x);         } finally {             try {                 if (r != null) r.close();                 if (in != null) in.close();             } catch (IOException y) {                 fail(service, "Error closing configuration file", y);             }         }         return names.iterator();     }          // Private inner class implementing fully-lazy provider lookupload     private class LazyIterator         implements Iterator<S>     {              Class<S> service;         ClassLoader loader;         Enumeration<URL> configs = null;         Iterator<String> pending = null;         String nextName = null;              private LazyIterator(Class<S> service, ClassLoader loader) {             this.service = service;             this.loader = loader;         }              //判断是否拥有下一个实例         private boolean hasNextService() {             //如果拥有下一个实例,直接返回true             if (nextName != null) {                 return true;             }             //如果实现类的全名为null             if (configs == null) {                 try {                     //获取全文件名,文件相对路径+文件名称(包名+接口名)                     String fullName = PREFIX + service.getName();                     //类加载器为空,则通过ClassLoader.getSystemResources()方法获取                     if (loader == null)                         configs = ClassLoader.getSystemResources(fullName);                     else                         //类加载器不为空,则直接通过类加载器获取                         configs = loader.getResources(fullName);                 } catch (IOException x) {                     fail(service, "Error locating configuration files", x);                 }             }             while ((pending == null) || !pending.hasNext()) {                 //如果configs中没有更过的元素,则直接返回false                 if (!configs.hasMoreElements()) {                     return false;                 }                 //解析包结构                 pending = parse(service, configs.nextElement());             }             nextName = pending.next();             return true;         }              private S nextService() {             if (!hasNextService())                 throw new NoSuchElementException();             String cn = nextName;             nextName = null;             Class<?> c = null;             try {                 //加载类对象                 c = Class.forName(cn, false, loader);             } catch (ClassNotFoundException x) {                 fail(service,                      "Provider " + cn + " not found");             }             if (!service.isAssignableFrom(c)) {                 fail(service,                      "Provider " + cn  + " not a subtype");             }             try {                 //通过c.newInstance()生成对象实例                 S p = service.cast(c.newInstance());                 //将生成的对象实例保存到缓存中(LinkedHashMap<String,S>)                 providers.put(cn, p);                 return p;             } catch (Throwable x) {                 fail(service,                      "Provider " + cn + " could not be instantiated",                      x);             }             throw new Error();          // This cannot happen         }              public boolean hasNext() {             if (acc == null) {                 return hasNextService();             } else {                 PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {                     public Boolean run() { return hasNextService(); }                 };                 return AccessController.doPrivileged(action, acc);             }         }              public S next() {             if (acc == null) {                 return nextService();             } else {                 PrivilegedAction<S> action = new PrivilegedAction<S>() {                     public S run() { return nextService(); }                 };                 return AccessController.doPrivileged(action, acc);             }         }              public void remove() {             throw new UnsupportedOperationException();         }          }          //迭代ServiceLoader的方法     public Iterator<S> iterator() {         return new Iterator<S>() {             //获取保存实现类的LinkedHashMap<String,S>的迭代器             Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();             //判断是否有下一个元素             public boolean hasNext() {                 //如果knownProviders存在元素,则直接返回true                 if (knownProviders.hasNext())                     return true;                 //返回延迟加载器是否存在元素                 return lookupIterator.hasNext();             }             //获取下一个元素             public S next() {                 //如果knownProviders存在元素,则直接获取                 if (knownProviders.hasNext())                     return knownProviders.next().getValue();                 //获取延迟迭代器lookupIterator中的元素                 return lookupIterator.next();             }                  public void remove() {                 throw new UnsupportedOperationException();             }              };     }          //通过ClassLoader加载指定类的Class,并将返回结果封装到ServiceLoader对象中     public static <S> ServiceLoader<S> load(Class<S> service,                                             ClassLoader loader)     {         return new ServiceLoader<>(service, loader);     }          //根据类的Class对象加载指定的类,返回ServiceLoader对象     public static <S> ServiceLoader<S> load(Class<S> service) {         //获取当前线程的类加载器         ClassLoader cl = Thread.currentThread().getContextClassLoader();         //动态加载指定的类,将类加载到ServiceLoader中         return ServiceLoader.load(service, cl);     }          public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {         ClassLoader cl = ClassLoader.getSystemClassLoader();         ClassLoader prev = null;         while (cl != null) {             prev = cl;             cl = cl.getParent();         }         return ServiceLoader.load(service, prev);     }               public String toString() {         return "java.util.ServiceLoader[" + service.getName() + "]";     } }

SPI总结

最后,对Java提供的SPI机制进行简单的总结

优点:

能够实现项目解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

缺点:

  • 多个并发多线程使用ServiceLoader类的实例是不安全

  • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。

“如何理解Java中的SPI机制”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

--结束END--

本文标题: 如何理解Java中的SPI机制

本文链接: https://www.lsjlt.com/news/281813.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何理解Java中的SPI机制
    本篇内容介绍了“如何理解Java中的SPI机制”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!SPI的概念SPI在Java中的全称为Servi...
    99+
    2023-06-15
  • Java和Dubbo的SPI机制原理解析
    SPI: 简单理解就是,你一个接口有多种实现,然后在代码运行时候,具体选用那个实现,这时候我们就可以通过一些特定的方式来告诉程序寻用那个实现类,这就是SPI。 JAVA的SPI 全称...
    99+
    2022-11-11
  • 一文带你了解Java中的SPI机制
    目录1: SPI机制简介2: SPI原理3: 使用场景4: 源码论证5: 实战6: 优缺点6.1 优点6.2 缺点1: SPI机制简介 SPI 全称是 ...
    99+
    2023-05-15
    Java SPI机制原理 Java SPI机制使用 Java SPI机制
  • Java中的SPI机制是什么
    这篇文章主要介绍“Java中的SPI机制是什么”,在日常操作中,相信很多人在Java中的SPI机制是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中的SPI机制是什么”的疑惑有所帮助!接下来,请跟...
    99+
    2023-07-05
  • Java中的SPI机制案例分享
    目录1 简单介绍2 SPI 案例3 SPI 的原理剖析1 简单介绍 当我们封装了一套接口,其它项目想要调用我们的接口只需要引入我们写好的包,但是其它项目如果想要对我们的接口进行扩展,...
    99+
    2022-11-13
  • Java中的SPI机制有哪些作用
    Java中的SPI机制有哪些作用?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Java的SPI机制实例详解SPI的全名为Service Provider Interface.普...
    99+
    2023-05-31
    java spi机制
  • spring-boot中的SPI机制实例讲解
    一、从java类加载机制说起 java中的类加载器负载加载来自文件系统、网络或者其他来源的类文件。jvm的类加载器默认使用的是双亲委派模式。三种默认的类加载器Bootstrap Cl...
    99+
    2022-11-12
  • Apache Dubbo的SPI机制是如何实现的
    目录一、SPI1.1 JDK自带SPI实现 1.2 Dubbo SPI 二、加载-ExtensionLoader 2.1 获取ExtensionLoader的实例 2.2 加载扩展类...
    99+
    2022-11-12
  • 如何理解Java代理机制
    本篇内容主要讲解“如何理解Java代理机制”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何理解Java代理机制”吧!动态代理中是如何使用反射的全文脉络思维导图如下: 常规编码方式在学习代理之前...
    99+
    2023-06-15
  • 如何理解Java回调机制
    本篇内容介绍了“如何理解Java回调机制”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!系统调用的分类应用系统模块之间的调用,通常分为:同步调...
    99+
    2023-06-15
  • 如何理解AJPFX中面向对象及java的机制
    本篇文章为大家展示了如何理解AJPFX中面向对象及java的机制,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。变量的作用域和生命周期什么关系?2.java除了在类体和方法体(包括参数)中可以声明变量...
    99+
    2023-06-02
  • java中注解机制及其原理的详解
    java中注解机制及其原理的详解什么是注解注解也叫元数据,例如我们常见的@Override和@Deprecated,注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。它主要...
    99+
    2023-05-31
    java 注解机制 ava
  • 如何理解Java常见知识点中的分派机制
    如何理解Java常见知识点中的分派机制,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。在Java中,符合“编译时可知,运行时不可变”这个要求的方法主要是静态方法和私有方法。这...
    99+
    2023-06-05
  • Java CAS机制的一些理解
    目录多线程实践什么是CAS机制为何AtomicInteger线程安全图解CAS机制ABA问题什么是ABA问题有什么影响解决总结多线程实践 public class test { ...
    99+
    2022-11-12
  • Java 详解异常的处理机制
    目录1.异常概述与异常体系结构1.1异常概述1.2运行时异常与编译时异常1.3异常体系结构2.常见异常1.ArrayIndexOutOfBoundsException2.NullPo...
    99+
    2022-11-12
  • java 代理机制的实例详解
    java 代理机制的实例详解前言: java代理分静态代理和动态代理,动态代理有jdk代理和cglib代理两种,在运行时生成新的子类class文件。本文主要练习下动态代理,代码用于备忘。对于代理的原理和机制,网上有很多写的很好的,...
    99+
    2023-05-31
    java 代理机制 ava
  • 如何理解mysql的锁机制
    本篇文章为大家展示了如何理解mysql的锁机制,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。 锁0.1 锁机制当前MySQL已经支持 ISAM, MyIS...
    99+
    2022-10-19
  • Java虚拟机中的双亲委派机制怎么理解
    Java虚拟机中的双亲委派机制怎么理解,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类...
    99+
    2023-06-22
  • Java中的反射机制详解
    目录一、什么是反射?二、为什么要用反射三、Class类四、获取Class类对象的四种方式五.通过反射构造一个类的实例①使用Class.newInstance②通过反射先获取构造方法再...
    99+
    2022-11-12
  • 详解Java的Proxy动态代理机制
    目录一、Jvm加载对象二、代理模式2.1、基本描述2.2、静动态模式三、静态代理四、动态代理4.1、场景描述4.2、基础API案例4.3、代理类结构4.4、JDK源码五、源代码地址一...
    99+
    2022-11-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作