jdbc 클래스 캐리어, spi 서비스 메커니즘

21262 단어 jvm자바jdbcspi
요 며칠 동안 자바 류 로드 체 제 를 보고 spi 서비스 체제 가 부모 위임 모델 을 파괴 하 는 것 을 보 았 습 니 다. 특히 전형 적 인 spi 서비스 jdbc 구동 을 연 구 했 습 니 다. 먼저 코드 를 실행 하고 my sql jdbc 구동 의 클래스 로드 (maven 프로젝트 는 jdbc 구동 의존 도 를 도 입 했 고 버 전 은 5.1.41) 를 살 펴 보 았 습 니 다.
public static void main(String[] args)
    {
        Enumeration drivers = DriverManager.getDrivers();
        Driver driver;
        while (drivers.hasMoreElements())
        {
            driver = drivers.nextElement();
            System.out.println(driver.getClass() + "------" + driver.getClass().getClassLoader());
        }
        System.out.println(DriverManager.class.getClassLoader());
    }

출력 결 과 는 다음 과 같 습 니 다.
class com.mysql.jdbc.Driver------sun.misc.Launcher$AppClassLoader@2a139a55
class com.mysql.fabric.jdbc.FabricMySQLDriver------sun.misc.Launcher$AppClassLoader@2a139a55
null

