将Spring的environment的配置动态绑定到配置类

在Spring中,我们有很多配置,如果一个个设置的话比较繁琐,参考Spring Boot的

@ConfigurationProperties(prefix = "spring.nbp")的方式类动态将spring.nbp前缀的配置全部绑定到我们的配置类就比较方便了。

好了,我们先上个实际使用示例,假设我们需要将spring.nbp前缀的配置绑定到DispatchProperty:

DispatchProperty property = BinderUtils.relaxedDataBinderBind(this.environment, DispatchProperty.PREFIX, DispatchProperty.class);

一行代码就搞定,那么这个类是如何帮我们来实现配置动态绑定的呢?我们抽丝剥茧来看一下关键类:

BinderUtils

package com.xxx.arch.mw.nbp.client.spring.bind;

import org.springframework.core.env.ConfigurableEnvironment;

import java.lang.reflect.Constructor;

/**
 * @created 2022-11-24 12:38 AM
 * @description:
 */
public class BinderUtils {

    public static  T relaxedDataBinderBind(ConfigurableEnvironment environment, String prefix, Class type) {
        T instance;
        try {
            Constructor constructor = type.getDeclaredConstructor();
            instance = constructor.newInstance();
        } catch (Throwable e) {
            throw new IllegalArgumentException(e);
        }
        if (environment != null) {
            new RelaxedDataBinder(instance, prefix)
                    .bind(new PropertySourcesPropertyValues(environment.getPropertySources()));
        }
        return instance;
    }
}

RelaxedDataBinder

package com.xxx.arch.mw.nbp.client.spring.bind;


import com.xxx.commons.data.constant.CommonConstants;
import org.springframework.beans.*;
import org.springframework.beans.propertyeditors.FileEditor;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.validation.DataBinder;

import java.beans.PropertyEditor;
import java.util.*;

/**
 * Binder implementation that allows caller to bind to maps and also allows property names
 * to match a bit loosely (if underscores or dashes are removed and replaced with camel
 * case for example).
 *
 * @author Dave Syer
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @see RelaxedNames
 */
public class RelaxedDataBinder extends DataBinder {

    private static final Set> EXCLUDED_EDITORS;
    private static final Object BLANK = new Object();
    private static final String REGEX = "[_\\-\\.]";
    private static final String EMPTY = "";

    static {
        Set> excluded = new HashSet>();
        excluded.add(FileEditor.class);
        EXCLUDED_EDITORS = Collections.unmodifiableSet(excluded);
    }

    private String namePrefix;

    private boolean ignoreNestedProperties;

    private MultiValueMap nameAliases = new LinkedMultiValueMap();

    /**
     * Create a new {@link RelaxedDataBinder} instance.
     *
     * @param target the target into which properties are bound
     */
    public RelaxedDataBinder(Object target) {
        super(wrapTarget(target));
    }

    /**
     * Create a new {@link RelaxedDataBinder} instance.
     *
     * @param target     the target into which properties are bound
     * @param namePrefix An optional prefix to be used when reading properties
     */
    public RelaxedDataBinder(Object target, String namePrefix) {
        super(wrapTarget(target), (StringUtils.hasLength(namePrefix) ? namePrefix : DEFAULT_OBJECT_NAME));
        this.namePrefix = cleanNamePrefix(namePrefix);
    }

    private static Object wrapTarget(Object target) {
        if (target instanceof Map) {
            @SuppressWarnings("unchecked") Map map = (Map) target;
            target = new MapHolder(map);
        }
        return target;
    }

    private String cleanNamePrefix(String namePrefix) {
        if (!StringUtils.hasLength(namePrefix)) {
            return null;
        }
        return (namePrefix.endsWith(CommonConstants.DOT) ? namePrefix : namePrefix + CommonConstants.DOT);
    }

    /**
     * Flag to disable binding of nested properties (i.e. those with period separators in
     * their paths). Can be useful to disable this if the name prefix is empty and you
     * don't want to ignore unknown fields.
     *
     * @param ignoreNestedProperties the flag to set (default false)
     */
    public void setIgnoreNestedProperties(boolean ignoreNestedProperties) {
        this.ignoreNestedProperties = ignoreNestedProperties;
    }

    /**
     * Set name aliases.
     *
     * @param aliases a map of property name to aliases
     */
    public void setNameAliases(Map> aliases) {
        this.nameAliases = new LinkedMultiValueMap(aliases);
    }

    /**
     * Add aliases to the {@link org.springframework.validation.DataBinder}.
     *
     * @param name  the property name to alias
     * @param alias aliases for the property names
     * @return this instance
     */
    public RelaxedDataBinder withAlias(String name, String... alias) {
        for (String value : alias) {
            this.nameAliases.add(name, value);
        }
        return this;
    }

