1. Introduction
The Springfox suite of java libraries are all about automating the generation of machine and human readable specifications for JSON APIs written using the spring family of projects. Springfox works by examining an application, once, at runtime to infer API semantics based on spring configurations, class structure and various compile time java Annotations.
1.1. History
Springfox has evolved from a project originally created by Marty Pitt and was named swagger-springmvc. Much kudos goes to Marty.
1.2. Goals
-
To extend support for a number of the evolving standards targeted at JSON API specification and documentation such as: swagger, RAML and jsonapi.
-
To extend support for spring technologies other than spring webmvc
-
Philosophically, we want to discourage using (swagger-core) annotations that are not material to the service description at runtime. For e.g. the jackson annotations should always trump or have more weight than
@ApiModelProperty
or for e.g.@NotNull
or specifying @RequestParam#required should always win. Annotations are to to be used only to supplement documentation or override/tweak the resulting spec in cases where its not possible to infer service/schema characteristics.
1.4. Development Environment
-
File >> open >> build.gradle
-
Make sure to check the 'use the default gradle wrapper' option.
-
First time build
./gradlew cleanIdea idea
-
To get more output from any gradle commands/tasks append a
-i
(info) or-d
(debug) e.g.
./gradlew build -i
-
To publish to local maven repository
./gradlew clean build publishToMavenLocal -i
This build is optimized for releasing software to bintray/sonatype. In order for gradle to figure out the version the gradle plugin relies on local folder being a cloned git repository. Downloading the source archive and building will NOT work! |
1.4.2. Building reference documentation
To build all the current documentation (builds hand written docs and javadocs):
./gradlew allDocs
The docs are generated in the build/all-docs
folder. To publish the current documentation (snapshot)
./gradlew releaseDocs
1.4.3. Updating the contract tests
When developing new contract test, to make it easy to update the existing tests with new contracts, uncomment the following lines in swagger-contract-tests/build.gradle. Usually this happens when we add a new contract test for a bug we’ve fixed or a feature that was added, we create an endpoint in BugsController or FeatureDemonstrationService to demonstrate the new fix or behavior.
1
2
3
4
5
// NOTE: Uncomment to bulk update contracts
//test {
// systemProperty("contract.tests.root", "$projectDir/src/test/resources")
// systemProperty("contract.tests.update", true)
//}
1.5. Releasing
To release a non-snapshot version of Springfox:
-
Execute the the release commands: The below environment variables are required to run a release:
-
GITHUB_TOKEN
- this is the github token -
BINTRAY_USER_NAME
- this is your bintray user -
BINTRAY_PASSWORD
- this is your bintray token -
SONATYPE_USER_NAME
-
SONATYPE_PASSWORD
-
GPG_PASSPHRASE
- if your gpg key has a passphrase or '' -
SONATYPE_SYNC
- automatically sync to maven central
Alternatively you could set the following gradle/project properties (-Pproperty)
- githubToken
- this is the github token
- bintrayUsername
- this is your bintray user
- bintrayApiKey
- this is your bintray token
- gpgPassphrase
- if your gpg key has a passphrase or ''
- ossUser
- ossPassword
- ossSync
- automatically sync to maven central
Recommend using [autoenv](https://github.com/kennethreitz/autoenv) with a .env
file at the root of the repo.
./gradlew clean build release [-PreleaseType=<MAJOR|MINOR|PATCH>]
./gradlew releaseDocs
The release steps are as follows:
- check that the git workspace is clean
- check that the local git branch is master
- check that the local git branch is the same as origin
- gradle test
- gradle check
- upload (publish) all artifacts to Bintray
- Bumps the project version in version.properties
- Git tag the release
- Git push
1.5.2. Override deploy
To bypass the standard release flow and upload directly to bintray use the following task - manually set the version in version.properties
./gradlew clean build bintrayUpload -PreleaseType=<MAJOR|MINOR|PATCH>
--stacktrace
1.5.3. Releasing documentation
To update the docs for an existing release pass the updateMode
switch
./gradlew releaseDocs
1.5.4. Contributing
Please see the wiki for some guidelines
1.6. Support
If you find issues or bugs please submit them via the Springfox Github project
2. Getting Started
2.1. Dependencies
The Springfox libraries are hosted on bintray and jcenter. The artifacts can be viewed accessed at the following locations:
-
Release:
-
Snapshot
Springfox has multiple modules and the dependencies will vary depending on the desired API specification standard. Below outlines how to include the springfox-swagger2 module which produces Swagger 2.0 API documentation.
2.1.1. Gradle
Release
repositories {
jcenter()
}
dependencies {
// For Spring boot Projects
implementation "io.springfox:springfox-boot-starter:3.0.0"
// Uncomment for regular spring mvc projects and commend above dependency
// implementation "io.springfox:springfox-oas:3.0.0"
}
Snapshot
repositories {
maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local/' }
}
dependencies {
// For Spring boot Projects
implementation "io.springfox:springfox-boot-starter:3.0.0"
// Uncomment for regular spring mvc projects and commend above dependency
// implementation "io.springfox:springfox-oas:3.0.0-SNAPSHOT"
}
2.1.2. Maven
Release
<pom>
<repositories>
<repository>
<id>jcenter-snapshots</id>
<name>jcenter</name>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!-- Uncomment for regular spring mvc projects and commend above dependency
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-oas</artifactId>
<version>3.0.0</version>
</dependency>
-->
</pom>
Snapshot
<pom>
<repositories>
<repository>
<id>jcenter-snapshots</id>
<name>jcenter</name>
<url>http://oss.jfrog.org/artifactory/oss-snapshot-local/</url>
</repository>
</repositories>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>
<!-- Uncomment for regular spring mvc projects and commend above dependency
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-oas</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>
-->
</pom>
2.1.3. Migrating from existing 2.x version
Spring Boot Applications
-
Remove library inclusions of earlier releases. Specifically remove
springfox-swagger2
andspringfox-swagger-ui
inclusions. -
Remove the
@EnableSwagger2
annotations -
Add the
springfox-boot-starter
-
Springfox 3.x removes dependencies on guava and other 3rd party libraries (not zero dep yet! depends on spring plugin and open api libraries for annotations and models) so if you used guava predicates/functions those will need to transition to java 8 function interfaces
Regular spring mvc
-
Remove library inclusions of earlier releases. Specifically remove
springfox-swagger2
andspringfox-swagger-ui
inclusions. -
For OpenAPI add the
@EnableOpenApi
annotation (and@EnableSwagger2
for swagger 2.0) -
For OpenAPI add the
springfox-oas
library dependency (for swagger 2.0 usespringfox-swagger2
) -
Springfox 3.x removes dependencies on guava and other 3rd party libraries (not zero dep yet! depends on spring plugin and open api libraries for annotations and models) so if you used guava predicates/functions those will need to transition to java 8 function interfaces
Changes in swagger-ui
For non-boot applications springfox-swagger-ui is no longer automatically enabled by adding the dependency. It needs to be explicitly register
using a resource handler configurer (WebFluxConfigurer or WebMvcConfigurer ). Here is how it is configured in
springfox-boot-starter
|
swagger-ui location has moved from http://host/context-path/swagger-ui.html to http://host/context-path/swagger-ui/index.html
OR http://host/context-path/swagger-ui/ for short. This makes it work much better with pulling it as a web jar and turning it off
using configuration properties if not needed.
|
For WebMvc SwagggerUIWebMvcConfigurer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SwaggerUiWebMvcConfigurer implements WebMvcConfigurer {
private final String baseUrl;
public SwaggerUiWebMvcConfigurer(String baseUrl) {
this.baseUrl = baseUrl;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String baseUrl = StringUtils.trimTrailingCharacter(this.baseUrl, '/');
registry.
addResourceHandler(baseUrl + "/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
.resourceChain(false);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController(baseUrl + "/swagger-ui/")
.setViewName("forward:" + baseUrl + "/swagger-ui/index.html");
}
}
For WebFlux SwagggerUIWebMvcConfigurer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SwaggerUiWebFluxConfigurer implements WebFluxConfigurer {
private final String baseUrl;
public SwaggerUiWebFluxConfigurer(String baseUrl) {
this.baseUrl = baseUrl;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String baseUrl = StringUtils.trimTrailingCharacter(this.baseUrl, '/');
registry.
addResourceHandler(baseUrl + "/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
.resourceChain(false);
}
}
3. Quick start guides
3.1. Springfox Spring MVC and Spring Boot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
*
* Copyright 2015-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
*
* http://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 springfox.springconfig;
import com.fasterxml.classmate.TypeResolver;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.async.DeferredResult;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseBuilder;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.schema.WildcardType;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.ParameterType;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.Tag;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.DocExpansion;
import springfox.documentation.swagger.web.ModelRendering;
import springfox.documentation.swagger.web.OperationsSorter;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger.web.TagsSorter;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import springfox.petstore.controller.PetController;
import java.util.List;
import static java.util.Collections.*;
import static springfox.documentation.schema.AlternateTypeRules.*;
@SpringBootApplication
@EnableSwagger2 (1)
@ComponentScan(basePackageClasses = {
PetController.class
})(2)
public class Swagger2SpringBoot {
public static void main(String[] args) {
SpringApplication.run(Swagger2SpringBoot.class, args);
}
@Bean
public Docket petApi() {
return new Docket(DocumentationType.SWAGGER_2)(3)
.select() (4)
.apis(RequestHandlerSelectors.any()) (5)
.paths(PathSelectors.any()) (6)
.build() (7)
.pathMapping("/") (8)
.directModelSubstitute(LocalDate.class, String.class) (9)
.genericModelSubstitutes(ResponseEntity.class)
.alternateTypeRules(
newRule(typeResolver.resolve(DeferredResult.class,
typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
typeResolver.resolve(WildcardType.class))) (10)
.useDefaultResponseMessages(false) (11)
.globalResponses(HttpMethod.GET, (12)
singletonList(new ResponseBuilder()
.code("500")
.description("500 message")
.representation(MediaType.TEXT_XML)
.apply(r ->
r.model(m ->
m.referenceModel(ref ->
ref.key(k ->
k.qualifiedModelName(q ->
q.namespace("some:namespace")
.name("ERROR")))))) (13)
.build()))
.securitySchemes(singletonList(apiKey())) (14)
.securityContexts(singletonList(securityContext())) (15)
.enableUrlTemplating(true) (21)
.globalRequestParameters((22)
singletonList(new springfox.documentation.builders.RequestParameterBuilder()
.name("someGlobalParameter")
.description("Description of someGlobalParameter")
.in(ParameterType.QUERY)
.required(true)
.query(q -> q.model(m -> m.scalarModel(ScalarType.STRING)))
.build()))
.tags(new Tag("Pet Service", "All apis relating to pets")) (23)
.additionalModels(typeResolver.resolve(AdditionalModel.class)); (24)
}
@Autowired
private TypeResolver typeResolver;
private ApiKey apiKey() {
return new ApiKey("mykey", "api_key", "header"); (16)
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex("/anyPath.*")) (17)
.build();
}
List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope
= new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return singletonList(
new SecurityReference("mykey", authorizationScopes)); (18)
}
@Bean
SecurityConfiguration security() {
return SecurityConfigurationBuilder.builder() (19)
.clientId("test-app-client-id")
.clientSecret("test-app-client-secret")
.realm("test-app-realm")
.appName("test-app")
.scopeSeparator(",")
.additionalQueryStringParams(null)
.useBasicAuthenticationWithAccessCodeGrant(false)
.enableCsrfSupport(false)
.build();
}
@Bean
UiConfiguration uiConfig() {
return UiConfigurationBuilder.builder() (20)
.deepLinking(true)
.displayOperationId(false)
.defaultModelsExpandDepth(1)
.defaultModelExpandDepth(1)
.defaultModelRendering(ModelRendering.EXAMPLE)
.displayRequestDuration(false)
.docExpansion(DocExpansion.NONE)
.filter(false)
.maxDisplayedTags(null)
.operationsSorter(OperationsSorter.ALPHA)
.showExtensions(false)
.showCommonExtensions(false)
.tagsSorter(TagsSorter.ALPHA)
.supportedSubmitMethods(UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS)
.validatorUrl(null)
.build();
}
}
3.2. Configuration explained
This library extensively uses googles guava library. For e.g. when you
see newArrayList(…) its actually the guava equivalent of creating an normal array list and adding item(s) to it.
|
//This guava code snippet
List<Something> guavaList = newArrayList(new Something());
//... is equivalent to
List<Something> list = new ArrayList<>();
list.add(new Something());
1 | Enables Springfox swagger 2 |
2 | Instructs spring where to scan for API controllers |
3 | Docket , Springfox’s, primary api configuration mechanism is initialized for swagger specification 2.0 |
4 | select() returns an instance of ApiSelectorBuilder to give fine grained control over the endpoints exposed
via swagger. |
5 | apis() allows selection of RequestHandler 's using a predicate. The example here uses an any predicate
(default). Out of the box predicates provided are any , none , withClassAnnotation , withMethodAnnotation and
basePackage . |
6 | paths() allows selection of Path 's using a predicate. The example here uses an any predicate (default). Out of the box we provide predicates for regex , ant , any , none . |
7 | The selector needs to be built after configuring the api and path selectors. |
8 | Adds a servlet path mapping, when the servlet has a path mapping. This prefixes paths with the provided path mapping. |
9 | Convenience rule builder that substitutes LocalDate with String when rendering model properties |
10 | Convenience rule builder that substitutes a generic type with one type parameter with the type
parameter. In this example ResponseEntity<T> with T .
alternateTypeRules allows custom rules that are a bit more involved.
The example substitutes DeferredResult<ResponseEntity<T>> with T generically. |
11 | Flag to indicate if default http response codes need to be used or not. |
12 | Allows globally overriding response messages for different http methods. In this example we override the 500
error code for all GET requests … |
13 | … and indicate that it will use the response model Error (which will be defined elsewhere) |
14 | Sets up the security schemes used to protect the apis. Supported schemes are ApiKey, BasicAuth and OAuth |
15 | Provides a way to globally set up security contexts for operation. The idea here is that we provide a way to select operations to be protected by one of the specified security schemes. |
16 | Here we use ApiKey as the security schema that is identified by the name mykey |
17 | Selector for the paths this security context applies to. |
18 | Here we use the same key defined in the security scheme mykey |
19 | Optional swagger-ui security configuration for oauth and apiKey settings |
20 | Optional swagger-ui ui configuration currently only supports the validation url |
21 | * Incubating * setting this flag signals to the processor that the paths generated should try and use form style query expansion. As a result we could distinguish paths that have the same path stem but different query string combinations. An example of this would be two apis: First, http://example.org/findCustomersBy?name=Test to find customers by name. Per RFC 6570, this would be represented as http://example.org/findCustomersBy{?name}. Second, http://example.org/findCustomersBy?zip=76051 to find customers by zip. Per RFC 6570, this would be represented as http://example.org/findCustomersBy{?zip}. |
22 | Allows globally configuration of default path-/request-/headerparameters which are common for every rest operation of the api, but aren`t needed in spring controller method signature (for example authenticaton information). Parameters added here will be part of every API Operation in the generated swagger specification. on how the security is setup the name of the header used may need to be different. Overriding this value is a way to override the default behavior. |
23 | Adding tags is a way to define all the available tags that services/operations can opt into. Currently this only has name and description. |
24 | Are there models in the application that are not "reachable"? Not reachable is when we have models that we would like to be described but aren’t explicitly used in any operation. An example of this is an operation that returns a model serialized as a string. We do want to communicate the expectation of the schema for the string. This is a way to do exactly that. |
There are plenty of more options to configure the Docket
. This should provide a good start.
3.3. Springfox Spring Data Rest
In version greater than 2.6.0, support for spring data rest was added.
This is still in incubation. |
In order to use it (non-spring-boot)
-
add the
springfox-data-rest
dependency (via Gradle or Maven). -
Import the configuration from the
springfox-data-rest
module (via Java or xml config) as shown below
3.3.2. Maven
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-data-rest</artifactId>
<version>3.0.0</version>
</dependency>
3.3.3. java config
//For java config
@Import({ ... springfox.documentation.spring.data.rest.configuration.SpringDataRestConfiguration.class, ...})
3.3.4. xml config
Import the bean in your xml configuration by defining a bean of the following type
<bean class="springfox.documentation.spring.data.rest.configuration.SpringDataRestConfiguration.class" />
for spring boot, this section is not needed. Springfox will autoconfigure itself based on the detection of spring data rest components. |
3.4. Springfox Support for JSR-303
In version greater than 2.3.2, support for bean validation annotations was added, specifically for @NotNull, @Min, @Max, and @Size.
In order to use it
-
add the
springfox-bean-validators
dependency.
3.4.2. Maven
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-bean-validators</artifactId>
<version>3.0.0</version>
</dependency>
-
Import the configuration from the
springfox-bean-validators
module as shown below
3.5. Springfox Swagger UI
The springfox-swagger-ui
web jar ships with Swagger UI. To include it in a standard
Spring Boot application you can add the dependency as follows:
dependencies {
compile 'io.springfox:springfox-swagger-ui:3.0.0'
}
Pulling in the dependency creates a webjar containing the swagger-ui static content. It adds a JSON endpoint
/swagger-resources
which lists all of the swagger resources and versions configured for a given
application. The Swagger UI page should then be available at http://localhost:8080/swagger-ui.html
The swagger ui version is specified in ./build.gradle where swaggerUiVersion
is a git tag on the
https:// github.com/swagger-api/swagger-ui[swagger-ui repo].
All content is served from a webjar convention, relative url taking the following form:
webjars/springfox-swagger-ui/3.0.0/swagger-ui.html
By default Spring Boot has sensible defaults for serving content from webjars. To configure vanilla spring web mvc apps to serve webjar content see the webjar documentation
Swagger-Ui that comes bundled with springfox uses meta-urls to configure itself and discover documented endpoints. The urls for the discovery are as shown below.
Url | New Url in 2.5.+ | Purpose |
---|---|---|
/configuration/security |
/swagger-resources/configuration/security |
Configuring swagger-ui security |
/configuration/ui |
/swagger-resources/configuration/ui |
Configuring swagger-ui options |
Since swagger ui is a static resource it needs to rely on known endpoints to configure itself at runtime. So these ☝️ are *cool uris that cannot change. There is some customization that is possible, but swagger-ui needs to be available at the root of the webcontext.
Regarding where swagger-ui itself is served and where the api docs are served those are totally configurable.
3.6. Springfox RFC6570 support
Support has been dropped experimental feature as swagger-ui and the spec itself has better support for this. |
-
Turn enableUrlTemplating
OFF
; (see #21)
3.7. Springfox Spring-Integration Support incubating
Keep in mind this is experimental! |
As of Springfox 3.0 we offer experimental support for Spring Integration http inbound endpoints.
The current implementation produces documentation for your endpoints, as far as possible automatically, based on static code analysis. Since the ultimate http responses in spring-integration cannot be determined statically from an http inbound endpoint, we use spring-restdocs to provide response body examples.
We consider the support experimental because it has not been tested with a wide range of spring-integration applications and because we have only started to evaluate the possibilities we have with spring-restdocs.
Depending on which web technology you choose, you need to include the appropriate springfox-spring-integration-webflux
or springfox-spring-integration-webmvc
dependency and use its respective tooling to produce documentation for your http
responses, based on spring-restdocs.
See the readme for springfox-spring-integration-webmvc and springfox-spring-integration-webflux for details.
3.8. Securing swagger-ui
The user contributed example uses OAuth2 and cookies-based auth in the browser. (Credit: @evser)
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and().exceptionHandling().accessDeniedHandler(new AccessDeniedHandlerImpl())
.and().logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(ssoFilter(ApplicationConfiguration.API_BASE_PATH + "/login"), BasicAuthenticationFilter.class)
.requiresChannel().anyRequest().requireSecure();
}
@Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean frb = new FilterRegistrationBean();
frb.setFilter(filter);
frb.setOrder(SecurityProperties.DEFAULT_FILTER_ORDER);
return frb;
}
@Bean
@ConfigurationProperties("oauth2.client")
public OAuth2ProtectedResourceDetails authDetails() {
return new AuthorizationCodeResourceDetails();
}
@Bean
public SecurityConfiguration swaggerSecurityConfiguration() {
return new SecurityConfiguration("client-id", "client-secret", "realm",
"", "{{X-XSRF-COOKIE}}", ApiKeyVehicle.HEADER, "X-XSRF-TOKEN", ",");
}
private Filter ssoFilter(String path) {
OAuth2ClientAuthenticationProcessingFilter oAuth2ClientAuthenticationFilter = new OAuth2ClientAuthenticationProcessingFilter(path);
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(authDetails(), oauth2ClientContext);
DefaultRedirectStrategy defaultRedirectStrategy = new DefaultRedirectStrategy();
oAuth2ClientAuthenticationFilter.setRestTemplate(oAuth2RestTemplate);
oAuth2ClientAuthenticationFilter.setTokenServices(resourceServerTokenServices);
oAuth2ClientAuthenticationFilter.setAuthenticationSuccessHandler(
(request, response, authentication) -> {
String redirectUrl = request.getParameter(REDIRECT_URL_PARAM);
if (redirectUrl == null) {
redirectUrl = DEFAULT_REDIRECT_URL;
} else {
if (!redirectUrlValidator.validateRedirectUrl(redirectUrl)) {
request.setAttribute(MESSAGE_ATTRIBUTE_NAME,
messageSource.getMessage("ivalid.redirect.url", new String[] { redirectUrl }, LocaleContextHolder.getLocale()));
response.sendError(HttpStatus.FORBIDDEN.value());
}
}
defaultRedirectStrategy.sendRedirect(request, response, redirectUrl);
});
return oAuth2ClientAuthenticationFilter;
}
And configure the Docket to be secured via the AUTHORIZATION
header:
@Bean
public Docket api() throws IOException, URISyntaxException {
final List<ResponseMessage> globalResponses = Arrays.asList(
new ResponseMessageBuilder()
.code(200)
.message("OK")
.build(),
new ResponseMessageBuilder()
.code(400)
.message("Bad Request")
.build(),
new ResponseMessageBuilder()
.code(500)
.message("Internal Error")
.build());
final ApiInfo apiInfo = new ApiInfo("REST API", new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(CHANGELOG_FILENAME)))
.lines()
.collect(Collectors.joining(System.lineSeparator())),
"1.0.0-RC1", "", new Contact("team", "", "bla@bla.com"), "", "", Collections.emptyList());
return new Docket(DocumentationType.SWAGGER_2),
.securitySchemes(Arrays.asList(new ApiKey("Token Access", HttpHeaders.AUTHORIZATION, In.HEADER.name()))))
.useDefaultResponseMessages(false)
.globalResponseMessage(RequestMethod.GET, globalResponses)
.globalResponseMessage(RequestMethod.POST, globalResponses)
.globalResponseMessage(RequestMethod.DELETE, globalResponses)
.globalResponseMessage(RequestMethod.PATCH, globalResponses)
.select()
.apis(RequestHandlerSelectors.basePackage("com.controller"))
.build()
.apiInfo(apiInfo)
.directModelSubstitute(Temporal.class, String.class);
}
3.9. Springfox samples
The springfox-demos repository contains a number of samples.
4. Architecture
4.1. Background
When we started work on 2.0 swagger specification we realized that we’re rewriting the logic to infer the service models and the schema. So we decided to take a step back and break it out into a two step process. First infer the service model into an internal representation. Second create a mapping layer that can map the internal models to different specification formats. Out of the box we will support swagger 1.2 and swagger 2.0, but this leads us to the possibility of supporting other formats and other scenarios as well e.g. RAML, ALPS and hypermedia formats.
4.2. Component Model
The different Springfox modules are split up as shown below.
+-----------------------------------------------------------------------------------------+
| springfox-core |
| |
| Contains the internal service and schema description models along with their builders. |
+------------------------------------------+----------------------------------------------+
^
+------------------------------------------+----------------------------------------------+
| springfox-spi |
| |
| Contains the service provider interfaces that can be used to extend and enrich the |
| service models, e.g. swagger specific annotation processors. |
+------------------------------------------+----------------------------------------------+
|
|
+--------------------------|------------------------+
| | |
+----------------------------------+ | +--------------------------------------+
| springfox-schema | | | springfox-spring-web |
| | | | |
| Schema inference extensions that | | | spring web specific extensions that |
| help build up the schema for the | | | can build the service models based |
| parameters, models and responses | | | on RequestMapping information. |
+----------------------------------+ | | This is the heart library that |
| | infers the service model. |
| +--------------------------------------+
|
+------------------------------------+------------------------------------+
| springfox-swagger-common |
| |
| Common swagger specific extensions that are aware of the different |
| swagger annotations. |
+----------+--------------------------------------------------------------+
^ ^ ^
+----------+---------+ +----------+---------+ +-----...
| | | | |
| springfox-swagger1 | | springfox-swagger2 | |
| | | | |
+--------------------+ +--------------------+ +-----...
Configurations, and mapping layer that know how to convert the service models
to swagger 1.2 and swagger 2.0 specification documents.
Also contains the controller for each of the specific formats.
5. Swagger
Springfox supports both version 1.2 and version 2.0 of the Swagger specification. Where possible, the Swagger 2.0 specification is preferable.
The swagger-core annotations, as provided by swagger-core, are typically used to decorate the java source code of an API which is being 'swaggered'. Springfox is aware of the Swagger-Core Annotations and will favor those annotations over inferred defaults.
5.1. Swagger 1.2 vs Swagger 2.0
One major difference between the two swagger specification is the composition of the generated swagger documentation.
With Swagger 1.2 an applications API is represented as a Resource Listing
and multiple API Declarations
which has the
implication of producing multiple JSON files
With Swagger 2.0 things are much simpler and an application’s API can be represented in a single JSON file.
5.2. Moving from swagger-springmvc?
Here is a guide to help with the transition from 1.0.2 to 2.0.
Legacy documentation is available here.
5.3. Springfox configuration and demo applications
The springfox-demos repository contains a number of sample Spring application which can be used a reference.
6. Configuring Springfox
To enable support for swagger specification 1.2 use the
annotation@EnableSwagger
To enable support for swagger specification 2.0 use the
annotation@EnableSwagger2
To document the service we use a
. This is changed to be more inline with the fact that expressing the
contents of the documentation is agnostic of the format the documentation is rendered.Docket
Docket stands for A summary or other brief statement of the contents of a document; an abstract.
Docket
helps configure a subset of the services to be documented and groups them by name. Significant changes
to this is the ability to provide an expressive predicate based for api selection.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import static springfox.documentation.builders.PathSelectors.*;
import static com.google.common.base.Predicates.*;
@Bean
public Docket swaggerSpringMvcPlugin() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("business-api")
.select()
//Ignores controllers annotated with @CustomIgnore
.apis(not(withClassAnnotation(CustomIgnore.class)) //Selection by RequestHandler
.paths(paths()) // and by paths
.build()
.apiInfo(apiInfo())
.securitySchemes(securitySchemes())
.securityContext(securityContext());
}
//Here is an example where we select any api that matches one of these paths
private Predicate<String> paths() {
return or(
regex("/business.*"),
regex("/some.*"),
regex("/contacts.*"),
regex("/pet.*"),
regex("/springsRestController.*"),
regex("/test.*"));
}
For a list of handy predicates Look at RequestHandlerSelectors and PathSelectors.
6.1. Configuring the ObjectMapper
A simple way to configure the object mapper is to listen for the
event. Regardless of
whether there is a customized ObjectMapper in play with a corresponding MappingJackson2HttpMessageConverter, the
library always has a configured ObjectMapper that is customized to serialize swagger 1.2 and swagger 2.0 types.ObjectMapperConfigured
In order to do this implement the
interface. The event has a handle
to the ObjectMapper that was configured. Configuring application specific ObjectMapper customizations in this
application event handler guarantees that application specific customizations will be applied to each and every
ObjectMapper that is in play.ApplicationListener<ObjectMapperConfigured>
If you encounter a NullPointerException during application startup like this issue. Its because most likely the
isn’t working.
These adapter especially in a non-spring-boot scenarios will only get loaded if the @EnableWebMvc
annotation is present.WebMvcConfigurerAdapter
If using Spring Boot Web MVC, there is no need to use the @EnableWebMvc annotation, as the framework automatically detects Web MVC usage and configures itself as appropriate.
In this scenario, Springfox will not correctly generate and expose the Swagger UI endpoint (/swagger-ui.html
) if @EnableWebMvc is present in the application.
Caveat to using the library is that it depends on Jackson for serialization, more importantly the ObjectMapper
. A
good example of where this breaks down is the following issue when using Gson serialization
6.2. Customizing the swagger endpoints.
By default the swagger service descriptions are generated at the following urls
Swagger version | Documentation Url | Group |
---|---|---|
1.2 |
/api-docs |
implicit default group |
1.2 |
/api-docs?group=external |
external group via docket.groupName() |
2.0 |
/v2/api-docs |
implicit default group |
2.0 |
/v2/api-docs?group=external |
external group via docket.groupName() |
To customize these endpoints, loading a property source with the following properties allows the properties to be overridden
Swagger version | Override property |
---|---|
1.2 |
springfox.documentation.swagger.v1.path |
2.0 |
springfox.documentation.swagger.v2.path |
6.3. Configuring startup.
If you’d like to delay the startup of springfox, you could choose to set auto-startup to false. The property to use
is springfox.documentation.auto-startup
and this could either be passed in as a -D
jvm arg or via a property in
application.yml/properties
file.
Override property | description |
---|---|
true |
This is the default value, which starts scanning for endpoints automatically when the spring contexts is refreshed. |
false |
This setting starts scanning for endpoints only when when the |
Change this default to false with caution. This implies managing the startup of the plugins prior to
requesting the swagger endpoints in a thread-safe manner.
|
6.4. Overriding descriptions via properties
Added support for resolving properties in property sources to replace expressions in certain annotations. In order to
use it simply define properties in application.properties
, application.yml
file or property files in your
classpath with values that you’d like to see replaced in known annotations. For e.g. @ApiModelProperty(value="${property1.description}")
will evaluate property1.description
from the available properties. If none is found, it will render the
un-resolved expression as-is.
Currently supported list of annotations are in order of priority within the annotation:
Annotation | Attribute | Target Property | Description |
---|---|---|---|
ApiModelProperty |
value |
ModelProperty#description |
e.g. |
ApiModelProperty |
description |
ModelProperty#description |
e.g. |
ApiParam |
value |
Parameter#description |
e.g. |
ApiImplicitParam |
value |
Parameter#description |
e.g. |
ApiOperation |
notes |
Operation#notes |
e.g. |
ApiOperation |
summary |
Operation#summary |
e.g. |
RequestParam |
defaultValue |
Parameter#defaultValue |
e.g. |
RequestHeader |
defaultValue |
Parameter#defaultValue |
e.g. |
For a detailed explanation see here.
6.5. Overriding property datatypes
Using the ApiModelProperty#dataType
we can override the inferred data types. However it is restricted
to only allow data types to be specified with a fully qualified class name. For e.g. if we have the following
definition
1
2
3
4
5
6
7
8
9
// if com.qualified.ReplaceWith is not a Class that can be created using Class.forName(...)
// Original will be replaced with the new class
@ApiModelProperty(dataType = "com.qualified.ReplacedWith")
public Original getOriginal() { ... }
// if ReplaceWith is not a Class that can be created using Class.forName(...) Original will be preserved
@ApiModelProperty(dataType = "ReplaceWith")
public Original getAnotherOriginal() { ... }
In the case of ApiImplicitParam#dataType , since the type itself is usually a scalar type (string, int)
use one of the base types specified in the Types class ⇒
springfox-schema/src/main/java/springfox/documentation/schema/Types.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
* @deprecated use @see {@link ScalarType} instead
*/
@Deprecated
public class Types {
private static final Set<String> BASE_TYPES = Stream.of(
"int",
"date",
"string",
"double",
"float",
"boolean",
"byte",
"object",
"long",
"date-time",
6.6. Docket XML Configuration
To use the plugin you must create a spring java configuration class which uses spring’s @Configuration
.
This config class must then be defined in your xml application context.
1
2
3
4
5
6
7
<!-- Required so springfox can access spring's RequestMappingHandlerMapping -->
<mvc:annotation-driven/>
<!-- Required to enable Spring post processing on @Configuration classes. -->
<context:annotation-config/>
<bean class="com.yourapp.configuration.MySwaggerConfig"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@EnableSwagger //Loads the spring beans required by the framework
public class MySwaggerConfig {
/**
* Every Docket bean is picked up by the swagger-mvc framework - allowing for multiple
* swagger groups i.e. same code base multiple swagger resource listings.
*/
@Bean
public Docket customDocket(){
return new Docket(); //some customization goes here
}
}
6.7. Docket Spring Java Configuration
-
Use the
@EnableSwagger
or@EnableSwagger2
annotation. -
Define one or more Docket instances using springs
@Bean
annotation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
@EnableWebMvc //NOTE: Only needed in a non-springboot application
@EnableSwagger2
@ComponentScan("com.myapp.controllers")
public class CustomJavaPluginConfig {
@Bean //Don't forget the @Bean annotation
public Docket customImplementation(){
return new Docket()
.apiInfo(apiInfo());
//... more options available
}
//...
}
6.8. Support for documentation from property file lookup
Starting with 2.7.0
we support looking up description from the following annotations given a property just like
property place holders resolve a value annotation @Value(${key})
. The following annotations attributes support
description resolution.
-
@ApiParam#value()
-
@ApiImplicitParam#value()
-
@ApiModelProperty#value()
-
@ApiOperation#value()
-
@ApiOperation#notes()
-
@RequestParam#defaultValue()
-
@RequestHeader#defaultValue()
Below are examples of how it would work
@ApiOperation(value = "Find pet by Status",
notes = "${SomeController.findPetsByStatus.notes}"...) (1)
@RequestMapping(value = "/findByStatus", method = RequestMethod.GET, params = {"status"})
public Pet findPetsByStatus(
@ApiParam(value = "${SomeController.findPetsByStatus.status}", (2)
required = true,...)
@RequestParam("status",
defaultValue="${SomeController.findPetsByStatus.status.default}") String status) { (3)
//...
}
@ApiOperation(notes = "Operation 2", value = "${SomeController.operation2.value}"...) (4)
@ApiImplicitParams(
@ApiImplicitParam(name="header1", value="${SomeController.operation2.header1}", ...) (5)
)
@RequestMapping(value = "operation2", method = RequestMethod.POST)
public ResponseEntity<String> operation2() {
return ResponseEntity.ok("");
}
1 | Example of @ApiOperation#notes() |
2 | Example of @ApiParam#value() |
3 | Example of @RequestParam#defaultValue() |
4 | Example of @ApiOperation#value() |
5 | Example of @ApiImplicitParams#value() |
public class SomeModel {
@ApiModelProperty(value = "${SomeModel.someProperty}", ...) (1)
private long someProperty;
}
1 | Example of @ApiModelProperty#value() |
To provide these properties via external properties just add it to your application property file or any property source configured by the application as shown below. When a property place holder cannot be found the default behavior is to echo the expression as-is.
SomeController.findPetsByStatus.notes=Finds pets by status
SomeController.findPetsByStatus.status=Status could be one of ...
SomeController.operation2.header1=Header for bla bla...
SomeController.operation2.value=Operation 2 do something...
SomeModel.someProperty=Some property description
6.8.1. Swagger group
A swagger group is a concept introduced by this library which is simply a unique identifier for a Swagger Resource Listing within your application. The reason this concept was introduced was to support applications which require more than one Resource Listing. Why would you need more than one Resource Listing? - A single Spring Web MVC application serves more than one API e.g. publicly facing and internally facing. - A single Spring Web MVC application serves multiple versions of the same API. e.g. v1 and v2
In most cases an application will not need more than one Resource Listing and the concept of swagger groups can be ignored.
6.8.2. Configuring the output of operationId in a Swagger 2.0 spec
As defined operationId
was
introduced in the Swagger 2.0 spec, the operationId
parameter, which was referred to as nickname
in pre-2.0
versions of the Swagger spec, provides the author a means by which to describe an API operation with a friendly name
. This field is often used by consumers of a Swagger 2.0 spec in order to name functions in generated clients. An
example of this can be seen in the swagger-codegen project.
The default value of operationId
according to Springfox
By default, when using Springfox in Swagger 2.0 mode, the value of operationID
will be rendered using the
following structure: “[java_method_name_here]Using[HTTP_verb_here]”. For example, if one has a method getPets()
connected to an HTTP GET verb, Springfox will render getPetsUsingGET
for the operationId.
Customizing the value of operationId
In the event you wish to override the default operationId
which Springfox renders, you may do so by providing the
nickname
element in an @ApiOperation
annotation.
6.8.3. Changing how Generic Types are Named
By default, types with generics will be labeled with '\u00ab'(<<), '\u00bb'(>>), and commas. This can be problematic
with things like swagger-codegen. You can override this behavior by implementing your own GenericTypeNamingStrategy
.
For example, if you wanted List<String>
to be encoded as 'ListOfString' and Map<String, Object>
to be encoded as 'MapOfStringAndObject' you could set the forCodeGeneration
customization option to true
during
plugin customization:
1
docket.forCodeGeneration(true|false);
6.9. Caching
The caching feature that was introduced in 2.1.0 has been removed. Springfox no longer uses the cache abstraction to improve performance the api scanners and readers. It has been rolled into the library as an internal implementation detail as of 2.1.2. This is a runtime breaking change, however, since its not really breaking api compatibility change other than the introduction of configuration change in consuming applications, we’re not incrementing the minor version.
6.10. Configuring Security Schemes and Contexts an Overview
The security provisions in SpringFox at a high level, without getting into the code, has different pieces that all work together in concert
-
The API itself needs to be protected. This is achieved by using, for simplicity sake, spring security and may also use a combination of servlet container and tomcat/jersey etc.
-
The security scheme which describes the techniques you’ve used to protect the api. Spring fox supports whatever schemes swagger specification supports (ApiKey, BasicAuth and OAuth2 (certain profiles))
-
Finally the security contexts which actually provides information on which api’s are protected by which schemes. I think in your example, you’re missing the last piece of the puzzle, the security context see 15.
6.11. Example application
For an examples for spring-boot, vanilla spring applications take a look examples in the demo application.
7. Configuring springfox-staticdocs
Support for this module has been deprecated in 2.7.0. Since swagger2markup doesnt support jdk6 anymore it is difficult for build to co-exist with the newer version of swagger2markup. Please use the latest instructions provided in the awesome Swagger2Markup Library. |
8. Security
Thanks to Javed Mohammed we now have an example oauth demo.
this is based on swagger-ui pre 3.x |
9. Plugins
9.1. Introduction
Any plugin or extensibility hook that is available is available in the SPI
module. In the spi
module, anything that ends in *Plugin
is generally an extensibility point that is meant for
library consumers to consume.
The bean validation (JSR-303) is a great example of a contribution to support bean validations. Its fairly simple and small in scope and should
give an idea how one goes about creating a plugin. Its a set of plugins are act on ModelProperty
, hence they
are implementations of ModelPropertyBuilderPlugin
.
9.2. Plugins Available For Extensibility
To explicitly state the extensibility points that are available: * At the schema level .Schema Plugins
Name | Description |
---|---|
|
for enriching models |
|
for enriching model properties |
|
these are for overriding names for models |
Name | Description |
---|---|
|
for adding custom api descriptions (see example). |
|
for enriching api listings |
|
for providing your own defaults |
|
for enriching the documentation context |
|
for parameter expansion used in the context of |
|
for enriching operations |
|
for providing additional models that you might load a different way |
|
for enriching parameters (see example) |
Name | Description |
---|---|
|
Specifically used with a servlet application, it is useful for transforming the generated open api. Out of the box it comes with one plugin that sets the host name and base path. Typically, this the most common thing to use this plugin for. Having said that this plugin has the power to rewrite the whole specification if required. |
|
Specifically used with a web flux application, it is useful for transforming the generated open api. Out of the box it comes with one plugin that sets the host name and base path. Typically, this the most common thing too use this plugin for. Having said that this plugin has the power to rewrite the whole specification if required. |
Name | Description |
---|---|
|
Specifically used with a servlet application, it is useful for transforming the generated open api. Out of the box it comes with one plugin that sets the host name and base path. Typically, this the most common thing to use this plugin for. Having said that this plugin has the power to rewrite the whole specification if required. |
|
Specifically used with a web flux application, it is useful for transforming the generated open api. Out of the box it comes with one plugin that sets the host name and base path. Typically, this the most common thing to use this plugin for. Having said that this plugin has the power to rewrite the whole specification if required. |
9.3. Steps To Create A Plugin
The same patterns apply to all of the extensibility mechanisms |
-
Implement one of the above plugin interfaces
-
Give the plugin an order for e.g. ApiParamParameterBuilder has an order specified in the bean. In general spring plugins get the highest priority, The swagger plugins (the ones that process all the
@Api…
annotations) layer information on top. So the order that you’d write will need to layer information at the end. -
Each plugin has
-
a *context and provides access to any information that the plugin might need to do its job
-
a *builder for the type of object that the plugin is intended to support for e.g. a
ModelPropertyBuilderPlugin
will have access to aModelPropertyBuilder
. This builder is what is used to build the model after all the plugins have had access to contribute/enrich the underlying object.
-
-
Update any builder properties your plugin cares about
-
Register the plugin as a
@bean
, so that the plugin registry can pick it up.
That is it!
9.3.1. Example OperationBuilderPlugin
This example by @koderman is a custom extension for AWS Amazon Api Gateway integration.
9.3.2. Example ParameterBuilderPlugin
Here is an example of how to add parameters by hand.
Consider this controller, VersionedController.java
1
2
3
4
5
6
@GetMapping("/user")
public ResponseEntity<User> getUser(
@VersionApi int version, (1)
@RequestParam("id") String id) {
throw new UnsupportedOperationException();
}
1 | Parameter annotated with @VersionApi |
We then create a plugin VersionApiReader.java to provide custom parameter information.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000) (1)
public class VersionApiReader implements ParameterBuilderPlugin {
private TypeResolver resolver;
public VersionApiReader(TypeResolver resolver) {
this.resolver = resolver;
}
@Override
public void apply(ParameterContext parameterContext) {
ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();
Optional<VersionApi> requestParam = methodParameter.findAnnotation(VersionApi.class);
if (requestParam.isPresent()) { (2)
parameterContext.requestParameterBuilder()
.in(ParameterType.HEADER)
.name("v")
.query(q -> q.model(m -> m.scalarModel(ScalarType.STRING))); (3)
parameterContext.requestParameterBuilder()
.in(ParameterType.HEADER)
.name("v")
.query(q -> q.style(ParameterStyle.SIMPLE)
.model(m -> m.scalarModel(ScalarType.STRING)));
}
}
@Override
public boolean supports(DocumentationType documentationType) {
return true; (4)
}
}
1 | Specify an order for the plugin to execute. Higher the number, later the plugin is applied. |
2 | Check if the VersionApi is applied to the parameter. |
3 | Build the parameter with the necessary information using the builder methods. |
4 | Return true if we want this plugin to apply to all documentation types. |
9.3.3. Example ApiListingScannerPlugin
Here is an example of how to add Api Descriptions by hand.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
private final CachingOperationNameGenerator operationNames;
/**
* @param operationNames - CachingOperationNameGenerator is a component bean
* that is available to be autowired
*/
public Bug1767ListingScanner(CachingOperationNameGenerator operationNames) {(9)
this.operationNames = operationNames;
}
@Override
public List<ApiDescription> apply(DocumentationContext context) {
return new ArrayList<>(
Arrays.asList( (1)
new ApiDescription(
"test",
"/bugs/1767",
"This is a bug summary",
"This is a bug",
Collections.singletonList( (2)
new OperationBuilder(operationNames)
.authorizations(new ArrayList<>())
.codegenMethodNameStem("bug1767GET") (3)
.method(HttpMethod.GET)
.notes("This is a test method")
.parameters(
Collections.singletonList( (4)
new springfox.documentation.builders.ParameterBuilder()
.description(
"search by "
+ "description")
.type(new TypeResolver()
.resolve(String.class))
.name("description")
.parameterType("query")
.parameterAccess("access")
.required(true)
.modelRef(new springfox.documentation.schema.ModelRef(
"string")) (5)
.build()))
.requestParameters(
Collections.singletonList( //<4a>
new RequestParameterBuilder()
.description("search by description")
.name("description")
.required(true)
.in(ParameterType.QUERY)
.query(q -> q.model(m -> m.scalarModel(ScalarType.STRING))) //<5b>
.build()))
.responses(responseMessages()) (6)
.responseModel(new springfox.documentation.schema.ModelRef("string")) (7)
.responses(responses()) //<6b>
.build()),
false),
new ApiDescription(
"different-group",
(8)
"/different/2219",
"This is a bug summary",
"This is a bug",
Collections.singletonList(
new OperationBuilder(
operationNames)
.authorizations(new ArrayList<>())
.codegenMethodNameStem("bug2219GET")
.method(HttpMethod.GET)
.notes("This is a test method")
.parameters(
Collections.singletonList(
new springfox.documentation.builders.ParameterBuilder()
.description("description of bug 2219")
.type(new TypeResolver().resolve(String.class))
.name("description")
.parameterType("query")
.parameterAccess("access")
.required(true)
.modelRef(new springfox.documentation.schema.ModelRef("string"))
.build()))
.requestParameters(
Collections.singletonList(
new RequestParameterBuilder()
.description("description of bug 2219")
.name("description")
.in("query")
.required(true)
.query(q -> q.model(m -> m.scalarModel(ScalarType.STRING)))
.build()))
.responses(responseMessages())
.responseModel(new springfox.documentation.schema.ModelRef("string"))
.build()),
false)));
}
/**
* @return Set of response messages that overide the default/global response messages
*/
private Set<Response> responseMessages() { (8)
return singleton(new springfox.documentation.builders.ResponseBuilder()
.code("200")
.description("Successfully received bug 1767 or 2219 response")
.representation(MediaType.TEXT_PLAIN)
.apply(r -> r.model(m -> m.scalarModel(ScalarType.STRING)))
.build());
}
/**
* @return Set of response messages that overide the default/global response messages
*/
private Collection<Response> responses() { //<8b>
return singletonList(new ResponseBuilder()
.code("200")
.description("Successfully received bug 1767 or 2219 response")
.representation(MediaType.ALL)
.apply(r -> r.model(m -> m.scalarModel(ScalarType.STRING))
.build())
.build());
}
1 | Add a list of custom ApiDescription s |
2 | Add a list of Operation s for each description |
3 | NOTE: the code generated names are not guaranteed to be unique. For these custom endpoints it is the responsibility of the service author to ensure. |
4 | Added parameters to the operation |
5 | NOTE: It is important to also ensure we pass in a model reference even for primitive types |
6 | Set of response messages that overide the default/global response messages |
7 | Response model reference of the default model returned by the operation. Usually the response model returned in a 200 or 201 response. |
8 | Optionally provide a group name, if provided this api will only appear under that group. If absent, the api will appear under the default group. |
9 | CachingOperationNameGenerator is a component bean that is available to be autowired |
10. Additional extensibility options
Name | Description | Since |
---|---|---|
|
for combining apis that react to the same API endpoint given the same input criteria but produces different output.
We provide a |
2.7.0 |
|
To provide a convention based type rules. Ideally we use these when its cumbersome to define individual types either
because there are just too many, or even in some cases where it involves creating mixin types just for the sake of
OpenAPI documentation. |
2.7.0 |
|
Extensibility which allows overriding how the swagger resources are served. By default we serve APIs hosted on the same application. This can be used to aggregate APIs as well. |
2.7.0 |
1. This has a shortcoming currently in that, currently the response still hides one of the response representations. This is a limitation of the OAI Spec 2.0. |
10.1. Examples of alternate type rule convention
10.1.1. JacksonSerializerConvention
Here is how we configure the JacksonSerializerConvention
.
1
2
3
4
@Bean
AlternateTypeRuleConvention jacksonSerializerConvention(TypeResolver resolver) {
new JacksonSerializerConvention(resolver, "springfox.documentation.spring.web.dummy.models")
}
10.1.2. Creating a convention
Below is an example for creating a rule that automatically provides a convention for configuring Pageable
type.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Bean
public AlternateTypeRuleConvention pageableConvention(
final TypeResolver resolver,
final RepositoryRestConfiguration restConfiguration) {
return new AlternateTypeRuleConvention() {
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public List<AlternateTypeRule> rules() {
return singletonList(
newRule(resolver.resolve(Pageable.class), resolver.resolve(pageableMixin(restConfiguration)))
);
}
};
}
It involves creating a generated in-memory type that allows the springfox inference engine to use the new type
instead of Pageable
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private Type pageableMixin(RepositoryRestConfiguration restConfiguration) {
return new AlternateTypeBuilder()
.fullyQualifiedClassName(
String.format("%s.generated.%s",
Pageable.class.getPackage().getName(),
Pageable.class.getSimpleName()))
.property(p -> p.name(restConfiguration.getPageParamName())
.type(Integer.class)
.canRead(true)
.canWrite(true))
.property(p -> p.name(restConfiguration.getLimitParamName())
.type(Integer.class)
.canRead(true)
.canWrite(true))
.property(p -> p.name(restConfiguration.getSortParamName())
.type(String.class)
.canRead(true)
.canWrite(true))
.build();
}
10.2. Aggregating multiple swagger specifications in the same swagger-ui
You need to create a bean that implements the SwaggerResourcesProvider
interface. Typically you’d have to make a composite bean that uses multiple InMemorySwaggerResourcesProvider
and adds your own json file to it as well.
You need to make this new bean a @Primary bean. otherwise the you’d get an exception about ambiguous beans. |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class SwaggerWsEndpointsConfig {
@Primary
@Bean
public SwaggerResourcesProvider swaggerResourcesProvider(InMemorySwaggerResourcesProvider defaultResourcesProvider) {
return () -> {
SwaggerResource wsResource = new SwaggerResource();
wsResource.setName("ws endpoints");
wsResource.setSwaggerVersion("2.0");
wsResource.setLocation("/v2/websockets.json");
List<SwaggerResource> resources = new ArrayList<>(defaultResourcesProvider.get());
resources.add(wsResource);
return resources;
};
}
}
11. Spring Data Rest extensibility
These are incubating features and may change. |
Name |
Description |
Since |
|
This is an extensibility point to extract entity specific operations. This extractor is to allow creation of
|
2.7.0 |
|
This is an extensibility point to extract operations related to entity associations. This extractor is to allow
creation of |
2.7.0 |
|
Additionally the glue that makes all this possible is the |
2.7.0 |
12. Answers to common questions and problems
-
It can be a little confusing:
-
Swagger Spec is a specification.
-
Swagger Api - an implementation of that specification that supports jax-rs, restlet, jersey etc.
-
Springfox libraries in general - another implementation of the specification focused on the spring based ecosystem.
-
Swagger.js and Swagger-ui - are client libraries in javascript that can consume swagger specification.
-
springfox-swagger-ui - the one that you’re referring to, is just packaging swagger-ui in a convenient way so that spring services can serve it up.
-
-
Thanks to @chrishuttonch for describing the solution to this issue
I switched on the excludeFieldsWithoutExposeAnnotation() which meant that none of the objects would produce any data. To get around this I created several serializers for the following classes: |
.registerTypeAdapter(springfox.documentation.service.ApiListing.class, new SwaggerApiListingJsonSerializer())
.registerTypeAdapter(springfox.documentation.spring.web.json.Json.class, new SwaggerJsonSerializer())
.registerTypeAdapter(springfox.documentation.swagger.web.SwaggerResource.class, new SwaggerResourceSerializer())
.registerTypeAdapter(springfox.documentation.service.ResourceListing.class, new SwaggerResourceListingJsonSerializer())
.registerTypeAdapter(springfox.documentation.swagger.web.SwaggerResource.class, new SwaggerResourceSerializer())
.registerTypeAdapter(springfox.documentation.swagger.web.SecurityConfiguration.class, new SwaggerSecurityConfigurationSerializer())
.registerTypeAdapter(springfox.documentation.swagger.web.UiConfiguration.class, new SwaggerUiConfigurationSerializer());
-
It is possible you’re experiencing one of the following issues
-
NPE During startup?
Running in debugger revealed that I had two instances of WebApplicationInitializers in my war. Spring is refreshing context with each one and is resulting in second instance of OptimizedModelPropertiesProvider
withoutonApplicationEvent
call. I was able to fix it by removing secondWebApplicationInitializer
in my code. Seems this is related to spring-boot issue #221 [1] -
Object Mapper Customizations Not Working?
Sometimes there are multiple ObjectMapper
in play and it may result in the customizations not working [2] Spring Boot inHttpMessageConverters
first adds the Spring Boot configuredMappingJackson2HttpMessageConverter
and then it adds the defaultMappingJackson2HttpMessageConverter
from Spring MVC. This causes theObjectMapperConfigured
event to fire twice, first for the configured converter (which is actually used) and then for the default converter. So when you f.e. set a custom property naming strategy then inObjectMapperBeanPropertyNamingStrategy
this is overwritten by the second event. The following code fixes this:
-
@Configuration
public class MyWebAutoConfiguration extends WebMvcConfigurerAdapter {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
ObjectMapper objectMapper = null;
for (HttpMessageConverter converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jacksonConverter =
((MappingJackson2HttpMessageConverter) converter);
if (objectMapper == null) {
objectMapper = jacksonConverter.getObjectMapper();
} else {
jacksonConverter.setObjectMapper(objectMapper);
}
}
}
}
}
-
The easiest way to to configure dates is via
Docket#directModelSubstitute(LocalDateTime.class, String.class)
. If these are ISO 8601 dates that conform to a string format i.e.yyyy-MM-dd’T’HH:mm’Z'
. However you won’t have any format or validation info.
Use java.sql.Date works great for date precision and java.util.Date for date-time precision [3]
|
The way to correctly map the "Date" and "DateTime" types to their corresponding swagger types:
-
Substitute "Date" types (java.util.LocalDate, org.joda.time.LocalDate) by java.sql.Date.
-
Substitute "DateTime" types (java.util.ZonedDateTime, org.joda.time.LocalDateTime, …) by java.util.Date.
docket
.directModelSubstitute(LocalDate.class, java.sql.Date.class)
.directModelSubstitute(LocalDateTime.class, java.util.Date.class)
Q. How does one use @ModelAttribute
annotation. It doesn’t seem to render the model properties as scalar
properties?
-
In order for
@ModelAttribute
annotated types to be inferred the properties need to be bean properties. If the intent is immutability and passing in an object, the preferred approach is to make that a request body, in which case the immutability will follow the rules laid out by jackson to determine what constitutes a request "view" of the request object.
Getters/setters are a clean way to indicate what values can come in to a operation. While it may not be apparent in a trivial model with one level nesting; the design choice will become clear when we realize that model attributes can be arbitrarily nested. Consider (pseudo code in C# for brevity)
Person {String firstName {get;set;}
String lastName {get;set;}
Category category {get;set;}
Category {String name {get;set;}
String description {get;}
So one could set properties:
-
firstName
-
lastName
-
category.name
Now we don’t want category to be able to set description via the operation, how do we control/specify that? It makes it hard to reason about which fields in an object are not intended to be mutated. This is the reason we chose to limit it to objects that expose getters and setters.
I know spring supports fields as well, and it will fall back to fields if setters are not found. |
Q. How should we resolve multiple object mappers available as beans especially when using spring-hateoas?
-
The idea is to provide a
@Primary
ObjectMapper. Based on answer provided by @prabhat1790 in issue #890
private static final String SPRING_HATEOAS_OBJECT_MAPPER = "_halObjectMapper";
@Autowired
@Qualifier(SPRING_HATEOAS_OBJECT_MAPPER)
private ObjectMapper springHateoasObjectMapper;
@Primary
@Bean
@Order(value=Ordered.HIGHEST_PRECEDENCE)
@DependsOn(SPRING_HATEOAS_OBJECT_MAPPER)
public ObjectMapper objectMapper() {
return springHateoasObjectMapper;
}
and set the order of the other bean to lowest precedence.
-
Logical explanation of how one might go about doing this is available in the swagger google group Additionally this comment further discusses issues with doing this.
-
This is because of how plugins work and how their priority layers information
-
@PathVariables
are always marked as required. -
@ApiParam
is an optional annotation to describe additional meta-information like description etc. -
@ApiParam#required()
is defaulted to false, unless you set it to true.
-
Springfox uses plugins to layer information. There are a set of plugins that are spring specific that apply the inferred values on to the internal service models. The swagger annotation related metadata is layered on top of the spring-mvc descriptions. By definition, plugins don’t know and should not know about each other or previously inferred values (in your case required attribute).
So if you choose to augment the definitions with @ApiParam
then you need to be explicit and set the value to true.
-
To do this, you’d have to
-
add an alternate type rule for
Optional<T>
seegenericModelSubstitutes
in docket -
implement your own ModelPropertyBuilderPlugin
-
and override the read only property if you find an
Optional
type. See here for an example.
-
Keep in mind that you need the plugin to fire after this plugin… so order it accordingly
-
This is a known limitation of swagger-spec. There is a work around for it but, swagger-ui won’t play nice with it. I have a PR which address that issue. Would be great if you vote up the PR and the underlying issue
This PR has been closed! |
-
Excerpted from an explanation for issue 963…
(springfox) uses the context path as the starting point.
What you really need to is to define a dynamic servlet registration and create 2 dockets .. one for api and one for api/v2. This SO post might help
...
Dynamic servlet = servletContext.addServlet("v1Dispatcher", new DispatcherServlet(ctx1));
servlet.addMapping("/api");
servlet.setLoadOnStartup(1);
Dynamic servlet = servletContext.addServlet("v2Dispatcher", new DispatcherServlet(ctx2));
servlet.addMapping("/api/v2");
servlet.setLoadOnStartup(1);
-
Excerpted from issue 983…
I was able to get it working by modifying the dispatcherServlet
to listen on /* , but this prevented swagger-ui.html
from being served. To fix this to let the swagger-ui.html
bypass the dispatcherServlet
i had to create a new
servlet mapping:
<servlet>
<servlet-name>RestServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/swagger-ui.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>RestServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
Also had to let the webjar through the dispatcher servlet:
<mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/"/>
Tricky to get working, but it works. Perhaps there is a better way to remap swagger-ui.html or let it pass through the dispatcherServlet.
swagger-ui.html is the name of the swagger-ui page. While it cannot be changed one can configure the
application such that landing on a particular URL re-directs the browser to the real swagger-ui location.
[4]
|
For e.g. One could move Swagger UI under /documentation
using this code.
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addRedirectViewController("/documentation/v2/api-docs", "/v2/api-docs?group=restful-api");
registry.addRedirectViewController("/documentation/swagger-resources/configuration/ui","/swagger-resources/configuration/ui");
registry.addRedirectViewController("/documentation/swagger-resources/configuration/security","/swagger-resources/configuration/security");
registry.addRedirectViewController("/documentation/swagger-resources", "/swagger-resources");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.
addResourceHandler("/documentation/swagger-ui.html**").addResourceLocations("classpath:/META-INF/resources/swagger-ui.html");
registry.
addResourceHandler("/documentation/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
However, it still requires a redirect to /documentation/swagger-ui.html
because the path name is
hard-coded.
-
If the following types…
ToSubstitute[] array;
List<ToSubstitute> list;
Need to look like this over the wire…
Substituted[] array;
List<Substituted> list;
This is how the rules need to be configured
rules.add(newRule(resolver.arrayType(ToSubstitute), resolver.arrayType(Substituted)))
rules.add(newRule(resolver.resolve(List, ToSubstitute), resolver.resolve(List, Substituted)))
-
Use the
protocols
method to configure the docket to indicate supported schemes.
docket.protocols(newHashSet("http", "https"))
-
There is a demo application that describes how java-xml configuration needs to be setup.
-
This should be available in v2.3 thanks to this PR by @cbornet. It is still in incubation but host name can be configured per docket
docket.host("http://maybe-an-api-gateway.host");
Q. Infinite loop when springfox tries to determine schema for objects with nested/complex constraints?
-
If you have recursively defined objects, I would try and see if providing an alternate type might work or perhaps even ignoring the offending classes e.g. order using the docket. ignoredParameterTypes(Order.class). This is usually found in Hibernate domain objects that have bidirectional dependencies on other objects.
Tags which are first class constructs just like operations, models etc. and what you see on operations are
references to those Tags. The typical workflow is to register tags in a docket and use the tag definitions on
operations(@ApiOperation
)/controllers(@Api
) to point to these registered tags (in the docket) by name.
The convenience we have in place just to reduce the amount of boiler plate for the developer is to provide a default description that happens to be the same as the tag name. So in effect we are synthesizing a pseudo Tag by referencing one on the operation.
By defining the Tag on the docket, we are referencing a real tag defined by you.
Thanks to @Pyohwan's suggestion
@Configuration
annotation may not working with @EnableSwagger2.
So shouldn’t attach @Configration. So if you have a configuration class that pulls in the springfox configuration
using the @EnableSwagger2
like below, try removing the @Configuration
on this class as shown below.
@EnableSwagger2
public class SwaggerConfig {
...
and use @Import
annotation on WebMvcConfigurerAdapter
or similar configuration class.
@Configuration
@EnableWebMvc
@ComponentScan(...)
@Import(SwaggerConfig.class)
public class MvcConfig extends WebMvcConfigurerAdapter {
...
*21. How to add CORS support? (thanks @gangakrishh)
Based on the spring guide, Creating a WebMvcConfigurer
we can
configure a request mapping to allow specific origins.
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/some-request-mapping").allowedOrigins("http://localhost:9000");
}
};
If that doesn’t work, most likely the cors registration is happening earlier than the SpringFox beans are registered. To fix this, we can register a CORS filter registration bean, that registers our endpoints. In the example below, we’ve registered it for all endpoints.
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
//https://stackoverflow.com/questions/31724994/spring-data-rest-and-cors
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
config.setAllowCredentials(false);
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}
*22. How to configure the docket when using Immutables? (thanks https://github .com/kevinm416[@kevinm416])
This is related to #1490.The way to configure this
is to create a custom alternateTypeRules in the Docket
config. For e.g. if you have an immutable MyClass
that
generates ImmutableMyClass
, then we would add a rule for it as shown below.
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.alternateTypeRules(AlternateTypeRules.newRule(MyClass.class,
ImmutableMyClass.class));
If you’re using a library for you models, you may need to make the Immutable visible outside the package. |
If you change the datatype to "__file" once 2.7.0 is released, it will fix your issue.
The reason we use "__file" is because if a consuming library defines a custom type File then that type ends
up getting treated just as if it were a file data type, even if that wasn’t the intent. To distinguish the usage of
the custom type from the natively understood file primitive we introduced this convention.
|
*24. How to configure springfox for Vavr/Javaslang Jackson module support?
We need to first create an alternate type rules convention that tells springfox to treat the vavr persistent collections just like the native java collections. The following example only configurers Set, List and Map types.
Optionally, to use jackson effectively, for non-collection types we can register an application listener that registers the vavr module with the objectmapper that is being used.
@Component
public class SwaggerJacksonModuleWithVavr implements ApplicationListener<ObjectMapperConfigured> {
@Override
public void onApplicationEvent(ObjectMapperConfigured event) {
event.getObjectMapper().registerModule(new VavrModule());
}
}
*25. Why are properties missing from certain models/types?
A good example of when this might happen is when using Immutables (the library) that are not particularly obvious. Immutables are great, except that, unless u craft your model correctly it might lead to confusing results, as you’ve seen. Also how springfox infers the model is opposite to how the service consumes it. By that I mean that, take for e.g. Model has properties a (read/write), b (write only), c (readonly) we read the writable properties for requests and readable properties for responses.
So if your model is only used for requests, it will have a and b And if your model is only used of responses it will have a and c and if it is present in both request and response it will have a, b and c.
We’re working to make this better thanks to model enhancements work by Maksim
*26. Why is http://host:port/swagger-ui.html
blank? Or why do I get a 404 when I navigate to
http://host:port/swagger-ui.html
?
In non-spring boot application ensure that the resource handlers are added for the springfox-swagger-ui webjars
If its a spring boot application:
-
check to see if the
spring.resources.add-mappings
property is set to true. -
check to see if spring security is applied that the appropriate resources are permitted.
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
http
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.accessDeniedHandler(accessDeniedHandler)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
//.antMatchers("/actuator/**").permitAll()
.antMatchers("/actuator/**").hasAuthority("ADMIN")
.antMatchers(
HttpMethod.GET,
"/v2/api-docs",
"/swagger-resources/**",
"/swagger-ui.html**",
"/webjars/**",
"favicon.ico"
).permitAll()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
http
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class)
.headers()
.cacheControl();
}
}
Or we could simply ignore these resources altogether
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/v2/api-docs",
"/swagger-resources/**",
"/swagger-ui.html**",
"/webjars/**");
}
}
*27. Controller receives parameter as a String but we are expecting a json Type. How can we model this?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RequestMapping(value = "/2031", method = RequestMethod.POST)
@ResponseBody
@ApiOperation(value = "/2031")
@ApiImplicitParams({
@ApiImplicitParam(
name = "contents",
dataType = "CustomTypeFor2031",
examples = @io.swagger.annotations.Example(
value = {
@ExampleProperty(value = "{'property': 'test'}", mediaType = "application/json")
})) (1)
})
public void save(@PathVariable("keyId") String keyId,
@PathVariable("id") String id,
@RequestBody String contents (2)
) {
}
public static class CustomTypeFor2031 { (3)
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}
1 | Assume we have a controller action that has a String parameter (contents). This is how we can override the
on-the-wire type (CustomTypeFor2031). |
2 | The method parameter that comes in as a String that is actually interpreted as CustomTypeFor2031 |
3 | CustomTypeFor2031 complex type that we read from json String. |
Once we do this we need to configure this change so that we can add the model to the definitions section.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
return new Docket(DocumentationType.SWAGGER_2)
.groupName("featureService")
.useDefaultResponseMessages(false)
.additionalModels(resolver.resolve(FeatureDemonstrationService.CustomTypeFor2031.class)) (1)
.securitySchemes(authorizationTypes)
.produces(new HashSet<>(
Arrays.asList(
"application/xml",
"application/json")))
.alternateTypeRules(newRule(
LocalDate.class,
String.class))
.select().paths(PathSelectors.regex(".*/features/.*"))
.build();
1 | Add CustomTypeFor2031 as a additional model. |