본문 바로가기

Java

[Java] Class.forName(String className) 그리고 Service Provider Interface


JAVA의 jdbc를 공부 할 때 많이 보는 Class.forName("oracle.jdbc.OracleDrvier"); 이런 실행 문장이 있습니다.

단지, 사용 할 DBMS에 맞는 드라이버의 full name을 인수로 주었을 뿐인데
그리고 보통 저 메서드를 실행하고 리턴값을 받지도 않죠.. 그런데 저 문장을 실행하고 나면 바로 DriverManager라는
클래스에서 Connection을 얻어 쓸 수 있습니다.

어떻게 저렇게 작동하는 것일까요?

각 DBMS 마다 드라이버와 Connection의 구현이 다를 것 입니다.
하지만 Java를 사용하는 입장에서는 각 DBMS 마다 다른 API를 사용하여 DB에 연결하는 것을 원하지 않죠.
그렇게 된다면 DBMS가 바뀌는 날에는 끔찍한 참사가 일어나겠죠.. --;

소스를 다 수정 해줘야 하니..

하지만 JAVA를 설계하고 만드는 사람들이 모든 DBMS에 대해서 위 API를 구현 할 수는 없을 것 입니다.

Java에서 제공하여 주는 것은 Connection , Driver 인터페이스와 Driver를 등록하고 그 드라이버의
Connection을 리턴해주는 DriverManager 클래스입니다.

그리고 Java에서는 Driver와 Connection 인터페이스를 각 벤더에서 구현하도록 하고
DriverManager라는 클래스를 통해 사용자가 이들을 사용 하도록 하였습니다. 보통, ojdbc14.jar 뭐 이런 파일을
클래스 패스에 추가하여 사용하지요? 이게 Driver와 Connection 인터페이스를 각 DBMS에 맞게 구현한 라이브러리 입니다.

일반적으로 jdbc의 사용은 아래와 같습니다.
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection conn = null;
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORA92", "scott", "tiger");
Statement..
view raw 1.java hosted with ❤ by GitHub
아무것도 하지 않고 Class.forName만 사용하여 드라이버의 이름을 호출하기만 했는데
알아서 DriverManager에 Driver가 등록되고 거기서 우리는 Connection을 얻어 쓸 수 있습니다.
그리고, 드라이버 클래스가 없으면 ClassNotFoundException이 떨어집니다.

어떻게 저렇게 동작 할까요?


우선 기본적은 SPI는 이렇습니다.
이해를 쉽게 하기 위해서 인터페이스와 클래스 이름을 jdbc와 똑같이 해보겠습니다.
package serviceprovider;
public interface Connection {
}
//
package serviceprovider;
public interface Driver {
Connection getConnection();
}
view raw 2.java hosted with ❤ by GitHub
Java에서는 위와 같이 두개의 인터페이스를 제공합니다.

그리고 DriverManager 클래스를 통해 사용자들은 DriverManager로 부터 Connection을 얻을 수 있습니다.
package serviceprovider;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class DriverManager {
private DriverManager() {}
private static final Map<String,Driver> drivers = new ConcurrentHashMap<String,Driver>();
public static final String DEFAULT_DRIVER_NAME = "default";
public static void registerDefaultPrivider(Driver d) {
System.out.println("Driver 등록");
registerDriver(DEFAULT_DRIVER_NAME, d);
}
public static void registerDriver(String name, Driver d) {
drivers.put(name,d);
}
public static Connection getConnection() {
return getConnection(DEFAULT_DRIVER_NAME);
}
public static Connection getConnection(String name) {
Driver d = drivers.get(name);
if(d==null)
throw new IllegalArgumentException();
return d.getConnection();
}
}
//Effective Java SE P11 참고
view raw 3.java hosted with ❤ by GitHub
사용하는 입장에선 이제 DriverManager.getConnection() 혹은 DriverManager.getConnection(String name) 메서드를 통해
원하는 Connection을 얻을 수 있고 이것으로부터 Statement를 받아서 SQL을 보내고 결과를 받을 수 있습니다.
그럼 이제 Class.forName() 메서드가 이 SPI 프레임워크에서 어떻게 사용되는지 보겠습니다.

