Java tutorial
/* * Copyright 2002-2018 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.web.server.adapter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebExceptionHandler; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebHandler; import org.springframework.web.server.handler.ExceptionHandlingWebHandler; import org.springframework.web.server.handler.FilteringWebHandler; import org.springframework.web.server.i18n.LocaleContextResolver; import org.springframework.web.server.session.DefaultWebSessionManager; import org.springframework.web.server.session.WebSessionManager; /** * This builder has two purposes: * * <p>One is to assemble a processing chain that consists of a target {@link WebHandler}, * then decorated with a set of {@link WebFilter WebFilters}, then further decorated with * a set of {@link WebExceptionHandler WebExceptionHandlers}. * * <p>The second purpose is to adapt the resulting processing chain to an {@link HttpHandler}: * the lowest-level reactive HTTP handling abstraction which can then be used with any of the * supported runtimes. The adaptation is done with the help of {@link HttpWebHandlerAdapter}. * * <p>The processing chain can be assembled manually via builder methods, or detected from * a Spring {@link ApplicationContext} via {@link #applicationContext}, or a mix of both. * * @author Rossen Stoyanchev * @author Sebastien Deleuze * @since 5.0 * @see HttpWebHandlerAdapter */ public final class WebHttpHandlerBuilder { /** Well-known name for the target WebHandler in the bean factory. */ public static final String WEB_HANDLER_BEAN_NAME = "webHandler"; /** Well-known name for the WebSessionManager in the bean factory. */ public static final String WEB_SESSION_MANAGER_BEAN_NAME = "webSessionManager"; /** Well-known name for the ServerCodecConfigurer in the bean factory. */ public static final String SERVER_CODEC_CONFIGURER_BEAN_NAME = "serverCodecConfigurer"; /** Well-known name for the LocaleContextResolver in the bean factory. */ public static final String LOCALE_CONTEXT_RESOLVER_BEAN_NAME = "localeContextResolver"; /** Well-known name for the ForwardedHeaderTransformer in the bean factory. */ public static final String FORWARDED_HEADER_TRANSFORMER_BEAN_NAME = "forwardedHeaderTransformer"; private final WebHandler webHandler; @Nullable private final ApplicationContext applicationContext; private final List<WebFilter> filters = new ArrayList<>(); private final List<WebExceptionHandler> exceptionHandlers = new ArrayList<>(); @Nullable private WebSessionManager sessionManager; @Nullable private ServerCodecConfigurer codecConfigurer; @Nullable private LocaleContextResolver localeContextResolver; @Nullable private ForwardedHeaderTransformer forwardedHeaderTransformer; /** * Private constructor to use when initialized from an ApplicationContext. */ private WebHttpHandlerBuilder(WebHandler webHandler, @Nullable ApplicationContext applicationContext) { Assert.notNull(webHandler, "WebHandler must not be null"); this.webHandler = webHandler; this.applicationContext = applicationContext; } /** * Copy constructor. */ private WebHttpHandlerBuilder(WebHttpHandlerBuilder other) { this.webHandler = other.webHandler; this.applicationContext = other.applicationContext; this.filters.addAll(other.filters); this.exceptionHandlers.addAll(other.exceptionHandlers); this.sessionManager = other.sessionManager; this.codecConfigurer = other.codecConfigurer; this.localeContextResolver = other.localeContextResolver; this.forwardedHeaderTransformer = other.forwardedHeaderTransformer; } /** * Static factory method to create a new builder instance. * @param webHandler the target handler for the request * @return the prepared builder */ public static WebHttpHandlerBuilder webHandler(WebHandler webHandler) { return new WebHttpHandlerBuilder(webHandler, null); } /** * Static factory method to create a new builder instance by detecting beans * in an {@link ApplicationContext}. The following are detected: * <ul> * <li>{@link WebHandler} [1] -- looked up by the name * {@link #WEB_HANDLER_BEAN_NAME}. * <li>{@link WebFilter} [0..N] -- detected by type and ordered, * see {@link AnnotationAwareOrderComparator}. * <li>{@link WebExceptionHandler} [0..N] -- detected by type and * ordered. * <li>{@link WebSessionManager} [0..1] -- looked up by the name * {@link #WEB_SESSION_MANAGER_BEAN_NAME}. * <li>{@link ServerCodecConfigurer} [0..1] -- looked up by the name * {@link #SERVER_CODEC_CONFIGURER_BEAN_NAME}. * <li>{@link LocaleContextResolver} [0..1] -- looked up by the name * {@link #LOCALE_CONTEXT_RESOLVER_BEAN_NAME}. * </ul> * @param context the application context to use for the lookup * @return the prepared builder */ public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) { WebHttpHandlerBuilder builder = new WebHttpHandlerBuilder( context.getBean(WEB_HANDLER_BEAN_NAME, WebHandler.class), context); List<WebFilter> webFilters = context.getBeanProvider(WebFilter.class).orderedStream() .collect(Collectors.toList()); builder.filters(filters -> filters.addAll(webFilters)); List<WebExceptionHandler> exceptionHandlers = context.getBeanProvider(WebExceptionHandler.class) .orderedStream().collect(Collectors.toList()); builder.exceptionHandlers(handlers -> handlers.addAll(exceptionHandlers)); try { builder.sessionManager(context.getBean(WEB_SESSION_MANAGER_BEAN_NAME, WebSessionManager.class)); } catch (NoSuchBeanDefinitionException ex) { // Fall back on default } try { builder.codecConfigurer( context.getBean(SERVER_CODEC_CONFIGURER_BEAN_NAME, ServerCodecConfigurer.class)); } catch (NoSuchBeanDefinitionException ex) { // Fall back on default } try { builder.localeContextResolver( context.getBean(LOCALE_CONTEXT_RESOLVER_BEAN_NAME, LocaleContextResolver.class)); } catch (NoSuchBeanDefinitionException ex) { // Fall back on default } try { builder.localeContextResolver( context.getBean(LOCALE_CONTEXT_RESOLVER_BEAN_NAME, LocaleContextResolver.class)); } catch (NoSuchBeanDefinitionException ex) { // Fall back on default } try { builder.forwardedHeaderTransformer( context.getBean(FORWARDED_HEADER_TRANSFORMER_BEAN_NAME, ForwardedHeaderTransformer.class)); } catch (NoSuchBeanDefinitionException ex) { // Fall back on default } return builder; } /** * Add the given filter(s). * @param filters the filter(s) to add that's */ public WebHttpHandlerBuilder filter(WebFilter... filters) { if (!ObjectUtils.isEmpty(filters)) { this.filters.addAll(Arrays.asList(filters)); updateFilters(); } return this; } /** * Manipulate the "live" list of currently configured filters. * @param consumer the consumer to use */ public WebHttpHandlerBuilder filters(Consumer<List<WebFilter>> consumer) { consumer.accept(this.filters); updateFilters(); return this; } private void updateFilters() { if (this.filters.isEmpty()) { return; } List<WebFilter> filtersToUse = this.filters.stream().peek(filter -> { if (filter instanceof ForwardedHeaderTransformer && this.forwardedHeaderTransformer == null) { this.forwardedHeaderTransformer = (ForwardedHeaderTransformer) filter; } }).filter(filter -> !(filter instanceof ForwardedHeaderTransformer)).collect(Collectors.toList()); this.filters.clear(); this.filters.addAll(filtersToUse); } /** * Add the given exception handler(s). * @param handlers the exception handler(s) */ public WebHttpHandlerBuilder exceptionHandler(WebExceptionHandler... handlers) { if (!ObjectUtils.isEmpty(handlers)) { this.exceptionHandlers.addAll(Arrays.asList(handlers)); } return this; } /** * Manipulate the "live" list of currently configured exception handlers. * @param consumer the consumer to use */ public WebHttpHandlerBuilder exceptionHandlers(Consumer<List<WebExceptionHandler>> consumer) { consumer.accept(this.exceptionHandlers); return this; } /** * Configure the {@link WebSessionManager} to set on the * {@link ServerWebExchange WebServerExchange}. * <p>By default {@link DefaultWebSessionManager} is used. * @param manager the session manager * @see HttpWebHandlerAdapter#setSessionManager(WebSessionManager) */ public WebHttpHandlerBuilder sessionManager(WebSessionManager manager) { this.sessionManager = manager; return this; } /** * Whether a {@code WebSessionManager} is configured or not, either detected from an * {@code ApplicationContext} or explicitly configured via {@link #sessionManager}. * @since 5.0.9 */ public boolean hasSessionManager() { return (this.sessionManager != null); } /** * Configure the {@link ServerCodecConfigurer} to set on the {@code WebServerExchange}. * @param codecConfigurer the codec configurer */ public WebHttpHandlerBuilder codecConfigurer(ServerCodecConfigurer codecConfigurer) { this.codecConfigurer = codecConfigurer; return this; } /** * Whether a {@code ServerCodecConfigurer} is configured or not, either detected from an * {@code ApplicationContext} or explicitly configured via {@link #codecConfigurer}. * @since 5.0.9 */ public boolean hasCodecConfigurer() { return (this.codecConfigurer != null); } /** * Configure the {@link LocaleContextResolver} to set on the * {@link ServerWebExchange WebServerExchange}. * @param localeContextResolver the locale context resolver */ public WebHttpHandlerBuilder localeContextResolver(LocaleContextResolver localeContextResolver) { this.localeContextResolver = localeContextResolver; return this; } /** * Whether a {@code LocaleContextResolver} is configured or not, either detected from an * {@code ApplicationContext} or explicitly configured via {@link #localeContextResolver}. * @since 5.0.9 */ public boolean hasLocaleContextResolver() { return (this.localeContextResolver != null); } /** * Configure the {@link ForwardedHeaderTransformer} for extracting and/or * removing forwarded headers. * @param transformer the transformer * @since 5.1 */ public WebHttpHandlerBuilder forwardedHeaderTransformer(ForwardedHeaderTransformer transformer) { this.forwardedHeaderTransformer = transformer; return this; } /** * Whether a {@code ForwardedHeaderTransformer} is configured or not, either * detected from an {@code ApplicationContext} or explicitly configured via * {@link #forwardedHeaderTransformer(ForwardedHeaderTransformer)}. * @since 5.1 */ public boolean hasForwardedHeaderTransformer() { return (this.forwardedHeaderTransformer != null); } /** * Build the {@link HttpHandler}. */ public HttpHandler build() { WebHandler decorated = new FilteringWebHandler(this.webHandler, this.filters); decorated = new ExceptionHandlingWebHandler(decorated, this.exceptionHandlers); HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated); if (this.sessionManager != null) { adapted.setSessionManager(this.sessionManager); } if (this.codecConfigurer != null) { adapted.setCodecConfigurer(this.codecConfigurer); } if (this.localeContextResolver != null) { adapted.setLocaleContextResolver(this.localeContextResolver); } if (this.forwardedHeaderTransformer != null) { adapted.setForwardedHeaderTransformer(this.forwardedHeaderTransformer); } if (this.applicationContext != null) { adapted.setApplicationContext(this.applicationContext); } adapted.afterPropertiesSet(); return adapted; } /** * Clone this {@link WebHttpHandlerBuilder}. * @return the cloned builder instance */ @Override public WebHttpHandlerBuilder clone() { return new WebHttpHandlerBuilder(this); } }