I am making a Spring application in which I'm sending data to kinesis. I have the accessKey
and secretKey
stored in application.properties. In the service implementation class I am using these variables but im getting an error that access key cannot be null.
The ProducerServiceImpl class:
public class ProducerServiceImpl extends ProducerService {
private static final Logger LOG = LoggerFactory.getLogger(ProducerServiceImpl.class);
@Value(value = "${aws.stream_name}")
private String streamName;
@Value(value = "${aws.region}")
private String awsRegion;
@Value(value = "${aws.access_key}")
private String awsAccessKey;
@Value(value = "${aws.secret_key}")
private String awsSecretKey;
private KinesisProducer kinesisProducer = null;
public ProducerServiceImpl() {
this.kinesisProducer = getKinesisProducer();
}
private KinesisProducer getKinesisProducer() {
if (kinesisProducer == null) {
BasicAWSCredentials awsCreds = new BasicAWSCredentials(awsAccessKey, awsSecretKey);
KinesisProducerConfiguration config = new KinesisProducerConfiguration();
config.setRegion(awsRegion);
config.setCredentialsProvider(new AWSStaticCredentialsProvider(awsCreds));
config.setMaxConnections(1);
config.setRequestTimeout(6000); // 6 seconds
config.setRecordMaxBufferedTime(5000); // 5 seconds
kinesisProducer = new KinesisProducer(config);
}
return kinesisProducer;
}
I think the reason is that because after the function, the constructor is called first, the variables are not being assigned the value from @Value.
CodePudding user response:
If you're using the @Value
annotation on fields, Spring will use field injection. This means that it first needs to create an instance of ProducerServiceImpl
(by calling your constructor), and then it will use reflection to initialize those fields annotated with @Value
.
So, since your constructor is invoked before the values are injected, they will be null
.
There are two basic solutions, one is to use constructor injection:
public class ProducerServiceImpl extends ProducerService {
// ... Fields
// Pass @Value as constructor parameters
public ProducerServiceImpl(@Value(value = "${aws.access_key}") String awsAccessKey) {
// Set the fields before calling getKinesisProducer()
// If you only need the fields to create the producer, you could also just pass them as arguments to the getKinesisProducer() function
this.awsAccessKey = awsAccessKey;
this.kinesisProducer = getKinesisProducer();
}
// ...
}
The other solution is to wait until Spring has initialized the bean. This can be done by moving the logic to a method annotated with @PostConstruct
:
public class ProducerServiceImpl extends ProducerService {
// ... Fields
// Replace the constructor by a method annotated with @PostConstruct
@PostConstruct
public void initializeKinesisProducer() {
this.kinesisProducer = getKinesisProducer();
}
// ...
}
However, the prefered solution is to move the entire KinesisProducer
setup to a Spring configuration class, and provide it as a bean. Since all Spring beans are singletons, you can get rid of that initialization code. For example:
// Create a new @Configuration class
@Configuration
public class KinesisConfiguration {
// Create a @Bean method to construct your KinesisProducer
@Bean
// Pass all @Value's as parameters
public KinesisProducer kinesisProducer(@Value("${aws.access_key} String awsAccessKey) {
BasicAWSCredentials awsCreds = new BasicAWSCredentials(awsAccessKey, awsSecretKey); KinesisProducerConfiguration config = new KinesisProducerConfiguration();
config.setRegion(awsRegion);
config.setCredentialsProvider(new AWSStaticCredentialsProvider(awsCreds));
config.setMaxConnections(1);
config.setRequestTimeout(6000); // 6 seconds
config.setRecordMaxBufferedTime(5000); // 5 seconds
return new KinesisProducer(config);
}
}
Now you can delete all that code in your service and use:
public class ProducerServiceImpl extends ProducerService {
// Inject your KinesisProducer, either through field injection or constructor injection
@Autowired
private KinesisProducer kinesisProducer;
}
CodePudding user response:
I would suggest moving your @Value
configuration properties to another class using @ConfigurationProperties
and inject that in your constructor.