43

I use ektorp to connect to CouchDB.

The way to build an ektorp HttpClient instance is to use builder pattern:

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

I am relatively new to Spring. Please advice me on how I can configure an HttpClient in my context to create it via the Builder.

One way to do this is with @Configuration. Are any other options?

Thermech
  • 4,371
  • 2
  • 39
  • 60
artemb
  • 9,251
  • 9
  • 48
  • 68

5 Answers5

56

You may try to implement FactoryBean interface:

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;
}}

And add to config following bean definition:

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

Then you can inject this bean to another beans, it will be resolved as StdHttpClient instance.

wax
  • 2,400
  • 3
  • 19
  • 28
8

I once stumbled on the same issue when I was developing FlexyPool, so this is what I did.

Basically, starting from the following 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;
    }
}

Using the Java-based configuration is straight-forward:

@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();
    }
}

But you can also use XML-based configuration as well:

<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"/>
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • Read through your article. I liked this way and found it unique. Could you help me understand the construct of last two constructor-arg ? I was unable to find any help on BUILDER construct being used there. – rajneesh2k10 Mar 22 '15 at 18:41
  • 1
    It's a Builder taking other Builders to resolve some dependencies. – Vlad Mihalcea Mar 22 '15 at 18:50
  • Oh... So the "BUILDER" is the property of "CodahaleMetrics" and "BitronixPoolAdapter" if not mistaken. – rajneesh2k10 Mar 22 '15 at 18:54
  • 1
    Yes. BUILDER is a default singleton Builder that you can use as a dependency. – Vlad Mihalcea Mar 22 '15 at 18:55
  • 2
    Can you expand on your example? It doesn't show how to call `setJmxEnabled` or `setMetricLogReporterPeriod` on the configs, and only uses the builder constructor – Krease Jun 01 '15 at 17:51
  • You can grab the GitHub repo and see it in action. – Vlad Mihalcea Jun 01 '15 at 18:48
  • @VladMihalcea I downloaded the GitHub repo and searched all the xml config files for examples of how to call setJmxEnabled and setMetricLogReporterPeriod and didn't find anything. – Ryan Apr 03 '18 at 21:10
  • Here's [one example](https://github.com/vladmihalcea/flexy-pool/wiki/HikariCP-Configuration). It's standard Spring config. – Vlad Mihalcea Apr 04 '18 at 04:53
2

Please check Spring FactoryBean and FactoryMethod documentation.

Script Runner
  • 282
  • 1
  • 3
1

While not explicit for your case; it is possible to extend a builder if it exposes properties via standard bean pattern set methods. i.e. if we take the org.apache.httpcomponents:httpclient HttpClientBuilder as an example we could have the following:

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;
    }

}

Now any method exposed by HttpClientBuilder is accessible to your factory bean. A configuration such as the following is now possible:

<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>
Brett Ryan
  • 26,937
  • 30
  • 128
  • 163
0

While FactoryBean is cleaner there is a more quick-n-dirty method, using SpEL.

This is how I've just configured the 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>

As you can see from the factory method's 3rd parameter, you can invoke a builder and its methods as a SpEL expression, with the nuance that classes have to be specified via their FQN. But that avoids you to write an entire boilerplate FactoryBean.

zakmck
  • 2,715
  • 1
  • 37
  • 53