The Builder pattern and the Spring framework

(Last Updated On: January 29, 2019)
Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

Introduction

I like to make use of the builder pattern whenever an object has both mandatory and optional properties. But building objects is usually the Spring framework responsibility, so let’s see how you can employ it using both Java and XML-based Spring configurations.

A Builder example

Let’s start with the following Builder class.

public final class Configuration<T extends DataSource> 
         extends ConfigurationProperties<T, Metrics, PoolAdapter<T>> {

    public static final long DEFAULT_METRIC_LOG_REPORTER_MILLIS = TimeUnit.MINUTES.toMillis(5);

    public static class Builder<T extends DataSource> {
    
        private final String uniqueName;
        private final T targetDataSource;
        private final PoolAdapterFactory<T> poolAdapterFactory;
        private MetricsFactory metricsFactory;
        private ConnectionProxyFactory connectionProxyFactory = ConnectionDecoratorFactoryResolver.INSTANCE.resolve();
        private boolean jmxEnabled = true;
        private boolean jmxAutoStart = false;
        private long metricLogReporterMillis = DEFAULT_METRIC_LOG_REPORTER_MILLIS;
        private EventListenerResolver eventListenerResolver;
        private long connectionAcquireTimeThresholdMillis = Long.MAX_VALUE;
        private long connectionLeaseTimeThresholdMillis = Long.MAX_VALUE;

        public Builder(
                String uniqueName, 
                T targetDataSource, 
                PoolAdapterFactory<T> poolAdapterFactory) {
            this.uniqueName = uniqueName;
            this.targetDataSource = targetDataSource;
            this.poolAdapterFactory = poolAdapterFactory;
        }

        public Builder<T> setMetricsFactory(
                MetricsFactory metricsFactory) {
            this.metricsFactory = metricsFactory;
            return this;
        }

        public Builder<T> setConnectionProxyFactory(
                ConnectionProxyFactory connectionProxyFactory) {
            this.connectionProxyFactory = connectionProxyFactory;
            return this;
        }

        public Builder<T> setJmxEnabled(
                boolean enableJmx) {
            this.jmxEnabled = enableJmx;
            return this;
        }

        public Builder<T> setJmxAutoStart(
                boolean jmxAutoStart) {
            this.jmxAutoStart = jmxAutoStart;
            return this;
        }

        public Builder<T> setMetricLogReporterMillis(
                long metricLogReporterMillis) {
            this.metricLogReporterMillis = metricLogReporterMillis;
            return this;
        }

        public Builder<T> setEventListenerResolver(
                EventListenerResolver eventListenerResolver) {
            this.eventListenerResolver = eventListenerResolver;
            return this;
        }

        public Builder<T> setConnectionAcquireTimeThresholdMillis(
                Long connectionAcquireTimeThresholdMillis) {
            if (connectionAcquireTimeThresholdMillis != null) {
                this.connectionAcquireTimeThresholdMillis = connectionAcquireTimeThresholdMillis;
            }
            return this;
        }

        public Builder<T> setConnectionLeaseTimeThresholdMillis(
                Long connectionLeaseTimeThresholdMillis) {
            if (connectionLeaseTimeThresholdMillis != null) {
                this.connectionLeaseTimeThresholdMillis = connectionLeaseTimeThresholdMillis;
            }
            return this;
        }
        
        public Configuration<T> build() {
            EventPublisher eventPublisher = EventPublisher.newInstance(eventListenerResolver);
            Configuration<T> configuration = new Configuration<T>(
                uniqueName, 
                targetDataSource, 
                eventPublisher
            );
            configuration.setJmxEnabled(jmxEnabled);
            configuration.setJmxAutoStart(jmxAutoStart);
            configuration.setMetricLogReporterMillis(metricLogReporterMillis);
            configuration.setConnectionAcquireTimeThresholdMillis(connectionAcquireTimeThresholdMillis);
            configuration.setConnectionLeaseTimeThresholdMillis(connectionLeaseTimeThresholdMillis);
            if(metricsFactory == null) {
                metricsFactory = MetricsFactoryResolver.INSTANCE.resolve();
            }
            configuration.metrics = metricsFactory.newInstance(configuration);
            configuration.poolAdapter = poolAdapterFactory.newInstance(configuration);
            configuration.connectionProxyFactory = connectionProxyFactory;
            return configuration;
        }
    }

    private final T targetDataSource;
    private Metrics metrics;
    private PoolAdapter<T> poolAdapter;
    private ConnectionProxyFactory connectionProxyFactory;

    private Configuration(
            String uniqueName, 
            T targetDataSource, 
            EventPublisher eventPublisher) {
        super(uniqueName, eventPublisher);
        this.targetDataSource = targetDataSource;
    }

    public T getTargetDataSource() {
        return targetDataSource;
    }

    public Metrics getMetrics() {
        return metrics;
    }

    public PoolAdapter<T> getPoolAdapter() {
        return poolAdapter;
    }

    public ConnectionProxyFactory getConnectionProxyFactory() {
        return connectionProxyFactory;
    }
}

