第五十八章 Spring之假如让你来写Boot——配置文件篇(下)

Spring源码阅读目录

第一部分——IOC篇

第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇

第二部分——AOP篇

第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
第十三章 Spring之假如让你来写AOP——AOP联盟篇
第十四章 Spring之假如让你来写AOP——雏形篇
第十五章 Spring之假如让你来写AOP——Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
第十八章 Spring之假如让你来写AOP——Advice(通知)下篇
第十九章 Spring之假如让你来写AOP——番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP——Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇
第二十三章 Spring之假如让你来写AOP——融入IOC容器篇
第二十四章 Spring之源码阅读——AOP篇

第三部分——事务篇

第二十五章 Spring之曾经的老朋友——事务
第二十六章 Spring之假如让你来写事务——初稿篇
第二十七章 Spring之假如让你来写事务——铁三角篇
第二十八章 Spring之假如让你来写事务——属性篇
第二十九章 Spring之假如让你来写事务——状态篇
第三十章 Spring之假如让你来写事务——管理篇
第三十一章 Spring之假如让你来写事务——融入IOC容器篇
第三十二章 Spring之源码阅读——事务篇

第四部分——MVC篇

第三十三章 Spring之梦开始的地方——MVC
第三十四章 Spring之假如让你来写MVC——草图篇
第三十五章 Spring之假如让你来写MVC——映射器篇
第三十六章 Spring之假如让你来写MVC——拦截器篇
第三十七章 Spring之假如让你来写MVC——控制器篇
第三十八章 Spring之假如让你来写MVC——适配器篇
第三十九章 Spring之假如让你来写MVC——番外篇:类型转换
第四十章 Spring之假如让你来写MVC——ModelAndView篇
第四十一章 Spring之假如让你来写MVC——番外篇:数据绑定
第四十二章 Spring之假如让你来写MVC——视图篇
第四十三章 Spring之假如让你来写MVC——上传文件篇
第四十四章 Spring之假如让你来写MVC——异常处理器篇
第四十五章 Spring之假如让你来写MVC——国际化篇
第四十六章 Spring之假如让你来写MVC——主题解析器篇
第四十七章 Spring之假如让你来写MVC——闪存管理器篇
第四十八章 Spring之假如让你来写MVC——请求映射视图篇
第四十九章 Spring之假如让你来写MVC——番外篇:属性操作
第五十章 Spring之假如让你来写MVC——融入IOC容器篇
第五十一章 Spring之源码阅读——MVC篇

第五部分——Boot篇

第五十二章 Spring之再进一步——Boot
第五十三章 Spring之假如让你来写Boot——环境篇
第五十四章 Spring之假如让你来写Boot——注解篇(上)
第五十五章 Spring之假如让你来写Boot——注解篇(下)
第五十六章 Spring之假如让你来写Boot——SPI篇
第五十七章 Spring之假如让你来写Boot——配置文件篇(上)
第五十八章 Spring之假如让你来写Boot——配置文件篇(下)
第五十九章 Spring之假如让你来写Boot——番外篇:再谈Bean定义
第六十章 Spring之假如让你来写Boot——自动装配篇
第六十一章 Spring之假如让你来写Boot——番外篇:杂谈Starter
第六十二章 Spring之假如让你来写Boot——番外篇:重构BeanFactory
第六十三章 Spring之假如让你来写Boot——番外篇:再谈ApplicationContext
第六十四章 Spring之假如让你来写Boot——内嵌Web容器篇
第六十五章 Spring之假如让你来写Boot——Main方法启动篇
第六十六章 Spring之最终章——结语篇



前言

    对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
在这里插入图片描述

    所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》


     书接上回,在上篇 第五十七章 Spring之假如让你来写Boot——配置文件篇(上) 中,A君 因为昨天思维有点乱,配置文件还没有完全实现就跑路了,今天也该还昨天的欠下的账了。。。接下来看看 A君 会有什么骚操作吧

尝试动手写IOC容器

    出场人物:A君(苦逼的开发)、老大(项目经理)

    背景:老大 要求 A君在一周内开发个简单的 IOC容器

    前情提要:A君 因为昨天思维有点乱,配置文件还没有完全实现就跑路了,今天也该还昨天的欠下的账了。。。

