欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > IT业 > DangerWind-RPC-framework---四、SPI

DangerWind-RPC-framework---四、SPI

2024/10/25 21:17:57 来源:https://blog.csdn.net/m0_60424152/article/details/140336458  浏览:    关键词:DangerWind-RPC-framework---四、SPI

        SPI 即 Service Provider Interface ,可以理解为专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。很多框架都使用了 Java 的 SPI 机制,比如:Spring 框架、数据库加载驱动、日志接口、以及 Dubbo 的扩展实现等等。参考Dubbo的SPI机制,来实现本RPC框架的SPI部分。

        举个例子,client端在与server端进行通信时,需要对消息进行序列化。序列化时可以使用序列化算法有很多,包括Hessian、Kryo、ProtoStuff。系统的需求是根据消息中的序列化算法名称来调用相关序列化算法对应的类中的方法来进行序列化与反序列化,加之为了便于扩展,需要使用SPI来进行解耦。

        SPI的使用方式如下:

Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class).getExtension(codecName);

        codecName是序列化算法名称,需要根据该名称加载出对应的类。

    private final Class<?> type;private ExtensionLoader(Class<?> type) {this.type = type;}// 每个SPI接口都有自身的ExtensionLoaderpublic 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 oneExtensionLoader<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;}

        每个SPI接口都有自身的ExtensionLoader,调用getExtensionLoader时,首先会进行一系列的合法检查操作,之后会尝试获取该接口的ExtensionLoader,先尝试本地缓存CHM中获取,获取不到的话再创建Loader对象。

        之后通过getExtension获取实例,实例也进行了本地缓存,缓存中没有的话再创建实例。

    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// 缓存holderHolder<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;}

          获取到类的Class对象后可以通过反射的方式创建此对象。 

   // 缓存   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文件的映射,根据标识名获取classClass<?> 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;}

         关键是获取Class对象的过程 ,即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 checkif (classes == null) {synchronized (cachedClasses) {classes = cachedClasses.get();if (classes == null) {classes = new HashMap<>();// load all extensions from our extensions directoryloadDirectory(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 commentfinal int ci = line.indexOf('#');if (ci >= 0) {// string after # is comment so we ignore itline = 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 emptyif (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

       逐层的方法调用,实现了加载META-INF/extensions/路径下对应SPI配置文件从而加载Class对象并获取实例的过程,重要部分可参考注释。

       需要注意META-INF/extensions/下的文件名需要与代码里一致,代码里指定的文件名是SPI接口类的全类名。文件里的内容也需要按照(实现类标识=实现类全类名)来编写,这样才能与代码一致,程序才可以正确解析文件,并使用类加载器加载对应的Class。最后按照<实现类标识,实现类Class对象>进行缓存。

       由于(类标识,Class对象)、(Class对象,对象实例)、(类标识,对象实例)这三个缓存的存在,后续可以直接传入标识获取到对应类的实例,也优化了RPC框架的性能。

 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com