Driver 인터페이스를 보면 아래와 같은 명세가 있습니다.
 * <P>When a Driver class is loaded, it should create an instance of
 * itself and register it with the DriverManager. This means that a
 * user can load and register a driver by calling
 * <pre>
 *   <code>Class.forName("foo.bah.Driver")</code>
 * </pre>
Driver 클래스가 클래스로더에 의해서 로드가 되면 DriverManager 클래스에 등록이 되어야 하고 그것은 결국
Class.forName(String name) 메서드에 의해서 작동한다고 되어있습니다.
앞 포스트에서 이야기 했지만 이것은 static 필드를 통해서 가능합니다.
Class.forName(String name) 클래스에 의해 클래스가 로드 될 때 static 필드의 내용이 실행되는 것을 이용해 자기 자신을
DriverManager 클래스에 등록하는 것 입니다.

실제로 OracleDriver 클래스를 보면 아래와 같은 부분이 있습니다.
static
{
defaultDriver = null;
Timestamp timestamp = Timestamp.valueOf("2000-01-01 00:00:00.0");
try
{
if(defaultDriver == null)
{
defaultDriver = new OracleDriver();
DriverManager.registerDriver(defaultDriver);
}
}
catch(RuntimeException runtimeexception) { }
catch(SQLException sqlexception) { }
}
view raw 4.java hosted with ❤ by GitHub


그럼 한번 Driver 인터페이스를 상속한 Driver 구현 클래스를 작성해 보겠습니다.
제가 MyDB라는 DB를 만들었고 거기에 맞는 Driver를 JAVA 개발자들에 제공하기 위해
MyDriver를 만드는 것이지요.
package serviceprovider;
public class MyDriver implements Driver {
private Log logger = LogFactory.getLog(MyDriver.class);
private static Driver defaultDriver;
static {
defaultDriver = new MyDriver();
serviceprovider.DriverManager.registerDefaultPrivider(defaultDriver);
}
@Override
public Connection getConnection() {
System.out.println("MyDriver's Connection return");
return null;
}
}
view raw 5.java hosted with ❤ by GitHub


이제 jdbc 사용하는 것 처럼 사용해 보겠습니다.
package serviceprovider;
public class Test {
private Log logger = LogFactory.getLog(Test.class);
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("MyDriver");
Connection connection = DriverManager.getConnection();
}
}
view raw 6.java hosted with ❤ by GitHub
결과는..?

Driver 등록
MyDriver's Connection return

다른 DBMS가 나와도 그 벤더사는
Driver와 Connection 인터페이스를 구현하여 제공하면 Java를 사용하는 개발자는
다른 DBMS 드라이버와 동일한 API를 사용 할 수 있게 됩니다.

사실 위에서 얘기한 static 필드의 사용이 SPI의 핵심은 아닙니다.
static 필드는 단지 Class.forName 메서드를 사용해 Service Provider를 이용 할 수 있는 방법을 제공하기 위해
사용 된 것이지요..

SPI는 Driver, Connection 인터페이스와 실제 그 인터페이스를 구현하는 구현체 클래스가
완전히 분리 되어 제공된다는 것이 포인트입니다.
인터페이스를 사용해 틀을 만들어 놓고, 그 틀에 맞춰서 각각의 서비스 제공자들이 자신의 서비스에 맞춰서
구현 클래스를 제공하도록 하는 것입니다.

새벽에 나와서 에러가 없으니 딱히 할 건 없고..
멍 하다가..
풍주형님의 static에 대해서 생각해보라는 댓글과 어제 메신저로 얘기해주신 spi에 대해 듣고 써봤습니다..