四十七版 配置文件

配置文件加载器

    现在配置文件资源有了、也可以定位到配置文件的位置了,那么接下来要做事情,就是把配置文件加载出来。之前提到过配置文件可以再分为:配置树、标准配置文件,那么配置文件加载器自然而然也有多个了,有多个?不用多说,定义个接口。A君 新增ConfigDataLoader接口,代码如下:

/**
 * 配置文件加载
 *
 * @param <R>
 */
public interface ConfigDataLoader<R extends ConfigDataResource> {

    /**
     * 是否可加载
     *
     * @param context
     * @param resource
     * @return
     */
    default boolean isLoadable(ConfigDataLoaderContext context, R resource) {
        return true;
    }

    /**
     * 加载配置文件
     *
     * @param context
     * @param resource
     * @return
     * @throws IOException
     * @throws ConfigDataResourceNotFoundException
     */
    ConfigData load(ConfigDataLoaderContext context, R resource)
            throws IOException, ConfigDataResourceNotFoundException;

}

老样子,还是以标准配置文件为例。StandardConfigDataLoader代码如下:

public class StandardConfigDataLoader implements ConfigDataLoader<StandardConfigDataResource> {

    private static final ConfigData.PropertySourceOptions PROFILE_SPECIFIC =
            ConfigData.PropertySourceOptions.always(ConfigData.Option.PROFILE_SPECIFIC);

    private static final ConfigData.PropertySourceOptions NON_PROFILE_SPECIFIC =
            ConfigData.PropertySourceOptions.ALWAYS_NONE;

    @Override
    public ConfigData load(ConfigDataLoaderContext context, StandardConfigDataResource resource)
            throws IOException, ConfigDataNotFoundException {
        /**
         * 空文件夹
         */
        if (resource.isEmptyDirectory()) {
            return ConfigData.EMPTY;
        }
        /**
         * 判断文件是否存在
         */
        ConfigDataResourceNotFoundException.throwIfDoesNotExist(resource, resource.getResource());
        /**
         * 获取资源信息
         */
        StandardConfigDataReference reference = resource.getReference();

        Resource originTrackedResource = OriginTrackedResource.of(resource.getResource(),
                Origin.from(reference.getConfigDataLocation()));
        String name = String.format("Config resource '%s' via location '%s'", resource,
                reference.getConfigDataLocation());
        /**
         * 调用具体加载器进行加载
         */
        List<PropertySource<?>> propertySources = reference.getPropertySourceLoader()
                .load(name, originTrackedResource);
        ConfigData.PropertySourceOptions options = (resource.getProfile() != null) ? PROFILE_SPECIFIC : NON_PROFILE_SPECIFIC;
        return new ConfigData(propertySources, options);
    }
}

总的来说,干的事情并不多,就简单校验下资源是否存在。其他其他事又扔给了其他类去做了

标准配置文件加载器

    上文只是对配置文件进行了粗略的分类。接下来就是对具体配置文件类型进行分类了,像标准配置文件中就可以分:propertiesyml等。在这种情况下,肯定得定义个接口来规范不同的配置文件。A君 定义PropertySourceLoader接口,代码如下:


/**
 * 配置加载器
 */
public interface PropertySourceLoader {

    /**
     * 获取文件拓展名
     *
     * @return
     */
    String[] getFileExtensions();

    /**
     * 加载配置
     *
     * @param name
     * @param resource
     * @return
     * @throws IOException
     */
    List<PropertySource<?>> load(String name, Resource resource) throws IOException;

}

现在主流的配置文件主要分为:YamlPropertyYaml有层级结构,使用起来比较方便一点,所以会更流行些。这里 A君 就以Yaml为例。新增YamlPropertySourceLoader类,代码如下:

public class YamlPropertySourceLoader implements PropertySourceLoader {

    /**
     * 支持的后缀
     *
     * @return
     */
    @Override
    public String[] getFileExtensions() {
        return new String[]{"yml", "yaml"};
    }

