001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.autoconfigure;
018
019import java.nio.charset.StandardCharsets;
020import java.util.concurrent.CountDownLatch;
021import java.util.concurrent.atomic.AtomicBoolean;
022
023import javax.validation.Configuration;
024import javax.validation.Validation;
025
026import org.apache.catalina.mbeans.MBeanFactory;
027
028import org.springframework.boot.context.event.ApplicationFailedEvent;
029import org.springframework.boot.context.event.ApplicationReadyEvent;
030import org.springframework.boot.context.event.ApplicationStartingEvent;
031import org.springframework.boot.context.event.SpringApplicationEvent;
032import org.springframework.boot.context.logging.LoggingApplicationListener;
033import org.springframework.context.ApplicationListener;
034import org.springframework.core.annotation.Order;
035import org.springframework.format.support.DefaultFormattingConversionService;
036import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
037import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
038
039/**
040 * {@link ApplicationListener} to trigger early initialization in a background thread of
041 * time consuming tasks.
042 * <p>
043 * Set the {@link #IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME} system property to
044 * {@code true} to disable this mechanism and let such initialization happen in the
045 * foreground.
046 *
047 * @author Phillip Webb
048 * @author Andy Wilkinson
049 * @author Artsiom Yudovin
050 * @since 1.3.0
051 */
052@Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
053public class BackgroundPreinitializer
054                implements ApplicationListener<SpringApplicationEvent> {
055
056        /**
057         * System property that instructs Spring Boot how to run pre initialization. When the
058         * property is set to {@code true}, no pre-initialization happens and each item is
059         * initialized in the foreground as it needs to. When the property is {@code false}
060         * (default), pre initialization runs in a separate thread in the background.
061         * @since 2.1.0
062         */
063        public static final String IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME = "spring.backgroundpreinitializer.ignore";
064
065        private static final AtomicBoolean preinitializationStarted = new AtomicBoolean(
066                        false);
067
068        private static final CountDownLatch preinitializationComplete = new CountDownLatch(1);
069
070        @Override
071        public void onApplicationEvent(SpringApplicationEvent event) {
072                if (!Boolean.getBoolean(IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME)
073                                && event instanceof ApplicationStartingEvent
074                                && preinitializationStarted.compareAndSet(false, true)) {
075                        performPreinitialization();
076                }
077                if ((event instanceof ApplicationReadyEvent
078                                || event instanceof ApplicationFailedEvent)
079                                && preinitializationStarted.get()) {
080                        try {
081                                preinitializationComplete.await();
082                        }
083                        catch (InterruptedException ex) {
084                                Thread.currentThread().interrupt();
085                        }
086                }
087        }
088
089        private void performPreinitialization() {
090                try {
091                        Thread thread = new Thread(new Runnable() {
092
093                                @Override
094                                public void run() {
095                                        runSafely(new ConversionServiceInitializer());
096                                        runSafely(new ValidationInitializer());
097                                        runSafely(new MessageConverterInitializer());
098                                        runSafely(new MBeanFactoryInitializer());
099                                        runSafely(new JacksonInitializer());
100                                        runSafely(new CharsetInitializer());
101                                        preinitializationComplete.countDown();
102                                }
103
104                                public void runSafely(Runnable runnable) {
105                                        try {
106                                                runnable.run();
107                                        }
108                                        catch (Throwable ex) {
109                                                // Ignore
110                                        }
111                                }
112
113                        }, "background-preinit");
114                        thread.start();
115                }
116                catch (Exception ex) {
117                        // This will fail on GAE where creating threads is prohibited. We can safely
118                        // continue but startup will be slightly slower as the initialization will now
119                        // happen on the main thread.
120                        preinitializationComplete.countDown();
121                }
122        }
123
124        /**
125         * Early initializer for Spring MessageConverters.
126         */
127        private static class MessageConverterInitializer implements Runnable {
128
129                @Override
130                public void run() {
131                        new AllEncompassingFormHttpMessageConverter();
132                }
133
134        }
135
136        /**
137         * Early initializer to load Tomcat MBean XML.
138         */
139        private static class MBeanFactoryInitializer implements Runnable {
140
141                @Override
142                public void run() {
143                        new MBeanFactory();
144                }
145
146        }
147
148        /**
149         * Early initializer for javax.validation.
150         */
151        private static class ValidationInitializer implements Runnable {
152
153                @Override
154                public void run() {
155                        Configuration<?> configuration = Validation.byDefaultProvider().configure();
156                        configuration.buildValidatorFactory().getValidator();
157                }
158
159        }
160
161        /**
162         * Early initializer for Jackson.
163         */
164        private static class JacksonInitializer implements Runnable {
165
166                @Override
167                public void run() {
168                        Jackson2ObjectMapperBuilder.json().build();
169                }
170
171        }
172
173        /**
174         * Early initializer for Spring's ConversionService.
175         */
176        private static class ConversionServiceInitializer implements Runnable {
177
178                @Override
179                public void run() {
180                        new DefaultFormattingConversionService();
181                }
182
183        }
184
185        private static class CharsetInitializer implements Runnable {
186
187                @Override
188                public void run() {
189                        StandardCharsets.UTF_8.name();
190                }
191
192        }
193
194}