spring: Implement initialization of dynamic beans|Get object array configuration file

0 Preface

Recently, I have to complete the toolkit component of the middleware, which involves reading the object array configuration file and loading it as a bean. Because of the spring 4.3.25.RELEASEversion used, many springboot related features cannot be supported, so this is recorded. To facilitate subsequent students who have the same situation to refer to

1. Get the object array configuration file

First, the object array configuration file is as follows:

minio.clients[0].name=xxx
minio.clients[0].endpoint=http://ip1:9000
minio.clients[0].access.key=admin
minio.clients[0].secret.key=asd
minio.clients[0].default.bucket=wu
minio.clients[1].name=yyy
minio.clients[1].endpoint=http://ip2:9000
minio.clients[1].access.key=admin
minio.clients[1].secret.key=asd
minio.clients[1].default.bucket=wu

The format converted into yml is as follows:

minio:
  clients:
    - name: xxx
      endpoint: http://ip1:9000
      access:
        key: admin
      secret:
        key: asd
      default:
        bucket: wu
    - name: yyy
      endpoint: http://ip2:9000
        access:
          key: admin
        secret:
          key: asd
        default:
          bucket: wu

If it is a springboot project, we can use one directly @ConfigurationProperties(prefix="minio.clients")and then configure an entity class to achieve it. However, because we are encountering an older version of the spring project here, this annotation is not supported. So try to implement it in other ways.


First of all, the @Value form@Value can indeed help us read configuration items, but it can only be used for configuration items of basic types or arrays of basic types. For array configuration files of our object items, it is not supported. In spring, except for this method, and can also Environmentbe implemented directly by operating objects

Environment form
We can see that through the environment.getProperty method, we can get the configuration items we want, so this method is obviously feasible.

At the same time, when Environment reads configuration items, it needs to specify the configuration file, so it needs to be declared with help. @PropertySourceAt the same time, because this is a toolkit, it means that the configuration file may not be available. If not, there is no need to initialize the bean. If there is a corresponding configuration file, the bean will be automatically initialized. , this is what we want to achieve

Click on @PropertySourcethe annotation and we can see an ignoreResourceNotFoundattribute. The attribute name has told us its function. By setting its value to true, we can read the configuration item when the configuration file exists, and no error will be reported when it does not exist.

Insert image description here

The complete example is as follows:

@Configuration
@PropertySource(value = {
    
    "classpath:applicaiton.properties","classpath:minio.properties"}, ignoreResourceNotFound=true)
public class MinioMultiConfiguration {
    
    
 
    @Resource
    private Environment environment;
 	
 	...   
}    

How to dynamically obtain non-fixed length array configuration items?
Secondly, we need to observe the requirements here, because there are actually multiple minio.clients configurations to be obtained. Here we only list two, because what is implemented is a toolkit, and many more may be configured in the future, so the length is unexpected. .

Then we need to get the length of this array. If it is springboot, we can List<配置实体类> listget the length of the collection directly through the defined collection. However, in spring here, we cannot directly get the length of the array, so in order to meet the demand, we can only take a The stupid way is to directly define a length configuration minio.clients.length=2. If there is a better way in the future, you can leave a message to discuss.

forThen we can obtain the configuration items through looping in the code

2. How to register beans to spring container

At the same time, because my needs here also need to initialize the corresponding MinioClient, then the created bean needs to be registered in the spring container. In addition to the @Beanannotation method, the registration to the container is as follows:

@Configuration
public class MinioConfiguration {
    
    
    /**
     * 对象存储服务的url
     */
    @Value("${minio.endpoint:null}")
    private String endpoint;

    /**
     * 用户ID
     */
    @Value("${minio.access.key:null}")
    private String accessKey;

    /**
     * 账户密码
     */
    @Value("${minio.secret.key:null}")
    private String secretKey;

    /**
     * 默认桶名
     */
    @Value("${minio.default.bucket:null}")
    private String defaultBucketName = "wu";

