Spring:使用构建器模式创建 bean

Posted

技术标签:

【中文标题】Spring:使用构建器模式创建 bean【英文标题】:Spring: Using builder pattern to create a bean 【发布时间】:2011-03-15 06:59:03 【问题描述】:

我使用ektorp 连接到 CouchDB。

构建 ektorp HttpClient 实例的方法是使用构建器模式:

HttpClient httpClient = new StdHttpClient.Builder()
                                .host("mychouchdbhost")
                                .port(4455)
                                .build();

我对 Spring 比较陌生。请告诉我如何在我的上下文中配置HttpClient 以通过Builder 创建它。

一种方法是使用@Configuration。还有其他选择吗?

【问题讨论】:

【参考方案1】:

请查看 Spring FactoryBean 和 FactoryMethod 文档。

【讨论】:

【参考方案2】:

你可以尝试实现FactoryBean接口:

public class HttpFactoryBean implements FactoryBean<HttpClient>

private String host;
private int port;


public HttpClient getObject() throws Exception 
    return new StdHttpClient.Builder()
                            .host(host)
                            .port(port)
                            .build();


public Class<? extends HttpClient> getObjectType() 
    return StdHttpClient.class;


public boolean isSingleton() 
    return true;


public void setHost(String host) 
    this.host = host;


public void setPort(int port) 
    this.port = port;

并添加到配置以下 bean 定义:

<beans ..."> 
   <bean name="myHttpClient" class="HttpFactoryBean">
       <property name="port" value="8080"/>
       <property name="host" value="localhost"/>
   </bean>
</beans>

然后你可以把这个bean注入另一个bean,它会被解析为StdHttpClient实例。

【讨论】:

【参考方案3】:

虽然没有明确说明您的情况;如果构建器通过标准 bean 模式 set 方法公开属性,则可以扩展构建器。即,如果我们以org.apache.httpcomponents:httpclient HttpClientBuilder 为例,我们可以有以下内容:

public class HttpClientFactoryBean
        extends HttpClientBuilder
        implements InitializingBean,
                   FactoryBean<HttpClient> 

    private HttpClient value;

    @Override
    public void afterPropertiesSet() throws Exception 
        this.value = build();
    

    @Override
    public HttpClient getObject() throws Exception 
        return value;
    

    @Override
    public Class<?> getObjectType() 
        return HttpClient.class;
    

    @Override
    public boolean isSingleton() 
        return true;
    


现在您的工厂 bean 可以访问 HttpClientBuilder 公开的任何方法。现在可以进行如下配置:

<beans id="httpClient" class="com.drunkendev.factory.HttpClientFactoryBean">
  <beans name="defaultCredentialsProvider" ref="credentialsProvider"/>
  <beans name="targetAuthenticationStrategy">
    <util:constant static-field="org.apache.http.impl.client.TargetAuthenticationStrategy.INSTANCE"/>
  </beans>
</beans>

【讨论】:

【参考方案4】:

我曾经在开发FlexyPool 时偶然发现了同样的问题,所以我就是这样做的。

基本上,从以下 Builder 开始:

public final class Configuration<T extends DataSource> extends ConfigurationProperties<T, Metrics, PoolAdapter<T>> 
 
    public static final long DEFAULT_METRIC_LOG_REPORTER_PERIOD = 5;
 
    public static class Builder<T extends DataSource> 
        private final String uniqueName;
        private final T targetDataSource;
        private final PoolAdapterBuilder<T> poolAdapterBuilder;
        private final MetricsBuilder metricsBuilder;
        private boolean jmxEnabled = true;
        private long metricLogReporterPeriod = DEFAULT_METRIC_LOG_REPORTER_PERIOD;
 
        public Builder(String uniqueName, T targetDataSource, MetricsBuilder metricsBuilder, PoolAdapterBuilder<T> poolAdapterBuilder) 
            this.uniqueName = uniqueName;
            this.targetDataSource = targetDataSource;
            this.metricsBuilder = metricsBuilder;
            this.poolAdapterBuilder = poolAdapterBuilder;
        
 
        public Builder setJmxEnabled(boolean enableJmx) 
            this.jmxEnabled = enableJmx;
            return this;
        
 
        public Builder setMetricLogReporterPeriod(long metricLogReporterPeriod) 
            this.metricLogReporterPeriod = metricLogReporterPeriod;
            return this;
        
 
        public Configuration<T> build() 
            Configuration<T> configuration = new Configuration<T>(uniqueName, targetDataSource);
            configuration.setJmxEnabled(jmxEnabled);
            configuration.setMetricLogReporterPeriod(metricLogReporterPeriod);
            configuration.metrics = metricsBuilder.build(configuration);
            configuration.poolAdapter = poolAdapterBuilder.build(configuration);
            return configuration;
        
    
 
    private final T targetDataSource;
    private Metrics metrics;
    private PoolAdapter poolAdapter;
 
    private Configuration(String uniqueName, T targetDataSource) 
        super(uniqueName);
        this.targetDataSource = targetDataSource;
    
 
    public T getTargetDataSource() 
        return targetDataSource;
    
 
    public Metrics getMetrics() 
        return metrics;
    
 
    public PoolAdapter<T> getPoolAdapter() 
        return poolAdapter;
    

