Kim Rudolph

Spring Boot Snippets

Patterns and examples extracted from recent Spring Boot projects.


FlyWay Migrations With Spring Context

It is possible to use the Spring ApplicationContext in Flyway Java migrations. An example repository at krudolph/spring-flyway-dbmigrations implements a solution using the hints mentioned in flyway/issues/1062.

public class V2__Set_Smiley extends BaseJavaMigration {

    SmileyUtil smileyUtil = SpringContextUtil.getBean(SmileyUtil.class);

    DemoEntityRepository demoEntityRepository = SpringContextUtil.getBean(DemoEntityRepository.class);

    @Override
    public void migrate(final Context context) {

        Iterable<DemoEntity> all = demoEntityRepository.findAll();

        for (DemoEntity demoEntity : all) {
            demoEntity.setSmiley(smileyUtil.smile());
        }

        demoEntityRepository.saveAll(all);
    }
}

Beware that these migrations might fail if @Entity objects are used that change in migrations later on like an additional @OneToMany. If possible, plain SQL migration should be used (context.getConnection()).

Log User And Timer

It is not always easy to get a quick overview of the current active users and request performance. The following log line might help before digging deeper into potential problems.

2018-11-09 17:35:30.248 -  INFO - 8204 - nio-8080-exec-1 - de.kimrudolph.filters.Timer : kermit :  213ms GET /home

Log patterns used to generate the log line:

logging.pattern.console=%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%6p} - ${PID:- } - %-15.15t - %-40.40logger{39} :%replace( %X{username} :){'  :', ''} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}}

OncePerRequestFilters guarantee a single execution per request dispatch. The filters allow adding the current authenticated user as Mapped Diagnostic Context (MDC).

@Component
public class LogUserFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(final HttpServletRequest request,
                                    final HttpServletResponse response,
                                    final FilterChain filterChain) throws ServletException, IOException {

        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication != null) {
            MDC.put("username", authentication.getName());
        }

        filterChain.doFilter(request, response);
        MDC.remove("username");
    }

}

Finally another filter to actually log the request path and timing.

@Slf4j
@Component
public class Timer extends OncePerRequestFilter {

    private final String[] IGNORE_SUFFIXES = {".css", ".js", ".ico", ".woff", ".woff2", ".png", ".map", ".eot"};

    @Override
    protected void doFilterInternal(final HttpServletRequest request,
                                    final HttpServletResponse response,
                                    final FilterChain filterChain) throws ServletException, IOException {

        final String path = request.getRequestURI();

        if (StringUtils.endsWithAny(path, IGNORE_SUFFIXES)) {
            filterChain.doFilter(request, response);
            return;
        }

        final LocalDateTime start = LocalDateTime.now();

        try {
            filterChain.doFilter(request, response);
        } finally {
            final LocalDateTime finished = LocalDateTime.now();
            final String duration = String.valueOf(Duration.between(start, finished).toMillis());
            log.info("{}ms {} {}", StringUtils.leftPad(duration, 4),
                     request.getMethod(), request.getRequestURI());
        }

    }
}

Tomcat Connector Customizers

Spring Boot provides several application properties for tomcat settings. But it is also possible to configure additional tomcat specific setting using a WebServerFactoryCustomizer.

@Configuration
public class TomcatCustomizationConfiguration implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    @Override
    public void customize(final TomcatServletWebServerFactory factory) {

        factory.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> connector.setMaxParameterCount(50_000));
    }
}

Simple Feature Toggles

Using a CI/CD pipeline needs a solution for feature toggles based on profiles and/or environments.

A requirement is switching the toggles using the application-{dev/test/production}.properties:

features.ORDER_CAR=true
features.ORDER_SPACESHIP=false

Also simple safeguards on methods with an annotation like @FeatureToggle("ORDER_SPACESHIP") and Thymeleaf templates integration:

<span th:if="${!feature.isActive('ORDER_SPACESHIP')}">ORDER_SPACESHIP feature is disabled</span>

An example repository at krudolph/spring-feature-toggles implements these features.

Of course there is also the complete, tested and rich on features Togglz project.