No adapter for handler with spring-boot custom starter - java

I am developing a spring-boot custom starter, and I want my components/beans to be initialized only if some conditions are met. These beans include a REST controller. I have made a #Configuration class for that purpose:
#Configuration
#ConditionalOnWebApplication
#Conditional(MyConditions.class)
public class MyConfiguration {
#Bean
public HandlerMapping registerController() {
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
Map<String, Object> urlMap = new HashMap<>();
urlMap.put("/custom", myController());
handlerMapping.setUrlMap(urlMap);
return handlerMapping;
}
#Bean
public MyController myController() {
return new MyController();
}
MyController class is:
public class MyController {
#GetMapping
public String hello() {
return "I'm here";
}
}
What I am trying to achieve is that localhost:8080/custom answers I'm here, but instead of that what I get is:
No adapter for handler [MyController#355b46cb]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler","path":"/custom"
I don't want any of my beans to be initialized if conditions are not met, that is why MyController is not annotated with #Controller or #RestController.
Any ideas on how I can achieve this?
Note: to test this, I have created a minimalistic SpringBoot project with a controller and I have added my custom starter as a dependency. MyConfiguration conditions are matched and beans are registered.

Related

How to create #Controller and #Service instance conditionally on Spring

I am using Spring Boot 1.5.9.
Is there a way to turn on/off #Controller and #Services?
Something such as #ConditionalOnProperty, #Conditional for beans.
#ConditionalController // <--- something like this
#RestController
public class PingController {
#Value("${version}")
private String version;
#RequestMapping(value = CoreHttpPathStore.PING, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Map<String, Object>> ping() throws Exception {
HashMap<String, Object> map = new HashMap<>();
map.put("message", "Welcome to our API");
map.put("date", new Date());
map.put("version", version);
map.put("status", HttpStatus.OK);
return new ResponseEntity<>(map, HttpStatus.OK);
}
}
Then use some configuration bean to load it up.
#ConditionalOnProperty should work for Controller (or Service) as well, since it is also a Spring bean.
Add to your PingController
#ConditionalOnProperty(prefix="ping.controller",
name="enabled",
havingValue="true")
#RestController
public class PingController {...}
and to the application.properties to turn it on/off
ping.controller.enabled=false
do you want to try and load the bean programmatically instead?
you could access the application context using one of the two mechanisms
#Autowired private ApplicationContext appContext;
or creating something like a bean factory by extending ApplicationAware
public class ApplicationContextProvider implements
ApplicationContextAware{
once you have a handle to the application context you can add a bean to the context programmatically.
By default in Spring, all the defined beans, and their dependencies, are created when the application context is created.
We can turn it off by configuring a bean with lazy initialization, the bean will only be created, and its dependencies injected, once they're needed.
You can enable lazy initialization by configuring application.properties.
spring.main.lazy-initialization=true
Setting the property value to true means that all the beans in the application will use lazy initialization.
All the defined beans will use lazy initialization, except for those that we explicitly configure with #Lazy(false).
Or you can do it through the #Lazy approach. When we put #Lazy annotation over the #Configuration class, it indicates that all the methods with #Bean annotation should be loaded lazily.
#Lazy
#Configuration
#ComponentScan(basePackages = "com.app.lazy")
public class AppConfig {
#Bean
public Region getRegion(){
return new Region();
}
#Bean
public Country getCountry(){
return new Country();
}
}

How to Inject Dependencies into a Servlet Filter with Spring Boot Filter Registration Bean?

I have a Servlet Filter within my Spring Boot (2.0.1) application that I'm registering with FilterRegistrationBean which I need it to be executed first (order of one) along the filter chain. The application is deployed to JBoss 7.2. This filter also has a dependency that is injected with #Autowired (see below):
package my.pkg.com
#SpringBootApplication
#ComponentScan(basePackages={"my.pkg.com"})
public class MyApp extends SpringBootServletInitializer {
public satic void main(String[] args) throws IOException {
SpringApplication.run(MyApp.class, args);
}
#Bean
#Order(1)
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> contextFilter = new FilterRegistrationBean<>();
contextFilter.setFilter(new MyFilter());
contextFilter.addUrlPattern("/api/*");
return contextFilter;
}
}
package my.pkg.com.filter
public class MyFilter extends Filter {
#Autowired
private MyService mySrv;
#Override
public void doFilter(…) {
mySrv.doSomething(); // mySrv is null
}
}
The problem is when the application is deployed and ran, when the Servlet request gets to MyFilter.doFilter(), mySrv is null which means MyFilter was never scanned for dependency injections.
I can verify through debugging MyService which is a #Repository in my.package.com.repository package does get initialized. It just never gets injected into MyFilter.
I can create a constructor for MyFilter to take MyService, then #Autowired MyService into MyApp and during filter registration, I can pass it to this constructor, which resolves the issue.
However, I want to know if there is anything I'm doing wrong that this dependency doesn't get injected into MyFilter with using the setup above alone.
If you create an object by yourself, using new, and this object is not returned by a #Bean-annotated method, then it's not a Spring bean, and Spring will thus not inject anything in it.
You can just add an #Bean-annotated method returning new MyFilter(), and call that method from myFilter() to get the bean, or add a MyFilter as argument to myFilter().
Example:
#Bean
#Order(1)
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> contextFilter = new FilterRegistrationBean<>();
contextFilter.setFilter(theActualFilter());
contextFilter.addUrlPattern("/api/*");
return contextFilter;
}
#Bean
public MyFilter theActualFilter() {
return new MyFilter(); // now this is a Spring bean
}
or
#Bean
#Order(1)
public FilterRegistrationBean<MyFilter> myFilter(MyFilter theActualFilter) {
FilterRegistrationBean<MyFilter> contextFilter = new FilterRegistrationBean<>();
contextFilter.setFilter(theActualFilter);
contextFilter.addUrlPattern("/api/*");
return contextFilter;
}
#Bean
public MyFilter theActualFilter() {
return new MyFilter(); // now this is a Spring bean
}
It's simple, add #Component annotation on your filter class and it will make #Autowired annotation working inside as Spring dependency injection will process your filter class and inject the service bean.

