How to get SpringDoc OpenAPI to work with Servlets? - spring

In my actual project, I exclude a couple packages with endpoints from the main ComponentScan. Instead, each one gets its own ServletRegistrationBean. This way they can all have their own JSON serialization configuration beans.
But if I do this, then SpringDoc does not detect any of my endpoints.
I'm using spring-boot 2.6.4 and springdoc-openapi-ui 1.6.6
package com.example;
#SpringBootApplication(scanBasePackages = "com.example")
#ComponentScan(basePackages = "com.example", excludeFilters = {
#ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.example.demo.*")
})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public ServletRegistrationBean<DispatcherServlet> api(ApplicationContext parent) {
return createChildServlet(
Arrays.asList("/api/*"),
DemoApiConfig.class,
"api",
parent);
}
private ServletRegistrationBean<DispatcherServlet> createChildServlet(List<String> urlMappings,
Class<?> configuration, String servletRegistrationBeanName, ApplicationContext parent) {
var applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(configuration);
applicationContext.setParent(parent);
var dispatcherServlet = new DispatcherServlet(applicationContext);
var servletRegistrationBean = new ServletRegistrationBean<>(dispatcherServlet,
urlMappings.toArray(new String[] {}));
servletRegistrationBean.setName(servletRegistrationBeanName);
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}
}
I've verified that this bean does get created, it just doesn't seem to be used.
package com.example.demo;
#EnableWebMvc
#Configuration
#ComponentScan
public class DemoApiConfig {
}
package com.example.demo;
#RestController
#RequestMapping("/demo/v1")
public class DemoController {
#Operation(summary = "Demo documentation")
#GetMapping("/test")
public ResponseEntity<String> test() {
return ResponseEntity.ok("Test");
}
}
If I remove the ComponentScan filter, then the endpoint exists twice as /api/demo/v1/test and /demo/v1/test, and springdoc only detects /demo/v1/test).
With the ComponentScan filter, only /api/demo/v1/test exists and springdoc does not detect it.
How do I get SpringDoc to detect endpoints that exist only inside Servlets?

Related

Swagger UI doesn't show my APIs when using paths regex

I hava a problem with swagger UI. When I use paths regex in my swagger configuration class, this doesn't find my APIs. When I use the value PathsSelectors.any() this find my APIs but show also my models. I should show only my API so I have decided to use the regex.
This is my Swagger config:
package com.my.project.configurations;
#org.springframework.context.annotation.Configuration
#PropertySource(value = "classpath:application.properties")
#EnableAutoConfiguration
public class Configuration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");
}
#Bean
public Docket productApi(){
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(
withClassAnnotation(RestController.class)
.and(RequestHandlerSelectors.basePackage("com.my.project.controller"))
)
.paths(regex("/rest.*"))
.build();
}
this is my Main:
package com.my.project;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#SpringBootApplication ( exclude = {SecurityAutoConfiguration.class} )
#EnableJpaRepositories("com.my.project.repository")
#EntityScan(basePackages= {"com.my.project.entities"})
public class MyProjectApplication {
public static void main(String[] args) {
SpringApplication.run(MyProjectApplication.class, args);
}
}
this is my restcontroller:
package com.my.project.controller;
#RestController
#RequestMapping(value = "/rest/pattern")
public class PatternController {
#Autowired
private IPattern patternSvc;
#GetMapping(value = { "/get-all-pattern" }, produces = { MediaType.APPLICATION_JSON_VALUE })
#ApiOperation(value = "get all pattern", tags = "pattern")
public ResponseEntity<BaseAjaxResponse<List<PatternDto>>> findAllPattern(){
HttpStatus status = HttpStatus.OK;
BaseAjaxResponse.BaseAjaxResponseBuilder<List<PatternDto>> builder = BaseAjaxResponse.<List<PatternDto>>builder();
try{
List<PatternDto> listPattern = this.patternSvc.findAllPattern();
if(!listPattern.isEmpty()){
builder
.description("Success")
.size(listPattern.size())
.code(status.value())
.payload(listPattern);
}
else{
builder
.description("Hidden list")
.code(status.value())
.size(0);
}
}catch(Exception e){
status = HttpStatus.INTERNAL_SERVER_ERROR;
builder
.description("No patterns found")
.size(0)
.code(status.value())
.errors(ApiError.builder()
.error(MyProjectInternalError.GENERIC_ERROR.keySet().toArray()[0].toString())
.datails(Collections.singletonList(
ErrorDetail
.builder()
.code(String.valueOf(MyProjectInternalError.GENERIC_ERROR.get("GENERIC ERROR")))
.field("generic")
.message(e.getMessage())
.build()
))
.build());
}
return ResponseEntity.status(status).body(builder.build());
}
}
what am i doing wrong?
I solved adding .ignoredParameterTypes(MyClass1.class, Myclass2.class, ...)
to my Docket bean and setting paths with PathSelectors.any() value