    @Override
    protected void doBind(MutablePropertyValues propertyValues) {
        super.doBind(modifyProperties(propertyValues, getTarget()));
    }

    /**
     * Modify the property values so that period separated property paths are valid for
     * map keys. Also creates new maps for properties of map type that are null (assuming
     * all maps are potentially nested). The standard bracket {@code[...]} dereferencing
     * is also accepted.
     *
     * @param propertyValues the property values
     * @param target         the target object
     * @return modified property values
     */
    private MutablePropertyValues modifyProperties(MutablePropertyValues propertyValues, Object target) {
        propertyValues = getPropertyValuesForNamePrefix(propertyValues);
        if (target instanceof MapHolder) {
            propertyValues = addMapPrefix(propertyValues);
        }
        BeanWrapper wrapper = new BeanWrapperImpl(target);
        wrapper.setAutoGrowNestedPaths(true);
        List sortedValues = new ArrayList();
        Set modifiedNames = new HashSet();
        List sortedNames = getSortedPropertyNames(propertyValues);
        for (String name : sortedNames) {
            PropertyValue propertyValue = propertyValues.getPropertyValue(name);
            PropertyValue modifiedProperty = modifyProperty(wrapper, propertyValue);
            if (modifiedNames.add(modifiedProperty.getName())) {
                sortedValues.add(modifiedProperty);
            }
        }
        return new MutablePropertyValues(sortedValues);
    }

    private List getSortedPropertyNames(MutablePropertyValues propertyValues) {
        List names = new LinkedList();
        for (PropertyValue propertyValue : propertyValues.getPropertyValueList()) {
            names.add(propertyValue.getName());
        }
        sortPropertyNames(names);
        return names;
    }

    /**
     * Sort by name so that parent properties get processed first (e.g. 'foo.bar' before
     * 'foo.bar.spam'). Don't use Collections.sort() because the order might be
     * significant for other property names (it shouldn't be but who knows what people
     * might be relying on, e.g. HSQL has a JDBCXADataSource where "databaseName" is a
     * synonym for "url").
     *
     * @param names the names to sort
     */
    private void sortPropertyNames(List names) {
        for (String name : new ArrayList(names)) {
            int propertyIndex = names.indexOf(name);
            BeanPath path = new BeanPath(name);
            for (String prefix : path.prefixes()) {
                int prefixIndex = names.indexOf(prefix);
                if (prefixIndex >= propertyIndex) {
                    // The child property has a parent in the list in the wrong order
                    names.remove(name);
                    names.add(prefixIndex, name);
                }
            }
        }
    }

    private MutablePropertyValues addMapPrefix(MutablePropertyValues propertyValues) {
        MutablePropertyValues rtn = new MutablePropertyValues();
        for (PropertyValue pv : propertyValues.getPropertyValues()) {
            rtn.add("map." + pv.getName(), pv.getValue());
        }
        return rtn;
    }

    private MutablePropertyValues getPropertyValuesForNamePrefix(MutablePropertyValues propertyValues) {
        if (!StringUtils.hasText(this.namePrefix) && !this.ignoreNestedProperties) {
            return propertyValues;
        }
        MutablePropertyValues rtn = new MutablePropertyValues();
        for (PropertyValue value : propertyValues.getPropertyValues()) {
            String name = value.getName();
            for (String prefix : new RelaxedNames(stripLastDot(this.namePrefix))) {
                for (String separator : new String[]{
                        CommonConstants.DOT,
                        CommonConstants.UNDERLINE_SEPARATOR
                }) {
                    String candidate = (StringUtils.hasLength(prefix) ? prefix + separator : prefix);
                    if (name.startsWith(candidate)) {
                        name = name.substring(candidate.length());
                        if (!(this.ignoreNestedProperties && name.contains(CommonConstants.DOT))) {
                            rtn.addPropertyValue(new PropertyValue(name, value.getValue()));
                        }
                    }
                }
            }
        }
        return rtn;
    }

    private String stripLastDot(String string) {
        if (StringUtils.hasLength(string) && string.endsWith(CommonConstants.DOT)) {
            string = string.substring(0, string.length() - 1);
        }
        return string;
    }

    private PropertyValue modifyProperty(BeanWrapper target, PropertyValue propertyValue) {
        String name = propertyValue.getName();
        String normalizedName = normalizePath(target, name);
        if (!normalizedName.equals(name)) {
            return new PropertyValue(normalizedName, propertyValue.getValue());
        }
        return propertyValue;
    }