Injecting library class as dependencies in spring project

I have multiple library classes in my project which need to be injected into a service class. This is the error statement for IntegrationFactory class:
Consider defining a bean of type 'com.ignitionone.service.programmanager.integration.IntegrationFactory' in your configuration.
This error is coming on almost every injection where this library class is injected.
I have already added the Library package in #ComponentScan, but, as it is read-only file, I can not annotate the library class. I came to know from some answer here that Spring can not inject classes which it does not manage. This library is not built on spring.
I have tried to create a #Bean method which returns the IntegrationFactory(class in question) in the class where #Inject is used, but this too does not seem to work.
How can this be done, preferably without creating a stub/copy class?
This is EngagementServiceImpl class snippet:
#Inject
public EngagementServiceImpl(EngagementRepository engagementRepository,
#Lazy IntegrationFactory integrationFactory, TokenRepository tokenRepository,
EngagementPartnerRepository engagementPartnerRepository, MetricsService metricsService) {
this.engagementRepository = engagementRepository;
this.integrationFactory = integrationFactory;
this.tokenRepository = tokenRepository;
this.engagementPartnerRepository = engagementPartnerRepository;
this.metricsService = metricsService;
}
This is injection part:
#Autowired
private EngagementService engagementService;
This is ConfigClass:
#Configuration
public class ConfigClass {
#Bean
public IntegrationFactory getIntegrationFactory(){
Map<String, Object> globalConfig = new HashMap<>();
return new IntegrationFactory(globalConfig);
}
#Bean
#Primary
public EntityDataStore getEntityDataStore(){
EntityModel entityModel = Models.ENTITY;
return new EntityDataStore(this.dataSource(), entityModel );
}
#ConfigurationProperties(prefix = "datasource.postgres")
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.build();
}
}
You need to add your bean definitions in a configuration class.
#Configuration
public class ServiceConfig {
#Bean
public IntegrationFactory getIntegrationFactory(){
// return an IntegrationFactory instance
}
}
Then you have to make sure your #Configuration class gets detected by Spring, either by having it within your scanned path or by manually importing it via #Import from somewhere withing you scanned path. An example of #Import, considering you are using Spring Boot.
#Import(ServiceConfig.class)
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Hope this helps!
Your Bean IntegrationFactory can't be found, as it is not annotated with any Spring stereotype and therefore not recognized by the component scan.
As you have multiple options to provide an instance of your class to the application context, read the Spring documentation (which also includes samples) to find out which one fits you the most:
https://docs.spring.io/spring/docs/5.1.0.RELEASE/spring-framework-reference/core.html#beans-java-basic-concepts
One Option would be to create a factory which provides an instance of your class to the application context, like it is stated in the documentation:
#Configuration
public class AppConfig {
#Bean
public IntegrationFactory myIntegrationFactory() {
return new IntegrationFactory();
}
}
Do not forget to add the Configuration to the application context.

Does spring #GetMapping work with MockMvc

