diff --git a/spring-boot-project/spring-boot-docs/build.gradle b/spring-boot-project/spring-boot-docs/build.gradle index 2128ada063..fa4dcb6ec9 100644 --- a/spring-boot-project/spring-boot-docs/build.gradle +++ b/spring-boot-project/spring-boot-docs/build.gradle @@ -86,6 +86,7 @@ dependencies { implementation("org.slf4j:jul-to-slf4j") implementation("org.springframework:spring-test") implementation("org.springframework:spring-web") + implementation("org.springframework:spring-webmvc") implementation("org.springframework:spring-webflux") implementation("org.springframework.data:spring-data-couchbase") implementation("org.springframework.data:spring-data-neo4j") diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc index 6123756b13..b3c6eefe94 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc @@ -1118,12 +1118,9 @@ Sometimes, classes annotated with `@ConfigurationProperties` might not be suitab In these cases, specify the list of types to process using the `@EnableConfigurationProperties` annotation. This can be done on any `@Configuration` class, as shown in the following example: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - @Configuration(proxyBeanMethods = false) - @EnableConfigurationProperties(AcmeProperties.class) - public class MyConfiguration { - } +include::{include-springbootfeatures}/externalizedconfiguration/enable/MyConfiguration.java[tag=*] ---- To use configuration property scanning, add the `@ConfigurationPropertiesScan` annotation to your application. @@ -1131,12 +1128,9 @@ Typically, it is added to the main application class that is annotated with `@Sp By default, scanning will occur from the package of the class that declares the annotation. If you want to define specific packages to scan, you can do so as shown in the following example: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - @SpringBootApplication - @ConfigurationPropertiesScan({ "com.example.app", "org.acme.another" }) - public class MyApplication { - } +include::{include-springbootfeatures}/externalizedconfiguration/enable/MyApplication.java[tag=*] ---- [NOTE] @@ -1170,27 +1164,9 @@ This style of configuration works particularly well with the `SpringApplication` To work with `@ConfigurationProperties` beans, you can inject them in the same way as any other bean, as shown in the following example: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - @Service - public class MyService { - - private final AcmeProperties properties; - - @Autowired - public MyService(AcmeProperties properties) { - this.properties = properties; - } - - //... - - @PostConstruct - public void openConnection() { - Server server = new Server(this.properties.getRemoteAddress()); - // ... - } - - } +include::{include-springbootfeatures}/externalizedconfiguration/use/MyService.java[tag=*] ---- TIP: Using `@ConfigurationProperties` also lets you generate metadata files that can be used by IDEs to offer auto-completion for your own keys. @@ -1205,13 +1181,9 @@ Doing so can be particularly useful when you want to bind properties to third-pa To configure a bean from the `Environment` properties, add `@ConfigurationProperties` to its bean registration, as shown in the following example: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - @ConfigurationProperties(prefix = "another") - @Bean - public AnotherComponent anotherComponent() { - ... - } +include::{include-springbootfeatures}/externalizedconfiguration/ThirdPartyConfiguration.java[tag=*] ---- Any JavaBean property defined with the `another` prefix is mapped onto that `AnotherComponent` bean in manner similar to the preceding `AcmeProperties` example. @@ -1225,22 +1197,9 @@ Common examples where this is useful include dash-separated environment properti As an example, consider the following `@ConfigurationProperties` class: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - @ConfigurationProperties(prefix="acme.my-project.person") - public class OwnerProperties { - - private String firstName; - - public String getFirstName() { - return this.firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - } +include::{include-springbootfeatures}/externalizedconfiguration/relaxed/OwnerProperties.java[tag=*] ---- With the preceding code, the following properties names can all be used: @@ -1357,18 +1316,9 @@ When lists are configured in more than one place, overriding works by replacing For example, assume a `MyPojo` object with `name` and `description` attributes that are `null` by default. The following example exposes a list of `MyPojo` objects from `AcmeProperties`: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - @ConfigurationProperties("acme") - public class AcmeProperties { - - private final List list = new ArrayList<>(); - - public List getList() { - return this.list; - } - - } +include::{include-springbootfeatures}/externalizedconfiguration/merge/list/AcmeProperties.java[tag=*] ---- Consider the following configuration: @@ -1421,18 +1371,9 @@ For `Map` properties, you can bind with property values drawn from multiple sour However, for the same property in multiple sources, the one with the highest priority is used. The following example exposes a `Map` from `AcmeProperties`: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - @ConfigurationProperties("acme") - public class AcmeProperties { - - private final Map map = new HashMap<>(); - - public Map getMap() { - return this.map; - } - - } +include::{include-springbootfeatures}/externalizedconfiguration/merge/map/AcmeProperties.java[tag=*] ---- Consider the following configuration: @@ -1588,18 +1529,9 @@ Spring Boot attempts to validate `@ConfigurationProperties` classes whenever the You can use JSR-303 `javax.validation` constraint annotations directly on your configuration class. To do so, ensure that a compliant JSR-303 implementation is on your classpath and then add constraint annotations to your fields, as shown in the following example: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - @ConfigurationProperties(prefix="acme") - @Validated - public class AcmeProperties { - - @NotNull - private InetAddress remoteAddress; - - // ... getters and setters - - } +include::{include-springbootfeatures}/externalizedconfiguration/validate/AcmeProperties.java[tag=*] ---- TIP: You can also trigger validation by annotating the `@Bean` method that creates the configuration properties with `@Validated`. @@ -1607,30 +1539,9 @@ TIP: You can also trigger validation by annotating the `@Bean` method that creat To ensure that validation is always triggered for nested properties, even when no properties are found, the associated field must be annotated with `@Valid`. The following example builds on the preceding `AcmeProperties` example: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - @ConfigurationProperties(prefix="acme") - @Validated - public class AcmeProperties { - - @NotNull - private InetAddress remoteAddress; - - @Valid - private final Security security = new Security(); - - // ... getters and setters - - public static class Security { - - @NotEmpty - public String username; - - // ... getters and setters - - } - - } +include::{include-springbootfeatures}/externalizedconfiguration/validate/nested/AcmeProperties.java[tag=*] ---- You can also add a custom Spring `Validator` by creating a bean definition called `configurationPropertiesValidator`. @@ -1685,15 +1596,9 @@ If the value of a property from an application property file is a `SpEL` express Spring Profiles provide a way to segregate parts of your application configuration and make it be available only in certain environments. Any `@Component`, `@Configuration` or `@ConfigurationProperties` can be marked with `@Profile` to limit when it is loaded, as shown in the following example: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - @Configuration(proxyBeanMethods = false) - @Profile("production") - public class ProductionConfiguration { - - // ... - - } +include::{include-springbootfeatures}/profiles/ProductionConfiguration.java[tag=*] ---- NOTE: If `@ConfigurationProperties` beans are registered via `@EnableConfigurationProperties` instead of automatic scanning, the `@Profile` annotation needs to be specified on the `@Configuration` class that has the `@EnableConfigurationProperties` annotation. @@ -2326,28 +2231,9 @@ Methods in your controller are mapped to HTTP by using `@RequestMapping` annotat The following code shows a typical `@RestController` that serves JSON data: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - @RestController - @RequestMapping(value="/users") - public class MyRestController { - - @RequestMapping(value="/{user}", method=RequestMethod.GET) - public User getUser(@PathVariable Long user) { - // ... - } - - @RequestMapping(value="/{user}/customers", method=RequestMethod.GET) - List getUserCustomers(@PathVariable Long user) { - // ... - } - - @RequestMapping(value="/{user}", method=RequestMethod.DELETE) - public User deleteUser(@PathVariable Long user) { - // ... - } - - } +include::{include-springbootfeatures}/webapplications/servlet/MyRestController.java[tag=*] ---- Spring MVC is part of the core Spring Framework, and detailed information is available in the {spring-framework-docs}/web.html#mvc[reference documentation]. @@ -2395,23 +2281,9 @@ By default, strings are encoded in `UTF-8`. If you need to add or customize converters, you can use Spring Boot's `HttpMessageConverters` class, as shown in the following listing: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - import org.springframework.boot.autoconfigure.http.HttpMessageConverters; - import org.springframework.context.annotation.*; - import org.springframework.http.converter.*; - - @Configuration(proxyBeanMethods = false) - public class MyConfiguration { - - @Bean - public HttpMessageConverters customConverters() { - HttpMessageConverter additional = ... - HttpMessageConverter another = ... - return new HttpMessageConverters(additional, another); - } - - } +include::{include-springbootfeatures}/webapplications/HttpMessageConvertersConfiguration.java[tag=*] ---- Any `HttpMessageConverter` bean that is present in the context is added to the list of converters. @@ -2427,25 +2299,9 @@ Custom serializers are usually https://github.com/FasterXML/jackson-docs/wiki/Ja You can use the `@JsonComponent` annotation directly on `JsonSerializer`, `JsonDeserializer` or `KeyDeserializer` implementations. You can also use it on classes that contain serializers/deserializers as inner classes, as shown in the following example: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - import java.io.*; - import com.fasterxml.jackson.core.*; - import com.fasterxml.jackson.databind.*; - import org.springframework.boot.jackson.*; - - @JsonComponent - public class Example { - - public static class Serializer extends JsonSerializer { - // ... - } - - public static class Deserializer extends JsonDeserializer { - // ... - } - - } +include::{include-springbootfeatures}/webapplications/json/MyJsonComponent.java[tag=*] ---- All `@JsonComponent` beans in the `ApplicationContext` are automatically registered with Jackson. @@ -2454,6 +2310,13 @@ Because `@JsonComponent` is meta-annotated with `@Component`, the usual componen Spring Boot also provides {spring-boot-module-code}/jackson/JsonObjectSerializer.java[`JsonObjectSerializer`] and {spring-boot-module-code}/jackson/JsonObjectDeserializer.java[`JsonObjectDeserializer`] base classes that provide useful alternatives to the standard Jackson versions when serializing objects. See {spring-boot-module-api}/jackson/JsonObjectSerializer.html[`JsonObjectSerializer`] and {spring-boot-module-api}/jackson/JsonObjectDeserializer.html[`JsonObjectDeserializer`] in the Javadoc for details. +The example above can be rewritten to use `JsonObjectSerializer`/`JsonObjectDeserializer` as follows: + +[source,java,indent=0] +---- +include::{include-springbootfeatures}/webapplications/json/object/MyJsonComponent.java[tag=*] +---- + [[boot-features-spring-message-codes]] @@ -2692,27 +2555,9 @@ To do so, extend `BasicErrorController`, add a public method with a `@RequestMap You can also define a class annotated with `@ControllerAdvice` to customize the JSON document to return for a particular controller and/or exception type, as shown in the following example: -[source,java,pending-extract=true,indent=0,subs="verbatim,quotes,attributes"] +[source,java,indent=0,subs="verbatim,quotes,attributes"] ---- - @ControllerAdvice(basePackageClasses = AcmeController.class) - public class AcmeControllerAdvice extends ResponseEntityExceptionHandler { - - @ExceptionHandler(YourException.class) - @ResponseBody - ResponseEntity handleControllerException(HttpServletRequest request, Throwable ex) { - HttpStatus status = getStatus(request); - return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status); - } - - private HttpStatus getStatus(HttpServletRequest request) { - Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); - if (statusCode == null) { - return HttpStatus.INTERNAL_SERVER_ERROR; - } - return HttpStatus.valueOf(statusCode); - } - - } +include::{include-springbootfeatures}/webapplications/servlet/MyControllerAdvice.java[tag=*] ---- In the preceding example, if `YourException` is thrown by a controller defined in the same package as `AcmeController`, a JSON representation of the `CustomErrorType` POJO is used instead of the `ErrorAttributes` representation. @@ -2757,21 +2602,11 @@ To map all `5xx` errors by using a FreeMarker template, your directory structure For more complex mappings, you can also add beans that implement the `ErrorViewResolver` interface, as shown in the following example: -[source,java,pending-extract=true,indent=0,subs="verbatim,quotes,attributes"] +[source,java,indent=0,subs="verbatim,quotes,attributes"] ---- - public class MyErrorViewResolver implements ErrorViewResolver { - - @Override - public ModelAndView resolveErrorView(HttpServletRequest request, - HttpStatus status, Map model) { - // Use the request or status to optionally return a ModelAndView - return ... - } - - } +include::{include-springbootfeatures}/webapplications/servlet/MyErrorViewResolver.java[tag=*] ---- - You can also use regular Spring MVC features such as {spring-framework-docs}/web.html#mvc-exceptionhandlers[`@ExceptionHandler` methods] and {spring-framework-docs}/web.html#mvc-ann-controller-advice[`@ControllerAdvice`]. The `ErrorController` then picks up any unhandled exceptions. @@ -2782,37 +2617,16 @@ The `ErrorController` then picks up any unhandled exceptions. For applications that do not use Spring MVC, you can use the `ErrorPageRegistrar` interface to directly register `ErrorPages`. This abstraction works directly with the underlying embedded servlet container and works even if you do not have a Spring MVC `DispatcherServlet`. -[source,java,pending-extract=true,indent=0,subs="verbatim,quotes,attributes"] +[source,java,indent=0,subs="verbatim,quotes,attributes"] ---- - @Bean - public ErrorPageRegistrar errorPageRegistrar(){ - return new MyErrorPageRegistrar(); - } - - // ... - - private static class MyErrorPageRegistrar implements ErrorPageRegistrar { - - @Override - public void registerErrorPages(ErrorPageRegistry registry) { - registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400")); - } - - } +include::{include-springbootfeatures}/webapplications/servlet/ErrorPageConfiguration.java[tag=*] ---- NOTE: If you register an `ErrorPage` with a path that ends up being handled by a `Filter` (as is common with some non-Spring web frameworks, like Jersey and Wicket), then the `Filter` has to be explicitly registered as an `ERROR` dispatcher, as shown in the following example: -[source,java,pending-extract=true,indent=0,subs="verbatim,quotes,attributes"] +[source,java,indent=0,subs="verbatim,quotes,attributes"] ---- - @Bean - public FilterRegistrationBean myFilter() { - FilterRegistrationBean registration = new FilterRegistrationBean(); - registration.setFilter(new MyFilter()); - ... - registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class)); - return registration; - } +include::{include-springbootfeatures}/webapplications/servlet/ServletFilterConfiguration.java[tag=*] ---- Note that the default `FilterRegistrationBean` does not include the `ERROR` dispatcher type. @@ -2854,21 +2668,9 @@ As of version 4.2, Spring MVC {spring-framework-docs}/web.html#mvc-cors[supports Using {spring-framework-docs}/web.html#mvc-cors-controller[controller method CORS configuration] with {spring-framework-api}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`] annotations in your Spring Boot application does not require any specific configuration. {spring-framework-docs}/web.html#mvc-cors-global[Global CORS configuration] can be defined by registering a `WebMvcConfigurer` bean with a customized `addCorsMappings(CorsRegistry)` method, as shown in the following example: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - @Configuration(proxyBeanMethods = false) - public class MyConfiguration { - - @Bean - public WebMvcConfigurer corsConfigurer() { - return new WebMvcConfigurer() { - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/api/**"); - } - }; - } - } +include::{include-springbootfeatures}/webapplications/servlet/CorsConfiguration.java[tag=*] ---- @@ -2881,61 +2683,21 @@ Unlike Spring MVC, it does not require the Servlet API, is fully asynchronous an Spring WebFlux comes in two flavors: functional and annotation-based. The annotation-based one is quite close to the Spring MVC model, as shown in the following example: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - @RestController - @RequestMapping("/users") - public class MyRestController { - - @GetMapping("/{user}") - public Mono getUser(@PathVariable Long user) { - // ... - } - - @GetMapping("/{user}/customers") - public Flux getUserCustomers(@PathVariable Long user) { - // ... - } - - @DeleteMapping("/{user}") - public Mono deleteUser(@PathVariable Long user) { - // ... - } - - } +include::{include-springbootfeatures}/webapplications/webflux/MyRestController.java[tag=*] ---- "`WebFlux.fn`", the functional variant, separates the routing configuration from the actual handling of the requests, as shown in the following example: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] +---- +include::{include-springbootfeatures}/webapplications/webflux/fn/RoutingConfiguration.java[tag=*] ---- - @Configuration(proxyBeanMethods = false) - public class RoutingConfiguration { - @Bean - public RouterFunction monoRouterFunction(UserHandler userHandler) { - return route(GET("/{user}").and(accept(APPLICATION_JSON)), userHandler::getUser) - .andRoute(GET("/{user}/customers").and(accept(APPLICATION_JSON)), userHandler::getUserCustomers) - .andRoute(DELETE("/{user}").and(accept(APPLICATION_JSON)), userHandler::deleteUser); - } - - } - - @Component - public class UserHandler { - - public Mono getUser(ServerRequest request) { - // ... - } - - public Mono getUserCustomers(ServerRequest request) { - // ... - } - - public Mono deleteUser(ServerRequest request) { - // ... - } - } +[source,java,indent=0] +---- +include::{include-springbootfeatures}/webapplications/webflux/fn/UserHandler.java[tag=*] ---- WebFlux is part of the Spring Framework and detailed information is available in its {spring-framework-docs}/web-reactive.html#webflux-fn[reference documentation]. @@ -2977,21 +2739,9 @@ For example, `+spring.jackson.*+` configuration keys are applied to the Jackson If you need to add or customize codecs, you can create a custom `CodecCustomizer` component, as shown in the following example: -[source,java,pending-extract=true,indent=0] +[source,java,indent=0] ---- - import org.springframework.boot.web.codec.CodecCustomizer; - - @Configuration(proxyBeanMethods = false) - public class MyConfiguration { - - @Bean - public CodecCustomizer myCodecCustomizer() { - return codecConfigurer -> { - // ... - }; - } - - } +include::{include-springbootfeatures}/webapplications/webflux/CodecConfiguration.java[tag=*] ---- You can also leverage <>. @@ -3062,21 +2812,9 @@ For that, you can add a bean of type `ErrorAttributes`. To change the error handling behavior, you can implement `ErrorWebExceptionHandler` and register a bean definition of that type. Because a `WebExceptionHandler` is quite low-level, Spring Boot also provides a convenient `AbstractErrorWebExceptionHandler` to let you handle errors in a WebFlux functional way, as shown in the following example: -[source,java,pending-extract=true,indent=0,subs="verbatim,quotes,attributes"] +[source,java,indent=0,subs="verbatim,quotes,attributes"] ---- - public class CustomErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler { - - // Define constructor here - - @Override - protected RouterFunction getRoutingFunction(ErrorAttributes errorAttributes) { - - return RouterFunctions - .route(aPredicate, aHandler) - .andRoute(anotherPredicate, anotherHandler); - } - - } +include::{include-springbootfeatures}/webapplications/webflux/CustomErrorWebExceptionHandler.java[tag=*] ---- For a more complete picture, you can also subclass `DefaultErrorWebExceptionHandler` directly and override specific methods. diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/ThirdPartyConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/ThirdPartyConfiguration.java new file mode 100644 index 0000000000..ee9d4ee8a1 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/ThirdPartyConfiguration.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.externalizedconfiguration; + +//tag::code[] +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +public class ThirdPartyConfiguration { + + @Bean + @ConfigurationProperties(prefix = "another") + public AnotherComponent anotherComponent() { + return new AnotherComponent(); + } + +} +// end::code[] + +class AnotherComponent { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/enable/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/enable/MyApplication.java new file mode 100644 index 0000000000..9e41e9e65b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/enable/MyApplication.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.externalizedconfiguration.enable; + +//tag::code[] +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +@SpringBootApplication +@ConfigurationPropertiesScan({ "com.example.app", "org.acme.another" }) +public class MyApplication { + +} +// end::code[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/enable/MyConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/enable/MyConfiguration.java new file mode 100644 index 0000000000..41d624a8d9 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/enable/MyConfiguration.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.externalizedconfiguration.enable; + +//tag::code[] +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(AcmeProperties.class) +public class MyConfiguration { + +} +// end::code[] + +class AcmeProperties { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/merge/list/AcmeProperties.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/merge/list/AcmeProperties.java new file mode 100644 index 0000000000..7419f33d00 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/merge/list/AcmeProperties.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.externalizedconfiguration.merge.list; + +//tag::code[] +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("acme") +public class AcmeProperties { + + private final List list = new ArrayList<>(); + + public List getList() { + return this.list; + } + +} +// end::code[] + +class MyPojo { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/merge/map/AcmeProperties.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/merge/map/AcmeProperties.java new file mode 100644 index 0000000000..250d1c44dc --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/merge/map/AcmeProperties.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.externalizedconfiguration.merge.map; + +//tag::code[] +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("acme") +public class AcmeProperties { + + private final Map map = new LinkedHashMap<>(); + + public Map getMap() { + return this.map; + } + +} +// end::code[] + +class MyPojo { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/relaxed/OwnerProperties.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/relaxed/OwnerProperties.java new file mode 100644 index 0000000000..0ebc6f17d8 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/relaxed/OwnerProperties.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.externalizedconfiguration.relaxed; + +//tag::code[] +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "acme.my-project.person") +public class OwnerProperties { + + private String firstName; + + public String getFirstName() { + return this.firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + +} +// end::code[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/use/MyService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/use/MyService.java new file mode 100644 index 0000000000..d6d74c52ac --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/use/MyService.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.externalizedconfiguration.use; + +//tag::code[] +import org.springframework.stereotype.Service; + +@Service +public class MyService { + + private final AcmeProperties properties; + + public MyService(AcmeProperties properties) { + this.properties = properties; + } + + public void openConnection() { + Server server = new Server(this.properties.getRemoteAddress()); + server.start(); + // ... + } + + // ... + +} +// end::code[] + +class AcmeProperties { + + Object getRemoteAddress() { + return null; + } + +} + +class Server { + + Server(Object remoteAddress) { + } + + void start() { + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/validate/AcmeProperties.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/validate/AcmeProperties.java new file mode 100644 index 0000000000..da5b5133a4 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/validate/AcmeProperties.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.externalizedconfiguration.validate; + +//tag::code[] +import java.net.InetAddress; + +import javax.validation.constraints.NotNull; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +@ConfigurationProperties(prefix = "acme") +@Validated +public class AcmeProperties { + + @NotNull + private InetAddress remoteAddress; + + public InetAddress getRemoteAddress() { + return this.remoteAddress; + } + + public void setRemoteAddress(InetAddress remoteAddress) { + this.remoteAddress = remoteAddress; + } + +} +// end::code[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/validate/nested/AcmeProperties.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/validate/nested/AcmeProperties.java new file mode 100644 index 0000000000..4e71fec39a --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/externalizedconfiguration/validate/nested/AcmeProperties.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.externalizedconfiguration.validate.nested; + +//tag::code[] +import java.net.InetAddress; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +@ConfigurationProperties(prefix = "acme") +@Validated +public class AcmeProperties { + + @NotNull + private InetAddress remoteAddress; + + @Valid + private final Security security = new Security(); + + public InetAddress getRemoteAddress() { + return this.remoteAddress; + } + + public void setRemoteAddress(InetAddress remoteAddress) { + this.remoteAddress = remoteAddress; + } + + public Security getSecurity() { + return this.security; + } + + public static class Security { + + @NotEmpty + private String username; + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + } + +} +// end::code[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/profiles/ProductionConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/profiles/ProductionConfiguration.java new file mode 100644 index 0000000000..4317d6faf4 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/profiles/ProductionConfiguration.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.profiles; + +//tag::code[] +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Configuration(proxyBeanMethods = false) +@Profile("production") +public class ProductionConfiguration { + + // ... + +} +// end::code[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/HttpMessageConvertersConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/HttpMessageConvertersConfiguration.java new file mode 100644 index 0000000000..84aae404e6 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/HttpMessageConvertersConfiguration.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.webapplications; + +//tag::code[] +import java.io.IOException; + +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.converter.AbstractHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; + +@Configuration(proxyBeanMethods = false) +public class HttpMessageConvertersConfiguration { + + @Bean + public HttpMessageConverters customConverters() { + HttpMessageConverter additional = new AdditionalHttpMessageConverter(); + HttpMessageConverter another = new AnotherHttpMessageConverter(); + return new HttpMessageConverters(additional, another); + } + +} +// end::code[] + +class AdditionalHttpMessageConverter extends AbstractHttpMessageConverter { + + @Override + protected boolean supports(Class clazz) { + return false; + } + + @Override + protected Object readInternal(Class clazz, HttpInputMessage inputMessage) + throws IOException, HttpMessageNotReadableException { + return null; + } + + @Override + protected void writeInternal(Object t, HttpOutputMessage outputMessage) + throws IOException, HttpMessageNotWritableException { + } + +} + +class AnotherHttpMessageConverter extends AdditionalHttpMessageConverter { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/json/MyJsonComponent.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/json/MyJsonComponent.java new file mode 100644 index 0000000000..e610051f19 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/json/MyJsonComponent.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.webapplications.json; + +//tag::code[] +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import org.springframework.boot.jackson.JsonComponent; + +@JsonComponent +public class MyJsonComponent { + + public static class Serializer extends JsonSerializer { + + @Override + public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException { + jgen.writeStringField("name", value.getName()); + jgen.writeNumberField("age", value.getAge()); + } + + } + + public static class Deserializer extends JsonDeserializer { + + @Override + public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + ObjectCodec codec = jsonParser.getCodec(); + JsonNode tree = codec.readTree(jsonParser); + String name = tree.get("name").textValue(); + int age = tree.get("age").intValue(); + return new MyObject(name, age); + } + + } + +} +// end::code[] + +class MyObject { + + MyObject(String name, int age) { + } + + String getName() { + return null; + } + + Integer getAge() { + return null; + + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/json/object/MyJsonComponent.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/json/object/MyJsonComponent.java new file mode 100644 index 0000000000..cd2a062f9b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/json/object/MyJsonComponent.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.webapplications.json.object; + +//tag::code[] +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.SerializerProvider; + +import org.springframework.boot.jackson.JsonComponent; +import org.springframework.boot.jackson.JsonObjectDeserializer; +import org.springframework.boot.jackson.JsonObjectSerializer; + +@JsonComponent +public class MyJsonComponent { + + public static class Serializer extends JsonObjectSerializer { + + @Override + protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider) + throws IOException { + jgen.writeStringField("name", value.getName()); + jgen.writeNumberField("age", value.getAge()); + } + + } + + public static class Deserializer extends JsonObjectDeserializer { + + @Override + protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec, + JsonNode tree) throws IOException { + String name = nullSafeValue(tree.get("name"), String.class); + int age = nullSafeValue(tree.get("age"), Integer.class); + return new MyObject(name, age); + } + + } + +} +// end::code[] + +class MyObject { + + MyObject(String name, int age) { + } + + String getName() { + return null; + } + + Integer getAge() { + return null; + + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/CorsConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/CorsConfiguration.java new file mode 100644 index 0000000000..6b253b255e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/CorsConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.webapplications.servlet; + +//tag::code[] +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration(proxyBeanMethods = false) +public class CorsConfiguration { + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/**"); + } + + }; + } + +} +// end::code[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/ErrorPageConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/ErrorPageConfiguration.java new file mode 100644 index 0000000000..6792a8fb86 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/ErrorPageConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.webapplications.servlet; + +//tag::code[] +import org.springframework.boot.web.server.ErrorPage; +import org.springframework.boot.web.server.ErrorPageRegistrar; +import org.springframework.boot.web.server.ErrorPageRegistry; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; + +@Configuration +public class ErrorPageConfiguration { + + @Bean + public ErrorPageRegistrar errorPageRegistrar() { + return this::registerErrorPages; + } + + private void registerErrorPages(ErrorPageRegistry registry) { + registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400")); + } + +} +// end::code[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/MyControllerAdvice.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/MyControllerAdvice.java new file mode 100644 index 0000000000..02e3ce2c70 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/MyControllerAdvice.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.webapplications.servlet; + +//tag::code[] +import javax.servlet.RequestDispatcher; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@ControllerAdvice(basePackageClasses = AcmeController.class) +public class MyControllerAdvice extends ResponseEntityExceptionHandler { + + @ResponseBody + @ExceptionHandler(MyException.class) + ResponseEntity handleControllerException(HttpServletRequest request, Throwable ex) { + HttpStatus status = getStatus(request); + return new ResponseEntity<>(new MyErrorBody(status.value(), ex.getMessage()), status); + } + + private HttpStatus getStatus(HttpServletRequest request) { + Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + HttpStatus status = HttpStatus.resolve(code); + return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR; + } + +} +// end::code[] + +class AcmeController { + +} + +class MyException extends RuntimeException { + +} + +class MyErrorBody { + + MyErrorBody(int value, String message) { + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/MyErrorViewResolver.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/MyErrorViewResolver.java new file mode 100644 index 0000000000..cfe32cf3b2 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/MyErrorViewResolver.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2020 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.docs.springbootfeatures.webapplications.servlet; + +//tag::code[] +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver; +import org.springframework.http.HttpStatus; +import org.springframework.web.servlet.ModelAndView; + +public class MyErrorViewResolver implements ErrorViewResolver { + + @Override + public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map model) { + // Use the request or status to optionally return a ModelAndView + if (status == HttpStatus.INSUFFICIENT_STORAGE) { + // We could add custom model values here + new ModelAndView("myview"); + } + return null; + } + +} +// end::code[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/MyRestController.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/MyRestController.java new file mode 100644 index 0000000000..5983f3a69f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/MyRestController.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.webapplications.servlet; + +//tag::code[] +import java.util.List; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/users") +public class MyRestController { + + private final UserRepository userRepository; + + private final CustomerRepository customerRepository; + + public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) { + this.userRepository = userRepository; + this.customerRepository = customerRepository; + } + + @GetMapping("/{user}") + public User getUser(@PathVariable Long userId) { + return this.userRepository.findById(userId).get(); + } + + @GetMapping("/{user}/customers") + List getUserCustomers(@PathVariable Long userId) { + return this.userRepository.findById(userId).map(this.customerRepository::findByUser).get(); + } + + @DeleteMapping("/{user}") + public void deleteUser(@PathVariable Long userId) { + this.userRepository.deleteById(userId); + } + +} +// end::code[] + +interface UserRepository extends CrudRepository { + +} + +interface CustomerRepository extends CrudRepository { + + List findByUser(User user); + +} + +class User { + + List getCustomers() { + return null; + } + +} + +class Customer { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/ServletFilterConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/ServletFilterConfiguration.java new file mode 100644 index 0000000000..9038e56af5 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/servlet/ServletFilterConfiguration.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.webapplications.servlet; + +//tag::code[] +import java.io.IOException; +import java.util.EnumSet; + +import javax.servlet.DispatcherType; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.GenericFilterBean; + +@Configuration +public class ServletFilterConfiguration { + + @Bean + public FilterRegistrationBean myFilter() { + FilterRegistrationBean registration = new FilterRegistrationBean<>(new MyFilter()); + // ... + registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class)); + return registration; + } + +} +// end::code[] + +class MyFilter extends GenericFilterBean { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/CodecConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/CodecConfiguration.java new file mode 100644 index 0000000000..01e2af6617 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/CodecConfiguration.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.webapplications.webflux; + +//tag::code[] +import org.springframework.boot.web.codec.CodecCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.codec.ServerSentEventHttpMessageReader; + +@Configuration(proxyBeanMethods = false) +public class CodecConfiguration { + + @Bean + public CodecCustomizer myCodecCustomizer() { + return (configurer) -> { + configurer.registerDefaults(false); + configurer.customCodecs().register(new ServerSentEventHttpMessageReader()); + // ... + }; + } + +} +// end::code[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/CustomErrorWebExceptionHandler.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/CustomErrorWebExceptionHandler.java new file mode 100644 index 0000000000..525b79acb2 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/CustomErrorWebExceptionHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.webapplications.webflux; + +//tag::code[] +import reactor.core.publisher.Mono; + +import org.springframework.boot.autoconfigure.web.WebProperties.Resources; +import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler; +import org.springframework.boot.web.reactive.error.ErrorAttributes; +import org.springframework.context.ApplicationContext; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder; + +@Component +public class CustomErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler { + + public CustomErrorWebExceptionHandler(ErrorAttributes errorAttributes, Resources resources, + ApplicationContext applicationContext) { + super(errorAttributes, resources, applicationContext); + } + + @Override + protected RouterFunction getRoutingFunction(ErrorAttributes errorAttributes) { + return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml); + } + + private boolean acceptsXml(ServerRequest request) { + return request.headers().accept().contains(MediaType.APPLICATION_XML); + } + + public Mono handleErrorAsXml(ServerRequest request) { + BodyBuilder builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR); + // ... additional builder calls + return builder.build(); + } + +} +// end::code[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/MyRestController.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/MyRestController.java new file mode 100644 index 0000000000..d4b6d528eb --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/MyRestController.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.webapplications.webflux; + +//tag::code[] +import java.util.List; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/users") +public class MyRestController { + + private final UserRepository userRepository; + + private final CustomerRepository customerRepository; + + public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) { + this.userRepository = userRepository; + this.customerRepository = customerRepository; + } + + @GetMapping("/{user}") + public Mono getUser(@PathVariable Long userId) { + return this.userRepository.findById(userId); + } + + @GetMapping("/{user}/customers") + Flux getUserCustomers(@PathVariable Long userId) { + return this.userRepository.findById(userId).flatMapMany(this.customerRepository::findByUser); + } + + @DeleteMapping("/{user}") + public void deleteUser(@PathVariable Long userId) { + this.userRepository.deleteById(userId); + } + +} +// end::code[] + +interface UserRepository extends ReactiveCrudRepository { + +} + +interface CustomerRepository extends ReactiveCrudRepository { + + Flux findByUser(User user); + +} + +class User { + + List getCustomers() { + return null; + } + +} + +class Customer { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/fn/RoutingConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/fn/RoutingConfiguration.java new file mode 100644 index 0000000000..089ee7ac37 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/fn/RoutingConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.webapplications.webflux.fn; + +//tag::code[] +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.server.RequestPredicate; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; + +import static org.springframework.web.reactive.function.server.RequestPredicates.DELETE; +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RequestPredicates.accept; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; + +@Configuration(proxyBeanMethods = false) +public class RoutingConfiguration { + + private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON); + + @Bean + public RouterFunction monoRouterFunction(UserHandler userHandler) { + // @formatter:off + return route( + GET("/{user}").and(ACCEPT_JSON), userHandler::getUser).andRoute( + GET("/{user}/customers").and(ACCEPT_JSON), userHandler::getUserCustomers).andRoute( + DELETE("/{user}").and(ACCEPT_JSON), userHandler::deleteUser); + // @formatter:on + } + +} +// end::code[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/fn/UserHandler.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/fn/UserHandler.java new file mode 100644 index 0000000000..8ddd5ef6a7 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/springbootfeatures/webapplications/webflux/fn/UserHandler.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2021 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.docs.springbootfeatures.webapplications.webflux.fn; + +//tag::code[] +import reactor.core.publisher.Mono; + +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; + +@Component +public class UserHandler { + + public Mono getUser(ServerRequest request) { + // ... + return ServerResponse.ok().build(); + } + + public Mono getUserCustomers(ServerRequest request) { + // ... + return ServerResponse.ok().build(); + } + + public Mono deleteUser(ServerRequest request) { + // ... + return ServerResponse.ok().build(); + } + +} +// end::code[]