Override Spring-Boot's GsonAutoConfiguration with custom Gson - spring-boot
Is there a way to override the GsonAutoConfiguration in spring-boot?
I would like to add some typeAdapter to the gson instance.
Preferably using java configurations
I've added the following to the application.properties.
spring.http.converters.preferred-json-mapper=gson
and the following class
#Configuration
#ConditionalOnClass(Gson.class)
public class GsonConfig {
#Bean
public Gson gson() {
return new GsonBuilder()
.registerTypeAdapter(DateTime.class, new DateTimeTypeAdapter())
.setPrettyPrinting().create();
}
}
I am also using Jersey in the mix as well.
So I've also have the following code, which also didn't work.
InternalApplication.java
import com.google.gson.GsonBuilder;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;
import org.immutables.gson.stream.GsonMessageBodyProvider;
import org.immutables.gson.stream.GsonProviderOptionsBuilder;
import org.joda.time.DateTime;
public class InternalApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(TestResource.class);
return classes;
}
#Override
public Set<Object> getSingletons() {
final Set<Object> singletons = new HashSet<>();
singletons.add(new GsonMessageBodyProvider(
new GsonProviderOptionsBuilder()
.gson(new GsonBuilder()
.registerTypeAdapter(DateTime.class, new DateTimeTypeAdapter())
.setPrettyPrinting()
.create())
.lenient(true)
.build()
)
);
return singletons;
}
}
Since when does Gson have anything to with Jersey in Spring Boot? It doesn't. What you really want to do is first disable Jackson (which is the default provider). Then you can register your GsonMessageBodyProvider.
Basically all you need to do is exclude the Jackson provider from your Maven/Gradle dependencies, as the Jersey starter pulls it in
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</exclusion>
</exclusions>
</dependency>
And I'm not quite sure why you are using an Application class, as Spring Boot doesn't support that for its auto-configuration. You should be using a ResourceConfig class
#Component
#ApplicationPath("/api")
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(TestResource.class);
register(new GsonMessageBodyProvider(...));
}
}
Related
Configure Spring Boot to use Custom Access Token converter
I'm trying to get user information from the access token provided by an Identity Provider. The Identity Provider that I'm using provides it's scope in the form of a string instead of a list because of which the DefaultAccessTokenConverter doesn't work for me. As a result I wish to extend it to a CustomAccessTokenConverter to override it's extractAuthentication() method. I'm using the following in my security config to make Spring use this custom class instead of the default one: #Configuration #EnableResourceServer public class SecurityConfig extends ResourceServerConfigurerAdapter { #Autowired private CustomAccessTokenConverter customAccessTokenConverter; // For validating the incoming access token and fetching user information from it #Bean public ResourceServerTokenServices createResourceServerTokenServices() { RemoteTokenServices tokenServices = new RemoteTokenServices(); tokenServices.setCheckTokenEndpointUrl(*Introspection URL*); tokenServices.setClientId(*Client ID*); tokenServices.setClientSecret(*Client Secret*); return tokenServices; } #Bean public AccessTokenConverter accessTokenConverter() { return customAccessTokenConverter; } } But, Spring still uses the DefaultAccessTokenConverter. What am I doing wrong? Please help me out here. Here is what my CustomAccessTokenConverter class looks like just for reference: #Component public class CustomAccessTokenConverter extends DefaultAccessTokenConverter { #Override public OAuth2Authentication extractAuthentication(Map<String, ?> map) { . . . return new OAuth2Authentication(request, user); } } I am using Spring Boot with the following dependencies: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.0.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-resource-server</artifactId> <version>5.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
ResourceTokenServices allows us to use our own AccessTokenConverter. Simply add the following to your security config: #Bean public ResourceServerTokenServices createResourceServerTokenServices() { RemoteTokenServices tokenServices = new RemoteTokenServices(); tokenServices.setCheckTokenEndpointUrl(*Introspection URL*); tokenServices.setClientId(*Client ID*); tokenServices.setClientSecret(*Client Secret*); // ADD THE NEXT LINE tokenServices.setAccessTokenConverter(customAccessTokenConverter); return tokenServices; }
#Timed not working despite registering TimedAspect explicitly - spring boot 2.1
I need to measure method-metrics using micrometer #Timed annotation. As it doesn't work on arbitrary methods; i added the configuration of #TimedAspect explicitly in my spring config. Have referred to this post for exact config Note: have tried adding a separate config class JUST for this, as well as including the TimedAspect bean as part of my existing configuration bean How to measure service methods using spring boot 2 and micrometer Yet, it unfortunately doesn't work. The Bean is registred and the invocation from config class goes thru successfully on startup. Found this while debugging. However, the code in the #Around never seems to execute. No error is thrown; and im able to view the default 'system' metrics on the /metrics and /prometheus endpoint. Note: This is AFTER getting the 'method' to be invoked several times by executing a business flow. I'm aware that it probably doesn't show up in the metrics if the method isn't invoked at all Versions: spring-boot 2.1.1, spring 5.3, micrometer 1.1.4, actuator 2.1 Tried everything going by the below posts: How to measure service methods using spring boot 2 and micrometer https://github.com/izeye/sample-micrometer-spring-boot/tree/timed-annotation https://github.com/micrometer-metrics/micrometer/issues/361 Update: So, the issue seems to be ONLY when the Timed is on an abstract method, which is called via another method. Was able to reproduce it via a simple example. Refer to the #Timed("say_hello_example") annotation. It simply gets ignored and doesnt show up when i hit the prometheus endpoint. Code: Abstract Class public abstract class AbstractUtil { public abstract void sayhello(); public void sayhellowithtimed(String passedVar) { System.out.println("Passed var =>"+passedVar); System.out.println("Calling abstract sayhello...."); sayhello(); } } Impl Class #Component #Scope("prototype") public class ExampleUtil extends AbstractUtil { public static final String HELLO = "HELLO"; #Timed("dirwatcher_handler") public void handleDirectoryWatcherChange(WatchEvent event){ System.out.println("Event kind:" + event.kind() + ". File affected: " + event.context()); } #Timed("say_hello_example") #Override public void sayhello() { System.out.println(HELLO); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } A simple DirWatcher implementation class... package com.example; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Scope; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import java.io.IOException; import java.nio.file.*; #Component #Scope("prototype") public class StartDirWatcher implements ApplicationListener<ApplicationStartedEvent> { #Value("${directory.path:/apps}") public String directoryPath; #Autowired private ExampleUtil util; private void monitorDirectoryForChanges() throws IOException, InterruptedException { WatchService watchService = FileSystems.getDefault().newWatchService(); Path path = Paths.get(directoryPath); path.register( watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); WatchKey key; while ((key = watchService.take()) != null) { for (WatchEvent<?> event : key.pollEvents()) { util.handleDirectoryWatcherChange(event); util.sayhellowithtimed("GOD_OF_SMALL_THINGS_onAPPEvent"); } key.reset(); } } #Override public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) { try { monitorDirectoryForChanges(); } catch (Throwable e) { System.err.println("ERROR!! "+e.getMessage()); e.printStackTrace(); } } } The Spring Boot Application Class package com.example; import io.micrometer.core.aop.TimedAspect; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.prometheus.PrometheusMeterRegistry; import org.springframework.boot.SpringApplication; import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; #EnableAspectJAutoProxy #ComponentScan #Configuration #SpringBootApplication public class ExampleStarter{ #Bean MeterRegistryCustomizer<PrometheusMeterRegistry> metricsCommonTags() { return registry -> registry.config().commonTags("app.name", "example.app"); } #Bean TimedAspect timedAspect(MeterRegistry reg) { return new TimedAspect(reg); } public static void main(String[] args) { SpringApplication.run(ExampleStarter.class, args); } } The main pom.xml file <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.metrics.timed.example</groupId> <artifactId>example-app</artifactId> <version>1.0-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.1.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>2.1.1.RELEASE</version> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.1.1.RELEASE</version> </dependency> </dependencies>
I use spring boot 2.2.6.RELEASE and this MetricConfig works for me #Configuration public class MetricConfig { #Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() { return registry -> registry.config().commonTags("application", "my app"); } #Bean TimedAspect timedAspect(MeterRegistry registry) { return new TimedAspect(registry); } } In application.yml management: endpoints: web: exposure: include: ["health", "prometheus"] endpoint: beans: cache: time-to-live: 10s
#Timed use AOP(Aspect oriented programming) concept, in which proxy doesn't pass on to the second level of the method. you can define the second level of method in new bean/class. this way #Timed will work for second level of method call.
I had the same problem, in my case I realised that the metric got visible under actuator/metrics only after the method had been called at least once. Unlike with manually created timers/counters, where they get visible directly after startup.
spring boot multiple data sources
I was trying to follow this link to configure two databases in my application. In the latest spring we don't have org.springframework.boot.autoconfigure.jdbc.TomcatDataSourceConfiguration class. What is the alternative for this to use. I am using gradle for my spring boot application
I prefer using "org.apache.tomcat.jdbc.pool.DataSource", and then configure my data sources manually. org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource(); I set the fully qualified name, because you should return "javax.sql.DataSource" from whichever factory method you make. Getting multiple data sources with spring-boot auto configuration is a pan, since the opinionated view is that you should just create a different service for each data source. That is not always possible, so here's everything I do when I need multiple DataSources in a single application. Disable the auto configuration like so: #SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class YourApp{} Create a configuration properties for your datasources: Properties file: the-first.datasource.url=<insert value> the-first.datasource.username=<insert value> the-first.datasource.pw=<insert value> the-first.datasource.min-idle=<insert value> the-first.datasource.max-idle=<insert value> the-first.datasource.max-active=<insert value> the-first.datasource.validation-query=SELECT 1 # etc ... the-second.datasource.url=<insert value> the-second.datasource.username=<insert value> the-second.datasource.pw=<insert value> the-second.datasource.min-idle=<insert value> the-second.datasource.max-idle=<insert value> the-second.datasource.max-active=<insert value> the-second.datasource.validation-query=SELECT 1 Configuration class: import lombok.*; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; #Getter #Setter #NoArgsConstructor #AllArgsConstructor #Component("theFirstDataSourceProperties") #ConfigurationProperties("the-first.datasource") public class TheFirstDataSourceProperties{ #NonNull private String password; #NonNull private String url; #NonNull private String username; private int minIdle; private int maxIdle; private int maxActive; #NonNull private String driverClassName; #NonNull private String validationQuery; } Add a data source configuration class: Be sure and mark one of them as the "#Primary" to help with injection. import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; /** * Copyright ${year} * * #author J. Keith Hoopes */ #Configuration public class ExampleOfMultipleDataSourceConfiguration{ #Bean(name = "theFirstDataSource") #Primary #Autowired public DataSource theFirstDataSource( TheFirstDataSourceProperties theFirstDataSourceProperties){ //Fully qualified to not conflict with generic DataSource org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource(); // General dataSource.setName("theFirstDataSourceName"); dataSource.setDriverClassName(theFirstDataSourceProperties.getDriverClassName()); // etc .... return dataSource; } #Bean(name = "bDataSource") #Autowired public DataSource theSecondDataSource( TheSecondDataSourceProperties theSecondDataSourceProperties){ //Fully qualified to not conflict with generic DataSource org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource(); // General dataSource.setName("theSecondDataSourceName"); dataSource.setDriverClassName(theSecondDataSourceProperties.getDriverClassName()); // etc .... return dataSource; } } Inject your custom DataSources where needed using #Qualifier so you get the correct one:) #Qualifier("theFirstDataSource") Profit? Yes. Oh, and here are the basic dependencies I use. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-metadata</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>net.sourceforge.jtds</groupId> <artifactId>jtds</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>
Configuring Swagger UI with Spring Boot
I am trying to configure Swagger UI with my Spring boot application. Although the v2/api-docs seems to be loading properly, the http://localhost:8080/swagger-ui.html does not load my annotated REST API. Here is what I have: pom.xml: ... <!--Swagger UI--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.4.0</version> </dependency> ... SwaggerConfig.java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import static springfox.documentation.builders.PathSelectors.regex; #Configuration #EnableSwagger2 public class SwaggerConfig { #Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(regex("/.*")) .build().apiInfo(apiInfo()); } private ApiInfo apiInfo() { ApiInfo apiInfo = new ApiInfo( "My Project's REST API", "This is a description of your API.", "version-1", "API TOS", "me#wherever.com", "API License", "API License URL" ); return apiInfo; } } http://localhost:8080/v2/api-docs: {"swagger":"2.0","info":{"description":"This is a description of your API.","version":"version-1","title":"My Project's REST API","termsOfService":"API TOS","contact":{"name":"me#wherever.com"},"license":{"name":"API License","url":"API License URL"}},"host":"localhost:8080","basePath":"/","tags":[{"name":"test-controller","description":"Test Controller"},{"name":"custom-field-controller","description":"Custom Field Controller"},{"name":"user-controller","description":"User Controller"},{"name":"users-controller","description":"Users Controller"},{"name":"crudapi-controller","description":"CRUDAPI Controller"},{"name":"basic-error-controller","description":"Basic Error Controller"}],"paths":{"/":{"get":{"tags":["crudapi-controller"],"summary":"greeting","operationId":"greetingUsingGET","consumes":["application/json"],"produces":["*/*"],"responses":{"200":{"description":"OK","schema":{"type":"string"}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}}},"/api/javainuse":{"get":{"tags":["test-controller"],"summary":"firstPage","operationId":"firstPageUsingGET","consumes":["application/json"],"produces":["*/*"],"responses":{"200":{"description":"OK","schema":{"type":"string"}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}}},"/error":{"get":{"tags":["basic-error-controller"],"summary":"errorHtml","operationId":"errorHtmlUsingGET","consumes":["application/json"],"produces":["text/html"],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/ModelAndView"}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}},"head":{"tags":["basic-error-controller"],"summary":"errorHtml","operationId":"errorHtmlUsingHEAD","consumes":["application/json"],"produces":["text/html"],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/ModelAndView"}},"401":{"description":"Unauthorized"},"204":{"description":"No Content"},"403":{"description":"Forbidden"}}},"post":{"tags":["basic-error-controller"],"summary":"errorHtml","operationId":"errorHtmlUsingPOST","consumes":["application/json"],"produces":["text/html"],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/ModelAndView"}},"201":{"description":"Created"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}},"put":{"tags":["basic-error-controller"],"summary":"errorHtml","operationId":"errorHtmlUsingPUT","consumes":["application/json"],"produces":["text/html"],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/ModelAndView"}},"201":{"description":"Created"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}},"delete":{"tags":["basic-error-controller"],"summary":"errorHtml","operationId":"errorHtmlUsingDELETE","consumes":["application/json"],"produces":["text/html"],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/ModelAndView"}},"401":{"description":"Unauthorized"},"204":{"description":"No Content"},"403":{"description":"Forbidden"}}},"options":{"tags":["basic-error-controller"],"summary":"errorHtml","operationId":"errorHtmlUsingOPTIONS","consumes":["application/json"],"produces":["text/html"],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/ModelAndView"}},"401":{"description":"Unauthorized"},"204":{"description":"No Content"},"403":{"description":"Forbidden"}}},"patch":{"tags":["basic-error-controller"],"summary":"errorHtml","operationId":"errorHtmlUsingPATCH","consumes":["application/json"],"produces":["text/html"],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/ModelAndView"}},"401":{"description":"Unauthorized"},"204":{"description":"No Content"},"403":{"description":"Forbidden"}}}},"/fields":{"get":{"tags":["custom-field-controller"],"summary":"greeting","operationId":"greetingUsingGET_1","consumes":["application/json"],"produces":["*/*"],"responses":{"200":{"description":"OK","schema":{"type":"string"}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}}},"/fields/{id}":{"get":{"tags":["custom-field-controller"],"summary":"fieldAPIController","operationId":"fieldAPIControllerUsingGET","consumes":["application/json"],"produces":["*/*"],"parameters":[{"name":"id","in":"path","description":"id","required":true,"type":"integer","format":"int32"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/CustomField"}}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}}},"/users":{"get":{"tags":["user-controller"],"summary":"greeting","operationId":"greetingUsingGET_2","consumes":["application/json"],"produces":["*/*"],"responses":{"200":{"description":"OK","schema":{"type":"string"}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}}},"/users/":{"get":{"tags":["users-controller"],"summary":"listUsers","operationId":"listUsersUsingGET","consumes":["application/json"],"produces":["*/*"],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/UserJPA"}}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}},"head":{"tags":["users-controller"],"summary":"listUsers","operationId":"listUsersUsingHEAD","consumes":["application/json"],"produces":["*/*"],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/UserJPA"}}},"401":{"description":"Unauthorized"},"204":{"description":"No Content"},"403":{"description":"Forbidden"}}},"post":{"tags":["users-controller"],"summary":"listUsers","operationId":"listUsersUsingPOST","consumes":["application/json"],"produces":["*/*"],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/UserJPA"}}},"201":{"description":"Created"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}},"put":{"tags":["users-controller"],"summary":"listUsers","operationId":"listUsersUsingPUT","consumes":["application/json"],"produces":["*/*"],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/UserJPA"}}},"201":{"description":"Created"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}},"delete":{"tags":["users-controller"],"summary":"listUsers","operationId":"listUsersUsingDELETE","consumes":["application/json"],"produces":["*/*"],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/UserJPA"}}},"401":{"description":"Unauthorized"},"204":{"description":"No Content"},"403":{"description":"Forbidden"}}},"options":{"tags":["users-controller"],"summary":"listUsers","operationId":"listUsersUsingOPTIONS","consumes":["application/json"],"produces":["*/*"],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/UserJPA"}}},"401":{"description":"Unauthorized"},"204":{"description":"No Content"},"403":{"description":"Forbidden"}}},"patch":{"tags":["users-controller"],"summary":"listUsers","operationId":"listUsersUsingPATCH","consumes":["application/json"],"produces":["*/*"],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/UserJPA"}}},"401":{"description":"Unauthorized"},"204":{"description":"No Content"},"403":{"description":"Forbidden"}}}},"/users/{id}":{"get":{"tags":["user-controller"],"summary":"userAPIController","operationId":"userAPIControllerUsingGET","consumes":["application/json"],"produces":["*/*"],"parameters":[{"name":"id","in":"path","description":"id","required":true,"type":"integer","format":"int32"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/Collection«UserJPA»"}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}}}},"definitions":{"UserJPA":{"type":"object"},"Collection«UserJPA»":{"type":"object"},"ModelAndView":{"type":"object","properties":{"empty":{"type":"boolean"},"model":{"type":"object"},"modelMap":{"type":"object","additionalProperties":{"type":"object"}},"reference":{"type":"boolean"},"status":{"type":"string","enum":["100","101","102","103","200","201","202","203","204","205","206","207","208","226","300","301","302","303","304","305","307","308","400","401","402","403","404","405","406","407","408","409","410","411","412","413","414","415","416","417","418","419","420","421","422","423","424","426","428","429","431","451","500","501","502","503","504","505","506","507","508","509","510","511"]},"view":{"$ref":"#/definitions/View"},"viewName":{"type":"string"}}},"CustomField":{"type":"object","properties":{"name":{"type":"string"}}},"View":{"type":"object","properties":{"contentType":{"type":"string"}}}}} The swagger-ui.html (http://localhost:8080/swagger-ui.html) does not show the expected REST calls: The error in swagger-ui.html from the code inspection: Failed to load resource: the server responded with a status of 404 (). I have googled around (tried web-config mvc too) but the error persists. Maybe I am missing a resource reference in the .iml file?
I had this issue today and fixed it by matching up the versions of my springfox-swagger2 and springfox-swagger-ui dependencies: <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency> There's very little other code to just get it up and running. One simple config class: #Configuration #EnableSwagger2 class SwaggerConfiguration { #Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage("com.foo.samples.swaggersample")) .paths(PathSelectors.any()) .build(); } } And my application.properties # location of the swagger json springfox.documentation.swagger.v2.path=/swagger.json (This is in Spring Boot).
Statement : Generate Swagger UI for the listing of all the REST APIs through Spring Boot Application. Follow the below steps to generate the Swagger UI through Spring Boot application: 1. Add following dependency in pom.xml – <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency> 2. Add the following piece of code in your main application class having the #EnableSwagger2 annotation. #EnableSwagger2 #SpringBootApplication public class MyApp { public static void main(String[] args) { SpringApplication.run(MyApp.class, args); } #Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2).select() .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)) .paths(PathSelectors.any()).build().pathMapping("/") .apiInfo(apiInfo()).useDefaultResponseMessages(false); } #Bean public ApiInfo apiInfo() { final ApiInfoBuilder builder = new ApiInfoBuilder(); builder.title("My Application API through Swagger UI").version("1.0").license("(C) Copyright Test") .description("List of all the APIs of My Application App through Swagger UI"); return builder.build(); } } 3. Add the below RootController class in your code to redirect to the Swagger UI page. In this way, you don’t need to put the dist folder of Swagger-UI in your resources directory. import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; #Controller #RequestMapping("/") public class RootController { #RequestMapping(method = RequestMethod.GET) public String swaggerUi() { return "redirect:/swagger-ui.html"; } } 4. Being the final steps, add the #Api and #ApiOperation notation in all your RESTControllers like below – import static org.springframework.web.bind.annotation.RequestMethod.GET; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; #RestController #RequestMapping("/hello") #Api(value = "hello", description = "Sample hello world application") public class TestController { #ApiOperation(value = "Just to test the sample test api of My App Service") #RequestMapping(method = RequestMethod.GET, value = "/test") // #Produces(MediaType.APPLICATION_JSON) public String test() { return "Hello to check Swagger UI"; } #ResponseStatus(HttpStatus.OK) #RequestMapping(value = "/test1", method = GET) #ApiOperation(value = "My App Service get test1 API", position = 1) public String test1() { System.out.println("Testing"); if (true) { return "Tanuj"; } return "Gupta"; } } Now your are done. Now to run your Spring Boot Application, go to browser and type localhost:8080. You will see Swagger UI having all the details of your REST APIs. Happy Coding. 🙂 The source code of the above implementation is also on my blog if you feel like checking it out.
Swagger is Available with V2 and V3 version More minimal config Check this Answer - https://stackoverflow.com/a/64333853/410439
Add a config class like this #Configuration public class WebMvcConfiguration extends WebMvcConfigurationSupport { #Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { // Make Swagger meta-data available via <baseURL>/v2/api-docs/ registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); // Make Swagger UI available via <baseURL>/swagger-ui.html registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/"); } }
Nowadays, just set springdoc.swagger-ui.disable-swagger-default-url=true
Making Aspectj work on a Spring servlet bean
I am trying to get an aspectprofiler working on a Jersey servlet registered in a spring project. The aspectprofiler is loaded, but don't notice when methods within the Jersey servlet are run. #EnableAutoConfiguration #Configuration #EnableAspectJAutoProxy(proxyTargetClass = true) public class App { public static void main(final String[] args) { final SpringApplicationBuilder sab = new SpringApplicationBuilder(ConsolidatedCustomerMasterApp.class); sab.run(args); } #Bean public ServletRegistrationBean jerseyServlet() { final ServletRegistrationBean registration = new ServletRegistrationBean(new ServletContainer(), "/*"); registration.addInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS, JerseyInitialization.class.getName()); return registration; } #Bean public AspectProfiler profiler() { return new AspectProfiler(); } } ... public class JerseyInitialization extends ResourceConfig { public JerseyInitialization() { packages("com.example.package"); } ... package com.example.package; //imports #Path("/test") public class RestService { #GET #Path("test") #Produces(MediaType.TEXT_PLAIN) public String test() { return "Something"; } } ... #Aspect public class AspectProfiler { private static final DefaultApplicationProfiler PROFILER = new DefaultApplicationProfiler( Arrays.<ProfilerOperator> asList( new StatsdProfilerOperator(), new LoggingProfilerOperator()) ); private static final String REST_MATCHER = "execution(* com.example.package..*.*(..))"; #Around(REST_MATCHER) public Object around(final ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("test"); return PROFILER.around(joinPoint); } }
On top of making the Jersey resource classes Spring #Components (and #ComponentScaning for them), you also need to make the ResourceConfig a Spring #Component also. You can see in the Spring Boot JerseyAutoConfigurer that it autowires the ResourceConfig, which it uses for the ServletContainer registration. One thing to also note is that it creates its own ServletRegistrationBean public ServletRegistrationBean jerseyServletRegistration() { ServletRegistrationBean registration = new ServletRegistrationBean( new ServletContainer(this.config), this.path); addInitParameters(registration); registration.setName("jerseyServlet"); return registration; } When you declare your own, you are overriding this one. You are not adding any special functionality that is not already provided, so just leave the default. Any Jersey specific configurations can be added in the application.properties file or through code configurations. As for dependencies, I'll just assume you have all the right dependencies. The following are what I used to test <!-- all 1.2.7.RELEASE --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jersey</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> See Also: spring-boot-sample-jersey - from project samples §26.2 JAX-RS and Jersey - from Spring Boot docs