ComponentScan.Filter not filtering #Configuration class in spring boot

ComponentScan.Filter not filtering #Configuration class. I'm using spring boot 2.2.12 with spring-context 5.2.12.
SpringBoot class
#EnableMBeanExport
#ComponentScan(basePackages = "com.init”,
excludeFilters = {
#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = com.init.server.ServerAConfig.class)})
#SpringBootApplication()
public class MyApplication extends SpringBootServletInitializer {
public static void main(String[] args) throws IOException {
SpringApplication.run(MyApplication.class);
}
}
Under the basepackage com.init, there is a configurtion class ServerAConfig.
#Configuration
#ComponentScan(basePackages = {"com.execute.server”})
public class ServerAConfig {
}
Under package com.execute.server I have class MyServerA.java
My expectation was, MyServerA will not be available in the ApplicationContext
for (String beanName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanName);
}
but when i run the above print after the boot up it shows MyServerA there in the ApplicationContext. My expectation was MyServerA will not be initialized.
Also tried with different FilterType.
I think you need to get rid of the #SpringBootApplication() annotation, it has a component scan built in that will scan everything in the directory the class is in:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Inherited
#SpringBootConfiguration
#EnableAutoConfiguration
#ComponentScan(excludeFilters = { #Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
#Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public #interface SpringBootApplication {
// ...
}
So you either add the annotations above that you want to keep and drop the #SpringBootApplication().
Or you can use the scanBasePackages or scanBasePackageClasses of the #SpringBootApplication() annotation and drop your own #Componentscan instead. I think the former method would be better, because this would be tedious.
You could also move the class you want to exclude so it is easier to define what you want to scan without scanning it using the second method.
Use AutoConfigurationExcludeFilter to filter auto configurations classes
#EnableMBeanExport
#ComponentScan(basePackages = "com.init”,
excludeFilters = {
#ComponentScan.Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)})
#SpringBootApplication()
public class MyApplication extends SpringBootServletInitializer {
public static void main(String[] args) throws IOException {
SpringApplication.run(MyApplication.class);
}
}

How does Spring Boot RestController works without SpringBootApplication?

In my project, I used #Configuration, #EnableAutoConfiguration, #ComponentScan and ImportResource configuration with annotation. I did not used #SpringBootApplication, but application is built successfully without #SpringBootApplication annotation. I don't understand why #RestController class not invoked?
#Configuration
#EnableAutoConfiguration(exclude = {
//removed default db config
DataSourceAutoConfiguration.class, XADataSourceAutoConfiguration.class})
#ComponentScan(basePackages = { "com.test.debasish.dummy" }, excludeFilters = {
#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = Test.class))})
#ImportResource( value = {"classpath*:*beans*.xml"})
public class TestApplication{
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
#RestController
public class TestController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#GetMapping("/test")
#ResponseBody
public Greeting getResource(#RequestParam(name="name", required=false, defaultValue="Stranger") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
You need to setup spring-webmvc for using #RestController.
Normally, it is done automatically by using spring-boot-starter-web.
More detail:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration
If you want to take complete control of Spring MVC, you can add your own #Configuration annotated with #EnableWebMvc, or alternatively add your own #Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of #EnableWebMvc.
It works because #springbootapplication annotation is also include #Configuration,
#EnableAutoConfiguration, #ComponentScan annotations. See the below picture
https://i.stack.imgur.com/PKkb8.jpg

Spring-Boot 2.3.0.RELEASE Unable to autowire RestTemplate for JUnit 5 test

I have configured the necessary Beans in #Configuration class but have not been able to get the RestTemplate injected into my test class for testing.
#Configuration
public class AppConfig {
#Bean
public ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
#Bean
public RestTemplate restTemplate(ProtobufHttpMessageConverter converter) {
RestTemplate http2Template = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
List<HttpMessageConverter<?>> converters = http2Template.getMessageConverters();
converters.add(converter);
http2Template.setMessageConverters(converters);
return http2Template;
}
}
Test class:
#ExtendWith(SpringExtension.class)
#AutoConfigureWebClient(registerRestTemplate = true)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {RestTemplate.class, ProtobufHttpMessageConverter.class})
#ActiveProfiles("dev")
public class GRPCRestApiTest {
#Autowired
private RestTemplate restTemplate;
#Test
public void GetOneCourseUsingRestTemplate() throws IOException {
assertNotNull(restTemplate, "autowired restTemplate is NULL!");
ResponseEntity<Course> course = restTemplate.getForEntity(COURSE_URL, Course.class);
assertResponse(course.toString());
HttpHeaders headers = course.getHeaders();
}
}
Any advice and insight is appreciated
The classes attribute of the annotation #SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {RestTemplate.class, ProtobufHttpMessageConverter.class}) takes component classes to load the application context. You should not put in here anything except your main Spring Boot class or leave it empty.
Furthermore #AutoConfigureWebClient(registerRestTemplate = true) as you want to use the bean you configure inside your application (at least that's what I understood from your question).
So your test setup should look like the following:
// #ExtendWith(SpringExtension.class) can be omitted as it is already part of #SpringBootTest
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#ActiveProfiles("dev")
public class GRPCRestApiTest {
#Autowired
private RestTemplate restTemplate;
#Test
public void GetOneCourseUsingRestTemplate() throws IOException {
assertNotNull(restTemplate, "autowired restTemplate is NULL!");
ResponseEntity<Course> course = restTemplate.getForEntity(COURSE_URL, Course.class);
assertResponse(course.toString());
HttpHeaders headers = course.getHeaders();
}
}
This should now start your whole Spring Boot context in dev profile and you should have access to all your beans you define inside your production code like AppConfig.

SPRING-WS No marshaller registered. Caused by SPRING IOC

I have a SOAP client service which works fine.
The SOAP headers and request are managed in a SOAPConnector class.
public class SOAPConnector extends WebServiceGatewaySupport {
public Object callWebService(String url, Object request) {
// CREDENTIALS and REQUEST SETTINGS...
return getWebServiceTemplate().marshalSendAndReceive(url, request, new SetHeader(requestHeader));
}
}
I'm receiving the requested Data once I call my (SoapConnector) service on the main Class.
#SpringBootApplication
public class SpringSoapSecurityDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSoapSecurityDemoApplication.class, args);
}
#Bean
public CommandLineRunner lookup(SOAPConnector soapConnector) {
return args -> {
String hotelCode = "****";
FutureBookingSummaryRequest request = new FutureBookingSummaryRequest();
FetchBookingFilters additionalFilters = new FetchBookingFilters();
// Some additionalFilters settings
request.setAdditionalFilters(additionalFilters);
FutureBookingSummaryResponse response = (FutureBookingSummaryResponse) soapConnector
.callWebService("MY WSDL URL", request);
System.err.println(response.getHotelReservations());
};
}
}
SO FAR IT WORKS FINE.
Then I've tried to create a separate service for the previous request.
BookingService.java
public class BookingService extends WebServiceGatewaySupport {
#Autowired
SOAPConnector soapConnector;
public String getReservations() {
String hotelCode = "****";
FutureBookingSummaryRequest request = new FutureBookingSummaryRequest();
FetchBookingFilters additionalFilters = new FetchBookingFilters();
// Some additionalFilters settings
request.setAdditionalFilters(additionalFilters);
FutureBookingSummaryResponse response = (FutureBookingSummaryResponse) soapConnector
.callWebService("MY WSDL URL", request);
System.err.println(response.getHotelReservations());
};}
In order to inject the SOAPCONNECTOR I've added #Service to SOAPCONNECTOR class , and #Autowired SOAPConnector soapConnector to the service calling it
Now once I call the created BookingService in the main class
#SpringBootApplication
public class SpringSoapSecurityDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSoapSecurityDemoApplication.class, args);
BookingService bookingService = new BookingService();
bookingService.getReservations();
}
}
The SOAPCONNECTOR stops working an I receive the following error :
No marshaller registered. Check configuration of WebServiceTemplate.
I'm pretty sure that's this issue is related to SPRING IOC , dependecy injection .. Since the SOAP service is well configured and working..
Note : I've checked this similiar question
Consuming a SOAP web service error (No marshaller registered. Check configuration of WebServiceTemplate) but the #Autowired didn't solve the issue.
Any help ?
In case someone is facing the same issue, it turned out that I've missed the #Configuration annotation on the beans configuration class. The right one should look like the following:
#Configuration
public class ConsumerConfig {
private String ContextPath = "somePackage";
private String DefaultUri = "someWsdlURI";
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
// this package must match the package in the <generatePackage> specified in
// pom.xml
marshaller.setContextPath(ContextPath);
return marshaller;
}
#Bean
public SOAPConnector checkFutureBookingSummary(Jaxb2Marshaller marshaller) {
SOAPConnector client = new SOAPConnector();
client.setDefaultUri(DefaultUri);
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}

Resources