Trying to call a class with constructor but #Autowired var is throwing Null Pointer Exception in Kotlin/Spring Boot - spring

I'm new to Kotlin and Spring Boot. I'm trying to call SearchService class with initialized constructors through ServiceClass, everything is fine until it's time to access Autowired env variable in SearchService - it throws Null Pointer Exception. I'm assuming I will have to Autowire SearchService in ServiceClass but then how will I initialize the constructor and name/isAlive variables in the performSearch method.
#Service
class ServiceClass {
#Autowired
lateinit var env: Environment
fun performSearch(req: String): String {
var searchService = SearchService("John", false)
result = searchService.searchAsync()
...
return result
}
}
#Repository
class SearchService(name: String = "", isAlive: Boolean = true) {
private var name: String = name
private var isAlive: Boolean = isAlive
#Autowired
lateinit var env: Environment
fun searchAsync(): String {
var endpoint = env.getProperty("endpoint").toString() + name //Error here
// makes call to get endpoint with Name and isAlive as params
...
return result
}
}

You're right that you need need to autowire the SearchService too. Spring can only autowire dependencies into components that it created. Because you're creating the SearchService yourself, Spring doesn't know that it exists, and won't autowire into it.
Your second question is: if you autowire the SearchService, how can you initialise its other properties like name and isAlive? This is a common problem with dependency injection, which arises when some of the parameters to a constructor are dependencies and others are data.
The solution is to use the factory design pattern. In dependency injection it's also sometimes called "assisted injection". Create another "factory" component whose only job is to create the SearchService for you:
#Component
class SearchServiceFactory(private val env: Environment) {
fun createSearchService(name: String = "", isAlive: Boolean = true) =
SearchService(env, name, isAlive)
}
You should then remove the #Repository annotation from the SearchService, since its creation isn't managed directly by Spring.
The factory can be autowired wherever you need to use it, and in turn has the Environment autowired into it. When you want to create the SearchService, you call createSearchService to provide values for the additional data parameters.
Your service class would change to the following:
#Service
class ServiceClass(private val factory: SearchServiceFactory) {
fun performSearch(req: String): String {
var searchService = factory.createSearchService("John", false)
result = searchService.searchAsync()
...
return result
}
}
Note that in the examples I've used constructor injection instead of #Autowired. Spring will automatically provide values for constructor arguments when instantiating a Kotlin class, the same as if it was an autowired field. Using constructor injection instead of field injection can help to ensure that problems like the one you encountered are detected at compile time rather than causing null pointer exceptions at runtime.

Related

How to get spring #Value annotated values injected even before calling the constructor?

In spring wiring is there a way to get the properties defined in application.properties to be wired to the corresponding field annotated with #Value before the constructor call.
Please see my comment in below code. I want the field prop1 (with its value) to be available to the constructor. Is it possible. Similarly I want the field prop1 (with its value) to be available to the getInstanceProp() method. Is it possible??
package com.demo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class WirableComponent {
#Value("${prop1}")
String prop1;
String instanceProp = getInstanceProp(); //null - I want prop1 value to be injected before making this method call so i can use it in the method.
public WirableComponent() {
System.err.println("no-args WirableComponent Constructor invoked");
System.err.println("value prop1: " + prop1); //null - I want prop1 value to be injected before constructor call so i can use it in constructor.
System.err.println("value instanceProp: " + instanceProp); //null
}
public String getInstanceProp() {
System.err.println("value prop1: " + prop1);
return null;
}
}
#value works like dependency injection rules of spring, when the application context loads and the bean for the WirableComponent class is created the prop1 value is can be instantiated through constructor injection(this can be used for the method call)
eg
#Component
public class WirableComponent {
private final String prop1;
public WirableComponent(#Value("${prop1}") String prop1){
this.prop1 = prop1;
}
}
Secondly although prop1 is a string literal, it takes value from spring context ,hence it cannot be available in the class until the constructor call happens as in Java:
At the byte code level.
An object is created but not initialised.
The constructor is called, passing the object as this
The object is fully constructed/created when the constructor returns.

spring boot component with string parameters

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.

Evaluate property from properties file in Spring's #EventListener(condition = "...")

