spring篇-(ssm自定义propertySource-加载classpath中application.yml|json|properties等配置文件实现)

写在前面

通过自定义PropertySource,实现加载classpath目录下面的application.json,
application.properties,application.yml等配置文件,让ssm项目也有springboot的配置功能

项目初始化

项目结构如下

locator包,就是存放这次核心功能所在的包,里面实现了加载application等配置文件的逻辑,resources目录,则负责创建这个bean,将配置加载的功能注入到spring容器,其他,同基本的ssm项目结构相同

在这里插入图片描述

pom文件如下

其中主要使用了yaml和fastjson来解析配置内容,并使用了fastjson的库,实现数据扁平化操作

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lhstack.custom.config</groupId>
    <artifactId>custom-config</artifactId>
    <version>0.0.1</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <servlet.version>4.0.1</servlet.version>
        <spring.version>5.2.12.RELEASE</spring.version>
        <snakeyaml.version>1.29</snakeyaml.version>
        <fastjson.version>1.2.73</fastjson.version>
        <commons-io.version>2.8.0</commons-io.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>${commons-io.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>${snakeyaml.version}</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>

</project>

定义locator抽象接口

通过此接口,定义配置加载规范,方便扩展后续的动态配置相关内容

package com.lhstack.custom.config.locator;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;

import java.io.IOException;
import java.util.List;

/**
 * @author lhstack
 * @date 2021/8/22
 * @class PropertySourceLocator
 * @since 1.8
 */
public interface PropertySourceLocator {

    /**
     * 加载propertySource
     *
     * @return
     */
    List<PropertySource<?>> locator() throws IOException;


    /**
     * 初始化环境变量
     *
     * @param environment
     * @param beanFactory
     */
    default void initEnvironment(Environment environment, ConfigurableListableBeanFactory beanFactory) {

    }

}

创建ApplicationPropertySouceLocator实现类

通过此类,实现application.json|yml|yaml|propertis等配置文件的加载,其中最先加载的配置优先级为最高

package com.lhstack.custom.config.locator;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * @author lhstack
 * @date 2021/8/22
 * @class ApplicationPropertySourceLocator
 * @since 1.8
 */
public class ApplicationPropertySourceLocator implements PropertySourceLocator {

    @Override
    public void initEnvironment(Environment environment, ConfigurableListableBeanFactory beanFactory) {

    }

    @Override
    public List<PropertySource<?>> locator() throws IOException {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        List<PropertySource<?>> list = new ArrayList<>();
        propertiesPropertySource(list, contextClassLoader);
        yamlPropertySource(list, contextClassLoader);
        jsonPropertySource(list, contextClassLoader);
        return list;
    }

    /**
     * 加载application.json的配置内容
     * @param list
     * @param contextClassLoader
     * @throws IOException
     */
    private void jsonPropertySource(List<PropertySource<?>> list, ClassLoader contextClassLoader) throws IOException {
        InputStream in = contextClassLoader.getResourceAsStream("application.json");
        if (Objects.nonNull(in)) {
            Map<String, Object> map = PropertySourceUtils.parseJson(in);
            list.add(new MapPropertySource("application.json", map));
            in.close();
        }
    }

    /**
     * 加载application.yml|yaml等配置文件
     * @param list
     * @param contextClassLoader
     * @throws IOException
     */
    private void yamlPropertySource(List<PropertySource<?>> list, ClassLoader contextClassLoader) throws IOException {
        InputStream in = contextClassLoader.getResourceAsStream("application.yml");
        if (Objects.nonNull(in)) {
            Map<String, Object> map = PropertySourceUtils.parseYaml(in);
            list.add(new MapPropertySource("application.yml", map));
            in.close();
        }
        in = contextClassLoader.getResourceAsStream("application.yaml");
        if (Objects.nonNull(in)) {
            Map<String, Object> map = PropertySourceUtils.parseYaml(in);
            list.add(new MapPropertySource("application.yaml", map));
            in.close();
        }
    }

    /**
     * 加载properties配置文件
     * @param list
     * @param contextClassLoader
     * @throws IOException
     */
    private void propertiesPropertySource(List<PropertySource<?>> list, ClassLoader contextClassLoader) throws IOException {
        InputStream in = contextClassLoader.getResourceAsStream("application.properties");
        if (Objects.nonNull(in)) {
            Properties properties = new Properties();
            properties.load(in);
            list.add(new PropertiesPropertySource("application.properties", properties));
            in.close();
        }
    }
}

扁平化和配置解析类PropertySourceUtils

package com.lhstack.custom.config.locator;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.IOUtils;
import org.yaml.snakeyaml.Yaml;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lhstack
 * @date 2021/8/22
 * @class PropertySourceUtils
 * @since 1.8
 */
public class PropertySourceUtils {

    public static Map<String, Object> parseYaml(InputStream in) {
        Yaml yaml = new Yaml();
        Map<String, Object> map = new HashMap<>();
        initSourceToMap("", yaml.loadAs(in, JSONObject.class), map);
        return map;
    }

    /**
     * 将数据扁平化
     */
    private static void initSourceToMap(String parentKey, JSONObject jsonObject, Map<String, Object> map) {
        jsonObject.forEach((k, v) -> {
            if (v instanceof Map<?, ?>) {
                if (parentKey == null || parentKey.isEmpty()) {
                    initSourceToMap(k, jsonObject.getJSONObject(k), map);
                } else {
                    initSourceToMap(parentKey + "." + k, jsonObject.getJSONObject(k), map);
                }
            } else {
                if (parentKey == null || parentKey.isEmpty()) {
                    map.put(k, v);
                } else {
                    map.put(parentKey + "." + k, v);
                }
            }
        });
    }

    public static Map<String, Object> parseJson(InputStream in) {
        Map<String, Object> map = new HashMap<>();
        try {
            byte[] bytes = IOUtils.toByteArray(in);
            initSourceToMap("", JSONObject.parseObject(new String(bytes, StandardCharsets.UTF_8)), map);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return map;
    }
}

创建PropertySourceLocatorProcessor,用于实现将PropertySouce注入到Environment中

package com.lhstack.custom.config.locator;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.Environment;

import java.util.Map;

/**
 * @author lhstack
 * @date 2021/8/22
 * @class CustomPropertySourcesPlaceholderConfigurer
 * @since 1.8
 */
public class PropertySourceLocatorProcessor implements EnvironmentAware, BeanFactoryPostProcessor {

    private AbstractEnvironment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = (AbstractEnvironment) environment;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            Map<String, PropertySourceLocator> beansOfType = beanFactory.getBeansOfType(PropertySourceLocator.class);
            ApplicationPropertySourceLocator applicationPropertySourceLocator = new ApplicationPropertySourceLocator();
            applicationPropertySourceLocator.initEnvironment(this.environment, beanFactory);
            applicationPropertySourceLocator.locator().forEach(propertySource -> this.environment.getPropertySources().addLast(propertySource));
            beansOfType.values().forEach(item -> {
                item.initEnvironment(this.environment, beanFactory);
            });
            for (PropertySourceLocator value : beansOfType.values()) {
                value.locator().forEach(e -> {
                    this.environment.getPropertySources().addLast(e);
                });
            }
            //定义PropertySourcesPlaceholderConfigurer,用于解析标签里面spel表达式
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = null;
            try {
                propertySourcesPlaceholderConfigurer = beanFactory.getBean(PropertySourcesPlaceholderConfigurer.class);
            } catch (Exception e) {
                propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
            }
            propertySourcesPlaceholderConfigurer.setEnvironment(this.environment);
            propertySourcesPlaceholderConfigurer.setPropertySources(this.environment.getPropertySources());
            propertySourcesPlaceholderConfigurer.postProcessBeanFactory(beanFactory);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

定义测试相关

spring配置如下

spring-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean class="com.lhstack.custom.config.locator.PropertySourceLocatorProcessor"/>

</beans>

spring-web.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>application/json</value>
                        <value>application/json;charset=utf-8</value>
                    </list>
                </property>
                <property name="fastJsonConfig">
                    <bean class="com.alibaba.fastjson.support.config.FastJsonConfig">
                        <property name="charset" value="UTF-8"/>
                        <property name="dateFormat" value="yyyy-MM-dd HH:mm:ss"/>
                    </bean>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <context:component-scan base-package="com.lhstack.custom.config.controller">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <bean class="com.lhstack.custom.config.controller.TestController$TestEnv">
        <property name="value" value="${spring.application.profiles}"/>
    </bean>
</beans>

application.yml

spring:
  application:
    name: custom-config
    profiles: dev

TestController.java

package com.lhstack.custom.config.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lhstack
 * @date 2021/8/22
 * @class TestController
 * @since 1.8
 */
@RestController
@RequestMapping
public class TestController {

    @Value("${spring.application.name}")
    private String name;

    @Autowired
    private TestEnv testEnv;

    @GetMapping
    public String test() {
        return name + "-" + testEnv.getValue();
    }

    public static class TestEnv{

        @Value("spring.application.profiles")
        private String value;

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }
}

启动并访问测试接口

在这里插入图片描述
在这里插入图片描述

写在后面

此功能主要实现原理: 实现spring的BeanFactoryPostProcessor和EnvironmentAware,其中通过EnvironmentAware获得Environtment对象,此对象为容器上下文的环境变量,BeanFactoryPostProcessor则可以获得Spring容器上下文的BeanFactory对象,可以从中获取到配置的PropertyLocator对象,同时解析标签的spel表达式对象PropertySourcesPlaceholderConfigurer也会使用到这个BeanFactory,在这个postProcessBeanFactory回调函数中,实现propertySource的加载和标签spel表达式的解析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值