首页 > 解决方案 > 提高可扩展配置对象的 spring 库的显式性

问题描述

我目前正在开发一个spring-library,它允许在使用之前从应用程序的另一部分调整用户定义的配置类(与@Configuration 无关):

interface ConfigAdjuster<T extends Config<T>> {
    void adjust(T t);
}

abstract class Config<T extends Config<T>> {
     @Autowired
     Optional<ConfigAdjuster<T>> adjuster;

     @PostConstruct
     private void init() {
         //i know this cast is somewhat unsafe, just ignore it for this question
         adjuster.ifPresent(a -> a.adjust((T)this));
     }
}

这可以按如下方式使用:

class MyConfig extends Config<MyConfig> {
    //imagine many fields of more complex types
    public String myData;
}

@Configuration
class MyConfigDefaults {
    @Profile("dev")
    @Bean 
    public MyConfig devDefaults() {
        //imagine setting defaults values here
        return new MyConfig();
    }
}

现在,使用 MyConfig 的库的使用者可以在其应用程序的某处执行以下操作:

@Bean
public ConfigAdjuster<MyConfig> adjustDefaults() {
    return cfg -> {
        cfg.myData = "something_other_than_default";
    }
}

我看到这种方法的最大问题是整个“调整配置”部分对用户来说有些隐藏。您不能轻易告诉您可以使用 ConfigAdjuster 更改默认配置。在最坏的情况下,用户尝试自动装配配置对象并尝试以这种方式修改它,这会导致未定义的行为,因为其他组件可能已经使用默认值进行了初始化。

有没有一种简单的方法可以使这种方法比现在更“有说服力”?整个想法是不要在多个项目中复制和粘贴整个默认配置 + 调整部分。

使所有这些更明确的一种方法是在 Config 的构造函数中要求调整器,但这会污染每个构造函数和继承类的使用。

对此有什么想法吗?

编辑:请注意,这是该库的简化版本,我确实知道私有 @PostConstruct 等的含义。如果您有其他方法可以在没有 @PostConstruct 的情况下实现所有这些,请分享:)

Edit2:让我再次概述这个库的主要目标:

  1. 允许为库用户定义默认配置对象
  2. 允许最终用户(使用此库使用依赖项)在使用默认配置之前覆盖默认配置的某些部分
  3. 从样板文件中保存库用户(例如,自行定义 2.)

标签: javaspringdesign-patternsconfiguration

解决方案


There is two solution for your problem:

1- define a generic Customizer something like:

public interface Customizer<T> {

    T customize(T t);

    boolean supports(Class target);
}

in your lib you have a config:

public class MyConfig {

    private String property;

    public MyConfig() {
    }

    public void setProperty(String property) {
        this.property = property;
    }
}

so your Default configuration should look something like this:

@Configuration
public class DefaultConfiguration {


    @Autowired(required = false)
    private List<Customizer> customizers;

    @Bean
    public MyConfig myConfig() {
        MyConfig myConfig = new MyConfig();
        myConfig.setProperty("default value");
        if (customizers != null) {     
          for (Customizer c : customizers) {
            if (c.supports(MyConfig.class)) {
                return (MyConfig) c.customize(myConfig);
            }
          }
        }
        return myConfig;
    }
}

this way, the only thing the user should do whenever he wants to customize you bean is to implement Customizer, and then declare it as a bean.

public class MyConfigCustomizer implements Customizer<MyConfig> {

    @Override
    public MyConfig customize(MyConfig myConfig) {
        //customization here
        return myConfig;
    }

    @Override
    public boolean supports(Class<?> target) {
        return MyConfig.class.isAssignableFrom(target);
    }
}

and he should declare it:

@Bean 
public Customizer<MyConfig> customizer(){
     return new MyConfigCustomizer ();
 }

I think this answers your question, but it's ugly (uncheched warnings and a List ...) not the best, as everything seems to the user customizable even it's not.

2- I suggest you expose interfaces for Beans that can be adjusted by the user, something like:

public interface MyConfigCustomizer{

MyConfig customize(MyConfig config);

}

your Default Configuration:

@Configuration
public class DefaultConfiguration {


    @Autowired(required = false)
    private MyConfigCustomizer customizer;

    @Bean
    public MyConfig myConfig() {
        MyConfig myConfig = new MyConfig();
        myConfig.setProperty("default value");
        if (customizer != null) {
            return customizer.customize(myconfig);
        }
        return myConfig;
    }

}

this way the user knows that MyConfig can be adjusted (and not all the beans).


推荐阅读