Understanding the instance variable names and the methods to create it using Spring #Bean annotation - spring

I have written a simple Spring Boot Application, which I would later extend to build a Spring REST client. I have a working code; I have tried to change a few instance variable names and method names and playing around.
Code:
#SpringBootApplication
public class RestClientApplication {
public static void main(String[] args) {
SpringApplication.run(RestClientApplication.class, args);
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
RestClientApplication.class)) {
System.out.println(" Getting RestTemplateBuilder : " + ctx.getBean("restTemplateBuilder"));
System.out.println(" Getting RestTemplate : " + ctx.getBean("restTemplate"));
}
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.build();
}
#Bean
public CommandLineRunner runner() {
return args -> { SOP("hello"); }
}
}
Observations:
The instance variable names follow the camel-case notation, as
expected. So, restTemplate and restTemplateBuilder works.
While creating a RestTemplate instance through restTemplate() method, I tried changing the name of the argument to builder. It works.
While creating a RestTemplate instance through restTemplate() method, I tried changing the name of the method to a random one and I get an exception that "No bean named 'restTemplate' available".
CommandLineRunner interface is implemented through a lambda expression. Accessing commandLineRunner throws an exception.
Question
Why do I see the results mentioned in point #2 and #3?

While creating a RestTemplate instance through restTemplate() method,
I tried changing the name of the argument to builder. It works.
This works because, By default spring autowire's by type. So it searches for a bean with type RestTemplateBuilder and it finds it and hence no error.
While creating a RestTemplate instance through restTemplate() method,
I tried changing the name of the method to a random one and I get an
exception that "No bean named 'restTemplate' available".
You are getting an exception not because you changed the method name, but because of this
ctx.getBean("restTemplate")
Because by default #Bean uses method name as name of the bean. (check this). So the name of the bean of type RestTemplate which is returned by your random method is name of your random method. Hence when you try to get a bean with name restTemplate, it throws exception.
But if you were to Autowire a bean of type RestTemplate, it would still work, because Spring will Autowire by type by default and it knows a bean of type RestTemplate(name as random method name).

Related

ScopeNotActiveException in spring boot rest api

I have one simple resource class and autowiring bean from BeanConfig class, which declared one bean.
Whenever request is hit on API, then i am getting an exception.
Code:
#Component
public class BeanConfig {
#Autowired
Environment environment;
#Bean
#RequestScope
public Map<String, String> eventAttributes() {
return new HashMap<>();
}
}
public class SampleResource {
#Autowired
SampleServiceImpl sampleService;
#Autowired
private Map<String, String> eventAttributes; }
Following error:
org.springframework.beans.factory.support.ScopeNotActiveException: Error creating bean with name 'scopedTarget.eventAttributes': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request

Class not able to access bean managed by Spring

I have Spring configuration file where I am defining beans but somehow this bean is not accessible from one of the class in same package, though same beans are accessible from Controller class which was annotated as #Controller. I was thinking may be this class was not managed by Spring but that's not the case.
1) Configuration class
#Bean
public FooConsumer fooConsumer() {
return new FooConsumer();
}
#Bean
public Map<String, ProxyConsumer> appProxyConsumerMap() {
Map<String, ProxyConsumer> proxyConsumer = new HashMap<String, ProxyConsumer>();
proxyConsumer.put(FOO_APP, FooConsumer());
return proxyConsumer;
}
#Bean
public FooEventConsumer fooEventConsumer() {
return new FooEventConsumer();
}
#Bean
public Map<String, FooConsumer> fooConsumerMap(){
Map<String, FooConsumer> fooEventConsumer = new HashMap<String, FooConsumer>();
fooEventConsumer.put(FOO_EVENT, fooEventConsumer());
}
2) Controller class
#Resource
#Qualifier("appProxyConsumerMap")
Map<String, ProxyConsumer> appProxyConsumerMap;
//proxyApp comes as path variable
ProxyConsumer consumer = appProxyConsumerMap.get(proxyApp);
//invoke consumer
boolean consumed = consumer.consumeEvent(eventRequest);
//here consumer is my FooConsumer class, till now all works fine.
3) now in FooConsumer class it tries to access Map bean named fooConsumerMap to get which event to call but somehow it returns null.
#Resource
#Qualifier("fooConsumerMap")
Map<String, FooConsumer> fooConsumerMap;
FooEventConsumer consumer = fooConsumerMap.get(eventType);
//Here fooConsumerMap comes as null in this class, though it comes as object in controller class , please advise.
In your configuration file, construct your FooConsumer bean with the FooConsumerMap bean declared in the same configuration.
You can autowire other beans into a configuration file, but to pull together beans within the file you pass them as constructor arguments.
Note that if you call a Bean annotated method multiple times, you will surprisingly always get the same instance even if the method logic constructs a new instance.
Check documentation at https://docs.spring.io/spring-javaconfig/docs/1.0.0.m3/reference/html/creating-bean-definitions.html

