background
In the SpringBoot environment, we have "incomplete" annotations.This is why SpringBoot replaces the xml configuration in traditional Spring projects.When using these annotations, we must understand the principles and conventions behind these annotations.
package org.springframework.boot.context.properties; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ConfigurationProperties { ...... }
Supported Types
List
custom.config.config1.folders[0]=/root custom.config.config1.folders[1]=/home/user1 custom.config.config1.folders[2]=/home/user2
Corresponding Java implementation
@ConfigurationProperties(prefix = "custom.config.config1") public class Config1Properties{ private List<String> folders; ... }
Map
custom.config.config1.map.key1=value1 custom.config.config1.map.key2=value2 custom.config.config1.map.key3=value3 custom.config.config1.map.key4=value4 custom.config.config1.map.key5=value5
Corresponding Java implementation
@ConfigurationProperties(prefix = "custom.config.config1") public class Config1Properties{ private Map<String, String> map; ... }
Object
custom.config.config1.server.host=host1 custom.config.config1.server.port=22 custom.config.config1.server.username=username1 custom.config.config1.server.password=password1
Corresponding Java implementation
@ConfigurationProperties(prefix = "custom.config.config1") public class Config1Properties{ private ServerProperties server; ... public static class ServerProperties { private String host; private int port; private String username; private String password; ... } }
Object List
custom.config.config1.servers[0].host=host1 custom.config.config1.servers[0].port=22 custom.config.config1.servers[0].username=username1 custom.config.config1.servers[0].password=password1 custom.config.config1.servers[1].host=host2 custom.config.config1.servers[1].port=22 custom.config.config1.servers[1].username=username2 custom.config.config1.servers[1].password=password2
Corresponding Java implementation
@ConfigurationProperties(prefix = "custom.config.config1") public class Config1Properties{ private List<ServerProperties> servers; ... public static class ServerProperties { private String host; private int port; private String username; private String password; ... } }
Use cases for Map
For example, if we need to connect multiple OSS (Ali Object Store) at the same time, then we can configure multiple in the way of Configuration Properties.It can also be dynamically injected into containers by Spring's loading.
Configuration center:
# OSS1 Configuration oss.multi.clients.accout.accessKeyId=xxx oss.multi.clients.accout.accessKeySecret=xxx oss.multi.clients.accout.privateEndpoint=xxx oss.multi.clients.accout.bucketName=bucket-b-test # OSS2 Configuration oss.multi.enabled=true oss.multi.clients.xdtrans.accessKeyId=xxx oss.multi.clients.xdtrans.accessKeySecret=xxx oss.multi.clients.xdtrans.privateEndpoint=xxx oss.multi.clients.xdtrans.bucketName=bucket-a-test
Corresponding Java implementation
@Data @EqualsAndHashCode(callSuper = false) @ConfigurationProperties(prefix = OssConstants.MULTI_CONFIG_PREFIX) public class MultiOssProperties { private Map<String, OssProperties> clients; @Data public static class OssProperties { private String accessKeyId; private String accessKeySecret; private String publicEndpoint; private String privateEndpoint; private String bucketName; private String object; }
Dynamically define the BeanDefinition we need.
public class MultiOssScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware { private ApplicationContext applicationContext; @Setter private MultiOssProperties multiOssProperties; @Override public void setBeanName(String name) { log.info("init bean {}", name); } @Override public void afterPropertiesSet() throws Exception { Objects.requireNonNull(this.multiOssProperties, "multiOssProperties Cannot be empty"); Objects.requireNonNull(this.applicationContext, "applicationContext Cannot be empty"); } // Dynamic Definition Bean @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { String beanSuffixName = StringUtils.capitalize(OssConstants.BEAN_SUFFIX_NAME); // The productCodes actually match the xdtrans of oss.multi.clients.xdtrans multiOssProperties.getClients().forEach((productCode, ossProperties) -> { AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(OssClient.class, () -> OssClientUtils.buildOssClient(ossProperties)) .getRawBeanDefinition(); beanDefinition.setInitMethodName("init"); beanDefinition.setDestroyMethodName("shutDown"); beanDefinitionRegistry.registerBeanDefinition(productCode + beanSuffixName, beanDefinition); }); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
binder to relate the configuration to the corresponding Java code:
@Configuration @EnableConfigurationProperties(MultiOssProperties.class) @ConditionalOnProperty(prefix = OssConstants.MULTI_CONFIG_PREFIX, value = "enabled") public class MultiOssAutoConfiguration { /** * Initialize multiple ossClient autoconfigurations * * @param environment Environment variable properties * @return OssClient Automatic Scan Registrar */ @Bean public MultiOssScannerConfigurer multiOssScannerConfigurer(Environment environment) { Binder binder = Binder.get(environment); MultiOssProperties properties = binder.bind(OssConstants.MULTI_CONFIG_PREFIX, MultiOssProperties.class).get(); MultiOssScannerConfigurer multiOssScannerConfigurer = new MultiOssScannerConfigurer(); multiOssScannerConfigurer.setMultiOssProperties(properties); return multiOssScannerConfigurer; } }
How to use it?
@Getter @AllArgsConstructor public enum OssTypeEnum { // Notice the beanName here to be consistent with the postProcessBeanDefinitionRegistry above XDtransOssClient("xdtransOssClient", "oss1"), DianDianOssClient("ddacctOssClient", "oss2"), ; private final String beanName; private final String desc; // Get it in the Spring container from BeanName public OssClient getBean() { return SpringContextHolder.getBean(beanName, OssClient.class); }
How does Binder map?
Bid with the code binder.bind above (OssConstants.MULTI_CONFIG_PREFIX, MultiOssProperties.class). get(); and
protected final <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context, boolean allowRecursiveBinding) { context.clearConfigurationProperty(); try { target = handler.onStart(name, target, context); if (target == null) { return null; } Object bound = bindObject(name, target, handler, context,allowRecursiveBinding); return handleBindResult(name, target, handler, context, bound); } catch (Exception ex) { return handleBindError(name, target, handler, context, ex); } }
If our key is: oss.multi.clients.accout.xxx
It actually corresponds to Map, so its reference name is clients.The key is accout, and the corresponding value is OssProperties.
private Object bindBean(ConfigurationPropertyName name, Bindable<?> target, BindHandler handler, Context context, boolean allowRecursiveBinding) { if (containsNoDescendantOf(context.getSources(), name) || isUnbindableBean(name, target, context)) { return null; } BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind( name.append(propertyName), propertyTarget, handler, context, false); Class<?> type = target.getType().resolve(Object.class); if (!allowRecursiveBinding && context.hasBoundBean(type)) { return null; } return context.withBean(type, () -> { Stream<?> boundBeans = BEAN_BINDERS.stream() .map((b) -> b.bind(name, target, context, propertyBinder)); return boundBeans.filter(Objects::nonNull).findFirst().orElse(null); }); }
A specific bind case.
private static final List<BeanBinder> BEAN_BINDERS; static { List<BeanBinder> binders = new ArrayList<>(); binders.add(new JavaBeanBinder()); BEAN_BINDERS = Collections.unmodifiableList(binders); } public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context, BeanPropertyBinder propertyBinder) { boolean hasKnownBindableProperties = hasKnownBindableProperties(name, context); Bean<T> bean = Bean.get(target, hasKnownBindableProperties); if (bean == null) { return null; } BeanSupplier<T> beanSupplier = bean.getSupplier(target); boolean bound = bind(propertyBinder, bean, beanSupplier); return (bound ? beanSupplier.get() : null); } // Return the corresponding object public BeanSupplier<T> getSupplier(Bindable<T> target) { return new BeanSupplier<>(() -> { T instance = null; if (target.getValue() != null) { instance = target.getValue().get(); } if (instance == null) { instance = (T) BeanUtils.instantiateClass(this.resolvedType); } return instance; }); }
Reference Address
If you like my article, you can focus on your personal subscription number.Welcome to leave a message and exchange at any time.If you want to join the WeChat group and discuss it together, add lastpass4u, the administrator's culture assistant, who will pull you into the group.