    /**
     * Normalize a bean property path to a format understood by a BeanWrapper. This is
     * used so that
     * 
    *
  • Fuzzy matching can be employed for bean property names
  • *
  • Period separators can be used instead of indexing ([...]) for map keys
  • *
* * @param wrapper a bean wrapper for the object to bind * @param path the bean path to bind * @return a transformed path with correct bean wrapper syntax */ protected String normalizePath(BeanWrapper wrapper, String path) { return initializePath(wrapper, new BeanPath(path), 0); } private String initializePath(BeanWrapper wrapper, BeanPath path, int index) { String prefix = path.prefix(index); String key = path.name(index); if (path.isProperty(index)) { key = getActualPropertyName(wrapper, prefix, key); path.rename(index, key); } if (path.name(++index) == null) { return path.toString(); } String name = path.prefix(index); TypeDescriptor descriptor = wrapper.getPropertyTypeDescriptor(name); if (descriptor == null || descriptor.isMap()) { if (isMapValueStringType(descriptor) || isBlanked(wrapper, name, path.name(index))) { path.collapseKeys(index); } path.mapIndex(index); extendMapIfNecessary(wrapper, path, index); } else if (descriptor.isCollection()) { extendCollectionIfNecessary(wrapper, path, index); } else if (descriptor.getType().equals(Object.class)) { if (isBlanked(wrapper, name, path.name(index))) { path.collapseKeys(index); } path.mapIndex(index); if (path.isLastNode(index)) { wrapper.setPropertyValue(path.toString(), BLANK); } else { String next = path.prefix(index + 1); if (wrapper.getPropertyValue(next) == null) { wrapper.setPropertyValue(next, new LinkedHashMap()); } } } return initializePath(wrapper, path, index); } private boolean isMapValueStringType(TypeDescriptor descriptor) { if (descriptor == null || descriptor.getMapValueTypeDescriptor() == null) { return false; } if (Properties.class.isAssignableFrom(descriptor.getObjectType())) { // Properties is declared as Map but we know it's really // Map return true; } Class valueType = descriptor.getMapValueTypeDescriptor().getObjectType(); return (valueType != null && CharSequence.class.isAssignableFrom(valueType)); } @SuppressWarnings("rawtypes") private boolean isBlanked(BeanWrapper wrapper, String propertyName, String key) { Object value = (wrapper.isReadableProperty(propertyName) ? wrapper.getPropertyValue(propertyName) : null); if (value instanceof Map) { if (((Map) value).get(key) == BLANK) { return true; } } return false; } private void extendCollectionIfNecessary(BeanWrapper wrapper, BeanPath path, int index) { String name = path.prefix(index); TypeDescriptor elementDescriptor = wrapper.getPropertyTypeDescriptor(name).getElementTypeDescriptor(); if (!elementDescriptor.isMap() && !elementDescriptor.isCollection() && !elementDescriptor.getType() .equals(Object.class)) { return; } Object extend = new LinkedHashMap(); if (!elementDescriptor.isMap() && path.isArrayIndex(index)) { extend = new ArrayList(); } wrapper.setPropertyValue(path.prefix(index + 1), extend); } private void extendMapIfNecessary(BeanWrapper wrapper, BeanPath path, int index) { String name = path.prefix(index); TypeDescriptor parent = wrapper.getPropertyTypeDescriptor(name); if (parent == null) { return; } TypeDescriptor descriptor = parent.getMapValueTypeDescriptor(); if (descriptor == null) { descriptor = TypeDescriptor.valueOf(Object.class); } if (!descriptor.isMap() && !descriptor.isCollection() && !descriptor.getType().equals(Object.class)) { return; } String extensionName = path.prefix(index + 1); if (wrapper.isReadableProperty(extensionName)) { Object currentValue = wrapper.getPropertyValue(extensionName); if ((descriptor.isCollection() && currentValue instanceof Collection) || (!descriptor.isCollection() && currentValue instanceof Map)) { return; } } Object extend = new LinkedHashMap(); if (descriptor.isCollection()) { extend = new ArrayList(); } if (descriptor.getType().equals(Object.class) && path.isLastNode(index)) { extend = BLANK; } wrapper.setPropertyValue(extensionName, extend); } private String getActualPropertyName(BeanWrapper target, String prefix, String name) { String propertyName = resolvePropertyName(target, prefix, name); if (propertyName == null) { propertyName = resolveNestedPropertyName(target, prefix, name); } return (propertyName != null ? propertyName : name); } private String resolveNestedPropertyName(BeanWrapper target, String prefix, String name) { StringBuilder candidate = new StringBuilder(); for (String field : name.split(REGEX)) { candidate.append(candidate.length() > 0 ? CommonConstants.DOT : EMPTY); candidate.append(field); String nested = resolvePropertyName(target, prefix, candidate.toString()); if (nested != null) { Class type = target.getPropertyType(nested); if ((type != null) && Map.class.isAssignableFrom(type)) { // Special case for map property (gh-3836). return nested + "[" + name.substring(candidate.length() + 1) + "]"; } String propertyName = resolvePropertyName(target, joinString(prefix, nested), name.substring(candidate.length() + 1)); if (propertyName != null) { return joinString(nested, propertyName); } } } return null; } private String resolvePropertyName(BeanWrapper target, String prefix, String name) { Iterable names = getNameAndAliases(name); for (String nameOrAlias : names) { for (String candidate : new RelaxedNames(nameOrAlias)) { try { if (target.getPropertyType(joinString(prefix, candidate)) != null) { return candidate; } } catch (InvalidPropertyException ex) { // swallow and continue } } } return null; } private String joinString(String prefix, String name) { return (StringUtils.hasLength(prefix) ? prefix + CommonConstants.DOT + name : name); } private Iterable getNameAndAliases(String name) { List aliases = this.nameAliases.get(name); if (aliases == null) { return Collections.singleton(name); } List nameAndAliases = new ArrayList(aliases.size() + 1); nameAndAliases.add(name); nameAndAliases.addAll(aliases); return nameAndAliases; } @Override public void registerCustomEditor(Class requiredType, PropertyEditor propertyEditor) { if (propertyEditor == null || !EXCLUDED_EDITORS.contains(propertyEditor.getClass())) { super.registerCustomEditor(requiredType, propertyEditor); } } @Override public void registerCustomEditor(Class requiredType, String field, PropertyEditor propertyEditor) { if (propertyEditor == null || !EXCLUDED_EDITORS.contains(propertyEditor.getClass())) { super.registerCustomEditor(requiredType, field, propertyEditor); } } /** * Holder to allow Map targets to be bound. */ static class MapHolder { private Map map; MapHolder(Map map) { this.map = map; } public Map getMap() { return this.map; } public void setMap(Map map) { this.map = map; } } /** * A path though properties of a bean. */ private static class BeanPath { private List nodes; BeanPath(String path) { this.nodes = splitPath(path); } public List prefixes() { List prefixes = new ArrayList(); for (int index = 1; index < this.nodes.size(); index++) { prefixes.add(prefix(index)); } return prefixes; } public boolean isLastNode(int index) { return index >= this.nodes.size() - 1; } private List splitPath(String path) { List nodes = new ArrayList(); String current = extractIndexedPaths(path, nodes); for (String name : StringUtils.delimitedListToStringArray(current, CommonConstants.DOT)) { if (StringUtils.hasText(name)) { nodes.add(new PropertyNode(name)); } } return nodes; } private String extractIndexedPaths(String path, List nodes) { int startRef = path.indexOf("["); String current = path; while (startRef >= 0) { if (startRef > 0) { nodes.addAll(splitPath(current.substring(0, startRef))); } int endRef = current.indexOf("]", startRef); if (endRef > 0) { String sub = current.substring(startRef + 1, endRef); if (sub.matches("[0-9]+")) { nodes.add(new ArrayIndexNode(sub)); } else { nodes.add(new MapIndexNode(sub)); } } current = current.substring(endRef + 1); startRef = current.indexOf("["); } return current; } public void collapseKeys(int index) { List revised = new ArrayList(); for (int i = 0; i < index; i++) { revised.add(this.nodes.get(i)); } StringBuilder builder = new StringBuilder(); for (int i = index; i < this.nodes.size(); i++) { if (i > index) { builder.append(CommonConstants.DOT); } builder.append(this.nodes.get(i).name); } revised.add(new PropertyNode(builder.toString())); this.nodes = revised; } public void mapIndex(int index) { PathNode node = this.nodes.get(index); if (node instanceof PropertyNode) { node = ((PropertyNode) node).mapIndex(); } this.nodes.set(index, node); } public String prefix(int index) { return range(0, index); } public void rename(int index, String name) { this.nodes.get(index).name = name; } public String name(int index) { if (index < this.nodes.size()) { return this.nodes.get(index).name; } return null; } private String range(int start, int end) { StringBuilder builder = new StringBuilder(); for (int i = start; i < end; i++) { PathNode node = this.nodes.get(i); builder.append(node); } if (builder.toString().startsWith((CommonConstants.DOT))) { builder.replace(0, 1, EMPTY); } return builder.toString(); } public boolean isArrayIndex(int index) { return this.nodes.get(index) instanceof ArrayIndexNode; } public boolean isProperty(int index) { return this.nodes.get(index) instanceof PropertyNode; } @Override public String toString() { return prefix(this.nodes.size()); } private static class PathNode { protected String name; PathNode(String name) { this.name = name; } } private static class ArrayIndexNode extends PathNode { ArrayIndexNode(String name) { super(name); } @Override public String toString() { return "[" + this.name + "]"; } } private static class MapIndexNode extends PathNode { MapIndexNode(String name) { super(name); } @Override public String toString() { return "[" + this.name + "]"; } } private static class PropertyNode extends PathNode { PropertyNode(String name) { super(name); } public MapIndexNode mapIndex() { return new MapIndexNode(this.name); } @Override public String toString() { return CommonConstants.DOT + this.name; } } } }

PropertyOrigin

package com.xxx.arch.mw.nbp.client.spring.bind;

import org.springframework.core.env.PropertySource;

/**
 * The origin of a property, specifically its source and its name before any prefix was
 * removed.
 *
 * @author Andy Wilkinson
 * @since 1.3.0
 */
public class PropertyOrigin {
    private final PropertySource source;

    private final String name;

    PropertyOrigin(PropertySource source, String name) {
        this.name = name;
        this.source = source;
    }

    public PropertySource getSource() {
        return this.source;
    }

    public String getName() {
        return this.name;
    }
}

OriginCapablePropertyValue

package com.xxx.arch.mw.nbp.client.spring.bind;

import org.springframework.beans.PropertyValue;
import org.springframework.core.env.PropertySource;

public class OriginCapablePropertyValue extends PropertyValue {
    private static final String ATTRIBUTE_PROPERTY_ORIGIN = "propertyOrigin";

    private final PropertyOrigin origin;

    OriginCapablePropertyValue(PropertyValue propertyValue) {
        this(propertyValue.getName(), propertyValue.getValue(),
                (PropertyOrigin) propertyValue.getAttribute(ATTRIBUTE_PROPERTY_ORIGIN));
    }

    OriginCapablePropertyValue(String name, Object value, String originName,
                               PropertySource originSource) {
        this(name, value, new PropertyOrigin(originSource, originName));
    }

    OriginCapablePropertyValue(String name, Object value, PropertyOrigin origin) {
        super(name, value);
        this.origin = origin;
        setAttribute(ATTRIBUTE_PROPERTY_ORIGIN, origin);
    }

    public PropertyOrigin getOrigin() {
        return this.origin;
    }

    @Override
    public String toString() {
        String name = (this.origin != null ? this.origin.getName() : this.getName());
        String source = (this.origin.getSource() != null
                ? this.origin.getSource().getName() : "unknown");
        return "'" + name + "' from '" + source + "'";
    }

    public static PropertyOrigin getOrigin(PropertyValue propertyValue) {
        if (propertyValue instanceof OriginCapablePropertyValue) {
            return ((OriginCapablePropertyValue) propertyValue).getOrigin();
        }
        return new OriginCapablePropertyValue(propertyValue).getOrigin();
    }
}

FlatPropertySources

package com.xxx.arch.mw.nbp.client.spring.bind;

import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;

import java.util.Iterator;

/**
 * Convenience class to flatten out a tree of property sources without losing the
 * reference to the backing data (which can therefore be updated in the background).
 */
public class FlatPropertySources implements PropertySources {

    private PropertySources propertySources;

    public FlatPropertySources(PropertySources propertySources) {
        this.propertySources = propertySources;
    }

    @Override
    public Iterator> iterator() {
        MutablePropertySources result = getFlattened();
        return result.iterator();
    }

    @Override
    public boolean contains(String name) {
        return get(name) != null;
    }

    @Override
    public PropertySource get(String name) {
        return getFlattened().get(name);
    }

    private MutablePropertySources getFlattened() {
        MutablePropertySources result = new MutablePropertySources();
        for (PropertySource propertySource : this.propertySources) {
            flattenPropertySources(propertySource, result);
        }
        return result;
    }

    private void flattenPropertySources(PropertySource propertySource, MutablePropertySources result) {
        Object source = propertySource.getSource();
        if (source instanceof ConfigurableEnvironment) {
            ConfigurableEnvironment environment = (ConfigurableEnvironment) source;
            for (PropertySource childSource : environment.getPropertySources()) {
                flattenPropertySources(childSource, result);
            }
        } else {
            result.addLast(propertySource);
        }
    }

}

PropertySourcesPropertyValues

package com.xxx.arch.mw.nbp.client.spring.bind;


import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.util.Assert;
import org.springframework.util.PatternMatchUtils;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

/**
 * A {@link org.springframework.beans.PropertyValues} implementation backed by a {@link org.springframework.core.env.PropertySources}, bridging
 * the two abstractions and allowing (for instance) a regular {@link org.springframework.validation.DataBinder} to be
 * used with the latter.
 *
 * @author Dave Syer
 * @author Phillip Webb
 */
public class PropertySourcesPropertyValues implements PropertyValues {

    private static final Pattern COLLECTION_PROPERTY = Pattern.compile("\\[(\\d+)\\](\\.\\S+)?");

    private final PropertySources propertySources;

    private final Collection nonEnumerableFallbackNames;

    private final PropertyNamePatternsMatcher includes;

    private final Map propertyValues = new LinkedHashMap();

    private final ConcurrentHashMap> collectionOwners = new ConcurrentHashMap>();

    private final boolean resolvePlaceholders;

    /**
     * Create a new PropertyValues from the given PropertySources.
     *
     * @param propertySources a PropertySources instance
     */
    public PropertySourcesPropertyValues(PropertySources propertySources) {
        this(propertySources, true);
    }

    /**
     * Create a new PropertyValues from the given PropertySources that will optionally
     * resolve placeholders.
     *
     * @param propertySources     a PropertySources instance
     * @param resolvePlaceholders {@code true} if placeholders should be resolved.
     * @since 1.5.2
     */
    public PropertySourcesPropertyValues(PropertySources propertySources, boolean resolvePlaceholders) {
        this(propertySources, (Collection) null, PropertyNamePatternsMatcher.ALL, resolvePlaceholders);
    }

    /**
     * Create a new PropertyValues from the given PropertySources.
     *
     * @param propertySources            a PropertySources instance
     * @param includePatterns            property name patterns to include from system properties and
     *                                   environment variables
     * @param nonEnumerableFallbackNames the property names to try in lieu of an
     *                                   {@link org.springframework.core.env.EnumerablePropertySource}.
     */
    public PropertySourcesPropertyValues(PropertySources propertySources, Collection includePatterns,
                                         Collection nonEnumerableFallbackNames) {
        this(propertySources, nonEnumerableFallbackNames, new PatternPropertyNamePatternsMatcher(includePatterns),
                true);
    }

    /**
     * Create a new PropertyValues from the given PropertySources.
     *
     * @param propertySources            a PropertySources instance
     * @param nonEnumerableFallbackNames the property names to try in lieu of an
     *                                   {@link org.springframework.core.env.EnumerablePropertySource}.
     * @param includes                   the property name patterns to include
     * @param resolvePlaceholders        flag to indicate the placeholders should be resolved
     */
    PropertySourcesPropertyValues(PropertySources propertySources, Collection nonEnumerableFallbackNames,
                                  PropertyNamePatternsMatcher includes, boolean resolvePlaceholders) {
        Assert.notNull(propertySources, "PropertySources must not be null");
        Assert.notNull(includes, "Includes must not be null");
        this.propertySources = propertySources;
        this.nonEnumerableFallbackNames = nonEnumerableFallbackNames;
        this.includes = includes;
        this.resolvePlaceholders = resolvePlaceholders;
        PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
        for (PropertySource source : propertySources) {
            processPropertySource(source, resolver);
        }
    }

    private void processPropertySource(PropertySource source, PropertySourcesPropertyResolver resolver) {
        if (source instanceof EnumerablePropertySource) {
            processEnumerablePropertySource((EnumerablePropertySource) source, resolver, this.includes);
        } else {
            processNonEnumerablePropertySource(source, resolver);
        }
    }

    private void processEnumerablePropertySource(EnumerablePropertySource source,
                                                 PropertySourcesPropertyResolver resolver, PropertyNamePatternsMatcher includes) {
        if (source.getPropertyNames().length > 0) {
            for (String propertyName : source.getPropertyNames()) {
                if (includes.matches(propertyName)) {
                    Object value = getEnumerableProperty(source, resolver, propertyName);
                    putIfAbsent(propertyName, value, source);
                }
            }
        }
    }

    private Object getEnumerableProperty(EnumerablePropertySource source, PropertySourcesPropertyResolver resolver,
                                         String propertyName) {
        try {
            if (this.resolvePlaceholders) {
                return resolver.getProperty(propertyName, Object.class);
            }
        } catch (RuntimeException ex) {
            // Probably could not resolve placeholders, ignore it here
        }
        return source.getProperty(propertyName);
    }

    private void processNonEnumerablePropertySource(PropertySource source,
                                                    PropertySourcesPropertyResolver resolver) {
        // We can only do exact matches for non-enumerable property names, but
        // that's better than nothing...
        if (this.nonEnumerableFallbackNames == null) {
            return;
        }
        for (String propertyName : this.nonEnumerableFallbackNames) {
            if (!source.containsProperty(propertyName)) {
                continue;
            }
            Object value = null;
            try {
                value = resolver.getProperty(propertyName, Object.class);
            } catch (RuntimeException ex) {
                // Probably could not convert to Object, weird, but ignorable
            }
            if (value == null) {
                value = source.getProperty(propertyName.toUpperCase(Locale.ENGLISH));
            }
            putIfAbsent(propertyName, value, source);
        }
    }

    @Override
    public PropertyValue[] getPropertyValues() {
        Collection values = this.propertyValues.values();
        return values.toArray(new PropertyValue[values.size()]);
    }

    @Override
    public PropertyValue getPropertyValue(String propertyName) {
        PropertyValue propertyValue = this.propertyValues.get(propertyName);
        if (propertyValue != null) {
            return propertyValue;
        }
        for (PropertySource source : this.propertySources) {
            Object value = source.getProperty(propertyName);
            propertyValue = putIfAbsent(propertyName, value, source);
            if (propertyValue != null) {
                return propertyValue;
            }
        }
        return null;
    }

    private PropertyValue putIfAbsent(String propertyName, Object value, PropertySource source) {
        if (value != null && !this.propertyValues.containsKey(propertyName)) {
            PropertySource collectionOwner = this.collectionOwners.putIfAbsent(
                    COLLECTION_PROPERTY.matcher(propertyName).replaceAll("[]"), source);
            if (collectionOwner == null || collectionOwner == source) {
                PropertyValue propertyValue = new OriginCapablePropertyValue(propertyName, value, propertyName, source);
                this.propertyValues.put(propertyName, propertyValue);
                return propertyValue;
            }
        }
        return null;
    }

    @Override
    public PropertyValues changesSince(PropertyValues old) {
        MutablePropertyValues changes = new MutablePropertyValues();
        // for each property value in the new set
        for (PropertyValue newValue : getPropertyValues()) {
            // if there wasn't an old one, add it
            PropertyValue oldValue = old.getPropertyValue(newValue.getName());
            if (oldValue == null || !oldValue.equals(newValue)) {
                changes.addPropertyValue(newValue);
            }
        }
        return changes;
    }

    @Override
    public boolean contains(String propertyName) {
        return getPropertyValue(propertyName) != null;
    }

    @Override
    public boolean isEmpty() {
        return this.propertyValues.isEmpty();
    }

    static interface PropertyNamePatternsMatcher {

        PropertyNamePatternsMatcher ALL = new PropertyNamePatternsMatcher() {

            @Override
            public boolean matches(String propertyName) {
                return true;
            }

        };

        PropertyNamePatternsMatcher NONE = new PropertyNamePatternsMatcher() {

            @Override
            public boolean matches(String propertyName) {
                return false;
            }

        };

        /**
         * Return {@code true} of the property name matches.
         *
         * @param propertyName the property name
         * @return {@code true} if the property name matches
         */
        boolean matches(String propertyName);

    }

    static class PatternPropertyNamePatternsMatcher implements PropertyNamePatternsMatcher {

        private final String[] patterns;

        PatternPropertyNamePatternsMatcher(Collection patterns) {
            this.patterns = (patterns != null ? patterns.toArray(new String[patterns.size()]) : new String[] {});
        }

        @Override
        public boolean matches(String propertyName) {
            return PatternMatchUtils.simpleMatch(this.patterns, propertyName);
        }

    }

}

RelaxedNames

package com.xxx.arch.mw.nbp.client.spring.bind;


import org.springframework.util.StringUtils;

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Generates relaxed name variations from a given source.
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @see RelaxedDataBinder
 */
public final class RelaxedNames implements Iterable {

    private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("([^A-Z-])([A-Z])");

    private static final Pattern SEPARATED_TO_CAMEL_CASE_PATTERN = Pattern
            .compile("[_\\-.]");

    private final String name;

    private final Set values = new LinkedHashSet();

    /**
     * Create a new {@link RelaxedNames} instance.
     *
     * @param name the source name. For the maximum number of variations specify the name
     *             using dashed notation (e.g. {@literal my-property-name}
     */
    public RelaxedNames(String name) {
        this.name = (name != null ? name : "");
        initialize(RelaxedNames.this.name, this.values);
    }

    @Override
    public Iterator iterator() {
        return this.values.iterator();
    }

    private void initialize(String name, Set values) {
        if (values.contains(name)) {
            return;
        }
        for (Variation variation : Variation.values()) {
            for (Manipulation manipulation : Manipulation.values()) {
                String result = name;
                result = manipulation.apply(result);
                result = variation.apply(result);
                values.add(result);
                initialize(result, values);
            }
        }
    }

    /**
     * Name variations.
     */
    enum Variation {

        NONE {
            @Override
            public String apply(String value) {
                return value;
            }

        },

        LOWERCASE {
            @Override
            public String apply(String value) {
                return (value.isEmpty() ? value : value.toLowerCase(Locale.ENGLISH));
            }

        },

        UPPERCASE {
            @Override
            public String apply(String value) {
                return (value.isEmpty() ? value : value.toUpperCase(Locale.ENGLISH));
            }

        };

        public abstract String apply(String value);

    }

    /**
     * Name manipulations.
     */
    enum Manipulation {

        NONE {
            @Override
            public String apply(String value) {
                return value;
            }

        },

        HYPHEN_TO_UNDERSCORE {
            @Override
            public String apply(String value) {
                return (value.indexOf('-') != -1 ? value.replace('-', '_') : value);
            }

        },

        UNDERSCORE_TO_PERIOD {
            @Override
            public String apply(String value) {
                return (value.indexOf('_') != -1 ? value.replace('_', '.') : value);
            }

        },

        PERIOD_TO_UNDERSCORE {
            @Override
            public String apply(String value) {
                return (value.indexOf('.') != -1 ? value.replace('.', '_') : value);
            }

        },

        CAMELCASE_TO_UNDERSCORE {
            @Override
            public String apply(String value) {
                if (value.isEmpty()) {
                    return value;
                }
                Matcher matcher = CAMEL_CASE_PATTERN.matcher(value);
                if (!matcher.find()) {
                    return value;
                }
                matcher = matcher.reset();
                StringBuffer result = new StringBuffer();
                while (matcher.find()) {
                    matcher.appendReplacement(result, matcher.group(1) + '_'
                            + StringUtils.uncapitalize(matcher.group(2)));
                }
                matcher.appendTail(result);
                return result.toString();
            }

        },

        CAMELCASE_TO_HYPHEN {
            @Override
            public String apply(String value) {
                if (value.isEmpty()) {
                    return value;
                }
                Matcher matcher = CAMEL_CASE_PATTERN.matcher(value);
                if (!matcher.find()) {
                    return value;
                }
                matcher = matcher.reset();
                StringBuffer result = new StringBuffer();
                while (matcher.find()) {
                    matcher.appendReplacement(result, matcher.group(1) + '-'
                            + StringUtils.uncapitalize(matcher.group(2)));
                }
                matcher.appendTail(result);
                return result.toString();
            }

        },

        SEPARATED_TO_CAMELCASE {
            @Override
            public String apply(String value) {
                return separatedToCamelCase(value, false);
            }

        },

        CASE_INSENSITIVE_SEPARATED_TO_CAMELCASE {
            @Override
            public String apply(String value) {
                return separatedToCamelCase(value, true);
            }

        };

        private static final char[] SUFFIXES = new char[]{'_', '-', '.'};

        public abstract String apply(String value);

        private static String separatedToCamelCase(String value,
                                                   boolean caseInsensitive) {
            if (value.isEmpty()) {
                return value;
            }
            StringBuilder builder = new StringBuilder();
            for (String field : SEPARATED_TO_CAMEL_CASE_PATTERN.split(value)) {
                field = (caseInsensitive ? field.toLowerCase(Locale.ENGLISH) : field);
                builder.append(
                        builder.length() != 0 ? StringUtils.capitalize(field) : field);
            }
            char lastChar = value.charAt(value.length() - 1);
            for (char suffix : SUFFIXES) {
                if (lastChar == suffix) {
                    builder.append(suffix);
                    break;
                }
            }
            return builder.toString();
        }

    }

    /**
     * Return a {@link com.guahao.mrpc.config.bind.RelaxedNames} for the given source camelCase source name.
     *
     * @param name the source name in camelCase
     * @return the relaxed names
     */
    public static RelaxedNames forCamelCase(String name) {
        StringBuilder result = new StringBuilder();
        for (char c : name.toCharArray()) {
            result.append(Character.isUpperCase(c) && result.length() > 0
                    && result.charAt(result.length() - 1) != '-'
                    ? "-" + Character.toLowerCase(c) : c);
        }
        return new RelaxedNames(result.toString());
    }

}

你可能感兴趣的:(spring,数据库,sql)