    @Bean
    public MinioClient minioClient() throws Exception{
    
    
        MinioProperties minioProperties = new MinioProperties(endpoint, accessKey, secretKey, defaultBucketName);
        if(StringUtils.isEmpty(minioProperties.getEndpoint())){
    
    
            return null;
        }
        return new MinioClient(minioProperties.getEndpoint(), minioProperties.getAccessKey(), minioProperties.getSecretKey());
    }
}

You can also BeanFactoryregister through, as shown below

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("beanName", new MinioClient());

You can see that because we need to read configuration items in a loop, the number of beans is variable, so the fixed @Beanform will definitely not work. We can only register through beanFactory.

Bean registration timing
We know that beans must be registered when the project starts, but there are many stages during startup. For example, the beans we initialized actually need to be passed @Autowiredor @Resourcereferenced, so we definitely need to register these two Register it before referencing it, otherwise you will get an error that the bean cannot be found.

There are several ways to execute the method when the project starts in spring. The more commonly used @PostConstructmethod is the annotated method, but the execution order of this method is @Bean>> , so it will definitely not work.@Autowired@PostConstruct

So we try another way BeanFactoryPostProcessorto achieve it by declaring the interface postProcessBeanFactory方法. This can make some customized modifications to the beanFactory, and we can register the beans in these modifications.

At the same time, because we need to obtain configuration items through Environment, we also need to declare that we register Environment EnvironmentAwarethrough methods. Of course, you can also choose to obtain them throughsetEnvironmentbeanFactory.getBean("environment")

The complete sample code is as follows:

public class MinioMultiBeanFactoryPostProcessor implements BeanFactoryPostProcessor, EnvironmentAware {
    
    
    private final static Logger log = LogManager.getLogger(MinioMultiBeanFactoryPostProcessor.class);

    private Environment environment;

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

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    
    
        if(!environment.containsProperty("minio.clients.length")){
    
    
            log.error("未识别到minio.clients.length,取消配置多个minioClient");
            return;
        }
        Integer length = 0;
        try {
    
    
            length = environment.getProperty("minio.clients.length", Integer.class);
        }catch (Exception e){
    
    
            throw new RuntimeException("minioClient初始化失败,minio.clients.length数据类型为Int");
        }
        for (int i = 0; length != null && i < length; i++) {
    
    
            String name = environment.getProperty("minio.clients["+i+"].name");
            String endpoint = environment.getProperty("minio.clients["+i+"].endpoint");
            String access = environment.getProperty("minio.clients["+i+"].access.key");
            String secret = environment.getProperty("minio.clients["+i+"].secret.key");
            String bucket = environment.getProperty("minio.clients["+i+"].default.bucket");
            try{
    
    
                // 自定义对象
                MinioProperties minioProperties = new MinioProperties(endpoint, access, secret, bucket);
                // 创建client
                MinioClient minioClient = new MinioClient(minioProperties.getEndpoint(), minioProperties.getAccessKey(), minioProperties.getSecretKey());
                beanFactory.registerSingleton(name+"MinioClient", minioClient);
            }catch (Exception e){
    
    
                log.error(String.format("minioClient初始化失败:%s", ExceptionUtil.getErrorInfo(e)));
            }
        }
    }
}

Then we need to combine the above-mentioned @Bean> @Autowiredsequence, because what we customized BeanFactoryPostProcessoris just a simple class now, and we also need to declare it as a bean to achieve the purpose of modifying the BeanFactory, so we @Beaninitialize it through

@Configuration
@PropertySource(value = {
    
    "classpath:application.properties","classpath:minio.properties"}, ignoreResourceNotFound=true)
public class MinioMultiConfiguration {
    
    

    @Bean
    public MinioMultiBeanFactoryPostProcessor minioMultiBeanFactoryPostProcessor(){
    
    
        return new MinioMultiBeanFactoryPostProcessor();
    }

}

At this point, we have completed the operation of initializing dynamic beans. The above method is applicable to any spring project, and is more suitable for projects that need to build intermediate packages. You can refer to it selectively.

Guess you like

Origin blog.csdn.net/qq_24950043/article/details/133011128