Camel HeaderFilterStrategy Bean Registration in Spring

I am having difficulty with Spring-Camel getting a HeaderFilterStrategy class registered as a Bean so it can be found by the Camel Route. My attempts to annotate the HeaderFilterStrategy custom class seem futile... so how do I register this thing so it gets found at run time?
I have a camel application with a route utilizing a custom HeaderFilterStrategy
The Strategy Class looks like :
public class HeaderFilter implements HeaderFilterStrategy {
#Override
public boolean applyFilterToCamelHeaders(String s, Object o, Exchange exchange) {
return false;
}
#Override
public boolean applyFilterToExternalHeaders(String s, Object o, Exchange exchange) {
return true;
}
}
I register it with camel using a simple registry:
SimpleRegistry registry = new SimpleRegistry();
registry.put("HeaderFilter" ,new HeaderFilter());
.
.
final CamelContext ctx = new DefaultCamelContext(registry);
And I reference it in my Route in
.to("https://myhost/endpoint&headerFilterStrategy=#HeaderFilter")
And all like Ralphy on Christmas night with his trusty Red Rider BB Gun, all is right with the world.
So, now I am trying to take this pure camel app and put it under Spring. I make sure all the appropriate Camel, and Spring-Camel and Spring things are imported.. However, when I attempt to annotate my HeaderStrategy as a Bean for Spring and it fails:
#Component
public class HeaderFilter implements HeaderFilterStrategy {
#Bean
#Override
public boolean applyFilterToCamelHeaders(String s, Object o, Exchange exchange) {
return false;
}
#Override
public boolean applyFilterToExternalHeaders(String s, Object o, Exchange exchange) {
return true;
}
}
Now when I do this, the IDE basically tells me it can't autowire any of the parameters in the method calls becaue there is more than one bean of type String or Object and no beans of type Exchange found..
At Runtime, Camel does attempt to interpret the route, but throws a failure with "No Qualifying bean type of "java.lang.String" available, since this is the first parameter in the method call...
So, How do I get this thing to be able register with annotations correctly? Or manually register this bean without it attempting to autowire? All I need is the class to be registered as a BEAN so it can be found by camel at runtime... Or at least that is what I understand needs to happen... so how the heck to I do this?
I figured it out, I was not properly using the annotationsI added the following to my AppConfig class:
#Configuration
public class AppConfig{
#Bean
public HeaderFilter HeaderFilter(){
return new HeaderFilter();
}
}
I am not sure if the suggestion above will work, but this clearly does.

CommandLineRunner and Beans (Spring)

