I am trying to access application.properties values in spring boot application's service class. But every time value is null, so it throws NullPointException. I can get the right value for port in controller class(if i add #Autowired in controller) but not in service class. What changes are required to make this properties available through out the application?
Controller looks like:
#RestController
public class MyController {
MyService ss = new MyService();
#RequestMapping(value = "/myapp/abcd", method = RequestMethod.POST, consumes = {"application/json"})
public ResponseMessage sendPostMethod(#RequestBody String request) {
log.debug(" POST Request received successfully; And request body is:"+ request);
ResponseMessage response = ss.processRequest(request);
return response;
}
}
And Service class is:
#Service
public class MyService {
private ApplicationProperties p;
#Autowired
public setProps(ApplicationProperties config) {
this.p = config;
}
public ResponseMessage processRequest(String request) {
System.out.println("Property value is:"+ p.getPort());
}
}
ApplicationProperties.java looks like:
#Component
#Getter
#Setter
#ConfigurationProperties
public class ApplicationProperties {
String port;
}
Finally, application.properties file has:
port=1234
I have even tried passing ApplicationProperties instance from controller to service's default constructor, but it did not work. When I debug, value persist while application startup, but when I make a rest web service POST call, it is null.
In your controller MyController, you have to inject the service, replacing:
MyService ss = new MyService();
by:
#Autowired
MyService ss;
Also, instead of your ApplicationProperties class, you can use #Value annotation, from Spring, to load properties from application.properties file. Take a look at this code:
import org.springframework.beans.factory.annotation.Value;
// ...
#Service
public class MyService {
#Value("${port}")
private String port;
// ...
}
I had the same issue but for other reasons.
For me the solution was to add spring. before each parameter in application.properties.
So e.g. "spring.flyway.user=root".
Related
I have a standard rest-api spring-boot application.
Controller with injected service
#RestController
#RequestMapping("/foo")
#AllArgsConstructor
public class SomeController {
private final SomeService someService;
public void someMethod(){
someService.toDoSomething();
}
}
and service with injected other beans
#Setter
public class SomeService {
private AnotherVeryImportantBean anotherVeryImportantBean;
private RestTemplateBuilder restTemplate;
public void toDoSomething() {
anotherVeryImportantBean.someAction();
}
In my case, the bean AnotherVeryImportantBean is created in another dependency, which I connect to the spring-boot application. Whether to create a bean or not is decided by a variable in the application.yml file.
like this:
another.service.enabled: true
Of course I have config class for service
#Configuration
#RequiredArgsConstructor
public class SomeConfig {
private final AnotherVeryImportantBean anotherVeryImportantBean;
#Bean
public SomeService someService(RestTemplateBuilder restTemplate) {
SomeService foo = new SomeService();
foo.setAnotherVeryImportantBean(anotherVeryImportantBean);
foo.setRestTemplate(restTemplate);
return foo;
}
The problem is that this controller and service are not the only ones in the application. I would like the application not to crash completely if this particular controller and service are not formed. If the bean is not created for some reason, I just don't use that functionality (this controller).
At this point, the application crashes because AnotherService cannot be injected into someService (In case where, for some reason, it was not created).
I tried adding an annotation to config class
#ConditionalOnBean(AnotherVeryImportantBean.class)
like this:
#ConditionalOnBean(AnotherVeryImportantBean.class)
#Configuration
#RequiredArgsConstructor
public class SomeConfig {
private AnotherVeryImportantBean anotherVeryImportantBean;
#Bean
public SomeService someService(RestTemplateBuilder restTemplate) {
SomeService foo = new SomeService();
foo.setAnotherVeryImportantBean(anotherVeryImportantBean);
foo.setRestTemplate(restTemplate);
return foo;
}
But the problem is that conditional in SomeConfig checks if the bean is in the container before it is created.
How can I handle an error when a bean cannot inject another dependency into itself?
An existing utility exists that I am importing to my project. It is written similarly as these two classes
#Service
public class ServiceAccessorImpl implements ServiceAccessor {
#Autowired private ServiceConfiguration serviceConfiguration;
public Response executeCall(){
return callEndPoint(serviceConfiguration.getServiceEndPoint());
}
}
#Configuration
#Data //automatically adds getters and setters
#ConfigurationProperties(prefix="config")//pulls serviceEndPoint value from external config
//Assume external config has config.serviceEndPoint = "www.endpoint1.com"
public class ServiceConfiguration {
private String serviceEndPoint;
}
In a separate project below I am importing the above into my project. I would like to have two instances of the same service with two unique and respective configuration classes. so that service1 is linked to config1 and service2 is linked to config2. My reasoning is I want one instance that only pulls the endpoint from the external configuration and another instance that I can use to set the endpoint. I have tried using things like #Qualifier but I cant figure out how to link the correct config with the correct service. I have a feeling that this may not be possible because ServiceConfiguration is privately scoped within ServiceAccessorImpl and I have no access through setters or constructors.
Controller Endpoint. The below is psuedo code of how I would like to implement my design. Autowiring in a single instance and using either endpoint works for that endpoint but not for both as shown below.
#ComponentScan(basePackageClass = ServiceAccessorImpl.class)
public class ServiceAccessorController {
#Autowired private ServiceAccessor serviceAccessor1;
#Autowired private ServiceConfiguration serviceConfiguration1;
#Autowired private ServiceAccessor serviceAccessor2;
#Autowired private ServiceConfiguration serviceConfiguration2;
Response CallEndpoint1(){
//www.endpoint1.com is already set here from external config
return serviceAccessor1.executeCall();
}
Response CallEndpoint1(){
serviceConfiguration2.setServiceEndPoint("www.endpoint2.com")
return serviceAccessor2.executeCall();
}
}
Thank you in advance
If you need multiple instances of the same implementation, it's easier to not annotate it as a bean, and have a #Configuration provide the beans instead.
public class ServiceAccessorImpl implements ServiceAccessor {
private ServiceConfiguration serviceConfiguration;
public ServiceAccessorImpl(ServiceConfiguration configuration) {
this.serviceConfiguration = configuration;
}
public Response executeCall(){
return callEndPoint(serviceConfiguration.getServiceEndPoint());
}
}
// this one should just have #ConfigurationProperties, not #Configuration
#Data
#ConfigurationProperties(prefix="config")
public class ServiceConfiguration {
private String serviceEndPoint;
}
then in your service you can have a Configuration providing both instances:
#Configuration
public class BeansConfiguration {
#Qualifier("service1")
#Primary // optional, Spring will autowire this instance by default if no qualifier is given
#Bean
public service1(#Autowired ServiceConfiguration config) {
// use the default config bean
return new ServiceAccessorImpl(config);
}
#Qualifier("service2")
#Bean
public service2() {
return new ServiceAccessorImpl(new ServiceConfiguration("www.endpoint2.com"));
}
}
then you may consume both by using the qualifiers (note that you don't have to inject the configs here):
public class ServiceAccessorController {
#Autowired
private ServiceAccessor serviceAccessor1;
#Autowired
#Qualifier("service2")
private ServiceAccessor serviceAccessor2;
Response CallEndpoint1(){
return serviceAccessor1.executeCall();
}
Response CallEndpoint2(){
return serviceAccessor2.executeCall();
}
}
I try to test my spring app but encounter following problem:
In "normal mode"(mvn spring-boot:run) the app starts as expected and adapterConfig gets set and is NOT NULL. When I start my testclass to test the MVC, adapterConfig does not get set. Spring ignores the whole config class.
test:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = StudentController.class)
public class StudentControllerTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private StudentService service;
#MockBean
private StudentRepository repository;
#Test
public void shouldReturnABC() throws Exception{
MvcResult result = this.mockMvc.perform(get("/students/abc")).andReturn();
}
}
controller:
#RestController
#RequestMapping("/students")
#PermitAll
public class StudentController {
#Autowired
StudentService studentService;
//get
#GetMapping("/abc")
public String abc (){
return "abc";
}
config:
#Configuration
public class SpringBootKeycloakConfigResolver implements KeycloakConfigResolver {
private KeycloakDeployment keycloakDeployment;
private AdapterConfig adapterConfig;
#Autowired
public SpringBootKeycloakConfigResolver(AdapterConfig adapterConfig) {
this.adapterConfig = adapterConfig;
}
#Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
if (keycloakDeployment != null) {
return keycloakDeployment;
}
keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfig);
return keycloakDeployment;
}
}
adapterConfig is null when hitting the test but gets set & created when hitting it the normal way, any idea?
Using #WebMvcTest, the container will inject only components related to Spring MVC (#Controller, #ControllerAdvice, etc.) not the full configuration use #SpringBootTest with #AutoConfigureMockMvc instead.
Spring Boot Javadoc
Keycloak's AutoConfiguration is not included by #WebMvcTest.
You could
Include it manually via #Import(org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration.class)
Or use #SpringBootTest
with spring boot 2.5 i had I had to import KeycloakAutoConfiguration into my test.
#WebMvcTest(value = ApplicationController.class, properties = "spring.profiles.active:test")
#Import(KeycloakAutoConfiguration.class)
public class WebLayerTest {
// ... test code ....
}
I am new in Spring Boot. Does anyone know how to call the method amazonSQSClient() in the main class?
#Configuration
public class AWSConfiguration {
#Value("${aws.access.key.encrypted}")
private String amazonAWSAccessKey;
#Value("${aws.secret.key.encrypted}")
private String amazonAWSSecretKey;
#Value("${aws.region.encrypted}")
private String amazonAWSRegion;
#Bean
public AmazonSQS amazonSQSClient() {
AWSCredentials awsCredentials = new BasicAWSCredentials(amazonAWSAccessKey, amazonAWSSecretKey);
AmazonSQS client = AmazonSQSClientBuilder.standard()
.withRegion(amazonAWSRegion)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials)).build();
return client;
}
}
You shouldn't need to. That's the point of using the #Bean annotation, Spring will inject an instance of AmazonSQS whenever you need one. So, you might have a class like this that uses the SQS client:
#Service
public class QueueService {
private AmazonSQS amazonSQS;
#Autowired
public QueueService(AmazonSQS sqs) {
this.amazonSQS = sqs;
}
}
The Spring framework is smart enough to see that it needs to inject an instance of AmazonSQS, and by marking the method amazonSqsClient with the #Bean annotation, you are telling Spring to use that method whenever it needs to create an instance of AmazonSQS.
My configuration class where I initialize the hessian bean
#RefreshScope
#Configuration // #Component also not working
public class HessianConfiguration {
#Value("${sample.hessian.url}")
private String sampleUrl;
#Bean
public HessianProxyFactoryBean initSampleBean() {
HessianProxyFactoryBean invoker = new HessianProxyFactoryBean();
invoker.setServiceUrl(sampleUrl);
invoker.setServiceInterface(Sample.class);
return invoker;
}
}
And a sample component class where I use the bean.
#RefreshScope
#Component
public class SampleService {
#Autowired
Sample sample;
public String doRemoteOperation(String value){
return sample.doRemoteOperation(value);
}
}
sample.hessian.url is retrieved from spring config server.
But after changing the value of sample.hessian.url and calling "refresh" endpoint, the autowired sample bean is still trying to hit the old url value.
What am I doing wrong here ?