当前位置: 首页 > 后端技术 > Java

JDK的SPI机制

时间:2023-04-01 13:31:34 Java

SPI概念介绍SPI是ServiceProviderInterface的缩写,泛指厂商实现并部署在应用程序ClassPath下的服务提供者接口。ServiceLoader类ServiceLoader是JDK6中提供的一种SPI实现方案。下面通过JDBC编程的步骤来分析ServiceLoader的内部机制。JDBC编程一般有五个步骤:1.执行数据库驱动类加载Class.forName("com.mysql.jdbc.driver")2.创建数据库连接DriverManager.getConnection(url,user,password)3.创建SQL语句Connection#创建语句();4.执行SQL语句,处理结果集Statement#executeQuery()5.释放资源ResultSet#close()Statement#close()Connection#close()上面第二步创建数据库连接使用ServiceLoader来实现加载数据库驱动类。相关开发步骤及源码分析如下:第一步,定义服务接口publicinterfaceDriver{Connectionconnect(Stringurl,java.util.Propertiesinfo)throwsSQLException;}第二步,实现服务接口databasevendorto提供一个或多个实现Driver接口的驱动实现类。下面以mysql为例:}static{try{DriverManager.registerDriver(newDriver());}catch(SQLExceptionvar1){thrownewRuntimeException("无法注册驱动程序!");}}}第三步,将实现类注册到配置文件中在项目目录java的同级目录下新建目录resources/META-INF/services,并在其下新建配置文件java.sql.Driverservices目录(文件名是服务接口的全限定名),文件中的每一行都是实现类的全限定名。第四步com.mysql.cj.jdbc.Driver,(user)loadingservice服务加载在DriverManager类中实现,进入DriverManager类,静态初始化block代码。静态{loadInitialDrivers();println("JDBCDriverManagerinitialized");}进入loadInitialDrivers()方法,内部使用了ServiceLoader。privatestaticvoidloadInitialDrivers(){字符串驱动程序;try{drivers=AccessController.doPrivileged(newPrivilegedAction(){publicStringrun(){returnSystem.getProperty("jdbc.drivers");}});}catch(Exceptionex){drivers=null;}//如果驱动被打包为ServiceProvider,则加载它。//通过类加载器获取所有驱动程序//作为java.sql.Driver.class服务公开。//ServiceLoader.load()替换sun.misc.Providers()AccessController.doPrivileged(newPrivilegedAction(){publicVoidrun(){ServiceLoaderloadedDrivers=ServiceLoader.load(Driver.class);迭代器driversIterator=loadedDrivers.iterator();/*加载这些驱动程序,以便它们可以被实例化。*可能是驱动类可能不是那里*即可能有一个带有服务类的打包驱动程序*作为java.sql.Driver的实现,但实际类*可能丢失。在这种情况下,VM将在运行时尝试定位*和加载服务时抛出java.util.ServiceConfigurationError*。**添加一个trycatch块来捕获那些运行时错误*如果驱动程序在类路径中不可用但它*打包为服务并且该服务在类路径中。*/try{while(driversIterator.hasNext()){driversIterator.next();}}catch(Throwablet){//什么都不做}returnnull;}});println("DriverManager.initialize:jdbc.drivers="+drivers);if(drivers==null||drivers.equals("")){返回;}String[]driversList=drivers.split(":");println("司机人数:"+driversList.length);for(StringaDriver:driversList){try{println("DriverManager.Initialize:loading"+aDriver);Class.forName(aDriver,true,ClassLoader.getSystemClassLoader());}catch(Exceptionex){println("DriverManager.Initialize:loadfailed:"+ex);}}}进入ServiceLoader的load()方法,这里获取线程上下文类加载器ContextClassLoader,默认为应用类加载器,可以找到应用中的类进行加载publicstaticServiceLoaderload(Classservice){ClassLoadercl=Thread.currentThread().getContextClassLoader();returnServiceLoader.load(service,cl);}继续进入ServiceLoader的重载load()方法,调用ServiceLoader的构造函数privateServiceLoader(Classsvc,ClassLoadercl){service=Objects.requireNonNull(svc,"Service接口不能为空");装载机=(cl==null)?类加载器.getSystemClassLoader():cl;acc=(System.getSecurityManager()!=null)?AccessController.getContext():空;reload();}在reload()方法中,首先清除providers,然后构造一个LazyIterator。LazyIterator是在ServiceLoader内部实现的惰性迭代器。这个迭代器实际上会在遍历的时候加载资源。publicvoidreload(){providers.clear();lookupIterator=newLazyIterator(service,loader);}下面看LazyIterator的hasNextService()方法实现privatebooleanhasNextService(){if(nextName!=null){returntrue;}if(configs==null){try{StringfullName=PREFIX+service.getName();if(loader==null)configs=ClassLoader.getSystemResources(fullName);否则configs=loader.getResources(fullName);}catch(IOExceptionx){fail(service,"错误定位配置文件",x);}}while((pending==null)||!pending.hasNext()){if(!configs.hasMoreElements()){returnfalse;}pending=parse(service,configs.nextElement());}nextName=pending.next();returntrue;}通过PREFIX+service.getName()构建出了fullName,PREFIX的定义如下:privatestaticfinalStringPREFIX="META-INf/services/";service.getName()返回的是java.sql.Driver,所以fullName的值为META-INF/services/java.sql.Driver,是配置文件的路径,里面配置了厂商的数据库驱动实现类,例如:com.mysql.cj.jdbc.Driver,nextName的值为com.mysql.cj.jdbc.Driver。驱动实现类的实际实例化在nextService()方法中privateSnextService(){if(!hasNextService())thrownewNoSuchElementException();Stringcn=nextName;下一个名字=空;类c=null;try{c=Class.forName(cn,false,loader);}catch(ClassNotFoundExceptionx){fail(service,"Provider"+cn+"notfound");}if(!service.isAssignableFrom(c)){fail(service,"Provider"+cn+"不是子类型");}尝试{Sp=service.cast(c.newInstance());providers.put(cn,p);返回p;}catch(Throwablex){fail(service,"Provider"+cn+"无法实例化",x);抛出新的错误();//这不可能发生}这里是通过nextName(数据库驱动类的全限定名)获取Class对象,然后进行isAssignableFrom校验。验证通过后,通过反射机制实例化一个实例,并缓存在providers中。键是数据库驱动类的完全限定名,值是对应的实例化对象。providers的定义是一个LinkedHashMap。privateLinkedHashMapproviders=newLinkedHashMap<>();此时,数据库驱动类的所有实例都缓存在ServiceLoader类的提供者中。ServiceLoader实现了Iterable接口,可以通过遍历ServiceLoader在providers中查找数据库驱动类实例。数据库驱动类已经加载,驱动类的实例对象已经生成。建立连接的动作是DriverManager中的getConnection方法。源代码如下://Worker方法由公共getConnection()方法调用。privatestaticConnectiongetConnection(Stringurl,java.util.Propertiesinfo,Classcaller)throwsSQLException{/**当callerCl为null时,我们应该检查应用程序的*(间接调用此类)*类加载器,所以rt.jar*之外的JDBC驱动程序类可以从这里加载。*/ClassLoadercallerCL=caller!=null?调用者.getClassLoader():null;synchronized(DriverManager.class){//同步加载正确的类加载器。if(callerCL==null){callerCL=Thread.currentThread().getContextClassLoader();}}if(url==null){thrownewSQLException("Theurlcannotbenull","08001");}println("DriverManager.getConnection(\""+url+"\")");//遍历尝试建立连接的已加载注册驱动程序。//记住第一个引发的异常,这样我们就可以重新引发它。SQLException原因=null;for(DriverInfoaDriver:registeredDrivers){//如果调用者没有加载驱动程序的权限,则//跳过它。if(isDriverAllowed(aDriver.driver,callerCL)){try{println("trying"+aDriver.driver.getClass().getName());连接con=aDriver.driver.connect(url,info);if(con!=null){//成功!println("getConnection返回"+aDriver.driver.getClass().getName());返回(con);}}赶上(SQLExceptionex){if(reason==null){reason=ex;}}}else{println("skipping:"+aDriver.getClass().getName());}}//如果我们到了这里,没有人可以连接。if(reason!=null){println("getConnectionfailed:"+reason);抛出原因;}println("getConnection:没有找到适合"+url的驱动程序);thrownewSQLException("没有找到合适的驱动程序"+url,"08001");getConnection方法主要是遍历registeredDrivers,通过注册的Drivers建立连接。registeredDrivers中的Driver是什么时候创建并注册到registeredDrivers中的?以mysql为例,上面提到的com.mysql.cj.jdbc.Driver类的代码如下:DriverManager.registerDriver(新驱动程序());}catch(SQLExceptionvar1){thrownewRuntimeException("无法注册驱动程序!");}}}静态代码块会在Driver类加载的初始化阶段被调用,其自身的实例对象注册到registeredDrivers中。在JDBCSPI加载过程中,ServiceLoader的作用可以简单概括为数据区驱动类的加载(将驱动类实例对象注册到registeredDrivers)和实例对象的缓存。SPI加载机制是一种接口与实现分离的设计思想,可以通过简单的改变配置(例如:更换数据库驱动类)轻松实现多个具体实现之间的切换,这也体现了一种配置大于配置的思想编码。