JAVA SPI

36550 ワード

SPI発見メカニズム
SpringmvcのWebなしxml起動方式もSPI自動発見メカニズムで処理されており,いずれもプロファイルに実装クラス全限定名を加えることによって行われる.
標準インタフェース:
package spi;

public interface Fruit {
    void name();
}

挿抜式実現類1:
package spi;

public class Apple implements Fruit {

    @Override
    public void name() {
        System.out.println("apple");
    }
}

プラグイン実装クラス2:
package spi;

public class Orange implements Fruit {
    @Override
    public void name() {
        System.out.println("orange");
    }
}

プログラム呼び出し方法:
package spi;

import java.util.Iterator;
import java.util.ServiceLoader;

public class SPIMain {
    public static void main(String[] args) {
        ServiceLoader<Fruit> fruits = ServiceLoader.load(Fruit.class);
        Iterator<Fruit> iterator = fruits.iterator();
        while (iterator.hasNext()) {
            Fruit fruit = iterator.next();
            fruit.name();
        }
    }
}

具体的に読み取るプロファイル:META-INF/services/spi.Fruit
spi.Apple
spi.Orange

META-INF/services/はデフォルトの接頭辞、spi.Fruitは拡張が必要な標準インタフェースの全限定名であり,内容は具体的な実装クラスの全限定名である.SPIの実装クラスはパラメータを持たない構造方法を携帯しなければならない.
ServiceLoaderクラスの分析:
//  Iterable       
public final class ServiceLoader<S>
    implements Iterable<S>
{
	//           
    private static final String PREFIX = "META-INF/services/";

    //            
    private final Class<S> service;

    //                   
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;

    //      ,           
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    //         ,      
    private LazyIterator lookupIterator;
	......    
	
	//     
    private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

        private LazyIterator(Class<S> 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;
                }
                //                ,     providers       
                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 {
            	//         ,false                 
                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 {
            	//         ,           ,     service  
                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<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

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

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

    }

	//            
    private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                          List<String> names)
        throws IOException, ServiceConfigurationError
    {
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }
}