code what my question is about:
#SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String args[]) {
SpringApplication.run(Application.class);
}
#Bean
public Object test(RestTemplate restTemplate) {
Quote quote = restTemplate.getForObject(
"http://gturnquist-quoters.cfapps.io/api/random", Quote.class);
log.info(quote.toString());
return new Random();
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
#Bean
public CommandLineRunner run(RestTemplate restTemplate) throws Exception {
return args -> {
Quote quote = restTemplate.getForObject(
"http://gturnquist-quoters.cfapps.io/api/random", Quote.class);
log.info(quote.toString());
};
}
}
I'm very new to Spring. As far as I understood the #Bean annotation is responsible that an Object is saved in a IoC container, correct?
If so: Are first all Methods with #Bean collected and then executed?
In my example I added a method test() what does the same as run() but returns an Object (Random()) instead.
The result is the same so it is working with CommandLineRunner and Object.
Is there a Reason why it should return a CommandLineRunner i.e. use the syntax like run()?
Moreover: At that point I don't see so far the advantage to move methods to an container. Why not just execute it?
Thank you!
#Configuration classes (#SpringBootApplication extends #Configuration) are the place where the spring beans are registered.
#Bean is used to declare a spring bean. The method that is annotated with #Bean has to return an object(the bean). By default the spring beans are singletons, so once the method annotated with #Bean is executed and returns it's value this object lives til the end of the application.
In your case
#Bean
public Object test(RestTemplate restTemplate) {
Quote quote = restTemplate.getForObject(
"http://gturnquist-quoters.cfapps.io/api/random", Quote.class);
log.info(quote.toString());
return new Random();
}
this will produce s singleton bean of type Random with name 'test'. So if you try to inject (e.g. with #Autowire) a bean of that type or name in other spring bean you will get that value. So this is not a good use of #Bean annotation, unless you want exactly that.
CommandLineRunner on the other hand is a special bean that lets you execute some logic after the application context is loaded and started. So it makes sense to use the restTemplate here, call the url and print the returned value.
Not long ago the only way to register a Spring bean was with xml. So we had an xml files and bean declarations like this:
<bean id="myBean" class="org.company.MyClass">
<property name="someField" value="1"/>
</bean>
The #Configuration classes are the equivalent of the xml files and the #Bean methods are the equivalent of the <bean> xml element.
So it's best to avoid executing logic in bean methods and stick to creating objects and setting their properties.

Setting up Custom HandlerExceptionResolver in Spring MVC

I am trying to override WebMvcConfigurerAdapter.configureHandlerExceptionResolvers() and provide my own ExceptionHandlerExceptionResolver to Spring MVC. The motive behind this is to provide Custom Content Negotiation in such a way that if the user requests for any garbage in "Accept" header, I can return him a JSON response with "media not supported exception". I was partially able to acheive the configuration using the bellow setup.
#Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager(new ErrorContentNegotiation());
ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = exceptionHandlerExceptionResolver();
exceptionHandlerExceptionResolver.setContentNegotiationManager(contentNegotiationManager);
exceptionHandlerExceptionResolver.afterPropertiesSet();
exceptionResolvers.add(exceptionHandlerExceptionResolver);
}
#Bean
public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(getHttpMessageConverter());
ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new ExceptionHandlerExceptionResolver();
exceptionHandlerExceptionResolver.setMessageConverters(messageConverters);
return exceptionHandlerExceptionResolver;
}
public class ErrorContentNegotiationStrategy implements ContentNegotiationStrategy {
#Override
public List<MediaType> resolveMediaTypes(final NativeWebRequest webRequest) {
return Lists.newArrayList(Globals.JSON);
}
}
I am getting this exception when the spring starts up.
No qualifying bean of type [org.springframework.web.accept.ContentNegotiationStrategy] is defined: expected single matching bean but found 2: errorContentNegotiationStrategy,mvcContentNegotiationManager
Doesn't work when I add a #Qualifier annotation to my ErrorContentNegotiationStrategy class and give it a unique name. Throws the same Exception.
If I remove #Compoenent annotation and leave the code as is, then ErrorContentNegotiationStrategy() method in ErrorContentNegotiaionStrategy is not getting called.
Did anyone face this issue ?
Add the #Primary annotation to your ErrorContentNegotiationStrategy class:
Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value.
This should at least solve the exception during startup.
After debugging the issue, I found that I was trying to load 2 beans of same type ( which is what the error message says. One of the bean was actual implementation and other one was a Mock for test case. Since both resided in the same package #component scanned the base package and couldn't decide which one to load. I resolved the issue by using #profile, which helps in loading beans based on the profile you load. I used 2 profiles, one for testing and one for development.

Resources