Add boot-like ac bootstrap to spring-cloud-function-serverless-web

This commit is contained in:
Oleg Zhurakousky
2023-06-14 10:38:23 +02:00
parent 649f0089fd
commit 7a1980db1e
7 changed files with 624 additions and 27 deletions

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2023-2023 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.cloud.function.serverless.web;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.AsyncEvent;
import jakarta.servlet.AsyncListener;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.util.WebUtils;
/**
* Implementation of Async context for {@link ProxyMvc}.
*
* @author Oleg Zhurakousky
*/
public class ProxyAsyncContext implements AsyncContext {
private final HttpServletRequest request;
@Nullable
private final HttpServletResponse response;
private final List<AsyncListener> listeners = new ArrayList<>();
@Nullable
private String dispatchedPath;
private long timeout = 10 * 1000L;
private final List<Runnable> dispatchHandlers = new ArrayList<>();
public ProxyAsyncContext(ServletRequest request, @Nullable ServletResponse response) {
this.request = (HttpServletRequest) request;
this.response = (HttpServletResponse) response;
}
public void addDispatchHandler(Runnable handler) {
Assert.notNull(handler, "Dispatch handler must not be null");
synchronized (this) {
if (this.dispatchedPath == null) {
this.dispatchHandlers.add(handler);
}
else {
handler.run();
}
}
}
@Override
public ServletRequest getRequest() {
return this.request;
}
@Override
@Nullable
public ServletResponse getResponse() {
return this.response;
}
@Override
public boolean hasOriginalRequestAndResponse() {
return (this.request instanceof ProxyHttpServletRequest && this.response instanceof ProxyHttpServletResponse);
}
@Override
public void dispatch() {
dispatch(this.request.getRequestURI());
}
@Override
public void dispatch(String path) {
dispatch(null, path);
}
@Override
public void dispatch(@Nullable ServletContext context, String path) {
synchronized (this) {
this.dispatchedPath = path;
this.dispatchHandlers.forEach(Runnable::run);
}
}
@Nullable
public String getDispatchedPath() {
return this.dispatchedPath;
}
@Override
public void complete() {
ProxyHttpServletRequest mockRequest = WebUtils.getNativeRequest(this.request, ProxyHttpServletRequest.class);
if (mockRequest != null) {
mockRequest.setAsyncStarted(false);
}
for (AsyncListener listener : this.listeners) {
try {
listener.onComplete(new AsyncEvent(this, this.request, this.response));
}
catch (IOException ex) {
throw new IllegalStateException("AsyncListener failure", ex);
}
}
}
@Override
public void start(Runnable runnable) {
runnable.run();
}
@Override
public void addListener(AsyncListener listener) {
this.listeners.add(listener);
}
@Override
public void addListener(AsyncListener listener, ServletRequest request, ServletResponse response) {
this.listeners.add(listener);
}
public List<AsyncListener> getListeners() {
return this.listeners;
}
@Override
public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException {
return BeanUtils.instantiateClass(clazz);
}
/**
* By default this is set to 10000 (10 seconds) even though the Servlet API
* specifies a default async request timeout of 30 seconds. Keep in mind the
* timeout could further be impacted by global configuration through the MVC
* Java config or the XML namespace, as well as be overridden per request on
* {@link org.springframework.web.context.request.async.DeferredResult DeferredResult}
* or on
* {@link org.springframework.web.servlet.mvc.method.annotation.SseEmitter SseEmitter}.
* @param timeout the timeout value to use.
* @see AsyncContext#setTimeout(long)
*/
@Override
public void setTimeout(long timeout) {
this.timeout = timeout;
}
@Override
public long getTimeout() {
return this.timeout;
}
}

View File

