minhas informações de contato
Correspondência[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
SPI significa Service Provider Interface, que pode ser entendida como uma interface fornecida especificamente para provedores de serviços ou desenvolvedores que estendem funções de framework. O SPI separa a interface de serviço da implementação de serviço específica, separa o chamador do serviço e o implementador do serviço e pode melhorar a escalabilidade e a capacidade de manutenção do programa. Modificar ou substituir uma implementação de serviço não requer a modificação do chamador. Muitas estruturas usam o mecanismo SPI do Java, como: estrutura Spring, driver de carregamento de banco de dados, interface de log e implementação de extensão Dubbo, etc. Consulte o mecanismo SPI do Dubbo para implementar a parte SPI desta estrutura RPC.
Por exemplo, quando o cliente se comunica com o servidor, ele precisa serializar a mensagem. Existem muitos algoritmos de serialização que podem ser usados durante a serialização, incluindo Hessian, Kryo e ProtoStuff. O requisito do sistema é chamar o método na classe correspondente ao algoritmo de serialização relevante com base no nome do algoritmo de serialização na mensagem para realizar a serialização e desserialização. Além disso, para facilitar a expansão, o SPI precisa ser usado para desacoplamento. .
SPI é usado da seguinte forma:
- Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class)
- .getExtension(codecName);
codecName é o nome do algoritmo de serialização e a classe correspondente precisa ser carregada de acordo com este 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;
- }
Cada interface SPI tem seu próprio ExtensionLoader. Ao chamar getExtensionLoader, ele primeiro executará uma série de operações de verificação legal e, em seguida, tentará obter o ExtensionLoader da interface. Primeiro, tente obtê-lo do cache local CHM. obtido, crie um objeto Loader.
Depois disso, a instância é obtida por meio de getExtension e também é armazenada em cache localmente. Se não estiver no cache, crie a instância novamente.
- 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;
- }
Depois de obter o objeto Class da classe, você pode criar esse objeto por meio de reflexão.
- // 缓存
- 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;
- }
A chave é o processo de obtenção do objeto Class, ou seja, o método 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
A chamada do método camada por camada realiza o processo de carregamento do arquivo de configuração SPI correspondente no caminho META-INF/extensions/ para carregar o objeto Class e obter a instância. Consulte os comentários para partes importantes.
Deve-se observar que o nome do arquivo em META-INF/extensions/ precisa ser consistente com o código. O nome do arquivo especificado no código é o nome completo da classe da interface SPI. O conteúdo do arquivo também precisa ser escrito de acordo com (identificador da classe de implementação = nome completo da classe de implementação), para que possa ser consistente com o código, e o programa possa analisar corretamente o arquivo e usar o carregador de classes para carregue a classe correspondente. Finalmente, armazene em cache de acordo com <identificador de classe de implementação, objeto de classe de implementação>.
Devido à existência dos três caches (identificador de classe, objeto de classe), (objeto de classe, instância de objeto) e (identificador de classe, instância de objeto), você pode passar diretamente o identificador para obter a instância da classe correspondente, que também otimiza o desempenho da estrutura RPC.