How does Spring Boot RestController works without SpringBootApplication? - spring-boot

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

Related

How to get SpringDoc OpenAPI to work with Servlets?

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?

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);
}
}

#EnableTransactionManagement breaks my tests

So, I have a Spring Boot app. Recently I added an aspect I would like to apply after #Transaction aspect handler. So on my main app class I added #EnableTransactionManagement with lowering the order for transactions.
#SpringBootApplication
#EntityScan(basePackageClasses = { Application.class, Jsr310JpaConverters.class })
#EnableAutoConfiguration(exclude = RepositoryRestMvcAutoConfiguration.class)
#EnableSpringDataWebSupport
#EnableScheduling
// Need to lower the transaction aspect priority in order to SyncAspect have a higher prio
// The lower the number the higher the priority, defaults to HIGHEST_PRECEDENCE = -2147483648
#EnableTransactionManagement(order = Ordered.HIGHEST_PRECEDENCE + 2)
public class Application extends SpringBootServletInitializer {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
...
The order here does not matter (the test fails even when I just add #EnableTransactionManagement without any additional config) but after adding this annotation one of our tests failed. It is a #WebMVCTest and now it returns 404 for all the requests made in the test. Here is the test class declared.
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = OptionMenuController.class)
#WithMockUser(roles = { "ENTERPRISE_USER" })
#ActiveProfiles(profiles = {"develop"})
public class OptionMenuControllerTest extends AbstractFullFeatureSetControllerTest<OptionMenu> {
...
#Import(DummySecurityConfiguration.class)
public abstract class AbstractFullFeatureSetControllerTest<EntityType extends Identifiable>
extends AbstractControllerTest<EntityType>
...
#TestConfiguration
public class DummySecurityConfiguration {
#Bean(name = "store_based_auth")
public UserDetailsService storeBasedAuthService() {
return Mockito.mock(UserDetailsService.class);
}
#Bean(name = "webui")
public UserDetailsService webUiService() {
return Mockito.mock(UserDetailsService.class);
}
#Bean(name = "integration_api_auth")
public UserDetailsService integrationApiAuthService() {
return Mockito.mock(UserDetailsService.class);
}
#Bean(name = "onboarding_api_auth")
public UserDetailsService onboardingApiAuthService() {
return Mockito.mock(UserDetailsService.class);
}
#Bean
public JwtService jwtService() {
return Mockito.mock(JwtService.class);
}
}
I have no idea why it happens. It looks really strange. But my guess is it has something to do with this #WithMockUser annotation. Maybe when I added #EnableTransactionManagement on the main class it reconfigured something in a wrong way that a dummy user is not found any more that's why I get 404 for all the tests in the test class.
Any ideas or hints?
While debugging I found that the controller method is not ever run.

Can't make Spring's ImportAware to work

I am trying to create my own #EnableXxx-style annotation (namely #EnableCustomizedPropertySources). For this the annotation imports the class CustomizedPropertySourcesConfiguration which in turn implements ImportAware, in order to have access to attributes of the #EnableCustomizedPropertySources annotation.
Annotation:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Import(CustomizedPropertySourcesConfiguration.class)
public #interface EnableCustomizedPropertySources {
String externalFolderName();
String propertiesFileName();
(...)
}
Imported configuration class:
#Configuration
public class CustomizedPropertySourcesConfiguration implements ImportAware {
protected AnnotationAttributes enableCustomizedPropertySourcesAttributes;
#Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> attributes = importMetadata.getAnnotationAttributes(EnableCustomizedPropertySources.class.getName(), false);
this.enableCustomizedPropertySourcesAttributes = AnnotationAttributes.fromMap(attributes);
}
#Bean
public PropertySourcesPlaceholderConfigurer propertySource() {
return (...);
}
}
The problem is, the method setImportMetadata is not invoked by Spring when I annotate some #Configuration class with my #EnableCustomizedPropertySources annotation, so I cannot access the annotations attributes.
The ImportAware class (CustomizedPropertySourcesConfiguration) needs both:
#Configuration
#Component

Injecting Configuration Properties is not working in my test

I'm stuck! If I skip tests and deploy to tomcat auto wiring the configuration properties file works. In my test, it fails! I'm not sure what I'm missing.
Here is my setup:
Spring Boot v 1.2.5.RELEASE
Application.yml
git:
localRepo: './powershell-status-scripts/'
remoteRepo: 'https://github.com/...'
RepositoryProperties this class has getters and setters for the properties
#Configuration
#ConfigurationProperties(locations = "classpath:application.yml", prefix = "git", ignoreUnknownFields = false)
public class RepositoryProperties {
private String localRepo;
private String remoteRepo;
public RepositoryProperties() {
}
public String getLocalRepo() {
return localRepo;
}
public void setLocalRepo(String localRepo) {
this.localRepo = localRepo;
}
public String getRemoteRepo() {
return remoteRepo;
}
public void setRemoteRepo(String remoteRepo) {
this.remoteRepo = remoteRepo;
}
}
Application.java
#EnableAutoConfiguration
#EnableConfigurationProperties
#ComponentScan(basePackages = "com.sendash.admin")
#EnableJpaRepositories("com.sendash.admin.dao.jpa")
#EnableSwagger
public class Application extends SpringBootServletInitializer {
private static final Class<Application> applicationClass = Application.class;
private static final Logger log = LoggerFactory.getLogger(applicationClass);
public static void main(String[] args) {
SpringApplication.run(applicationClass, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass);
}
}
GitService - Autowiring the properties works on tomcat!
#Service
#EnableConfigurationProperties
public class GitService {
#Autowired
private RepositoryProperties repositoryProperties;
public void updateLocalRepository() {
...
}
GitServiceTest this class fails on init because of a NPE. Properties is null.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Application.class)
#Profile("test")
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class })
public class GitServiceTest {
#Autowired
private static GitService manager;
#Autowired
private static RepositoryProperties properties;
private static final String localRepoLocation = properties.getLocalRepo();
I do realize after pasting this that #EnableConfigurationProperties is on both the Application.java and the GitService.java class. Stopping the duplication does not fix the problem.
If you want to use Spring Boot in your tests, you should configure the tests accordingly. To do that, remove the ContextConfiguration and add the following:
#SpringApplicationConfiguration(classes = Application.class, initializers = ConfigFileApplicationContextInitializer.class)
This should enable injecting the configuration properties.
I did change my ContextConfiguration as suggested, but my main problem was trying to autowire a static field. It was static for #BeforeClass test setup logic so I needed to move things around a bit, but I got it working. Thanks for the suggestion.

Resources