简介
1.什么是服务提供者框架?
Effective Java中对SPF的介绍是这样的:
- 服务者提供者框架是指这样的一个系统:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来.
从这句话中我们可以推出SPF的3个组件:
-
1.服务接口(服务提供者是提供服务给客户端的,所以服务接口是必须的.)
-
2.服务提供者接口(既然有了服务接口当然需要有东西来实现它了(当然在书中提到服务提供者接口不是必须的,我们可以通过反射的方法来实现服务进行服务注册))
-
3.服务访问API(服务有了,有服务提供商提供了服务实现,但是我们如何在客户端如何调用所需要的服务呢?所有就必须需要服务访问API了)
其实除了1.2.3组件,还有一个不可缺少的组件为:
- 4.提供者注册API;
- 试想一下Oracle公司在Java9发布了一个服务接口名为(AnotherService),现在有许多的服务提供者(比如Mysql,DB2等公司)针对这个服务提供了实现(在其官网上发布了实现Jar包),我们程序员现在需要使用到这个服务来完成项目,得到了Jar包,Oracle也提供了客户端访问API(使用AnotherService服务的人都是客户端),我们在自己的程序中调用访问API拿到服务提供者的实现服务.但是我们想下这个过程,是不是缺少了一个重要的一步:
- 就是Oracle发布的服务接口,Mysql针对这个服务做了具体实现,但是Oracle如何将Mysql这个实现与服务接口关联?也就是要解决我们调用服务访问API能够拿到Mysql的服务实现,我们已经导入了Mysql的实现Jar包,这只是意味着有个服务提供者类(即有了服务实现类),我们还要将这个服务的具体实现注册到服务管理器中(即就是让程序员可以访问)
所以综上SPF的主要构成:
- 1.服务接口(Service Interface)
- 2.服务提供者接口(Service Provider Interface)
- 3.服务访问API(Service Access Interface)
- 4.服务注册API(Service Registration Interface)
最后,在究竟什么是SPF这个问题上我的理解就是:
做一个接口(让别人去实现),不管别人是怎样实现,做一个注册API去加载别人的实现(只是注册,只需拿到别人的实现类,不需要知道别人是如何实现),做一个接口是拿到别人的实现(如没有注册别人的实现,则报出错误信息);
2.为什么需要服务提供者框架(SPF)?
- 引用Effective Java中的一句话:系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来.
那这句话是什么意思呢?下面我用一个经典(我认为算是经典)的一个例子:JDBC
1.首先我们先思考我们是如何使用JDBC来操作数据库的(以Mysql为例,其他都一样):
- 1.获取mysql-connector-java-x.x.x-bin.jar包
- 2.加载数据库驱动Class.forName(“com.mysql.jdbc.Driver”);
- 3.获取数据库连接Connection conn = DriverManager.getConnection(url(jdbc:mysql://localhost:3306/dataBaseName/),user,password);
- 4.拿到数据库连接之后就可以对数据库进行基本操作了
- 5.通过数据源(DataSource)拿连接的过程,与这不一样,但是数据源的底层实现中还是必须通过DriverManager来拿到连接;
2.分析1.2操作:Class.forName(“com.mysql.jdbc.Driver”);
实际功能是加载mysql数据库驱动,Class.forName(className)是以全限定的类名获得这个类的Class对象,所以我们可以知道获取到了com.mysql.jdbc.Driver类的Class对象,但是只是这样貌似并没有完成加载驱动这个功能,不能忽略一点:Class.forName(className)方法会执行加载类的静态块(static)块
我们可以在官网下载mysql-connect-java-x.x.x.zip源码包.打开Driver类源码可以看到:
package.com.mysql.jdbc;
public class Driver extends NonRegisteringDriver
implements java.sql.Driver{
/**
*静态块,static块在什么时候运行呢?在JVM中,一个类的运行分为几个动作:
*1.加载 2.链接 3.初始化 链接又分为三部分(请关注下一篇博文ClassLoader)
*静态块在类初始化阶段执行,jvm主要完成静态变量初始化,静态块执行.
*第一次Class.forName(className)相当于Class.forName(className,true
* ,this.getClass().getClassLoader());
*true参数代表装载类的过程中初始化,反之false就代表不初始化
*/
static{
try{
java.sql.DriverManager.registerDriver(new Driver());
}catch(SQLException e){
throw new RuntimeException("Can't"register Driver);
}
}
/**
*重写构造方法
*/
public Driver() throws SQLException{
}
}
/*思考:那com.mysql.jdbc.Driver则是服务提供者接口实现,java.sql.Driver则是服务提供者接口.DriverManager则是注册API,遗漏了重要一步:
在java.sql.Driver接口中有个最重要的方法:Conncetion connect(String url,Properties info)//服务提供者提供服务的方法;在
Mysql中实现这个方法的在NonRegisteringDriver类中;
*/
下一步我们看看Mysql是如何对Connection服务接口进行包装实现的,首先找到Connection:
package com.mysql.jdbc;
//数据库连接接口,继承了JDK中的Connection,和mysql中的连接属性接口
public interface Connection extends java.sql.Connection,ConnectionProperties{ ... }
//数据库连接属性字段接口(提供了诸多getXXX方法)
public interface ConnectionProperties{ ..... }
//mysql数据库连接接口(多层包装,Connection中有诸多方法,而Mysql对于自己的连接又想添加诸多方法,就用多层包装的方式来进行解决这个问题)
public interface MySQLConnection extends Connection,ConnectionProperties{ ...... }
//真正的实现类(想深究的请去研究其实现源码)
public class ConnectionImpl extends ConnectionPropertiesImpl implements MySQLConection{ ...... }
最后一步探究DriverManager.registerDriver(new Driver);就是加载Mysql驱动过程 DriverManager由于是JDK实现的类:
package java.sql;
//驱动管理类(不知道怎么解释额!)
public class DriverManager{
//从这个定义我们知道,DriverInfo是对Driver的包装内部类,这里包含了类型推断,这在java8的新特性比较重要!!!
//registeredDrivers存放注册的驱动
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
....//诸多代码就不贴出来了;
//加载初始化驱动
static{
//自己去看源码,我就不贴咯
loadInitialDrivers();
......
}
...
public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException{
registerDriver(driver,null);
}
public static synchronized void registerDriver(java.sql.Driver driver,DriverAction da)throws SQLException {
//将驱动加载到registeredDrivers集合中
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
}
3.最后探究DriverManager.getConnection(String url,String user,String password)实现(即服务访问API如何实现),方法全都包含在DriverManager类中;
//为了省些篇幅,我就简写了
public static Connection getConnction(String url,String user,String password){
//将user,password封装成Properties对象
Properties info = new Properties();
info.put(...,....);
//核心方法
return (getConnction(url,info,Reflection.getCallerClass()));
}
private static Connection getConnection(String url,Properties info,Class<?> caller){
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class){
if(callerCL == null){
//若为空,则拿当前线程的上下文类加载器(这是Java这一方面不太优雅的设计,详情请关注下一篇博文<深入理解ClassLoader机制>)
callerCL = Thread.currentThread().getContextClassLoader();
}
}
......
//遍历注册驱动集合中所有的驱动类
for(DriverInfo aDriver : registeredDrivers) {
//判断这个驱动是否已经加载!
if(isDriverAllowed(aDriver.driver,callerCL)){
....
//拿到服务提供者实现的驱动类调用connect方法得到数据库连接
Connection conn = aDriver.driver.connect(url,info);
....
}
}
}
//思考:实际上就是Driver接口,Connection接口,DriverManager类,全部由JDK提供,Mysql等其他数据库商提供服务的实现:
//MySQLConnection,DB2SQLConncetion,OracleConncetion等等,并提供Driver类,实现注册驱动类.最后通过
//DriverManager.getConnection()拿到数据库提供商的数据库连接;这就是JDBC的原理,也就是服务提供者框架的原理;
4.解决一个问题:为什么需要用SPF来实现JDBC?
- 还是这句话:系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来.试想一下:如果不使用SPF来实现JDBC,数据库商有那么多的公司:Mysql,Oracle,DB2,NoSql数据库….等等,一个极端就是JDK将这些公司所有的数据库连接,驱动全部统一实现,全部加载到JDK中,可是这样会造成:
- 1.万一之后又出现了一种新的数据库(现在比较流行的NoSql),而你却没有提供实现,违背了可扩展性;
- 2.如果这样去阅读或者维护代码,效率很低,代码十分臃肿;
如果我遇到这个问题,基本的想法是抽象,如何抽象:
Mysql,Oracle等数据库商都是提供数据库连接,所以我们提供一个抽象的数据库连接接口(Connection),每个数据库的连接接口不一样,那么我们如何获取数据库连接呢,所以需要一个公开的数据库驱动(Driver接口),提供了驱动接口,可以拿到连接了,可是每一个公司的驱动实现不一样,我们首先想到去为每一个公司写驱动实现,之后我们发现如果将驱动实现抽离出来,写一个驱动管理类,实现驱动加载,获取连接功能;而驱动的实现我们可以自己编写自己的驱动实现(但是我们又不是做数据库的,不会怎么办),所以每一个数据库公司都会提供这样的一个驱动程序包,所以这就真正的实现了将服务,服务实现解耦出来.
设计模式中的桥接模式(Bridge模式)的意图就是:将抽象部分与实现部分分离,使它们都可以独立的变化.
- 设计模式中的策略模式(Strategy模式)的意图是:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。
- 策略模式让算法独立于使用它的客户而独立变化;
最后:SPF终于深入完了,下一篇就开始介绍:深究JVM中的类加载机制;
PS:附带提了2个设计模式,只是鼓励自己去理解,因为SPF与这两个模式的思想相近.