探究服务提供者框架,分析JDBC实现

Jun 3, 2016


简介

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与这两个模式的思想相近.