使用构建器模式创建一个Spring Bean

43

我使用ektorp来连接CouchDB。

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

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

我相对来说对Spring比较新,请给我一些建议,如何配置一个HttpClient在我的上下文中通过Builder创建它。

其中一个方法是使用@Configuration。还有其他选项吗?

5个回答

56

您可以尝试实现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注入到其他的beans中,它将会被解析为StdHttpClient实例。


8

我曾在开发FlexyPool时遇到过同样的问题,这就是我所做的。

基本上,从以下构建器开始:

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"/>

阅读了你的文章。我喜欢这种方式,发现它很独特。你能帮我理解最后两个构造函数吗?我找不到任何关于使用BUILDER结构的帮助。 - rajneesh2k10
1
这是一个Builder,它需要其他的Builders来解决一些依赖关系。 - Vlad Mihalcea
1
是的。BUILDER是一个默认的单例构建器,您可以将其用作依赖项。 - Vlad Mihalcea
2
你能详细说明一下你的例子吗?它没有展示如何在配置中调用 setJmxEnabledsetMetricLogReporterPeriod,只使用了构建器构造函数。 - Krease
你可以获取GitHub仓库并查看它的运行情况。 - Vlad Mihalcea
显示剩余4条评论

2
请查看Spring FactoryBean和FactoryMethod文档。

1

虽然对于您的情况没有明确说明,但如果构建器通过标准的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;
    }

}

现在,HttpClientBuilder 公开的任何方法都可以被您的工厂 bean 访问。现在可以实现以下配置:
<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>

0

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表达式调用,但需要通过完全限定名称指定类。这避免了您编写整个样板FactoryBean的麻烦。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接