Java-based configuration

If you’re using Spring Java-based configuration then this is how you’d do it:

@org.springframework.context.annotation.Configuration
public class FlexyPoolConfiguration {

    @Autowired
    private AbstractDataSourceBean poolingDataSource;

    @Value("${flexy.pool.uniqueId}")
    private String uniqueId;

    @Bean
    public Configuration<AbstractDataSourceBean> configuration() {
        return new Configuration.Builder<>(
            uniqueId,
            poolingDataSource,
            AtomikosPoolAdapter.FACTORY
        )
        .setJmxEnabled(true)
        .setMetricLogReporterMillis(TimeUnit.SECONDS.toMillis(5))
        .build();
    }

    @Bean(initMethod = "start", destroyMethod = "stop")
    public FlexyPoolDataSource dataSource() {
        Configuration<AbstractDataSourceBean> configuration = configuration();
        return new FlexyPoolDataSource<AbstractDataSourceBean>(
            configuration,
            new IncrementPoolOnTimeoutConnectionAcquiringStrategy.Factory(5),
            new RetryConnectionAcquiringStrategy.Factory(2)
        );
    }
}

XML-based configuration

The XML-based configuration is more verbose and not as intuitive as the Java-based configuration:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <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.adaptor.AtomikosPoolAdapter).FACTORY }"/>
        <property name="jmxAutoStart" value="true"/>
        <property name="metricLogReporterMillis" value="300000"/>
    </bean>

    <bean id="configuration" factory-bean="configurationBuilder" factory-method="build"/>

    <bean id="dataSource" class="com.vladmihalcea.flexypool.FlexyPoolDataSource" init-method="start" destroy-method="stop">
        <constructor-arg ref="configuration"/>
        <constructor-arg>
            <array>
                <bean class="com.vladmihalcea.flexypool.strategy.IncrementPoolOnTimeoutConnectionAcquiringStrategy.Factory">
                    <constructor-arg value="5"/>
                </bean>
                <bean class="com.vladmihalcea.flexypool.strategy.RetryConnectionAcquiringStrategy.Factory">
                    <constructor-arg value="2"/>
                </bean>
            </array>
        </constructor-arg>
    </bean>
</beans>

Awesome, right?

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

You can make use of the Builder pattern no matter the Spring configuration mode you’ve already chosen. If you have doubts about it’s usefulness, here are three compelling reasons you should be aware of.

Download free ebook sample

Newsletter logo
10 000 readers have found this blog worth following!

If you subscribe to my newsletter, you'll get:
  • A free sample of my Video Course about running Integration tests at warp-speed using Docker and tmpfs
  • 3 chapters from my book, High-Performance Java Persistence,
  • a 10% discount coupon for my book.

2 Comments on “The Builder pattern and the Spring framework

  1. What profit do you have using such code

    return new Configuration.Builder(
    UUID.randomUUID().toString(),
    poolingDataSource,
    CodahaleMetrics.BUILDER,
    BitronixPoolAdapter.BUILDER
    ).build();

    instead of simply passing params to Configuration constructor?

    What approach you can advice if I need dynamically construct bean (get param from rest api and use it for bean construction)?

    • I updated the article to make it more obvious why you need to use the Buidler patter.

      As for the dynamic API, it’s probably better to fetch the config via REST API and use that to bootstrap a different application using the config that you fetched.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Hypersistence Optimizer can automatically detect if you are using JPA and Hibernate properly