le mie informazioni di contatto
Posta[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
SPI sta per Service Provider Interface, che può essere intesa come un'interfaccia appositamente fornita ai fornitori di servizi o agli sviluppatori che estendono le funzioni del framework. SPI separa l'interfaccia del servizio dall'implementazione specifica del servizio, disaccoppia il chiamante del servizio e l'implementatore del servizio e può migliorare la scalabilità e la manutenibilità del programma. La modifica o la sostituzione dell'implementazione di un servizio non richiede la modifica del chiamante. Molti framework utilizzano il meccanismo SPI di Java, ad esempio: framework Spring, driver di caricamento del database, interfaccia di registro e implementazione dell'estensione Dubbo, ecc. Fare riferimento al meccanismo SPI di Dubbo per implementare la parte SPI di questo framework RPC.
Ad esempio, quando il client comunica con il server, deve serializzare il messaggio. Esistono molti algoritmi di serializzazione che possono essere utilizzati durante la serializzazione, inclusi Hessian, Kryo e ProtoStuff. Il requisito del sistema è chiamare il metodo nella classe corrispondente all'algoritmo di serializzazione pertinente in base al nome dell'algoritmo di serializzazione nel messaggio per eseguire la serializzazione e la deserializzazione. Inoltre, per facilitare l'espansione, è necessario utilizzare SPI per il disaccoppiamento .
L'SPI viene utilizzato come segue:
- Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class)
- .getExtension(codecName);
codecName è il nome dell'algoritmo di serializzazione e la classe corrispondente deve essere caricata in base a questo nome.
- private final Class<?> type;
-
- private ExtensionLoader(Class<?> type) {
- this.type = type;
- }
-
- // 每个SPI接口都有自身的ExtensionLoader
- public static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {
- if (type == null) {
- throw new IllegalArgumentException("Extension type should not be null.");
- }
- if (!type.isInterface()) {
- throw new IllegalArgumentException("Extension type must be an interface.");
- }
- if (type.getAnnotation(SPI.class) == null) {
- throw new IllegalArgumentException("Extension type must be annotated by @SPI");
- }
- // firstly get from cache, if not hit, create one
- ExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
- if (extensionLoader == null) {
- EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<S>(type));
- extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
- }
- return extensionLoader;
- }
Ogni interfaccia SPI ha il proprio ExtensionLoader Quando si chiama getExtensionLoader, eseguirà prima una serie di operazioni di controllo legale, quindi tenterà di ottenere l'ExtensionLoader dell'interfaccia. Se non è possibile, prova a ottenerlo ottenuto, creare un oggetto Loader.
Successivamente, l'istanza viene ottenuta tramite getExtension e anche l'istanza viene memorizzata nella cache locale. Se non è nella cache, creare nuovamente l'istanza.
- private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
-
- public T getExtension(String name) {
- if (StringUtil.isBlank(name)) {
- throw new IllegalArgumentException("Extension name should not be null or empty.");
- }
- // firstly get from cache, if not hit, create one
- // 缓存holder
- Holder<Object> holder = cachedInstances.get(name);
- if (holder == null) {
- cachedInstances.putIfAbsent(name, new Holder<>());
- holder = cachedInstances.get(name);
- }
- // create a singleton if no instance exists
- // holder为空,双重检查锁创建示例
- Object instance = holder.get();
- if (instance == null) {
- synchronized (holder) {
- instance = holder.get();
- if (instance == null) {
- instance = createExtension(name);
- holder.set(instance);
- }
- }
- }
- return (T) instance;
- }
Dopo aver ottenuto l'oggetto Class della classe, puoi creare questo oggetto attraverso la riflessione.
- // 缓存
- private static final Map<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
-
- private T createExtension(String name) {
- // load all extension classes of type T from file and get specific one by name
- // SPI接口对应的实现类,其标识名与class文件的映射,根据标识名获取class
- Class<?> clazz = getExtensionClasses().get(name);
- if (clazz == null) {
- throw new RuntimeException("No such extension of name " + name);
- }
- T instance = (T) EXTENSION_INSTANCES.get(clazz);
- if (instance == null) {
- try {
- // 缓存中不存在,则创建实例
- EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
- instance = (T) EXTENSION_INSTANCES.get(clazz);
- } catch (Exception e) {
- log.error(e.getMessage());
- }
- }
- return instance;
- }
La chiave è il processo per ottenere l'oggetto Class, ovvero il metodo getExtensionCalsses:
- // 该SPI接口所有实现类的标识与其Class对象的缓存
- private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
-
- private static final String SERVICE_DIRECTORY = "META-INF/extensions/";
-
- private Map<String, Class<?>> getExtensionClasses() {
- // get the loaded extension class from the cache
- // 根据Interface实现类的类名获取对应类的缓存
- Map<String, Class<?>> classes = cachedClasses.get();
- // double check
- if (classes == null) {
- synchronized (cachedClasses) {
- classes = cachedClasses.get();
- if (classes == null) {
- classes = new HashMap<>();
- // load all extensions from our extensions directory
- loadDirectory(classes);
- // 将Map集合存储在Holder中进行缓存
- cachedClasses.set(classes);
- }
- }
- }
- return classes;
- }
-
- private void loadDirectory(Map<String, Class<?>> extensionClasses) {
- // 固定路径下的文件,SPI接口的类名作为文件名,在此文件中规定需要加载的实现类
- String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();
- try {
- Enumeration<URL> urls;
- // 系统类加载器,它能够加载用户类路径(ClassPath)上的类和资源。对于SPI机制尤为重要,因为SPI的实现类通常是由应用程序提供并放置在应用程序的类路径下的
- ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
- // 获取当前类加载器加载的URL资源,文件名确定一般urls是唯一的
- urls = classLoader.getResources(fileName);
- if (urls != null) {
- while (urls.hasMoreElements()) {
- URL resourceUrl = urls.nextElement();
- // 使用classLoader加载资源,资源目标在resourceUrl下,加载后的class存储在extensionClasses Map集合当中
- loadResource(extensionClasses, classLoader, resourceUrl);
- }
- }
- } catch (IOException e) {
- log.error(e.getMessage());
- }
- }
-
- private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {
- String line;
- // read every line
- // #是注释,截取注释之前的部分
- while ((line = reader.readLine()) != null) {
- // get index of comment
- final int ci = line.indexOf('#');
- if (ci >= 0) {
- // string after # is comment so we ignore it
- line = line.substring(0, ci);
- }
- line = line.trim();
- if (line.length() > 0) {
- try {
- final int ei = line.indexOf('=');
- // 标识与类名
- String name = line.substring(0, ei).trim();
- String clazzName = line.substring(ei + 1).trim();
- // our SPI use key-value pair so both of them must not be empty
- if (name.length() > 0 && clazzName.length() > 0) {
- // 加载类
- Class<?> clazz = classLoader.loadClass(clazzName);
- // 在map中保存
- extensionClasses.put(name, clazz);
- }
- } catch (ClassNotFoundException e) {
- log.error(e.getMessage());
- }
- }
-
- }
- } catch (IOException e) {
- log.error(e.getMessage());
- }
- }
- kyro=github.javaguide.serialize.kyro.KryoSerializer
- protostuff=github.javaguide.serialize.protostuff.ProtostuffSerializer
- hessian=github.javaguide.serialize.hessian.HessianSerializer
La chiamata al metodo livello per livello realizza il processo di caricamento del file di configurazione SPI corrispondente nel percorso META-INF/extensions/ per caricare l'oggetto Classe e ottenere l'istanza. Fare riferimento ai commenti per le parti importanti.
Va notato che il nome del file in META-INF/extensions/ deve essere coerente con il codice. Il nome del file specificato nel codice è il nome completo della classe dell'interfaccia SPI. Anche il contenuto del file deve essere scritto in base a (identificatore della classe di implementazione = nome completo della classe di implementazione), in modo che possa essere coerente con il codice e il programma possa analizzare correttamente il file e utilizzare il caricatore di classi per caricare la Classe corrispondente. Infine, memorizza nella cache in base a <identificatore della classe di implementazione, oggetto classe della classe di implementazione>.
A causa dell'esistenza delle tre cache (identificatore di classe, oggetto di classe), (oggetto di classe, istanza di oggetto) e (identificatore di classe, istanza di oggetto), è possibile passare direttamente l'identificatore per ottenere l'istanza della classe corrispondente, che ottimizza anche le prestazioni del framework RPC.