    @Override
    public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
        /**
         * 是否存在org.yaml.snakeyaml.Yaml
         */
        if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", getClass().getClassLoader())) {
            throw new IllegalStateException(
                    "Attempted to load " + name + " but snakeyaml was not found on the classpath");
        }
        /**
         * 加载Yaml文件
         */
        List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();
        if (loaded.isEmpty()) {
            return Collections.emptyList();
        }
        List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size());
        for (int i = 0; i < loaded.size(); i++) {
            String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : "";
            propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber,
                    Collections.unmodifiableMap(loaded.get(i)), true));
        }
        return propertySources;
    }
}

Yaml解析器

    Yaml文件的解析这部分内容比较通用,而且已经有现成的jar包实现了,比如:snakeyaml 等。没必要重复造轮子,直接引用即可。现在要做的是把解析Yaml流程单独提取出来,方便之后公用。A君 提取个YamlProcessor抽象类,作为整个解析Yaml的公共定义,如下:

public abstract class YamlProcessor {

    /**
     * 解析方式
     */
    private ResolutionMethod resolutionMethod = ResolutionMethod.OVERRIDE;

    /**
     * 资源集
     */
    private Resource[] resources = new Resource[0];

    private List<DocumentMatcher> documentMatchers = Collections.emptyList();

    private boolean matchDefault = true;

    private Set<String> supportedTypes = Collections.emptySet();


    protected void process(MatchCallback callback) {
        /**
         * 创建Yaml对象,遍历资源
         */
        Yaml yaml = createYaml();
        for (Resource resource : this.resources) {
            boolean found = process(callback, yaml, resource);
            /**
             * 如果解析方式是第一次匹配,直接返回
             */
            if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND && found) {
                return;
            }
        }
    }

    /**
     * 创建Yaml对象
     *
     * @return
     */
    protected Yaml createYaml() {
        /**
         * 选项
         */
        LoaderOptions loaderOptions = new LoaderOptions();
        loaderOptions.setAllowDuplicateKeys(false);
        return new Yaml(new FilteringConstructor(loaderOptions), new Representer(),
                new DumperOptions(), loaderOptions);
    }

    private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
        int count = 0;
        try {
            try (Reader reader = new UnicodeReader(resource.getInputStream())) {
                /**
                 * 加载资源
                 */
                for (Object object : yaml.loadAll(reader)) {
                    if (object != null && process(asMap(object), callback)) {
                        count++;
                        if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND) {
                            break;
                        }
                    }
                }
            }
        } catch (IOException ex) {
            handleProcessError(resource, ex);
        }
        return (count > 0);
    }


    private boolean process(Map<String, Object> map, MatchCallback callback) {
        Properties properties = CollectionFactory.createStringAdaptingProperties();
        properties.putAll(getFlattenedMap(map));

        if (this.documentMatchers.isEmpty()) {
            callback.process(properties, map);
            return true;
        }

        MatchStatus result = MatchStatus.ABSTAIN;
        for (DocumentMatcher matcher : this.documentMatchers) {
            MatchStatus match = matcher.matches(properties);
            result = MatchStatus.getMostSpecific(match, result);
            if (match == MatchStatus.FOUND) {
                callback.process(properties, map);
                return true;
            }
        }

        if (result == MatchStatus.ABSTAIN && this.matchDefault) {
            callback.process(properties, map);
            return true;
        }

        return false;
    }



    public enum MatchStatus {

        /**
         * 找到
         */
        FOUND,

        /**
         * 未找到
         */
        NOT_FOUND,

        /**
         * 放弃
         */
        ABSTAIN;

        public static MatchStatus getMostSpecific(MatchStatus a, MatchStatus b) {
            return (a.ordinal() < b.ordinal() ? a : b);
        }
    }


    /**
     * 如何解析资源
     */
    public enum ResolutionMethod {

        /**
         * 替换
         */
        OVERRIDE,

        /**
         * 替换忽略失败
         */
        OVERRIDE_AND_IGNORE,

        /**
         * 第一个匹配
         */
        FIRST_FOUND
    }


    /**
     * 用于处理YAML解析结果的回调接口
     */
    @FunctionalInterface
    public interface MatchCallback {

        /**
         * 处理解析结果的给定表示形式
         */
        void process(Properties properties, Map<String, Object> map);
    }


    /**
     * 属性匹配接口
     */
    @FunctionalInterface
    public interface DocumentMatcher {
        MatchStatus matches(Properties properties);
    }

    /**
     * 过滤器构造器
     */
    private class FilteringConstructor extends Constructor {

        FilteringConstructor(LoaderOptions loaderOptions) {
            super(loaderOptions);
        }

        @Override
        protected Class<?> getClassForName(String name) throws ClassNotFoundException {
            return super.getClassForName(name);
        }
    }
	//省略其他代码。。。
}