使用基于 Java 的配置很简单:

@org.springframework.context.annotation.Configuration
public class FlexyDataSourceConfiguration 
 
    @Bean
    public Configuration configuration() 
        return new Configuration.Builder(
                UUID.randomUUID().toString(),
                poolingDataSource,
                CodahaleMetrics.BUILDER,
                BitronixPoolAdapter.BUILDER
        ).build();
    

但您也可以使用基于 XML 的配置:

<bean id="configurationBuilder" class="com.vladmihalcea.flexypool.config.Configuration$Builder">
    <constructor-arg value="uniqueId"/>
    <constructor-arg ref="poolingDataSource"/>
    <constructor-arg value="# T(com.vladmihalcea.flexypool.metric.codahale.CodahaleMetrics).BUILDER "/>
    <constructor-arg value="# T(com.vladmihalcea.flexypool.adaptor.BitronixPoolAdapter).BUILDER "/>
</bean>
 
<bean id="configuration" factory-bean="configurationBuilder" factory-method="build"/>

【讨论】:

通读您的文章。我喜欢这种方式,发现它很独特。你能帮我理解最后两个 constructor-arg 的构造吗?我找不到任何关于那里使用的 BUILDER 构造的帮助。 这是一个 Builder 带着其他 Builder 来解决一些依赖关系。 哦...所以“BUILDER”是“CodahaleMetrics”和“BitronixPoolAdapter”的属性,如果没有记错的话。 是的。 BUILDER 是一个默认的单例构建器,您可以将其用作依赖项。 你能扩展你的例子吗?它没有显示如何在配置上调用setJmxEnabledsetMetricLogReporterPeriod,并且只使用构建器构造函数【参考方案5】:

虽然FactoryBean 更干净,但还有一种更快捷的方法,使用SpEL。

这就是我刚刚配置Neo4j driver的方式:

<bean id = "neoDriver" class = "org.neo4j.driver.v1.GraphDatabase" 
        factory-method="driver">
    <constructor-arg value = "bolt://127.0.0.1:7687" />
    <constructor-arg>
        <bean class = "org.neo4j.driver.v1.AuthTokens" factory-method = "basic">
            <constructor-arg value = "neo4j" />
            <constructor-arg value = "***" />
        </bean>
    </constructor-arg>
    <constructor-arg type="org.neo4j.driver.v1.Config" 
        value = "#T(org.neo4j.driver.v1.Config).build ()
            .withConnectionAcquisitionTimeout ( 10, T(java.util.concurrent.TimeUnit).SECONDS )
            .withConnectionTimeout ( 10, T(java.util.concurrent.TimeUnit).SECONDS )
            .toConfig ()
        "
    />
</bean>

从工厂方法的第三个参数可以看出,您可以将构建器及其方法调用为 SpEL 表达式,其中的细微差别是必须通过其 FQN 指定类。但这避免了您编写完整的样板 FactoryBean。

【讨论】:

以上是关于Spring:使用构建器模式创建 bean的主要内容,如果未能解决你的问题,请参考以下文章

使用 Arrow 创建具有错误处理的对象构建器 - 模式匹配多个 Either

Java基于方法引用的构建器模式运用

Java基于方法引用的构建器模式运用

扩展SpringBoot Schema配置Bean

扩展SpringBoot Schema配置Bean

考虑用构建器