@@ -113,7 +113,7 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
private boolean asyncStarted = false;
private boolean asyncSupported = false;
private boolean asyncSupported = true;
private DispatcherType dispatcherType = DispatcherType.REQUEST;
@@ -163,6 +163,8 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
private final MultiValueMap<String, Part> parts = new LinkedMultiValueMap<>();
private AsyncContext asyncContext;
public ProxyHttpServletRequest(ServletContext servletContext, String method, String requestURI) {
this.servletContext = servletContext;
this.method = method;
@@ -246,8 +248,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
*/
@Nullable
public String getContentAsString() throws IllegalStateException, UnsupportedEncodingException {
// Assert.state(this.characterEncoding != null, "Cannot get content as a String for a null character encoding. "
// + "Consider setting the characterEncoding in the request.");
if (this.content == null) {
return null;
@@ -633,7 +633,10 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
@Override
public AsyncContext startAsync(ServletRequest request, @Nullable ServletResponse response) {
throw new UnsupportedOperationException();
Assert.state(this.asyncSupported, "Async not supported");
this.asyncStarted = true;
this.asyncContext = this.asyncContext == null ? new ProxyAsyncContext(request, response) : this.asyncContext;
return this.asyncContext;
}
public void setAsyncStarted(boolean asyncStarted) {
@@ -647,6 +650,7 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
public void setAsyncSupported(boolean asyncSupported) {
this.asyncSupported = asyncSupported;
this.dispatcherType = DispatcherType.ASYNC;
}
@Override
@@ -655,15 +659,16 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
}
public void setAsyncContext(@Nullable AsyncContext asyncContext) {
throw new UnsupportedOperationException();
this.asyncContext = asyncContext;
}
@Override
@Nullable
public AsyncContext getAsyncContext() {
return null;
return this.asyncContext;
}
public void setDispatcherType(DispatcherType dispatcherType) {
this.dispatcherType = dispatcherType;
}
@@ -692,7 +697,7 @@ public class ProxyHttpServletRequest implements HttpServletRequest {
@Override
@Nullable
public String getHeader(String name) {
return this.headers.containsKey(name) ? this.headers.get(name).toString() : null;
return this.headers.containsKey(name) ? this.headers.get(name).get(0) : null;
}
@Override

View File

@@ -41,6 +41,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.util.WebUtils;
/**
*
* @author Oleg Zhurakousky
@@ -160,7 +161,6 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
@Override
public void flushBuffer() {
}
@Override
@@ -248,7 +248,7 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
@Override
@Nullable
public String getHeader(String name) {
return this.headers.containsKey(name) ? this.headers.get(name).toString() : null;
return this.headers.containsKey(name) ? this.headers.get(name).get(0) : null;
}
/**

View File

@@ -41,13 +41,16 @@ import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.servlet.DispatcherServlet;
/**
@@ -59,7 +62,7 @@ import org.springframework.web.servlet.DispatcherServlet;
* @author Oleg Zhurakousky
*
*/
public class ProxyMvc {
public final class ProxyMvc {
private static Log LOG = LogFactory.getLog(ProxyMvc.class);
@@ -84,8 +87,7 @@ public class ProxyMvc {
}
public static ProxyMvc INSTANCE(Class<?>... componentClasses) {
AnnotationConfigServletWebApplicationContext applpicationContext = new AnnotationConfigServletWebApplicationContext();
applpicationContext.scan(componentClasses[0].getPackageName());
ConfigurableWebApplicationContext applpicationContext = ServerlessWebApplication.run(componentClasses, new String[] {});
return INSTANCE(applpicationContext);
}
@@ -97,21 +99,23 @@ public class ProxyMvc {
this.applicationContext = applicationContext;
ProxyServletContext servletContext = new ProxyServletContext();
this.applicationContext.setServletContext(servletContext);
this.dispatcher = new DispatcherServlet(this.applicationContext);
this.dispatcher.setDetectAllHandlerMappings(false);
this.applicationContext.refresh();
ServletRegistration.Dynamic reg = servletContext.addServlet("dispatcherServlet", dispatcher);
if (this.applicationContext.containsBean(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
this.dispatcher = this.applicationContext.getBean(DispatcherServlet.class);
}
else {
this.dispatcher = new DispatcherServlet(this.applicationContext);
this.dispatcher.setDetectAllHandlerMappings(false);
((GenericApplicationContext) this.applicationContext).registerBean(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME,
DispatcherServlet.class, () -> this.dispatcher);
}
ServletRegistration.Dynamic reg = servletContext.addServlet(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME, dispatcher);
reg.setLoadOnStartup(1);
this.servletContext = applicationContext.getServletContext();
try {
this.dispatcher.init(new ProxyServletConfig(this.servletContext));
try {
this.service(new ProxyHttpServletRequest(servletContext, "INFO", "/"), new ProxyHttpServletResponse());
}
catch (Exception e) {
//ignore as this is just a pre-warming attempt
}
}
catch (Exception e) {
throw new IllegalStateException("Faild to create Spring MVC DispatcherServlet proxy", e);
@@ -137,10 +141,17 @@ public class ProxyMvc {
this.service(request, response, (CountDownLatch) null);
}
public void service(HttpServletRequest request, HttpServletResponse response, CountDownLatch latch) throws Exception {
ProxyFilterChain filterChain = new ProxyFilterChain(this.dispatcher);
filterChain.doFilter(request, response);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
if (asyncManager.isConcurrentHandlingStarted()) {
this.dispatcher.service(request, response);
}
if (latch != null) {
latch.countDown();
}
@@ -170,7 +181,7 @@ public class ProxyMvc {
ProxyFilterChain(DispatcherServlet servlet) {
List<Filter> filters = new ArrayList<>();
servlet.getServletContext().getFilterRegistrations().values().forEach(fr -> filters.add(((ProxyFilterRegistration) fr).getFilter()));
servlet.getWebApplicationContext().getBeansOfType(Filter.class).values().forEach(f -> filters.add(f));
//servlet.getWebApplicationContext().getBeansOfType(Filter.class).values().forEach(f -> filters.add(f));
Assert.notNull(filters, "filters cannot be null");
Assert.noNullElements(filters, "filters cannot contain null values");
this.filters = initFilterList(servlet, filters.toArray(new Filter[] {}));
@@ -310,7 +321,7 @@ public class ProxyMvc {
@Override
public String getServletName() {
return "spring-serverless-proxy";
return DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME;
}
@Override

View File

@@ -227,7 +227,9 @@ public class ProxyServletContext implements ServletContext {
@Override
public FilterRegistration.Dynamic addFilter(String filterName, Filter filter) {
throw new UnsupportedOperationException("This ServletContext does not represent a running web container");
ProxyFilterRegistration registration = new ProxyFilterRegistration(filterName, filter);
filterRegistrations.put(filterName, registration);
return registration;
}
Map<String, FilterRegistration> filterRegistrations = new HashMap<>();

View File

@@ -0,0 +1,402 @@
/*
* Copyright 2023-2023 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.cloud.function.serverless.web;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aot.AotDetector;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationContextFactory;
import org.springframework.boot.Banner;
import org.springframework.boot.BootstrapRegistryInitializer;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.DefaultApplicationArguments;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.DefaultPropertiesPropertySource;
import org.springframework.boot.LazyInitializationBeanFactoryPostProcessor;
import org.springframework.boot.ResourceBanner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.boot.SpringBootVersion;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.ansi.AnsiColor;
import org.springframework.boot.ansi.AnsiOutput;
import org.springframework.boot.ansi.AnsiStyle;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.aot.AotApplicationContextInitializer;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.io.support.SpringFactoriesLoader.ArgumentResolver;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.metrics.StartupStep;
import org.springframework.util.Assert;
import org.springframework.web.context.ConfigurableWebApplicationContext;
/**
*
* @author Oleg Zhurakousky
*
*/
class ServerlessWebApplication extends SpringApplication {
private static final Log logger = LogFactory.getLog(ServerlessWebApplication.class);
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;
private boolean allowCircularReferences;
private boolean allowBeanDefinitionOverriding;
private boolean logStartupInfo = true;
private boolean lazyInitialization = false;
private WebApplicationType webApplicationType;
private List<ApplicationContextInitializer<?>> initializers;
public static ConfigurableWebApplicationContext run(Class<?>[] primarySources, String[] args) {
return new ServerlessWebApplication(primarySources).run(args);
}
ServerlessWebApplication(Class<?>... classes) {
super(classes);
}
@Override
public ConfigurableWebApplicationContext run(String... args) {
this.webApplicationType = WebApplicationType.SERVLET;
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableWebApplicationContext context = null;
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.getMainApplicationClass());
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Banner printedBanner = printBanner(environment);
context = (ConfigurableWebApplicationContext) createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
}
catch (Throwable ex) {
throw new IllegalStateException(ex);
}
return context;
}
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
ConfigurationPropertySources.attach(environment);
return environment;
}
private ConfigurableEnvironment getOrCreateEnvironment() {
ConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType);
if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {
environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);
}
return environment;
}
private SpringApplicationRunListeners getRunListeners(String[] args) {
ArgumentResolver argumentResolver = ArgumentResolver.of(SpringApplication.class, this);
argumentResolver = argumentResolver.and(String[].class, args);
List<SpringApplicationRunListener> listeners = getSpringFactoriesInstances(SpringApplicationRunListener.class, argumentResolver);
return new SpringApplicationRunListeners(logger, listeners, this.applicationStartup);
}
private Banner printBanner(ConfigurableEnvironment environment) {
ResourceLoader resourceLoader = (this.getResourceLoader() != null) ? this.getResourceLoader()
: new DefaultResourceLoader(null);
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, new SpringAwsBanner());
return bannerPrinter.print(environment, this.getMainApplicationClass(), System.out);
}
private DefaultBootstrapContext createBootstrapContext() {
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
ArrayList<BootstrapRegistryInitializer> bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
return bootstrapContext;
}
private <T> List<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, null);
}
private <T> List<T> getSpringFactoriesInstances(Class<T> type, ArgumentResolver argumentResolver) {
return SpringFactoriesLoader.forDefaultResourceLocation(getClassLoader()).load(type, argumentResolver);
}
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
addAotGeneratedInitializerIfNecessary(this.initializers);
applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {
autowireCapableBeanFactory.setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {
listableBeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
if (!AotDetector.useGeneratedArtifacts()) {
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
}
listeners.contextLoaded(context);
}
private void addAotGeneratedInitializerIfNecessary(List<ApplicationContextInitializer<?>> initializers) {
if (AotDetector.useGeneratedArtifacts()) {
List<ApplicationContextInitializer<?>> aotInitializers = new ArrayList<>(
initializers.stream().filter(AotApplicationContextInitializer.class::isInstance).toList());
if (aotInitializers.isEmpty()) {
String initializerClassName = this.getMainApplicationClass().getName() + "__ApplicationContextInitializer";
aotInitializers.add(AotApplicationContextInitializer.forInitializerClasses(initializerClassName));
}
initializers.removeAll(aotInitializers);
initializers.addAll(0, aotInitializers);
}
}
private static class PropertySourceOrderingBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
private final ConfigurableApplicationContext context;
PropertySourceOrderingBeanFactoryPostProcessor(ConfigurableApplicationContext context) {
this.context = context;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
DefaultPropertiesPropertySource.moveToEnd(this.context.getEnvironment());
}
}
private static class SpringApplicationBannerPrinter {
static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
static final String DEFAULT_BANNER_LOCATION = "banner.txt";
private static final Banner DEFAULT_BANNER = new SpringAwsBanner();
private final ResourceLoader resourceLoader;
private final Banner fallbackBanner;
SpringApplicationBannerPrinter(ResourceLoader resourceLoader, Banner fallbackBanner) {
this.resourceLoader = resourceLoader;
this.fallbackBanner = fallbackBanner;
}
Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
Banner banner = getBanner(environment);
banner.printBanner(environment, sourceClass, out);
return new PrintedBanner(banner, sourceClass);
}
private Banner getBanner(Environment environment) {
Banner textBanner = getTextBanner(environment);
if (textBanner != null) {
return textBanner;
}
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
return DEFAULT_BANNER;
}
private Banner getTextBanner(Environment environment) {
String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
Resource resource = this.resourceLoader.getResource(location);
try {
if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
return new ResourceBanner(resource);
}
}
catch (IOException ex) {
// Ignore
}
return null;
}
/**
* Decorator that allows a {@link Banner} to be printed again without needing to
* specify the source class.
*/
private static class PrintedBanner implements Banner {
private final Banner banner;
private final Class<?> sourceClass;
PrintedBanner(Banner banner, Class<?> sourceClass) {
this.banner = banner;
this.sourceClass = sourceClass;
}
@Override
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
sourceClass = (sourceClass != null) ? sourceClass : this.sourceClass;
this.banner.printBanner(environment, sourceClass, out);
}
}
}
private static class SpringAwsBanner implements Banner {
private static final String[] BANNER = { "", "\n"
+ " ____ _ _____ ______ _ _ _ \n"
+ " / ___| _ __ _ __(_)_ __ __ _ / / \\ \\ / / ___| | | __ _ _ __ ___ | |__ __| | __ _ \n"
+ " \\___ \\| '_ \\| '__| | '_ \\ / _` | / / _ \\ \\ /\\ / /\\___ \\ | | / _` | '_ ` _ \\| '_ \\ / _` |/ _` |\n"
+ " ___) | |_) | | | | | | | (_| |/ / ___ \\ V V / ___) | | |__| (_| | | | | | | |_) | (_| | (_| |\n"
+ " |____/| .__/|_| |_|_| |_|\\__, /_/_/ \\_\\_/\\_/ |____/ |_____\\__,_|_| |_| |_|_.__/ \\__,_|\\__,_|\n"
+ " |_| |___/ \n"
+ "" };
private static final String SPRING_BOOT = " :: Spring Boot :: ";
private static final int STRAP_LINE_SIZE = 42;
@Override
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {
for (String line : BANNER) {
printStream.println(line);
}
String version = SpringBootVersion.getVersion();
version = (version != null) ? " (v" + version + ")" : "";
StringBuilder padding = new StringBuilder();
while (padding.length() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) {
padding.append(" ");
}
printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT, AnsiColor.DEFAULT, padding.toString(),
AnsiStyle.FAINT, version));
printStream.println();
}
}
private static class SpringApplicationRunListeners {
private final List<SpringApplicationRunListener> listeners;
private final ApplicationStartup applicationStartup;
SpringApplicationRunListeners(Log log, List<SpringApplicationRunListener> listeners,
ApplicationStartup applicationStartup) {
this.listeners = List.copyOf(listeners);
this.applicationStartup = applicationStartup;
}
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
(step) -> {
if (mainApplicationClass != null) {
step.tag("mainApplicationClass", mainApplicationClass.getName());
}
});
}
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
doWithListeners("spring.boot.application.environment-prepared",
(listener) -> listener.environmentPrepared(bootstrapContext, environment));
}
void contextPrepared(ConfigurableApplicationContext context) {
doWithListeners("spring.boot.application.context-prepared", (listener) -> listener.contextPrepared(context));
}
void contextLoaded(ConfigurableApplicationContext context) {
doWithListeners("spring.boot.application.context-loaded", (listener) -> listener.contextLoaded(context));
}
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction) {
doWithListeners(stepName, listenerAction, null);
}
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
Consumer<StartupStep> stepAction) {
StartupStep step = this.applicationStartup.start(stepName);
this.listeners.forEach(listenerAction);
if (stepAction != null) {
stepAction.accept(step);
}
step.end();
}
}
}

View File

@@ -25,6 +25,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.function.test.app.Pet;
import org.springframework.cloud.function.test.app.PetStoreSpringAppConfig;
import org.springframework.http.HttpStatus;
@@ -45,7 +46,7 @@ public class RequestResponseTests {
@BeforeEach
public void before() {
this.mvc = ProxyMvc.INSTANCE(PetStoreSpringAppConfig.class, ProxyErrorController.class);
this.mvc = ProxyMvc.INSTANCE(ProxyErrorController.class, PetStoreSpringAppConfig.class);
}
@AfterEach