iT邦幫忙

0

Spring boot Properties value 去除空白

  • 分享至 

  • xImage
  •  

Spring Boot 設定 Properties Value 去除空白

概述

在設計Spring boot應用程式時,因環境不同需要設定不同的Properties Value,例如資料庫連線Server,spring.profiles.active,或其他自訂變數時,常常不小心在資料後面多了空白而產生錯誤但肉眼難發現,後來想到之前有說明 Spring Boot資料庫存取密碼保護的方法(https://ithelp.ithome.com.tw/articles/10398877),所以處理加密資料的 PropertySecureConvertero 取得Properties Value資料時, 順便將字串資料做Trim處理,避免因資料後面多個空白,而產生錯誤。主要調整 private void process(ConfigurableEnvironment environment) Method。

調整後的PropertySecureConverter如下:

package tw.lewishome.webapp;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * ConfigurationPropertySourcesConverter 是一個實現 {@link EnvironmentPostProcessor}
 * 和 {@link Ordered} 的類別,
 * 用於處理 Spring 應用程序的環境配置屬性源。
 * 
 * <p>
 * 此類別的主要功能是遍歷 {@link ConfigurableEnvironment} 中的屬性源,尋找
 * {@link org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource}
 * 類型的屬性源,
 * 並從中提取配置屬性。提取的屬性將被包裝在 {@link MapPropertySource} 中,並插入到環境的屬性源列表中。
 * </p>
 * 
 * <p>
 * 此類別的工作流程如下:
 * </p>
 * <ol>
 * <li>在
 * {@link #postProcessEnvironment(ConfigurableEnvironment, SpringApplication)}
 * 方法中調用 {@link #process(ConfigurableEnvironment)} 方法。</li>
 * <li>在 {@link #process(ConfigurableEnvironment)} 方法中,遍歷環境中的所有屬性源。</li>
 * <li>檢查每個屬性源是否為
 * {@link org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource}
 * 類型。</li>
 * <li>如果找到,則嘗試提取其配置屬性並將其添加為新的 {@link MapPropertySource}。</li>
 * </ol>
 * 
 * <p>
 * 此類別的優先順序由 {@link #getOrder()} 方法定義,預設為 {@link Ordered#HIGHEST_PRECEDENCE} +
 * 50。
 * </p>
 */
@Slf4j
public class PropertySecureConverter implements EnvironmentPostProcessor, Ordered {
    /**
     * Fix for javadoc warning :
     * use of default constructor, which does not provide a comment
     * Constructs a new PropertySecureConverter instance.
     * This is the default constructor, implicitly provided by the compiler
     * and can be used to create a new instance of the class.
     * 
     */
    public PropertySecureConverter() {
        // Constructor body (can be empty)
    }

    private static final Logger logger = LoggerFactory.getLogger(PropertySecureConverter.class);

    /**
     * 處理應用程序的環境設置。
     * 
     * 此方法在應用程序啟動過程中被調用,以便對可配置的環境進行後處理。
     * 
     * @param environment 可配置的環境,包含應用程序的屬性和配置。
     * @param application 當前的 Spring 應用程序實例。
     */
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {

        log.info("┌────────────────────────────────────────────────────┐");
		log.info("│ postProcessEnvironment started !                   │");
		log.info("└────────────────────────────────────────────────────┘");

        try {
            process(environment);
        } catch (Exception e) {
            logger.debug("Failed to process ConfigurationPropertySourcesPropertySource: {}", e.toString());
        }
    }

    /**
     * 處理給定的 ConfigurableEnvironment,從中提取配置屬性源並將其添加為 MapPropertySource。
     * 
     * <p>
     * 此方法遍歷環境中的所有 PropertySource,尋找特定類型的 PropertySource
     * (即 ConfigurationPropertySourcesPropertySource)。對於每個找到的配置屬性源,
     * 該方法嘗試提取其底層源並將其轉換為 Map。如果成功提取到的 Map 不為空,
     * 則將其作為新的 MapPropertySource 插入到環境中,並在日誌中記錄相關信息。
     * </p>
     * 
     * @param environment 要處理的 ConfigurableEnvironment 實例
     */
    private void process(ConfigurableEnvironment environment) {
        // 找尋所有 PropertySource 來源
        for (PropertySource<?> ps : environment.getPropertySources()) {
            if (ps == null)
                continue;
            String className = ps.getClass().getName();
            // 檢查是否為 ConfigurationPropertySourcesPropertySource 類型
            if ("org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource"
                    .equals(className)) {
                logger.info("Found ConfigurationPropertySourcesPropertySource: {}", ps.getName());
                try {
                    // 嘗試取得配置屬性來源
                    Object cfgPropSources = tryGetConfigurationPropertySources(ps);
                    // 如果成功取得,則進行 Looping 處理
                    if (cfgPropSources != null) {
                        Iterator<?> it = asIterator(cfgPropSources);
                        int idx = 0;
                        while (it != null && it.hasNext()) {
                            Object cfgSource = it.next();
                            // 嘗試取得底層來源
                            Object underlying = tryGetUnderlyingSource(cfgSource);
                            if (underlying != null) {
                                // 嘗試從底層來源提取 Map
                                Map<String, Object> extracted = tryExtractMap(underlying);
                                if (extracted != null && !extracted.isEmpty()) {
                                    // 建立並插入新的 MapPropertySource
                                    String newName = ps.getName() + "-cfg-extracted-" + idx++;
                                    MapPropertySource mps = new MapPropertySource(newName, extracted);
                                    // Insert before the original source
                                    environment.getPropertySources().addBefore(ps.getName(), mps);
                                    logger.info("Inserted extracted MapPropertySource '{}' with {} entries", newName,
                                            extracted.size());
                                }
                            }
                        }
                    }
                } catch (Throwable t) {
                    logger.debug("Error while extracting configuration property sources: {}", t.toString());
                }
            }
        }
    }

    /**
     * 將給定的物件轉換為Iterator。
     *
     * @param iterable 要轉換的物件,可以是任何實現了 Iterable 接口的物件,
     *                 或者具有 iterator() 方法的物件。
     * @return 如果物件為 null,則返回 null;如果物件是 Iterable 的實例,
     *         則返回其迭代器;如果物件具有 iterator() 方法,則返回該方法的返回值,
     *         否則返回 null。
     */
    private Iterator<?> asIterator(Object iterable) {
        if (iterable == null)
            return null;
        if (iterable instanceof Iterable) {
            return ((Iterable<?>) iterable).iterator();
        }
        try {
            Method iterator = iterable.getClass().getMethod("iterator");
            Object it = iterator.invoke(iterable);
            if (it instanceof Iterator)
                return (Iterator<?>) it;
        } catch (Exception ignored) {
        }
        return null;
    }

    /**
     * 嘗試獲取配置屬性來源。
     *
     * 此方法首先檢查給定的 PropertySource 物件是否具有名為
     * "getConfigurationPropertySources" 的方法。如果存在,則調用該方法並返回其結果。
     * 如果該方法不存在,則嘗試訪問名為 "configurationPropertySources" 的私有字段。
     * 如果該字段也不存在,則最後嘗試訪問名為 "sources" 的私有字段。
     * 
     * @param ps 要檢查的 PropertySource 物件
     * @return 返回配置屬性來源的對象,如果無法獲取,則返回 null
     */
    private Object tryGetConfigurationPropertySources(PropertySource<?> ps) {
        try {
            Method m = ps.getClass().getMethod("getConfigurationPropertySources");
            return m.invoke(ps);
        } catch (Exception e) {
            try {
                Field f = ps.getClass().getDeclaredField("configurationPropertySources");
                f.setAccessible(true);
                return f.get(ps);
            } catch (Exception ex) {
                try {
                    Field f2 = ps.getClass().getDeclaredField("sources");
                    f2.setAccessible(true);
                    return f2.get(ps);
                } catch (Exception ignored) {
                }
            }
        }
        return null;
    }

    /**
     * 嘗試從給定的配置來源中獲取底層來源。
     * 
     * @param cfgSource 要檢查的配置來源對象,可能為 null。
     * @return 如果找到底層來源,則返回該來源;否則返回 null。
     * 
     *         此方法將檢查配置來源對象的多個方法和字段,以獲取其底層來源。
     *         它首先嘗試調用以下方法:getUnderlyingSource、getSource、getMap 和 getProperties。
     *         如果這些方法都不存在或返回 null,則將檢查名為 source 和 underlyingSource 的字段。
     * 
     *         如果在任何步驟中找到非 null 的值,則立即返回該值。
     *         如果所有檢查都未找到有效的來源,則返回 null。
     */
    private Object tryGetUnderlyingSource(Object cfgSource) {
        if (cfgSource == null)
            return null;
        try {
            for (String mName : new String[] { "getUnderlyingSource", "getSource", "getMap", "getProperties" }) {
                try {
                    Method m = cfgSource.getClass().getMethod(mName);
                    Object val = m.invoke(cfgSource);
                    if (val != null)
                        return val;
                } catch (NoSuchMethodException ignored) {
                }
            }
            for (String fName : new String[] { "source", "underlyingSource" }) {
                try {
                    Field f = cfgSource.getClass().getDeclaredField(fName);
                    f.setAccessible(true);
                    Object val = f.get(cfgSource);
                    if (val != null)
                        return val;
                } catch (NoSuchFieldException ignored) {
                }
            }
        } catch (Exception ignored) {
        }
        return null;
    }

    /**
     * 嘗試從給定的物件中提取屬性映射。
     *
     * <p>
     * 此方法接受一個物件,並根據其類型提取屬性名稱和值,返回一個包含這些屬性的映射。如果物件為 {@code null},則返回 {@code null}。
     * </p>
     *
     * <p>
     * 支持的物件類型包括:
     * </p>
     * <ul>
     * <li>{@link Map}:直接將其內容複製到返回的映射中。</li>
     * <li>{@link PropertySource}:如果是
     * {@link org.springframework.core.env.EnumerablePropertySource},則提取其所有屬性名稱和值。</li>
     * <li>其他類型:通過反射查找 {@code getPropertyNames} 和 {@code getProperty} 方法來提取屬性。</li>
     * </ul>
     *
     * @param underlying 要提取屬性的物件
     * @return 包含屬性名稱和值的映射,如果未能提取任何屬性則返回 {@code null}
     */
    @SuppressWarnings("unchecked")
    private Map<String, Object> tryExtractMap(Object underlying) {
        // 如果底層來源為 null,則返回 null
        if (underlying == null)
            return null;
        Map<String, Object> map = new HashMap<>();
        try {
            // 檢查底層來源的類型並提取屬性
            if (underlying instanceof Map) {
                // 處理 Map 類型
                Map<String, Object> u = (Map<String, Object>) underlying;
                map.putAll(u);
                return map;
            }
            // 嘗試處理 PropertySource 類型
            if (underlying instanceof PropertySource) {
                PropertySource<?> ps = (PropertySource<?>) underlying;
                if (ps instanceof org.springframework.core.env.EnumerablePropertySource) {
                    // 處理 EnumerablePropertySource 類型
                    org.springframework.core.env.EnumerablePropertySource<?> eps = (org.springframework.core.env.EnumerablePropertySource<?>) ps;
                    for (String name : eps.getPropertyNames()) {
                        map.put(name, eps.getProperty(name));
                    }
                    return map;
                }
            }
            // 嘗試通過反射調用 getPropertyNames 和 getProperty 方法
            try {
                Method m = underlying.getClass().getMethod("getPropertyNames");
                Object names = m.invoke(underlying);
                if (names instanceof String[]) {
                    // 處理屬性名稱陣列
                    for (String n : (String[]) names) {
                        try {
                            Method gm = underlying.getClass().getMethod("getProperty", String.class);
                            Object v = gm.invoke(underlying, n);
                            map.put(n, v);
                        } catch (Exception ignored) {
                        }
                    }
                    return map;
                }
            } catch (Exception ignored) {
            }
        } catch (Exception ignored) {
            ignored.printStackTrace();
        }
        // 如果無法提取任何屬性,則返回 null ,否則返回提取的映射
        return map.isEmpty() ? null : map;
    }

    /**
     * 取得此屬性來源的優先順序。
     * 
     * <p>
     * 回傳的優先順序值為 {@link Ordered#HIGHEST_PRECEDENCE} + 50,
     * 表示此屬性來源在較高優先順序位置被載入。
     * 數值越小,優先順序越高。
     * </p>
     * 
     * @return 優先順序值,為 {@link Ordered#HIGHEST_PRECEDENCE} + 50
     */
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 50;
    }
}


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言