从哪里来
以java 最原始的jdbc编程开始
private static Connection getConn() {
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:33066/mysql";
String username = "root";
String password = "root";
Connection conn = null;
try {
Class.forName(driver); //classLoader,加载对应驱动
conn = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
这是一段jdbc连接mysql数据库的代码.
但是如果把Class.forName(driver);
这行注释掉其实也是可以正常运行的(jdk6+).
理解这段代码涉及到的概念有
- 面向接口编程
- 反射
- spi机制
- jdbc版本
- 类加载器
- 解耦
反射
我们知道Class.forName()
是反射的一种,作用是初始化字符串对应的类,这里是com.mysql.jdbc.Driver
.初始化的同时执行静态代码块.
package com.mysql.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
可以看到执行Class.forName("com.mysql.jdbc.Driver")
仅仅是将com.mysql.jdbc.Driver
注册给DriverManager
.
JDBC版本和java sdk的对应关系
- JDBC 1.0 随 JDK1.1 发布
- JDBC 2.0 随 JDK1.2 和 JDK1.3 发布
- JDBC 3.0 随 JDK1.4 发布
- JDBC 4.0 随 JDK1.6 发布
- JDBC 4.1 随 JDK1.7 发布
- JDBC 4.2 随 JDK1.8 发布
spi机制
一言不合就扒代码
conn = DriverManager.getConnection(url, username, password);
继续查看getConnection()
public static Connection getConnection(String url,String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
// 获取当前的类加载器
ClassLoader callerCL = DriverManager.getCallerClassLoader();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, callerCL));
}
继续查看getConnection()重载方法
// Worker method called by the public getConnection() methods.
private static Connection getConnection(String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {
java.util.Vector drivers = null;
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if(callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
if (!initialized) {
//重点在这个初始化方法
initialize();
}
...省略
}
// Class initialization.
static void initialize() {
if (initialized) {
return;
}
initialized = true;
//继续扒这个方法
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = (String) java.security.AccessController.doPrivileged(new sun.security.action.GetPropertyAction("jdbc.drivers"));
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider,
// load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
DriverService ds = new DriverService();
// Have all the privileges to get all the
// implementation of java.sql.Driver
//调用实现的run方法进行接口的所有实现自注册
java.security.AccessController.doPrivileged(ds);
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null) {
return;
}
...省略
}
DriverService
实现了 java.security.PrivilegedAction
接口
public interface PrivilegedAction<T> {
/**
* Performs the computation. This method will be called by
* <code>AccessController.doPrivileged</code> after enabling privileges.
*
* @return a class-dependent value that may represent the results of the
* computation. Each class that implements
* <code>PrivilegedAction</code>
* should document what (if anything) this value represents.
* @see AccessController#doPrivileged(PrivilegedAction)
* @see AccessController#doPrivileged(PrivilegedAction,
* AccessControlContext)
*/
T run();
}
java.security.PrivilegedAction
接口只有一个run方法 看注释知道通过AccessController.doPrivileged()
去调用run()
.
再看DriverService.run()
public Object run() {
// uncomment the followin line before mustang integration
// Service s = Service.lookup(java.sql.Driver.class);
// ps = s.iterator();
ps = Service.providers(java.sql.Driver.class);
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a sun.misc.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try {
while (ps.hasNext()) {
ps.next();
} // end while
} catch(Throwable t) {
// Do nothing
}
return null;
}
最终找到了自动获取驱动的sun.misc.Service
类.
变量ps
持有一个Iterator
的实现
public boolean hasNext() throws ServiceConfigurationError {
if (this.nextName != null) {
return true;
} else {
if (this.configs == null) {
try {
//根据拼接文件路径 最终结果是 META-INF/services/java.sql.Driver
String var1 = "META-INF/services/" + this.service.getName();
if (this.loader == null) {
this.configs = ClassLoader.getSystemResources(var1);
} else {
//类加载器加载对应的文件
this.configs = this.loader.getResources(var1);
}
} catch (IOException var2) {
Service.fail(this.service, ": " + var2);
}
}
while(this.pending == null || !this.pending.hasNext()) {
if (!this.configs.hasMoreElements()) {
return false;
}
this.pending = Service.parse(this.service, (URL)this.configs.nextElement(), this.returned);
}
this.nextName = (String)this.pending.next();
return true;
}
}
public Object next() throws ServiceConfigurationError {
if (!this.hasNext()) {
throw new NoSuchElementException();
} else {
String var1 = this.nextName;
this.nextName = null;
Class var2 = null;
try {
//将实现类的初始化放到这里 系统自动进行初始化,实现驱动的自动注册.
var2 = Class.forName(var1, false, this.loader);
} catch (ClassNotFoundException var5) {
Service.fail(this.service, "Provider " + var1 + " not found");
}
if (!this.service.isAssignableFrom(var2)) {
Service.fail(this.service, "Provider " + var1 + " not a subtype");
}
try {
return this.service.cast(var2.newInstance());
} catch (Throwable var4) {
Service.fail(this.service, "Provider " + var1 + " could not be instantiated: " + var4, var4);
return null;
}
}
}
这种自动获取接口对应的实现的机制就是java的spi机制.
是什么
经过上面的代码,可以说明的是,在jdbc4.0以前,也可以说在jdk6之前,进行jdbc连接数据库编程,是需要手动注册驱动的. 升级到jdbc4.0之后,jdbc4也基于spi的机制来自动发现驱动提供商了,驱动供应商可以通过META-INF/services/java.sql.Driver文件里指定实现类的方式来暴露驱动提供者.
JavaSPI 实际上是 “基于接口的编程+策略模式+配置文件” 组合实现的动态加载机制。
SPI机制的约定:
- 在META-INF/services/目录中创建以接口全限定名命名的文件该文件内容为Api具体实现类的全限定名
- 使用ServiceLoader类动态加载META-INF中的实现类
- 如SPI的实现类为Jar则需要放在主程序classPath中
- Api具体实现类必须有一个不带参数的构造方法
具体而言:
- 定义一组接口,假设是
weihai4099.github.io.spi.DemoService
- 写出接口的一个或多个实现(
weihai4099.github.io.spi.Demo1ServiceImpl
,weihai4099.github.io.spi.Demo2ServiceImpl
); - 在
src/main/resources/
(maven构建模式) 下建立/META-INF/services
目录, 新增一个以接口命名的文件weihai4099.github.io.spi.DemoService
, 内容是要应用的实现类(weihai4099.github.io.spi.Demo1ServiceImpl\r\nweihai4099.github.io.spi.Demo2ServiceImpl
); - 使用 ServiceLoader 来加载配置文件中指定的实现。
public static void main(String[] args) {
ServiceLoader<DemoService> services = ServiceLoader.load(DemoService.class);
Iterator<DemoService> it = services.iterator();
while (it.hasNext()) {
DemoService service = it.next();
service.sayHello();
}
}
可以在Github查看源码
在以前,我们一般是这种形式的写法,这种在生产上应该是尽量避免的,死耦合.
DemoService service = new Demo1ServiceImpl();
service.sayHello();
使用了spring,如果接口只有一个实现,spring ioc
自动将实现进行注入.如果有多个实现,我们还可以使用@Qualifier
注解根据beanname选择性注入.
如果进行编程式注入,还可以使用StringValueResolver
+@Value
动态注入.
现在又多了一种原生的可插拔式的声明方式.
使用场景
- 最早的
common-logging
apache最早提供的日志的门面接口。只有接口,没有实现。具体方案由各提供商实现, 发现日志提供商是通过扫描 META-INF/services/org.apache.commons.logging.LogFactory配置文件,通过读取该文件的内容找到日志提工商实现类。只要我们的日志实现里包含了这个文件,并在文件里制定 LogFactory工厂接口的实现类即可. - 之后的jdbc
- jsr303 Bean Validation
在spring mvc 中 ,我们可以对参数进行校验, 只需要在方法参数 添加
@Valid
注解,申明org.hibernate:hibernate-validator:5.4.2.Final
依赖.hibernate-validator
作为依赖传递了javax.validation:validation-api
的依赖.其中javax.validation:validation-api
是接口定义,是标准.hibernate-validator
是接口的实现.spring mvc自动在classpath下扫描发现验证接口的实现.进行验证,所依赖的就是java spi机制.使用spi的好处就是,可以随时替换其他实现,比如Apache BVal - Dubbo的自动服务发现机制
- servlet3.0 之后的ServletContainerInitializer初始化器
在spring4.0之后,我们可以实现
org.springframework.web.WebApplicationInitializer
并实现void onStartup(ServletContext servletContext) throws ServletException;
方法提前做一些组件的初始化操作,我们一般是进行java config@Bean
申明 bean. 同样在spring-web.jar的META-INF/services/
会发现以接口javax.servlet.ServletContainerInitializer
为名的文件,文件内容是该接口对应的实现org.springframework.web.WebApplicationInitializer
.