好了,接下来就是具体实现了,YamlProcessor抽象类基本把能做的事情都做了,实现类只需要定义些选项即可。A君 新增OriginTrackedYamlLoader类,代码如下:

class OriginTrackedYamlLoader extends YamlProcessor {

    private static final boolean HAS_RESOLVER_LIMIT = ReflectionUtils.findMethod(Resolver.class, "addImplicitResolver",
            Tag.class, Pattern.class, String.class, int.class) != null;

    private final Resource resource;

    OriginTrackedYamlLoader(Resource resource) {
        this.resource = resource;
        setResources(resource);
    }

    @Override
    protected Yaml createYaml() {
        LoaderOptions loaderOptions = new LoaderOptions();
        loaderOptions.setAllowDuplicateKeys(false);
        loaderOptions.setMaxAliasesForCollections(Integer.MAX_VALUE);
        loaderOptions.setAllowRecursiveKeys(true);
        return createYaml(loaderOptions);
    }

    private Yaml createYaml(LoaderOptions loaderOptions) {
        BaseConstructor constructor = new OriginTrackingConstructor(loaderOptions);
        Representer representer = new Representer();
        DumperOptions dumperOptions = new DumperOptions();
        Resolver resolver = HAS_RESOLVER_LIMIT ? new NoTimestampResolverWithLimit() : new NoTimestampResolver();
        return new Yaml(constructor, representer, dumperOptions, loaderOptions, resolver);
    }

    List<Map<String, Object>> load() {
        final List<Map<String, Object>> result = new ArrayList<>();
        process((properties, map) -> result.add(getFlattenedMap(map)));
        return result;
    }
    //省略其他代码。。。
}
配置导入

    折腾了这么大一圈,又是加载器,又是定位器。A君 自己都快晕了,更别说让别人使用了。常言道:士因士,女因媒。这里就需要一个‘媒人’来把他们凑成一对。A君 定义ConfigDataImporter类,代码如下:

/**
 * 配置导入
 */
public class ConfigDataImporter {

    /**
     * 配置资源定位器
     */
    private final ConfigDataLocationResolvers resolvers;

    /**
     * 配置加载器
     */
    private final ConfigDataLoaders loaders;

    /**
     * 配置未找到时回调
     */
    private final ConfigDataNotFoundAction notFoundAction;

    /**
     * 配置文件资源
     */
    private final Set<ConfigDataResource> loaded = new HashSet<>();

    /**
     * 配置文件路径
     */
    private final Set<ConfigDataLocation> loadedLocations = new HashSet<>();

    /**
     * 可选配置文件路径
     */
    private final Set<ConfigDataLocation> optionalLocations = new HashSet<>();


