zoukankan      html  css  js  c++  java
  • 模仿JDK实现SPI服务

    此文章不是介绍SPI服务的概念,是基于对SPI有一定了解,想深入研究其实现原理的文章。本文介绍了SPI实现的整个过程,并结合配置文件的配置模拟实际业务场景中服务的调用。话不多说,直接开码。

    1、准备环境

    1.1 安装lombok插件,pom文件导入以下依赖

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.8</version>
        <scope>provided</scope>
    </dependency>
    <!--日志-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.28</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.28</version>
    </dependency>
    <!--yaml读取-->
    <dependency>
        <groupId>org.jyaml</groupId>
        <artifactId>jyaml</artifactId>
        <version>1.3</version>
    </dependency>
    

    1.2 resources目录结构如下

    在这里插入图片描述

    1. 日志配置log4j.properties
    # 日记级别(单个级别) 文件/控制台
    log4j.rootLogger=debug, stdout,file
    
    # Redirect log messages to console
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Target=System.out
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
    
    # Rirect log messages to a log file
    log4j.appender.file=org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=test.log
    log4j.appender.file.MaxFileSize=5MB
    log4j.appender.file.MaxBackupIndex=10
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
    
    1. SPI接口文件com.freesky.spi.myspi.IHello
    com.freesky.spi.myspi.HelloImpl1
    com.freesky.spi.myspi.HelloImpl2
    
    1. 配置文件myapplication.yml
    hello:
      # 指定实现
      impl: com.freesky.spi.myspi.HelloImpl2
    

    2、准备服务

    2.1 创建服务接口

    public interface IHello {
        void hello();
    }
    

    2.2 准备两个实现类

    public class HelloImpl1 implements IHello {
        @Override
        public void hello() {
            System.out.println("妖怪!出来,我是你孙爷爷!");
        }
    }
    
    public class HelloImpl2 implements IHello {
        @Override
        public void hello() {
            System.out.println("大师兄不好了,师傅和二师兄被妖怪抓走了");
        }
    }
    

    2.2 yaml读取工具类

    import org.ho.yaml.Yaml;
    import java.io.InputStream;
    import java.util.Map;
    import java.util.Objects;
    
    /**
     * @author 陈玉林
     * @projectName java-basic
     * @className YamlUtil
     * @description TODO
     * @date 2019/12/12 21:27
     */
    public class YamlUtil {
        private YamlUtil() {}
        private static final String YAML_SPLIT_SYMBOL = "\.";
    
        @SuppressWarnings("unchecked")
        public static Map<String, String> getYamlMap(String ymalResourceName) {
            InputStream resourceAsStream = YamlUtil.class.getClassLoader().getResourceAsStream(ymalResourceName);
            return (Map<String, String>) Yaml.load(resourceAsStream);
        }
        public static String getYamlValue(Map map, String property) {
            Objects.requireNonNull(property, "属性不可为空");
            String[] propertyKeys = property.split(YAML_SPLIT_SYMBOL);
            for (String next : propertyKeys) {
                Object o = map.get(next);
                if (o instanceof Map) {
                    map = (Map) o;
                } else {
                    return String.valueOf(map.get(next));
                }
            }
            return "";
        }
    }
    

    2.3 实现服务加载器MyServiceLoader

    import lombok.extern.slf4j.Slf4j;
    import java.io.*;
    import java.net.URL;
    import java.util.*;
    
    @Slf4j
    public class MyServiceLoader<S> {
    
        /**
         * 配置文件的路径
         */
        private static final String PREFIX = "META-INF/myservices/";
        /**
         * 被加载的服务类或接口
         */
        private final Class<S> service;
        /**
         * 缓存已加载的服务类集合
         */
        private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
        /**
         * 类加载器
         */
        private final ClassLoader loader;
    
        private MyServiceLoader(Class<S> svc, ClassLoader cl) {
            service = Objects.requireNonNull(svc, "服务接口不可为空");
            loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
            reload();
        }
    
        private void reload() {
            providers.clear();
            String fullName = PREFIX + service.getName();
            try {
                Enumeration<URL> resources = loader.getResources(fullName);
                while (resources.hasMoreElements()) {
                    URL url = resources.nextElement();
                    String filename = url.getFile();
                    List<String> implNames = readLines(new File(filename));
                    Set<String> noRepeatImplNames = getNoRepeatImplNames(implNames);
                    log.info("实现类名称{}", Arrays.toString(noRepeatImplNames.toArray()));
                    if (!noRepeatImplNames.isEmpty()) {
                        noRepeatImplNames.forEach(name -> {
                            S result = getInstance(name);
                            if (Objects.nonNull(result)) {
                                providers.put(name, result);
                            }
                        });
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
        /**
         * @description 服务文件内容读取 
         * @status 使用中
         * @param 
         * @author 陈玉林
         * @date 2019/12/13 20:34
         */
        @SuppressWarnings("unchecked")
        private List<String> readLines(File file) throws IOException {
            if (file.exists()) {
                if (file.isDirectory()) {
                    throw new IOException("文件 '" + file + "'是文件夹");
                } else if (!file.canRead()) {
                    throw new IOException("文件 '" + file + "' 不可读");
                } else {
                    FileInputStream input = new FileInputStream(file);
                    InputStreamReader reader = new InputStreamReader(input);
                    BufferedReader bufferedReader = new BufferedReader(reader);
                    List<String> list = new ArrayList();
    
                    for(String line = bufferedReader.readLine(); line != null; line = bufferedReader.readLine()) {
                        list.add(line);
                    }
                    return list;
                }
            } else {
                throw new FileNotFoundException("文件"+file+"不存在");
            }
        }
    
        /**
         * @description 反射获取实现类对象 
         * @status 使用中
         * @param 
         * @author 陈玉林
         * @date 2019/12/13 20:34
         */
        private S getInstance(String name) {
            try {
                Class<?> aClass = Class.forName(name, false, loader);
                Object instance = aClass.newInstance();
                return service.cast(instance);
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * @param
         * @description 去重
         * @status 使用中
         * @author 陈玉林
         * @date 2019/12/13 17:28
         */
        private Set<String> getNoRepeatImplNames(List<String> implNames) {
            return new HashSet<>(implNames);
        }
    
        private List<S> getServices() {
            List<S> list = new ArrayList<>();
            Set<Map.Entry<String, S>> entries = providers.entrySet();
            entries.forEach(i -> {
                list.add(i.getValue());
            });
            return list;
        }
    
    
        public static <S> List<S> load(Class<S> s, ClassLoader cl) {
            return new MyServiceLoader<>(s, cl).getServices();
        }
    
    }
    

    2.4 测试

    import com.freesky.common.YamlUtil;
    import java.util.List;
    public class Main {
        public static void main(String[] args) {
            final String targetImpl = YamlUtil.getYamlValue(YamlUtil.getYamlMap("myapplication.yml"), "hello.impl");
            List<IHello> services = MyServiceLoader.load(IHello.class, MyServiceLoader.class.getClassLoader());
    
            services.forEach(item -> {
                //模拟调用场景,进行配置文件配置实现类的服务调用
                if (item.getClass().getName().equals(targetImpl)) {
                    item.hello();
                }
            });
        }
    }
    

    输出:

    2019-12-13 20:42:43 INFO  MyServiceLoader:44 - 实现类名称[com.freesky.spi.myspi.HelloImpl2, com.freesky.spi.myspi.HelloImpl1]
    大师兄不好了,师傅和二师兄被妖怪抓走了
    

    修改yml配置文件为以下内容

    hello:
      # 指定实现
      impl: com.freesky.spi.myspi.HelloImpl1
    

    输出:

    2019-12-13 20:42:18 INFO  MyServiceLoader:44 - 实现类名称[com.freesky.spi.myspi.HelloImpl2, com.freesky.spi.myspi.HelloImpl1]
    妖怪!出来,我是你孙爷爷!
    
    只有把命运掌握在自己手中,从今天起开始努力,即使暂时看不到希望,也要相信自己。因为比你牛几倍的人,依然在努力。
  • 相关阅读:
    Webpack的组成部分 半 详解
    代码简洁之道:编写干净的 React Components & JSX
    大体量点位置数据动态聚合Binning可视化效果
    树莓派FRP服务器自启动失败原因及解决办法
    nopCommerce自学笔记(一、环境搭建)-BigIcicle
    NopCommerce4.3中文版资源
    利用阿里云防勒索备份文件->ibdata 和 frm 文件恢复 MySQL 数据库
    ubuntu mysql设置sql_mode
    Ubuntu16.04 中PHP7.0 安装pdo_mysql 扩展
    相邻两个生产计划之间的衔接问题
  • 原文地址:https://www.cnblogs.com/freesky168/p/14358181.html
Copyright © 2011-2022 走看看