I would like to make the execution of an event handler dependent on whether or not a property is set to true in a properties file.
#EventListener(ContextRefreshedEvent.class, condition = "${service.enabled}")
public void onStartup() { }
However, this does not seem to work. I am getting the following error on startup:
org.springframework.expression.spel.SpelParseException: EL1043E:(pos 1): Unexpected token. Expected 'identifier' but was 'lcurly({)'
Is it possible to use a property from a properties file as a condition here?
The issue is condition argument is expecting a SPEL.
This works try it out.
In your bean where you have this #EventListener, add these lines
public boolean isServiceEnabled() {
return serviceEnabled;
}
#Value("${service.enabled}")
public boolean serviceEnabled;
change your declaration of evnt listener like this
#EventListener(classes = ContextRefreshedEvent.class, condition = "#yourbeanname.isServiceEnabled()")
public void onStartup() { }
change yourbeanname with the correct bean name .
I had the same annoying experience (with Spring Boot 2.4.2 on Java11).
In my case I had the boolean property in a #ConfigurationProperties class anyways in the same java file and still struggled a bit. First the #ConfigurationProperties need to be annotated as #Component to actually be a valid Bean and can be used in SpEL.
And I had to use the same long attributeName for the ConfigurationProperties in the Service itself and the EventListener Annotation for the Bean resolution to work fine. I needed some the ConfigurationProperties values also in another place of the Service, that's why they needed to be (Constructor) Autowired as well...
So this worked for me:
#ConfigurationProperties("my.custom.path")
#Component //Important to make this a proper Spring Bean
#Data //Lombok magic for getters/setters etc.
class MyCustomConfigurationProperties {
boolean refreshAfterStartup = true;
}
#Service
#RequiredArgsConstructor //Lombok for the constructor
#EnableConfigurationProperties(MyCustomConfigurationProperties.class)
#EnableScheduling
public class MyCustomService {
private final MyCustomConfigurationProperties myCustomConfigurationProperties;
#EventListener(value = ApplicationReadyEvent.class, condition = "#myCustomConfigurationProperties.refreshAfterStartup")
public void refresh() {
//the actual code I want to execute on startup conditionally
}
}

Inject properties name into class anotation

Is it possible to inject property name into the procedureName?
im using spring boot.
Try to use the next the next construction:
procedureName = "${procedure}" but it doesnt work
Also to write the special PropertySourcesPlaceholderConfigurer i think it not a good idea .
#NamedStoredProcedureQueries({
#NamedStoredProcedureQuery(name = "test",
procedureName = "${procedure}",
parameters = {
})
})
public class R
try to get property from properties-test.yml
Spring properties used to inject values in bean properties like below,
public class ClassWithInjectedProperty {
#Value("${props.foo}")
private String foo;
}
you case is not valid for value injection.

What's the best pattern for injecting a bean with arguments?

I have a number of cases in my app where client code wants to create a bean on-demand. In each case, the bean has 1 or 2 constructor arguments which are specified by the client method, and the rest are autowired.
Ex:
//client code
MyQuery createQuery() {
new MyQuery(getSession())
}
//bean class I want to create
//prototype scoped
class MyQuery {
PersistenceSession session
OtherBeanA a
OtherBeanB b
OtherBeanC c
}
I want A, B, and C to be autowired, but I have the requirement that 'session' has to be specified by the calling code. I want a factory interface like this:
interface QueryFactory {
MyQuery getObject(PersistenceSession session)
}
What's the most efficient way to wire up the factory? Is it possible to avoid writing a custom factory class that does new MyQuery(...)? Can ServiceLocatorFactoryBean be used for something like this?
You can use the #Autowired annotation on your other beans and then use the ApplicationContext to register the new bean. This assumes otherBeanA is an existing bean.
import org.springframework.beans.factory.annotation.Autowired
class MyQuery {
#Autowired
OtherBeanA otherBeanA
PersistenceSession persistenceSession
public MyQuery(PersistenceSession ps){
this.persistenceSession = ps
}
}
I'm not positive if this is the most efficient way to create a new bean, but it seems to be the best way at runtime.
import grails.util.Holders
import org.springframework.beans.factory.config.ConstructorArgumentValues
import org.springframework.beans.factory.support.GenericBeanDefinition
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.context.ApplicationContext
class MyQueryFactory {
private static final String BEAN_NAME = "myQuery"
static MyQuery registerBean(PersistenceSession ps) {
ApplicationContext ctx = Holders.getApplicationContext()
def gbd = new GenericBeanDefinition(
beanClass: ClientSpecific.MyQuery,
scope: AbstractBeanDefinition.SCOPE_PROTOTYPE,
autowireMode:AbstractBeanDefinition.AUTOWIRE_BY_NAME
)
def argumentValues = new ConstructorArgumentValues()
argumentValues.addGenericArgumentValue(ps)
gbd.setConstructorArgumentValues(argumentValues)
ctx.registerBeanDefinition(BEAN_NAME, gbd)
return ctx.getBean(BEAN_NAME)
}
}
Instead of using Holders, it's advised to use the ApplicationContext from dependecy inject if available, you could then pass this to the registerBean method.
static MyQuery registerBeanWithContext(PersistenceSession ps, ApplicationContext ctx) {
...
}
Calling class:
def grailsApplication
...
PersistenceSession ps = getRuntimePersistenceSession()
MyQueryFactory.registerBean(ps, grailsApplication.mainContext)
I changed the name of the method to truly reflect what it's doing - registering a spring bean as opposed to instantiating a MyQuery. I pass back the bean using the getBean method, but you also have access to the same bean using the ApplicationContext once it's been created.
def myQueryBean = MyQueryFactory.registerBean(ps)
// or somewhere other than where the factory is used
def grailsApplication
def myQueryBean = grailsApplication.mainContext.getBean('myQuery')

Resources