코드 에 Class. forName (") 코드 가 호출 되 지 않 았 음 을 볼 수 있 습 니 다. 그러나 DriverManager 에 두 개의 jdbc 드라이브 가 불 러 왔 습 니 다. 그러나 이 두 드라이브 는 모두 사용 하 는 응용 클래스 로 더 (AppClassLoader) 로 불 러 왔 습 니 다. DriverManager 자체 의 클래스 로 더 는 null 즉 BootstrapClassLoader 입 니 다. 부모 위임 모델 의 규칙 에 따라위임 체인 은 다음 과 같 습 니 다: SystemApp classloader - > Extension classloader - > Bootstrap classloader, 부모 로 더 BootstrapClassLoader 는 AppClassLoader 가 불 러 온 클래스 를 찾 을 수 없습니다. 이 때 스 레 드 컨 텍스트 로 더 를 사 용 했 습 니 다. Thread. current Thread (). setContextClassLoader () 는 위임 체인 왼쪽 의 클래스 로 더 를 스 레 드 컨 텍스트 로 더 로 설정 할 수 있 습 니 다.이 때 오른쪽 에 있 는 로 더 는 스 레 드 컨 텍스트 로 더 의뢰 서브 로 더 를 불 러 올 수 있 습 니 다.
DriverManager 의 원본 코드 를 볼 수 있 습 니 다.
/**
     * 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");
    }
    private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
sun.misc.Providers()
        AccessController.doPrivileged(new PrivilegedAction() {
            public Void run() {
                ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

DriverManager 가 초기 화 할 때 ServiceLoader 를 사용 하여 java. sql. Driver 의 실현 클래스 를 불 러 오 는 것 을 볼 수 있 습 니 다. 여 기 는 spi 서비스의 사상 으로 ServiceLoader 의 load 코드 를 볼 수 있 습 니 다.
public static  ServiceLoader load(Class service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
public static  ServiceLoader load(Class service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }
private ServiceLoader(Class svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

ServiceLoader 를 만 들 었 습 니 다. reload 방법 으로 불 러 옵 니 다. ServiceLoader 의 주요 매개 변수 와 reload 의 코드 는 다음 과 같 습 니 다.
private static final String PREFIX = "META-INF/services/";
public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

Lazy Iterator 는 게 으 른 로 딩 교체 기 입 니 다. 이 교체 기의 실현 을 보 세 요.
 private class LazyIterator
        implements Iterator
    {

        Class service;
        ClassLoader loader;
        Enumeration configs = null;
        Iterator pending = null;
        String nextName = null;

        private LazyIterator(Class service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        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;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction action = new PrivilegedAction() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction action = new PrivilegedAction() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

DriverManager 의 초기 화 코드 를 돌 이 켜 보면 다음 코드 를 볼 수 있 습 니 다.
 while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }

DriverManager 가 META - INF / services / java. sql. Driver 에서 모든 종류의 Class. forName () 방법 을 반복 적 으로 호출 하 는 것 을 알 수 있 습 니 다. 그러면 이 불 러 오 는 드라이버 는 어떻게 DriverManager 에 등록 되 어 있 습 니까?mysql 의 드라이버 구현 클래스 를 보면 드라이버 의 구현 이 초기 화 될 때 등 록 된 것 을 볼 수 있 습 니 다. 코드 는 다음 과 같 습 니 다.
static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

이 코드 는 java. sql. Driver 의 실현 클래스 를 DriverManager 에 등록 할 수 있 습 니 다. 이 코드 에 new Driver () 는 com. mysql. jdbc. Driver 가 좋 습 니 다. java. sql. Driver 를 마지막 으로 보면 spi 서 비 스 를 실현 하 는 데 없어 서 는 안 될 파일 META - INF / services / java. sql. Driver (이 특정한 자바. sql. Driver 의 인 터 페 이 스 를 실현 하 는 spi 서비스) 의 내용 은 다음 과 같 습 니 다.
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

이 두 가지 유형, 즉 글 의 시작 을 위 한 두 개의 jdbc 구동 을 볼 수 있다.
모든 버 전의 jdbc 드라이브 가 spi 서 비 스 를 실현 한 것 은 아 닙 니 다. 5.1.5 및 그 후의 버 전에 서 이 서 비 스 를 실현 한 것 같 습 니 다. 이전 버 전 은 Class. forName 방법 으로 드라이버 를 불 러 와 야 합 니 다. 그리고 ojdbc 의 드라이브 가 spi 서 비 스 를 실현 하지 못 한 것 같 습 니 다.
드라이버 관리자 에 spi 서 비 스 를 불 러 오 는 과정 을 알 게 되 었 습 니 다. 우 리 는 간단 한 jdbc 구동 (클래스 로 딩 부분 만 실현) 을 시도 할 수 있 습 니 다.
public class Driver implements java.sql.Driver
{

    static
    {
        try
        {
            DriverManager.registerDriver(new com.lcy.mysql.Driver());
        }
        catch (SQLException e)
        {
            throw new RuntimeException("register driver fail");
        }
    }

    @Override
    public Connection connect(String url, Properties info)
        throws SQLException
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public boolean acceptsURL(String url)
        throws SQLException
    {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info)
        throws SQLException
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int getMajorVersion()
    {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public int getMinorVersion()
    {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public boolean jdbcCompliant()
    {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public Logger getParentLogger()
        throws SQLFeatureNotSupportedException
    {
        // TODO Auto-generated method stub
        return null;
    }

}

초기 화 방법 만 적 었 습 니 다. 다른 방법 은 모두 기본 빈 칸 으로 이 루어 집 니 다. src / mian / resources 디 렉 터 리 에 새 파일 / META - INF / services / java. sql. driver 입력 내용 com.lcy.mysql.Driver 을 포장 하여 이전 글 에서 시 작 된 테스트 프로젝트 에 프로젝트 의존 도 를 도입 합 니 다 (같은 프로젝트 라면 직접 실행 하면 됩 니 다). 실행 결 과 는 다음 과 같 습 니 다.
class com.mysql.jdbc.Driver------sun.misc.Launcher$AppClassLoader@2a139a55
class com.mysql.fabric.jdbc.FabricMySQLDriver------sun.misc.Launcher$AppClassLoader@2a139a55
class com.lcy.mysql.Driver------sun.misc.Launcher$AppClassLoader@2a139a55
null

사용자 정의 com. lcy. mysql. Driver 를 불 러 왔 습 니 다.

좋은 웹페이지 즐겨찾기