두보 소스 --SPI 확장 메커니즘

AOP, IOC, MVC 프레임 워크와 기준으로 두보 사용이 가장 좋은 아 재 읽기.

SPI는 무엇인가

서비스 프로 바이더 인터페이스, 서비스 제공자 인터페이스의 이름을 SPI, 자바의 집합 타사 구현 또는 API의 확장에 의해 제공하는 데 사용됩니다.

JDK SPI 바이두에서 자체 실행, 여기에 이야기 소스의 예를 들어있을 수 있습니다 사용되지 않습니다.

SPI 기반 인터페이스를 디커플링의 핵심 아이디어, 정책 모델 구성은 구현 클래스의 동적 확장을 할 수 있습니다.

숙련 된 개발자가 확실히 많이 사용 Driver등 달성하기 위해 제품의를 oracle.jdbc.driver.OracleDriver하고 oracle.jdbc.OracleDriverJDBC 드라이버에 대한 ODBC (마이크로 소프트 연결 해당 데이터베이스)뿐만 아니라, 예를 들어, 우리는 JDK의 동적 확장을 수행하는 방법 분석 :

에서 mysql-connector-java-5.1.44.jar!/META-INF/services/java.sql.Driver파일 :

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
复制代码

모두는 java.sql.Driver우리가이 부하 살펴 구현 클래스의 완전한 이름 Driver.class:

    private static void loadInitialDrivers() {
        // 先从JVM启动参数里面获取到jdbc.drivers的值 (-Djdbc.drivers=xxx)
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                // JDK提供的加载SPI方式:ServiceLoader
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> 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);
            }
        }
    }
复制代码

나를 과정에 대해 설명하자 :

  1. JVM의 시작 속성을로드, GET적인 driverName
  2. 드라이버는 모든 구현 클래스 ServiceLoader로드
  3. JVM 특성을 드라이버 이름을 따르면 클래스를로드합니다 : Class.forName을 (적인 driverName를)

첫 번째 단계와 세 번째 부분은 우리가 잘 알고 있어야합니다. ServiceLoader에서의 주요 소스를 탐색하려면 :

#首先它实现了迭代类
public final class ServiceLoader<S> implements Iterable<S>
所以从该对象中拿到的迭代器, 是定制的迭代器,我们再调用迭代器的hasNext方法,事实上调用的是:

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    // service就是我们传进来的接口类对象
                    // PREFIX 是常量: private static final String PREFIX = "META-INF/services/";
                    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;
                }
                // 这里面会打开流去解析每一行, 把实现类全限定名加到names列表并返回其迭代器对象,用pending来接收
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
复制代码

따라서, 우리는 사용에 SPI JDK의 출시를 취소 할 수 있습니다 :

  1. SPI의 프로필 경로 : META-INF/services/변경할 수 없습니다.
  2. 정규화 된 클래스 이름 선언을 인터페이스 이름을 프로필
  3. 각 행에서 구현 프로필은 클래스의 정규화 된 클래스 이름입니다

자바 SPI 문제

두보 왜 자신의 자바 SPI를 선택하고 다시 그것을 설정되지 않았습니다.

  1. 확장은 매우 시간이 많이 걸리는 달성 초기화 경우 JDK 표준 SPI 모든 실현을 인스턴스화 한 번 확장 점 것뿐만 아니라, 쓸모에 대한 부하는 자원의 낭비 될 경우.
  2. 확장이로드 포인트에 실패 할 경우, 확장의도 이름이 요점을 파악하지 못한다.
  3. 그것은 AOP와 IOC를 지원하지 않습니다.

두보 SPI

두보는 SPI 확장은 JDK의 표준 SPI 확장 (서비스 공급자 인터페이스) 검색 메커니즘에서 제공 강화. 위의 문제의 개선은 표준 JDK를 SPI.

말을하지 않습니다 본 명세서에서 사용 된 바와 같이, 나는 학생들이 공식보기에 갈 수 사용하지 않은 : dubbo.apache.org/zh-cn/docs / ...

    public static void main(String[] args) {
        ExtensionLoader<Color> loader = ExtensionLoader.getExtensionLoader(Color.class);
        Color yellow = loader.getExtension("yellow");
        System.out.println(yellow.getColor());
    }