    public Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataLocation... locations) {
        return this.resolveAndLoad(null, null, null, Arrays.asList(locations));
    }

    public Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
                                                                      ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,
                                                                      List<ConfigDataLocation> locations) {
        try {
            /**
             * 多环境
             */
            Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;
            //定位资源
            List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);
            //加载资源
            return load(loaderContext, resolved);
        } catch (IOException ex) {
            throw new IllegalStateException("IO error on loading imports from " + locations, ex);
        }
    }

    private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
                                                     Profiles profiles, List<ConfigDataLocation> locations) {
        List<ConfigDataResolutionResult> resolved = new ArrayList<>(locations.size());
        /**
         * 遍历所有路径
         */
        for (ConfigDataLocation location : locations) {
            resolved.addAll(resolve(locationResolverContext, profiles, location));
        }
        return Collections.unmodifiableList(resolved);
    }

    private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
                                                     Profiles profiles, ConfigDataLocation location) {
        try {
            return this.resolvers.resolve(locationResolverContext, location, profiles);
        } catch (ConfigDataNotFoundException ex) {
            handle(ex, location, null);
            return Collections.emptyList();
        }
    }

    private Map<ConfigDataResolutionResult, ConfigData> load(ConfigDataLoaderContext loaderContext,
                                                             List<ConfigDataResolutionResult> candidates) throws IOException {
        Map<ConfigDataResolutionResult, ConfigData> result = new LinkedHashMap<>();
        for (int i = candidates.size() - 1; i >= 0; i--) {
            ConfigDataResolutionResult candidate = candidates.get(i);
            ConfigDataLocation location = candidate.getLocation();
            ConfigDataResource resource = candidate.getResource();
            /**
             * 是否可选
             */
            if (resource.isOptional()) {
                this.optionalLocations.add(location);
            }
            if (this.loaded.contains(resource)) {
                this.loadedLocations.add(location);
            } else {
                try {
                    /**
                     * 加载配置
                     */
                    ConfigData loaded = this.loaders.load(loaderContext, resource);
                    if (loaded != null) {
                        this.loaded.add(resource);
                        this.loadedLocations.add(location);
                        result.put(candidate, loaded);
                    }
                } catch (ConfigDataNotFoundException ex) {
                    handle(ex, location, resource);
                }
            }
        }
        return Collections.unmodifiableMap(result);
    }

	//省略其他代码。。。
}


别看这么多代码,就干两件事,第一步定位到配置文件,第二步加载配置文件。就这些,没了

测试

    好了,折腾了这么久,总算可以进入测试阶段了。得先利用 SPI 机制,把什么加载器、定位器啊的具体实现配置上去,这样就可以把所有的实现加载进去,已达到拓展的效果了。A君 修改spring.factories文件,改动如下:


# PropertySource Loaders
com.hqd.ch03.v47.boot.env.PropertySourceLoader=\
com.hqd.ch03.v47.boot.env.PropertiesPropertySourceLoader,\
com.hqd.ch03.v47.boot.env.YamlPropertySourceLoader

# ConfigData Location Resolvers
com.hqd.ch03.v47.boot.context.config.ConfigDataLocationResolver=\
com.hqd.ch03.v47.boot.context.config.StandardConfigDataLocationResolver

# ConfigData Loaders
com.hqd.ch03.v47.boot.context.config.ConfigDataLoader=\
com.hqd.ch03.v47.boot.context.config.StandardConfigDataLoader

接下来,就可以定义一个配置文件了。A君 新增application.yml文件,如下:

server:
  port: 8080  # 设置服务端口

spring:
  application:
    name: demo-application  # 应用名称
  datasource:
    url: jdbc:mysql://localhost:3306/demo?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver  # 数据库驱动

接着,就可以编写测试代码了,如下:

@Test
    public void v47() throws NoSuchMethodException, IOException {
        System.out.println("############# 第四十七版: 配置文件篇 #############");
        //定义默认加载器
        DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
        //定义资源定位器
        ConfigDataLocationResolvers configDataLocationResolvers =
                new ConfigDataLocationResolvers(new Binder(), resourceLoader);
        //配置文件导入
        ConfigDataImporter configDataImporter = new ConfigDataImporter(ConfigDataNotFoundAction.FAIL, configDataLocationResolvers,
                new ConfigDataLoaders(resourceLoader.getClassLoader()));
        Map<ConfigDataResolutionResult, ConfigData> configDataResolutionResultConfigDataMap =
                configDataImporter.resolveAndLoad(ConfigDataLocation.of("classpath:/v47/application.yml"));

        configDataResolutionResultConfigDataMap.forEach((k, v) -> {
            System.out.println(k.getLocation() + ":");
            List<PropertySource<?>> propertySources = v.getPropertySources();
            propertySources.forEach(ps -> {
                System.out.println(ps.getSource());
            });
        });
    }

测试结果如下:

在这里插入图片描述

好了,就结果来说,还是让人欣慰的。不过因为这两天 A君 的思路出了些问题,写的有点乱,还望大伙儿海涵。不过怎么样,今天就先到这里了。下班

在这里插入图片描述


总结

    正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穷儒公羊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值