Why is Spring boot DefaultFormattingConversionService created but not referenced?

Viewed 552

I am reading spring boot source code, when I read this, I am confused.

/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.validation.Configuration;
import javax.validation.Validation;

import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.ApplicationStartingEvent;
import org.springframework.boot.context.event.SpringApplicationEvent;
import org.springframework.boot.context.logging.LoggingApplicationListener;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;

/**
 * {@link ApplicationListener} to trigger early initialization in a background thread of
 * time consuming tasks.
 * <p>
 * Set the {@link #IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME} system property to
 * {@code true} to disable this mechanism and let such initialization happen in the
 * foreground.
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @author Artsiom Yudovin
 * @since 1.3.0
 */
@Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {

    /**
     * System property that instructs Spring Boot how to run pre initialization. When the
     * property is set to {@code true}, no pre-initialization happens and each item is
     * initialized in the foreground as it needs to. When the property is {@code false}
     * (default), pre initialization runs in a separate thread in the background.
     * @since 2.1.0
     */
    public static final String IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME = "spring.backgroundpreinitializer.ignore";

    private static final AtomicBoolean preinitializationStarted = new AtomicBoolean(false);

    private static final CountDownLatch preinitializationComplete = new CountDownLatch(1);

    @Override
    public void onApplicationEvent(SpringApplicationEvent event) {
        if (!Boolean.getBoolean(IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME)
                && event instanceof ApplicationStartingEvent && multipleProcessors()
                && preinitializationStarted.compareAndSet(false, true)) {
            performPreinitialization();
        }
        if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent)
                && preinitializationStarted.get()) {
            try {
                preinitializationComplete.await();
            }
            catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }

    private boolean multipleProcessors() {
        return Runtime.getRuntime().availableProcessors() > 1;
    }

    private void performPreinitialization() {
        try {
            Thread thread = new Thread(new Runnable() {

                @Override
                public void run() {
                    runSafely(new ConversionServiceInitializer());
                    runSafely(new ValidationInitializer());
                    runSafely(new MessageConverterInitializer());
                    runSafely(new JacksonInitializer());
                    runSafely(new CharsetInitializer());
                    preinitializationComplete.countDown();
                }

                public void runSafely(Runnable runnable) {
                    try {
                        runnable.run();
                    }
                    catch (Throwable ex) {
                        // Ignore
                    }
                }

            }, "background-preinit");
            thread.start();
        }
        catch (Exception ex) {
            // This will fail on GAE where creating threads is prohibited. We can safely
            // continue but startup will be slightly slower as the initialization will now
            // happen on the main thread.
            preinitializationComplete.countDown();
        }
    }

    /**
     * Early initializer for Spring MessageConverters.
     */
    private static class MessageConverterInitializer implements Runnable {

        @Override
        public void run() {
            new AllEncompassingFormHttpMessageConverter();
        }

    }

    /**
     * Early initializer for javax.validation.
     */
    private static class ValidationInitializer implements Runnable {

        @Override
        public void run() {
            Configuration<?> configuration = Validation.byDefaultProvider().configure();
            configuration.buildValidatorFactory().getValidator();
        }

    }

    /**
     * Early initializer for Jackson.
     */
    private static class JacksonInitializer implements Runnable {

        @Override
        public void run() {
            Jackson2ObjectMapperBuilder.json().build();
        }

    }

    /**
     * Early initializer for Spring's ConversionService.
     */
    private static class ConversionServiceInitializer implements Runnable {

        @Override
        public void run() {
            new DefaultFormattingConversionService();
        }

    }

    private static class CharsetInitializer implements Runnable {

        @Override
        public void run() {
            StandardCharsets.UTF_8.name();
        }

    }

}

These lines:

    /**
     * Early initializer for Spring's ConversionService.
     */
    private static class ConversionServiceInitializer implements Runnable {

        @Override
        public void run() {
            new DefaultFormattingConversionService();
        }

    }

Why just new a DefaultFormattingConversionService object, but did not assign to any reference. what it affect?

I read some times source code, but still confused.

Who could help me, thanks advance!

Update: I debugged, but still could not find anywhere retain the reference indirectly. avatar

2 Answers

TL;DR

You are correct. The constructor is called and the object itself is never referenced (directly). You can search as long as you want, you wont find any usage of those created objects.


Why would you do that?

The file you referenced was created by Phil Webb in 2015, having no Javadoc, but a detailed description in the commit message:

Improve performance with BackgroundPreinitializer

Add a BackgroundPreinitializer to trigger early initialization in a background thread of time consuming tasks. By moving certain initialization tasks to background thread and triggering them early we can improve the critical path when the application starts. For example, Tomcat's MBeanFactory class parses several XML files when first loaded. If we trigger the load in a background thread it completes before Tomcat actually needs to use it.

The initial set of initializers included with this commit are:

  • Tomcat MBeanFactory
  • javax.validation
  • Spring's AllEncompassingFormHttpMessageConverter

See gh-4252

The referenced issue gh-4252 lists some performance measurements and claims the performance improvement of that multithreaded pre-initialization is about 13%.

The method you quoted, constructing the DefaultFormattingConversionService was introduced in #5182bc4f2, also by mr. webb and - according to the commit message and the referenced issue (gh-6177) - hasn't much effect at all.

I think that as said before, here the only effect is to initialize the static things (fields and classes).

example if you look at the code of ConversionServiceInitializer : 3 static boolean are initialized by checking if specific classes are present in the class

private static final boolean jsr354Present = ClassUtils.isPresent(
        "javax.money.MonetaryAmount", DefaultFormattingConversionService.class.getClassLoader());

private static final boolean jsr310Present = ClassUtils.isPresent(
        "java.time.LocalDate", DefaultFormattingConversionService.class.getClassLoader());

private static final boolean jodaTimePresent = ClassUtils.isPresent(
        "org.joda.time.LocalDate", DefaultFormattingConversionService.class.getClassLoader());

The default constructor for ConversionServiceInitializer is also calling indirectly the initialization of all default converters

public DefaultFormattingConversionService()

-> this(null, true);

->

DefaultConversionService.addDefaultConverters(this);
if (registerDefaultFormatters) {
    addDefaultFormatters(this);
}

each of the 2 calls

  • DefaultConversionService.addDefaultConverters(this)
  • and addDefaultFormatters(this)

are initializing multiple default formaters and converters. most of them don't have any static initialization but some have.

Initializing an instance of DefaultConversionService at the begining of the application allow to initialize all the converters on startup so they won't be lazy initialized at a time an user is waiting for a response...

Related