复制代码

이 코드의 테스트 두보 SPI 조각, 당신이 볼 수 getExtensionLoader두보 SPI 입구, 우리는 두보 동적 확장 얼마나 봐.

Color yellow = loader.getExtension("yellow");클래스 이름 확장자를 얻는 구성 과정에서 클래스의 인스턴스를 통해이 코드 라인에서, 우리는 getExtension 방법에서 볼 (의 getBean 유사 spirng ()).

    public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        // 根据接口名获取它的持有类, 如果有从缓存中取, 没有就重新new
        final Holder<Object> holder = getOrCreateHolder(name);
        // 获取到实例
        Object instance = holder.get();
        // 双重检锁 获取和创建实例
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
复制代码

방법,이 방법은 확장 된 클래스의 인스턴스를 만들고, createExtension (이름)에 아무것도 의지가 있다고 가정하자.

    private T createExtension(String name) {
      // 根据接口名获取所有实现类
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            // 从缓存中获取实例
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            // 缓存(在时候就加载了)中没有, 调用反射创建
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // inject 依赖注入, 给实例注入需要的依赖对象
            injectExtension(instance);
            // 从缓存拿接口的包装类(也是实现类)
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            // 如果包装类不是空, 就遍历通过构造方法反射创建实例
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }
复制代码

단계를 포함 createExtension 방법 :

  1. 으로 getExtensionClasses구현 클래스의 모든 취득
  2. 반영하여 clazz.newInstance()확장 객체를 생성
  3. injectExtension(instance);종속성을 주입하는 것은 개체를 확장하는
  4. 인터페이스와 구성 파라미터의 방법 으로서는, 목적은 각각의 확장 객체 래퍼에 싸여

에서 wrapperClass.getConstructor(type).newInstance(instance);의 코드를보고, 래퍼는 객체 생성자 매개 변수 인터페이스 클래스의 객체 여야합니다.

모든 확장이 좋아하는 것을 어떻게 얻을 수 있습니까? 우리는 탐구 getExtensionClasses방법 :

    private Map<String, Class<?>> getExtensionClasses() {
        // 从缓存中拿map, 如果没有,就调用loadExtensionClasses
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
复制代码

이 방법은 매우 간단합니다, 캐시에서 맵을하고, 그렇지 않은 경우, 전화 loadExtensionClasses, 모양 loadExtensionClasses 방법이해야 할 일 :

    private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }
复制代码
  1. 전화 cacheDefaultExtensionName는 기본 구현 클래스를 얻을
  2. 여러 파일에서 loadDirectory 찾기 확장 클래스 구성 파일을 호출

cacheDefaultExtensionName을 얻을하는 방법을 살펴 보자는 기본 구현 클래스입니다 :

    private void cacheDefaultExtensionName() {
        // 获取SPI上的注解, 如果有设置value值,则取出, 这个值是默认实现类噢
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                if (names.length > 1) {
                    throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                if (names.length == 1) {
                    cachedDefaultName = names[0];
                }
            }
        }
    }
复制代码

이 방법은 매우 간단 대응하는 디폴트의 구현에 SPI 값 주석을 걸릴 것입니다.

    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls;
            // 阿里封装的获取 classLoader 的方法
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                // jdk的方法, 与java spi一样的噢
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            // 拿到url后遍历调用loadResource方法
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }
复制代码

