spring boot component with string parameters - spring

i have a component that reads a configuration value from application.properties and accepts a string parameter in its constructor as such..
#Component
public class Person
{
#Value("${greeting}")
String greeting;
String name;
public Person(String name)
{
this.name = name;
onGreet( greeting + ", " + name );
}
public void onGreet(String message)
{
}
}
I need to instantiate this component as follows and override its "onGreet" event in the calling code as follows:
Person jack = new Person("jack")
{
public void onGreet(String message)
{
System.out.println( message );
}
};
However I end up getting this..
Parameter 0 of constructor in demo11.Person required a bean of type 'java.lang.String' that could not be found.
My application.properties is as follows:
greeting=hello
What am I missing here? Thank you.

It is literally telling you that the only constructor that you have requires a parameter that Spring knows nothing about.
Add a #Value to that String name in the constructor (right before the parameter) like so public Person(#Value("${name}") String name) if you want Spring to initalize it or remove that constructor
EDIT: some more explanation:
Spring is a dependency injection container. Meaning you define beans and let Spring create and inject them for you. Defining beans can be done in several ways (Java configuration, annotations or xml) here you are using annotation way via #Component.
Now that you have defined your bean (aka component) for Spring it will create it. For it to create it it needs to call a constructor. For that you need to provide it with all information necessary for constructor call - meaning all parameters. If parameters are other classes they need to be defined as beans as well (For example via #Component) if they are simple types like String you need to provide #Value for them.
Lastly if you ever use new ... to define Spring managed beans then the whole Spring magic disappears since Spring doesnt know about this bean instantiation anymore and will not autowire anything into it. For all intenses and purposes Spring is not aware of any objects you create with new.

Related

Springboot #GetMapping method injection with object as parameter

I am new to Spring Boot. As I understand how constructor injection works then I can't tell why HelloController works - index method is not a constructor so where/why cat object instance is created? Would be glad to get some documentation or articles about it.
HelloController.java
#RestController
public class HelloController {
#GetMapping("/{name}")
public String index(#PathVariable("name") String name, Cat cat){
cat.setName(name);
return "<b>Hello " + cat.getName() + "</b>";
}
}
Cat.java
#Component
public class Cat {
#Getter #Setter
private String name;
public Cat(){
System.out.println("Created new Cat!");
}
}
This is an interesting question - only because of the way you have put it. By injection, do you mean creation of a singleton class (You have Cat marked as #Component)? Well to answer this, I added something extra to your print statement:
public Cat(){
System.out.println("Created new Cat! with hasCode: " + hashCode());
}
You see, the hashCode should not change for the same object. The results are not very surprising:
Created new Cat! with hasCode: 362563829
Created new Cat! with hasCode: 782885695
The first line was printed when the application was started. That is expected as the bean is created with a scope of singleton and the process completes before the application is completely loaded. The second output comes when I make a request to the endpoint in which case, Spring creates an instance of Cat and passes it as an argument to the #GetMapping. You get as many objects of Cat as your requests.
Along the same lines, if i remove #Component from Cat, the first line does not show up.
Moving along, I made another change to the RestController class:
#RestController
public class HelloController {
#Autowired
private Cat myCat;
#GetMapping("/{name}")
public String index(#PathVariable("name") String name, Cat cat){
cat.setName(name);
System.out.println("Hello " + myCat.hashCode());
return "Hello " + cat.getName() + "The age is: " + cat.getAge();
}
}
Here is the result of running this application:
What it shows is that the cat passed on to the controller method is not same as the one that was managed by Spring container.
Concussions
Rest controller method will be passed a new instance of a class everytime it is called.
The controller method is not pass the instance of a spring managed object
N.B.: I have not really come across any official documentation statinng above findings. Would be very happy to see one though
Bean is an object managed by the Spring context. Objects of these classes do not need to be created - Spring is responsible for that. The developer can point to a place where such an object can be deployed. This action is called "dependency injection."
Simply put, Spring is smart enough that while you mark the class with the appropriate annotation, he takes care of the rest :)
There are plenty of articles on this topic just use google "how objects are created in Spring."
In Spring, it is possible to define a bean in several ways, by: By using annotations, Appointing their instances in the methods of the configuration class, Using XML configuration.
There are a number of annotations to create a bean. Each has its own destiny. The most popular annotations include: Component, Service, Repository, Controller / RestController

Multiple Constructor injection using Java Annotations

The SPRING doc says the following
Spring Framework 4.3, an #Autowired annotation on such a constructor
is no longer necessary if the target bean only defines one constructor
to begin with. However, if several constructors are available, at
least one must be annotated to teach the container which one to use.
As i understand if there are multiple constructors and we have not annotated any of them then i will get an error . I ran the following code
#Component // this is bean id
public class TennisCoach implements Coach {
private FortuneService fortuneservice;
public TennisCoach(FortuneService thefortuneservice) {
System.out.println(" inside 1 arg constructter");
fortuneservice = thefortuneservice;
}
public TennisCoach() {
System.out.println(" inside 0 arg constructter");
}
I call that using the below code
TennisCoach theCoach = myapp.getBean("tennisCoach", TennisCoach.class);
But i didn't get the error .I got the O/P as
inside 0 arg constructter
Why?
It looks like the text you've quoted from the Spring docs doesn't apply to a case where one of the constructors is a no-args (default) constructor. You can see this very easily if you try and add an bean reference parameter to it.
Spring attempts to determine the candidate constructors using AutowiredAnnotationBeanPostProcessor and in your scenario, will not find any single or autowired constructor, so it will record the no-args one and instantiate it in SimpleInstantiationStrategy.

Spring #Transactional value param with SpEL (Spring expression language)

In one of my service classes I have some methods annotated as such :
#Transactional(value="foodb")
public Bar getMeSomething(){
}
I recently learned about #Value with the power of Spring EL to get some values stored in a properties file.
such as
#Value("${my.db.name}")
which works like a charm.
Now I'm trying to do the same with
#Transactional(value="${my.db.name}")
with no success ...
I get the following exception :
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named '${my.db.name}' is defined: No matching PlatformTransactionManager bean found for qualifier '${my.db.name}' - neither qualifier match nor bean name match!
Is what I am trying to do even supported by Spring ?
What can I do to get the my.db.name value inside that #Transactional annotation
Thanks
Nope, it's not supported.
Here's an excerpt from org.springframework.transaction.annotation.SpringTransactionAnnotationParser
public TransactionAttribute parseTransactionAnnotation(Transactional ann) {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
rbta.setPropagationBehavior(ann.propagation().value());
rbta.setIsolationLevel(ann.isolation().value());
rbta.setTimeout(ann.timeout());
rbta.setReadOnly(ann.readOnly());
rbta.setQualifier(ann.value()); // <<--- this is where the magic would be
// if it was there, but it isn't

Spring-Wicket: Bean Injection with Resource

I am writing tests for my wicket-application and need to inject a Spring Bean into a page (done by annotation) to do this.
Consider following code:
protected void setUp() {
tester = new WicketTester();
scanService = new ScanService();
ApplicationContextMock appctx=new ApplicationContextMock();
appctx.putBean("pxGenericService", new PxGenericServiceImpl());
tester.getApplication().getComponentInstantiationListeners().add(new SpringComponentInjector(tester.getApplication(), appctx));
}
This actually seem to work (no nullpointer). The problem is: the bean got a resource (variable with #Resource annotation) and when I run the test on the page, this resource turns out to be null (nullpointer exception). How do I fix this problem?
You also have to add an instance of all dependencies your bean has to the mock application context. So add an instance of the class PxGenericServiceImpl uses to appctx.
I don't think that SpringComponentInjector supports #Resource. The only supported annotations are #SpringBean and #Inject. See AnnotProxyFieldValueFactory:
#Override
public boolean supportsField(final Field field)
{
return field.isAnnotationPresent(SpringBean.class) || field.isAnnotationPresent(Inject.class);
}

Spring container - annotation for null argument

In my application that uses Spring container I created my own annotation, and I wanted at runtime get Class objects of classes that are annotated with my annotation. For this I wanted to utilize Spring container.
In my .xml configuration file I put
<context:component-scan base-package="some.package" >
<context:include-filter type="annotation" expression="some.package.Question" />
</context:component-scan>
so the classes that are annotated with my Question annotation are detected by Spring. Problem is that those classes don't have no parameter constructor, so now I have 2 options:
Define no-parameter constructor in those classes
Define the beans in .xml and use constructor-arg
but is it possible to annotate constructor arguments with some annotation, so Spring will know that it needs to pass null value during creation of a bean?
Also those beans will have prototype scope, and from the point of view of an application the contents of an constructor arguments are not known during the creation of a bean.
EDIT:
I had to use #Value("#{null}") for annotation constructor arguments
I think your first suggestion of using a no-arg constructor sounds cleaner - the reason is that the object created is, from your perspective, being considered properly initialized even though the instance variables have null values - this can be indicated by having a default constructor
If it cannot be changed, your approach of using #Value("#{null}") also works, I was able to test out in a test case:
#MyAnnotation
public class Component1 {
private String message;
#Autowired
public Component1(#Value("#{null}") String message){
this.message = message;
}
public String sayHello(){
return this.message;
}
}
This may not be what you're looking for, but if you want to re-use Spring's classpath scanner and wrap it in your own implementation, you can use the following;
Class annotation = [your class here ];
String offsetPath = [your path here ];
// Scan a classpath for a given annotation class
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
// MZ: Supply the include filter, to filter on an annotation class
scanner.addIncludeFilter(new AnnotationTypeFilter(annotation));
for (BeanDefinition bd : scanner.findCandidateComponents(offsetPath))
{
String name = bd.getBeanClassName();
try
{
Class classWithAnnotation = Class.forName(name);
}
catch (Exception e)
{
//Logger.fatal("Unable to build sessionfactory, loading of class failed: " + e.getMessage(), e);
return null;
}

Resources