I'm using a particular class (ClassA) in my controller as the request body, but within that class, my autowired ConfigurationProperties is null.
Controller:
#RestController
#RequestMapping(value = "/rest/v1/")
public class XyzController {
#Autowired
ServiceXyz serviceXyz;
#PostMapping(value = "/route")
public void route(#RequestBody ClassA classA) {
serviceXyz.methodAbc(classA);
}
}
ServiceXYZ:
#Service
public class ServiceXyz {
public boolean methodAbc(ClassA classA) {
return classA.methodA() && otherStuff();
}
}
ClassA.java:
#Component
public class ClassA {
#Autowired
ApplicationProperties applicationProperties;
public boolean methodA() {
return fieldA.equals(applicationProperties.someProperty());
}
}
ApplicationProperties.java:
#Component
#ConfigurationProperties(prefix="stuff")
public class ApplicationProperties {
// etc.
}
It's within ClassA.methodA that applicationProperties is null, even though everybody is marked with the correct annotations, and autowiring is working throughout the rest of the application.
Is it possible that this just doesn't work?
Autowiring works for objects from Spring context. In your request object of ClassA is parsed from JSON I think and is not taken from Spring context.
You'd better change your code to make ClassA as simple DTO and inject ApplicationProperties into your service class.
You can change your ClassA to this
public class ClassA {
public boolean methodA(ApplicationProperties applicationProperties) {
return fieldA.equals(applicationProperties.someProperty());
}
}
And your service to this:
#Service
public class ServiceXyz {
#Autowired
private ApplicationProperties applicationProperties;
public boolean methodAbc(ClassA classA) {
return classA.methodA(applicationProperties) && otherStuff();
}
}
Related
I'm working on a Spring boot application. My code is as follows
#Slf4j
#RestController
public class TestController {
#Autowired
private TestService testService;
#Autowired
private TestConfig testConfig;
#GetMapping("testService")
public void testService() {
testService.printConfigValue();
}
#GetMapping("updateConfig/{value}")
public void updateConfig(String value) {
testConfig.setValue(value);
}
#GetMapping("testConfig")
public void testConfig() {
log.info("INSIDE CONTROLLER - Config value = {}", testConfig.getValue());
}
}
#Data
#Configuration
public class TestConfig {
private String value;
}
#Slf4j
#Service
public class TestService {
#Autowired
private TestConfig testConfig;
public void printConfigValue() {
log.info("INSIDE SERVICE - Config value = {}", testConfig.getValue());
}
}
When I use the #GetMapping("updateConfig/{value}") endpoint say with a value of hello and call the testService & testConfig endpoints I'm receiving a null value. If I understand it correctly, Spring should treat its beans as singleton by default. So, if I update the Autowired config in the "updateConfig/{value}" endpoint it should show the updated value when hitting both #GetMapping("testService") & #GetMapping("testConfig") endpoints. But I'm getting a null value for the "value" field of TestConfig class. Can someone explain this? What am I missing here?
You need to add #PathVariable annotation
#GetMapping("updateConfig/{value}")
public void updateConfig(#PathVariable("value") String value) {
testConfig.setValue(value);
}
I have this situation:
public class Other {
public void test() {
new ClassA().process();
}
}
public class ClassA {
#Autowired
private ClassB classB;
public void process() {
classB.executeSomething(); //--> NUllPOinter because classA was not created by spring.
}
}
#Service
public class ClassB {
public void executeSomething() {
// execute something
}
}
I tried use ApplicationContext but the problem continued.
Someone, have a idea what i should do ?
Thanks.
This is always the right way to declare a class dependency (always for annotated ioc spring bean):
public class ClassA {
private final ClassB objectB;
public ClassA(final ClassB objectB) {
this.objectB = objectB;
}
public void process() {
objectB.executeSomething();
}
}
In Other class, we should retrieve the singleton ClassB instance. Then it must be a bean component.
#Component
public class Other {
private final ApplicationContext applicationContext;
#Autowired
public Other(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void test() {
new ClassA(applicationContext.getBean(ClassB.class)).process();
}
}
If Other class can't be a spring component, you can't retrieve the application context and then you can't inject the ClassB instance.
You can clearly autowire directly the ClassB object instance into Other component, but this example underline that you can handle bean fetching with an ApplicationContext ref.
I have that class as you can see below:
#Configuration
#PropertySource("classpath:sample.properties")
public class SampleConfig {
#Value("${attr1.prop}")
private String attr1;
#Value("${attr2.prop}")
private String attr2;
#Bean
public SampleService sampleService() {
return new SampleService(attr1);
}
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
I created SampleService bean with parameter attr1. Is possible get access to that properties which I load with #PropertySource later? For example after #Autowired.
Here is code of of using that bean:
#Service
public class SuperHotServiceImpl {
#Autowired
SampleService sammpleService;
public void fooFunc() {
// here I need some magic to get value of attr2.prop
sammpleService.setAttr(attr2);
}
}
Can you tell if it is possible an how? Thanks
If I understand properly your question, then this should help:
#Service
public class SuperHotServiceImpl {
#Autowired
private SampleConfig sammpleConfig;
public void fooFunc() {
System.out.println(sampleConfig.getAttr2());
}
}
Supposed that SampleConfig is annotated as #Component and that you provide the getter.
I have two class which depends on config variable:
#Component
#ConditionalOnProperty("config.db")
public class DatabaseTokenStore implements TokenStore {
}
#Component
#ConditionalOnMissingBean(DatabaseTokenStore.class)
public class SimpleTokenStore implements TokenStore {
}
so when db is true then DatabaseTokenStore class is autowired when false then SimpleTokenStore is autowired. Problem is that I can change this property in runtime with CRaSH. Then this mechanic will not work. Is there some way how to change implement of interface in runtime ?
Initialize both TokenStores on startup. And create a resolver to inject into classes where you need to work with them. Like so:
#Component
public class HelloStoreResolver {
#Autowired
private HelloStore oneHelloStore;
#Autowired
private HelloStore twoHelloStore;
public HelloStore get() {
if (condition) {
return oneHelloStore;
} else {
return twoHelloStore;
}
}
}
#Component
public class HelloController {
#Autowired
private HelloStoreResolver helloResolver;
//annotations omitted
public String sayHello() {
return helloResolver.get().hello();
}
}
Dao
#Repository
public interface LoginDao extends JpaRepository<Login, Integer> {
Login findByLogin(String login);
}
Validator
#Component
public class PasswordChangeValidator implements Validator {
private LoginDao loginDao;
#Override
public boolean supports(Class<?> aClass) {
return PasswordChange.class.equals(aClass);
}
#Override
public void validate(Object o, Errors errors) {
PasswordChange passwordChange = (PasswordChange) o;
**// There is a null pointer here because loginDao is null**
Login login = loginDao.findByLogin(passwordChange.getLoginKey());
}
public LoginDao getLoginDao() {
return loginDao;
}
#Autowired
public void setLoginDao(LoginDao loginDao) {
**// There is a debug point on the next line and it's hit on server startup and I can
// see the parameter us non-null**
this.loginDao = loginDao;
}
}
Controller
#Controller
#RequestMapping("api")
public class PasswordController {
#Autowired
PasswordService passwordService;
#InitBinder("passwordChange")
public void initBinder(WebDataBinder webDataBinder, WebRequest webRequest) {
webDataBinder.setValidator(new PasswordChangeValidator());
}
#RequestMapping(value = "/passwordChange", method = RequestMethod.POST)
public #ResponseBody PasswordInfo passwordInfo(#RequestBody #Valid PasswordChange passwordChange)
throws PasswordChangeException {
return passwordService.changePassword(passwordChange.getLoginKey(), passwordChange.getOldPassword(), passwordChange.getNewPassword());
}
}
I have the Dao listed above. This same dao bean gets injected in an #Service annotated class but not in #Component annotated Validator class. Well, not exactly the upon server startup I can see that the setter method gets called, but when I try to use this variable in a method the variable shows as null.
Does anybody see a problem with my configuration ? Please note that the loginDao bean gets injected into a service class, so the Context configuration is good.
Well there's your problem
webDataBinder.setValidator(new PasswordChangeValidator());
Spring can only manage beans it created. Here, you're creating the instance. Instead inject your bean into the #Controller and use it.
#Inject
private PasswordChangeValidator passwordChangeValidator;
...
webDataBinder.setValidator(passwordChangeValidator);