HelloController.java
#RestController
class HelloController {
#GetMapping(value = "{id}/hello")
public ModelAndView listAPI(#PathVariable("id") String profileId) {
ModelAndView mav = new ModelAndView();
return mav;
}
}
HelloControllerTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = HelloConfigTest.class)
class HelloControllerTest {
#Inject
private WebApplicationContext webApplicationContext;
#Inject
private Foo mockFoo
#InjectMocks
HelloController helloController;
private MockMvc mockMvc;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void testHello() throws Exception {
mockMvc.perform(
get("/{id}/campaigns", "id1"))
.andExpect(status().isOk()));
}
}
// I have another test that directly calls the controller method.
// So I need #InjectMocks to get an instance of the controller
#Test
public void test2() {
when(mockFoo.getX()).thenReturn(true);
helloController.saveAPI();
}
HelloConfigTest.java
#Configuration
#ComponentScan("com.test.controller")
class HelloConfigTest {
#Bean
public mockFoo() {
return Mockito.mock(Foo.class);
}
}
The response that I get here is 404 and I expect 200.
But it works and I get 200 if I change #GetMapping to #RequestMapping(value="{id}/hello", method=RequestMethod.GET)
Am I missing anything here ?
Your configuration is extremely bare bones
#Configuration
#ComponentScan("com.test.controller")
class HelloConfigTest {
It doesn't register any Spring MVC infrastructure beans, either implicitly or explicitly.
When MockMvc, internally, creates a TestDispatcherServlet to test your #Controller class, it has to defer to some default Spring MVC infrastructure types.
Among these infrastructure types is HandlerMapping which is
to be implemented by objects that define a mapping between requests and handler objects.
The default implementation used by the TestDispatcherSerlet is DefaultAnnotationHandlerMapping (an old class) which looks for #RequestMapping specifically, it doesn't recursively do a meta-annotation lookup. Your #GetMapping annotated method is therefore not found and not registered as a handler.
If, instead, you configure your application context with #EnableWebMvc
#Configuration
#ComponentScan("com.test.controller")
#EnableWebMvc
class HelloConfigTest {
Spring will implicitly register a RequestMappingHandlerMapping, which does do this "recursive" lookup for the annotation hierarchy (called merging). Since #GetMapping is annotated with #RequestMapping, the annotated handler method will be found and registered.
As for the #InjectMocks, note that the instance referenced by the field is different from the one used to handle the request performed by the MockMvc object. The former is managed by Mockito, the latter by Spring.

SpringBoot MethodInterceptor Auto Proxy

How to intercept #RestController methods using MethodInterceptor in SpringBoot?
Prior to using springboot, I have a simple Interceptor that logs the execution time of a bean method. Simply intercepts all spring bean methods through a default proxy definition.
#Component
public class MethodTimer implements MethodInterceptor {
#Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
final StopWatch stopWatch = new StopWatch();
stopWatch.start(methodInvocation.getMethod().toGenericString());
try {
return methodInvocation.proceed();
}
finally {
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
}
}
The spring configuration where com.mypackages contains the beans and the MethodInterceptor implementation.
<context:component-scan base-package="com.mypackages" />
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<property name="proxyTargetClass" value="true"/>
</bean>
The configuration above is loaded via
#ContextConfiguration("/application-context.xml")
Method executions are logged, everything seems to be working, life is good.
Moving to SpringBoot
I use the same code, switched to annotations (eliminating the *.xml) configuration
#SpringBootApplication(scanBasePackages = { "com.mypackages" })
#EnableAutoConfiguration
#EnableAspectJAutoProxy(proxyTargetClass=true)
Added a constructor to the MethodTimer with System.out.println() to make sure that the Interceptor has been loaded by spring boot
public MethodTimer() {
System.out.println("MethodTimer - Constructor");
}
And yes, it was loaded and created as "MethodTimer - Constructor" is found in the console logs.
However, none of the #RestController methods are intercepted. Below is a simple Rest Controller
#RestController
#RequestMapping("/test")
public class HelloWorldService {
#RequestMapping(method = RequestMethod.GET)
public String sayHello() {
System.out.println("Hello World!");
return "Hello World!";
}
}
Even tried creating a pure spring bean via #Component and #Autowired it in the #RestController to see if that pure spring bean is intercepted, but it was not intercepted as well.
A simple Test service bean. The test() method should be intercepted.
#Component
public class TestService {
public void test() {
System.out.println("TestService.test()");
}
}
Revised RestController
#RestController
#RequestMapping("/test")
public class HelloWorldService {
#Autowired
private TestService testService;
#RequestMapping(method = RequestMethod.GET)
public String sayHello() {
testService.test();
return "Hello World!";
}
}
Notes
The MethodTimer was loaded as the constructor was called showing the System.out.println log in the console, it seems however spring boot did not automatically determine that this bean/component is implementing MethodInterceptor.
The simple Spring Bean annotated with #Component bean was not intercepted.
The Rest Controller annotated with #RestController was not intercepted.
I have tried adding #Component, #Service in the RestController, it did not work.
I have tried tried #EnableAutoConfiguration, adding spring.aop.* configurations in the application.properties, did not work.
Tried using SpringBoot version 1.5.4.RELEASE, does not work.
I know I can always try to use Aspect as shown in the spring-boot-sample-aop example: https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples/spring-boot-sample-aop
But not able to use previous spring code and configure it with springboot is just too lame. If MethodInterceptor and DefaultAdvisorAutoProxyCreator is not supported anymore in spring boot, it should have been deprecated

Categories