당신이 자신을 가서보다 구체적인, 나는 다음에 수행 된 내용을 설명합니다 :

  1. 파일 스트림에 URL을 받기
  2. 의 각 라인 구문 분석 =할당 된 번호 앞에 것은 완전한 경로 구현의 후자의 클래스에 할당 된 이름입니다
  3. 각 행을 반영 구현 클래스, 및 호출 할당의 예 (단 이상 extensionClasses 할당 될 때까지지도의 새로운 객체) 최종 loadClass 메소드의 extensionClasses을 달성했다.
    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        // 如果是Adaptive注解的类, 就把它放到cacheAdaptiveClass缓存中
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
            // 分割name, 因为配置文件中name可以有很多个,然后每个name对应赋值一个实现类对象(即使对象都相同)
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }
复制代码
  1. 주석 클래스가 좋아 여부 적응 결정한다는 캐시에 넣고 cacheAdaptiveClass에 해당하는 같은
  2. 부서 이름, 파일 이름 구성은 많은 수 있기 때문에 후 각 (객체가 같은 경우에도) 구현 클래스 객체에 해당하는 이름을 할당

음, 여기에 확장 클래스 로딩은 다음 주입에 대한 의존도 (injectExtension 방법)에 완료 :

    private T injectExtension(T instance) {
        try {
            // 你一定很好奇,如果objectFactory是空怎么办,其实再
            if (objectFactory != null) { 
                // 遍历实例所有的方法
                for (Method method : instance.getClass().getMethods()) {
                    // 如果是setter方法
                    if (isSetter(method)) {
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         */
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        Class<?> pt = method.getParameterTypes()[0];
                        if (ReflectUtils.isPrimitives(pt)) {
                            continue;
                        }
                        try {
                            // 获取setter方法属性
                            String property = getSetterProperty(method);
                            // 根据第一个参数和类型 从List<ExtensionFactory>中获取实例
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                // 利用反射将获取的依赖注入到当前实例中
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("Failed to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }
复制代码
  1. ObjectFactory를가 비어있는 경우, IOC 두보의 확장을로드 할 첫 번째 클래스 (증거는 확장 클래스를로드 없습니다).
  2. 취득 매개 변수와 타입의 setter 메소드, 그리고 IOC의 두보 인스턴스 의존성에서 개체를 얻을 수 있습니다.
  3. 종속 객체 인스턴스에 주입되는 전류를 반영.

다음 호출하기 때문에 왜 ObjectFactory를하지, 그것을 비워야합니다 ExtensionLoader.getExtensionLoader(Color.class);IOC의를 초기화 할 수있는 클래스를 확장해야 두보 때 방법을, 자세한 내용은 아래를 참조하십시오.

당신이 getExtensionLoader를 호출 할 때 실제로는 IOC를 ObjectFactory를 인스턴스화를 놓고, 초기화 :

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        ...
        if (loader == null) {
             // 在这里new的对象
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
    
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
复制代码

그것은 이러한 방법이라고했다 :

  1. getAdaptiveExtension가 (캐시에서 가져온, 다음 메소드를 호출하지 않습니다)
  2. createAdaptiveExtension (캐시에서 가져온, 다음 메소드를 호출하지 않습니다)
  3. 4 getAdaptiveExtensionClass 메소드 호출 후 호출 방법 5
  4. 이 방식과 전면 (getExtensionClasses에 loader.getExtension("yellow");동일의 참조).
  5. 클래스와 반환에 보행 분석을위한 프록시와 Javassist 기본, 다음 컴파일러를 생성하는 바이트 코드를 사용하여 createAdaptiveExtensionClass 방법.

무의식적으로, SPI의 두보는 고유의 AOP를 가지고 IOC는 소스 코드를 읽습니다.

당신은 내가 AOP 아이 표시되지 않는, 궁금 할 것이다.

생성자 두보 래퍼의 결의는 다음 의존성 주입 래퍼의 인스턴스를 기억, AOP는 다음 논리 래퍼를 사용자 정의 할 수 있습니다 유사한 기능을 완료합니다.

때문에 충분히 공간,와 Javassist 부품 무거운의 부족은, 다시는 당황했다 읽을 경우, 다음을 읽어보십시오 측면에서 눈에 띄는 소스 코드를 읽고, 가장 중요한 활성 수신기 정신이다. 몇 번 멀티 - 시운전, 어서!

공공 우려 호에 오신 것을 환영합니다

GitHub의 주소 : github.com/fantj2016/j ...

추천

출처juejin.im/post/5e50ffaa6fb9a07cbe34643f