Java SPIメカニズム解説
Java SPI機構詳細説明
1. SPIとは何ですか?
SPIは、(Service Provider Interface)と呼ばれています。
JDKに組み込まれたサービスプロバイダ発見機構で、動的な置き換え発見機構である。
例えば、あるインタフェースがあり、実行時に動的にそのインタフェースに実装を追加したい場合、実装を追加すればよいのです。java.sql.Driverインターフェイスでは、よくこのようなケースに遭遇します。
mysql と postgresql はそれぞれ異なる実装をユーザが利用できるようにしています。
また、JavaのSPI機構は、あるインターフェースに対するサービス実装を見つけることができます。
このように、インタフェースは抽象的なSPIインタフェースに対応し、実装者はSPIインタフェースを実装し、呼び出し側はSPIインタフェースに依存することになります。
SPIインターフェースは呼び出し側で定義され、概念的には呼び出し側への依存度が高く、組織は呼び出し側があるパッケージにあり、実装は別のパッケージにある。
サービスプロバイダがインターフェースの実装を提供する場合、クラスパス下のMETA-INF/services/ディレクトリに、サービスインターフェースと同じ名前のファイルを作成する必要があります。
このファイルの中身は、インターフェイスの具体的な実装クラスです。他のアプリケーションがこのサービスを必要とするとき、(通常、依存関係として使用される)jarパッケージを調べることができます。
サービス実装を見つけるためのJDKのツールクラスは、java.util.ServiceLoader.ServiceLoaderです。
2. SPIの用途
データベースDriverManager、Spring、ConfigurableBeanFactoryなどは、すべてSPIの仕組みを利用している。ここでは、データベースDriverManagerの例で、その実装の内部構造を見ることができます。
DriverManagerは、異なるデータベースドライバを管理・登録するための、jdbcにおけるツールクラスです。1つのデータベースに対して、異なるデータベースドライバの実装が存在する場合があります。
特定のドライバ実装を使用する際に、既存のコードを修正せず、簡単な設定で結果を出したい。
mysqlドライバを使用する場合、DriverManagerが特定の識別されたドライバクラスをどのように取得するかという問題があります。
Class.forName("com.mysql.jdbc.Driver") を使って mysql ドライバをロードした後、その中の静的コードを実行して DriverManager にドライバを登録しています。
を使用します。
ドライバの実装
ドライバクラスの静的コードブロック内で
DriverManager
メソッドに登録し、ドライバマネージャにパラメータとして渡します。
package com.mysql.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
Mysql DriverManagerの実装
loadInitialDrivers
その内部静的コードブロックに
loadInitialDrivers
メソッドを使用します。
ServiceLoader
使い方は、上記の spi tool クラスを使用します。
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction
:
ServiceLoader
spiツールクラス
public final class ServiceLoader
implements Iterable
{
private static final String PREFIX = "META-INF/services/";
public final class ServiceLoader
implements Iterable
{
private static final String PREFIX = "META-INF/services/";
最初にjdbc.driversプロパティの値を見つけ、次にSPIメカニズムを通してドライバを見つける。
private boolean hasNextService() {
if (nextName ! = null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
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()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
loadInitialDrivers
META-INF/services/ フォルダ下のクラス名 filename (ここでは Driver.class.getName() に相当) のリソースがロードされ、VM に読み込まれていることがわかります。
/**
* Registers the given driver with the {@code DriverManager}.
* A newly-loaded driver class should call
* the method {@code registerDriver} to make itself
A newly-loaded driver class should call * the method {@code registerDriver} to make itself known to the {@code DriverManager}. If the driver is currently
If the driver is currently * registered, no action is taken.
If the driver is currently * registered, no action is taken.
* @param driver the new JDBC Driver that is to be registered with the
* {@code DriverManager}
* @param da the {@code DriverAction} implementation to be used when
* {@code DriverManager#deregisterDriver} is called
* @exception SQLException if a database access error occurs
* @exception NullPointerException if {@code driver} is null
* @since 1.8
@since 1.8 */
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver ! = null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
public class DriverManager {
// List of registered JDBC drivers
private final static CopyOnWriteArrayList
registeredDrivers = new CopyOnWriteArrayList<>();
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
Properties info = new java.util;
if (user ! = null) {
info.put("user", user);
}
if (password ! = null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(
String url, java.util.Properties info, Class<? > caller) throws SQLException {
/*
* 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.
*/
ClassLoader callerCL = caller ! = null ? caller.getClassLoader() : null;
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 + "\")");
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con ! = null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason ! = null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for " + url);
throw new SQLException("No suitable driver found for " + url, "08001");
}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
boolean result = false;
if(driver ! = null) {
Class<? > aClass = null;
try {
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
} catch (Exception ex) {
result = false;
result = false; }
result = ( aClass == driver.getClass() ) ? true : false;
}
return result;
}
loadInitialDrivers
コメント "Load these drivers, so they can be instantiated." は、SPIによってスキャンされたドライバをロードして、初期化をトリガーすることを意味します。つまり、静的コードブロックのトリガーです。
/**
* Registers the given driver with the {@code DriverManager}.
* A newly-loaded driver class should call
* the method {@code registerDriver} to make itself
A newly-loaded driver class should call * the method {@code registerDriver} to make itself known to the {@code DriverManager}. If the driver is currently
If the driver is currently * registered, no action is taken.
If the driver is currently * registered, no action is taken.
* @param driver the new JDBC Driver that is to be registered with the
* {@code DriverManager}
* @param da the {@code DriverAction} implementation to be used when
* {@code DriverManager#deregisterDriver} is called
* @exception SQLException if a database access error occurs
* @exception NullPointerException if {@code driver} is null
* @since 1.8
@since 1.8 */
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver ! = null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
ドライバマネージャのドライバリストに自分を登録する
public class DriverManager {
// List of registered JDBC drivers
private final static CopyOnWriteArrayList
registeredDrivers = new CopyOnWriteArrayList<>();
接続を取得する際に、リストから取得するドライバマネージャのconnectionメソッドを呼び出します。
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
Properties info = new java.util;
if (user ! = null) {
info.put("user", user);
}
if (password ! = null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(
String url, java.util.Properties info, Class<? > caller) throws SQLException {
/*
* 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.
*/
ClassLoader callerCL = caller ! = null ? caller.getClassLoader() : null;
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 + "\")");
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con ! = null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason ! = null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for " + url);
throw new SQLException("No suitable driver found for " + url, "08001");
}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
boolean result = false;
if(driver ! = null) {
Class<? > aClass = null;
try {
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
} catch (Exception ex) {
result = false;
result = false; }
result = ( aClass == driver.getClass() ) ? true : false;
}
return result;
}
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
ハートビート・エフェクトのためのHTML+CSS
-
HTML ホテル フォームによるフィルタリング
-
HTML+cssのボックスモデル例(円、半円など)「border-radius」使いやすい
-
HTMLテーブルのテーブル分割とマージ(colspan, rowspan)
-
ランダム・ネームドロッパーを実装するためのhtmlサンプルコード
-
Html階層型ボックスシャドウ効果サンプルコード
-
QQの一時的なダイアログボックスをポップアップし、友人を追加せずにオンラインで話す効果を達成する方法
-
sublime / vscodeショートカットHTMLコード生成の実装
-
HTMLページを縮小した後にスクロールバーを表示するサンプルコード
-
html のリストボックス、テキストフィールド、ファイルフィールドのコード例