I am in the process of developing a common java library with reusable logic to interact with some AWS services, that will in turn be used by several consumer applications. For reasons outlined here, and the fact that Spring Boot seems to provide a lot of boilerplate free code for things like SQS integration, I have decided to implement this common library as a custom spring boot starter with auto configuration.
I am also completely new to the Spring framework and as a result, have run into a problem where my auto-configured class's instance variables are not getting initialized via the AutoWired annotation.
To better explain this, here is a very simplified version of my common dependency.
CommonCore.java
#Component
public class CommonCore {
#AutoWired
ReadProperties readProperties;
#AutoWired
SqsListener sqsListener; // this will be based on spring-cloud-starter-aws-messaging
public CommonCore() {
Properties props = readProperties.loadCoreProperties();
//initialize stuff
}
processEvents(){
// starts processing events from a kinesis stream.
}
}
ReadProperties.java
#Component
public class ReadProperties {
#Value("${some.property.from.application.properties}")
private String someProperty;
public Properties loadCoreProperties() {
Properties properties = new Properties();
properties.setProperty("some.property", someProperty);
return properties;
}
}
CoreAutoConfiguration.java
#Configuration
public class CommonCoreAutoConfiguration {
#Bean
public CommonCore getCommonCore() {
return new CommonCore();
}
}
The common dependency will be used by other applications like so:
#SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class SampleConsumerApp implements ApplicationRunner {
#Autowired
CommonCore commonCore;
public SampleConsumerApp() {
}
public static void main(String[] args) {
SpringApplication.run(SampleConsumerApp.class, args);
}
#Override
public void run(ApplicationArguments args) {
try {
commonCore.processEvents();
} catch (Exception e) {
e.printStackTrace();
}
}
}
The main problem I have like I mentioned, is the AutoWired objects in the CommonCore instance are not getting initialized as expected. However, I think the actual problems are more deeply rooted; but due to my lack of understanding of the Spring framework, I am finding it difficult to debug this on my own.
I am hoping for a few pointers along these points
Does this approach of developing a custom starter make sense for my use case?
What is the reason for the AutoWired dependencies to not get initialized with this approach?
Wild guess, but I think it's because of the order of how things are constructed. I am talking about this class:
#Component
public class CommonCore {
#AutoWired
ReadProperties readProperties;
#AutoWired
SqsListener sqsListener; // this will be based on spring-cloud-starter-aws-messaging
public CommonCore() {
Properties props = readProperties.loadCoreProperties();
//initialize stuff
}
processEvents(){
// starts processing events from a kinesis stream.
}
}
You are trying to use a Spring injected component in a constructor, but constructor is called before Spring can do its #Autowire magic.
So one option is to autowire as a constructor argument
Something like this (untested):
#Component
public class CommonCore {
private final ReadProperties readProperties;
private final SqsListener sqsListener; // this will be based on spring-cloud-starter-aws-messaging
#AutoWired
public CommonCore(SqsListener sqsListener, ReadProperties readProperties) {
this.readProperties = readPropertis;
this.sqsListener = sqsListener;
Properties props = readProperties.loadCoreProperties();
//initialize stuff
}
processEvents(){
// starts processing events from a kinesis stream.
}
}
Sidenote: I prefer to use dependency injection via constructor arguments always, wherever possible. This also makes unit testing a lot easier without any Spring specific testing libraries.
Related
I have a bean with a constructor as follows. The password argument is resolved from the placeholder my.password, with a default value of DEFAULT. If the value of DEFAULT is passed, a warning is logged. Note - this Bean is contained within an imported third-party library.
#Bean
public class EncryptionBean {
public EncryptionBean(#Value("${my.password}") String password) {
if "DEFAULT".equals(password) {
// log warning message
} else {
// do stuff with the password
}
}
}
The password is retrieved at startup from an external system using a client SDK. This SDK object is itself provided as a Bean (also from a third-party library). After retrieving the password, I am setting it as a System property for the above EncryptionBean to have access to at the time of instantiation:
#Configuration
public class MyConfiguration {
#Autowired
public SDKObject sdkObject;
#PostConstruct
public void init() {
System.setProperty("my.password", sdkObject.retrievePassword());
// #Value("${my.password"}) should now be resolvable when EncryptionBean is instantiated
}
}
However, EncryptionBean is still being instantiated with a value of DEFAULT for my.password. I'm wondering if System.setProperty in #PostConstruct might be getting executed AFTER Spring has already instantiated the instance of EncryptionBean?
If so, is there a way to guarantee this property has been set before Spring instantiates EncryptionBean? I came across #DependsOn as a way to control the order Beans get instantiated by Spring, but since EncryptionBean comes from a third-party library, I haven't been able to make this annotation work.
Instead of setting a system property, you should create a Spring EnvironmentPostProcessor class to retrieve the password from the external source and add it to the Spring Environment. That would look something like this:
public class PasswordEnvironmentPostProcessor implements EnvironmentPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
SDKObject sdkObject = applicationContext.getBean(SDKObject.class);
Map<String, Object> properties = Collections.singletonMap("my.password", sdkObject.retrievePassword());
MapPropertySource propertySource = new MapPropertySource("password", properties);
environment.getPropertySources().addFirst(propertySource);
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Then you'll need to register this class with Spring by adding an entry to the file META-INF/spring.factories that looks like this:
org.springframework.boot.env.EnvironmentPostProcessor=com.example.PaswordEnvironmentPostProcessor
Documentation for this is available here: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.application.customize-the-environment-or-application-context.
I could not figure out a nice clean way to inject a property at runtime without a lot of boilerplate. But I came up with a clean way to do what you want to do without having to refresh the application context or messing with the 3rd party library implementation.
First we exclude the 3rd party bean from our application context:
#ComponentScan(excludeFilters = #ComponentScan.Filter(value = EncryptionBean.class, type = FilterType.ASSIGNABLE_TYPE))
#SpringBootApplication
public class SandboxApplication {
public static void main(String[] args) {
SpringApplication.run(SandboxApplication.class, args);
}
}
Then we create the Bean ourselves with the values we want.
#Configuration
public class MyConfiguration {
public final SDKObject sdkObject;
public MyConfiguration(SDKObject sdkObject) {
this.sdkObject = sdkObject;
}
#Bean
public EncryptionBean encryptionBean() {
return new EncryptionBean(sdkObject.retrievePassword());
}
}
I have a spring-boot app that now needs to support multiple Object stores and selectively use the desired store based on the environment. Essentially what i have done is create an interface that each store repository then implements.
I have simplified the code for the examples.
I have created 2 beans for each store type based on the spring profile determining the env:
#Profile("env1")
#Bean
public store1Sdk buildClientStore1() {
return new store1sdk();
}
#Profile("env2")
#Bean
public store2Sdk buildClientStore2() {
return new store2sdk();
}
in the service layer I have autowired the interface and then in the repositories i have used #Profile to specify which instance of the interface to use.
public interface ObjectStore {
String download(String fileObjectKey);
...
}
#Service
public class ObjectHandlerService {
#Autowired
private ObjectStore objectStore;
public String getObject(String fileObjectKey) {
return objectStore.download(fileObjectKey);
}
...
}
#Repository
#Profile("env1")
public class Store1Repository implements ObjectStore {
#Autowired
private Store1Sdk store1client;
public String download(String fileObjectKey) {
return store1client.getObject(storeName, fileObjectKey);
}
}
When I start the application with the configured "env" this actually runs as expected. however when running the test I get the "no qualifying bean of type ObjectStore. expected at least 1 bean which qualifies as autowire candidate."
#ExtendWith({ SpringExtension.class })
#SpringBootTest(classes = Application.class)
#ActiveProfiles("env1,test")
public class ComposerServiceTest {
#Autowired
private ObjectHandlerService service;
#Test
void download_success() {
String response = service.getObject("testKey");
...
}
}
As noted in the #ActiveProfile on the test class there are some other environments e.g. dev,test,prod. I have tried playing around with Component scan, having impl and interface in the same package, etc, to no success. I feel like I am missing something obvious with the test setup. But could be something with my overall application config? my main aim with the solution is to avoid having something a long the lines of
if (store1Sdk != null) {
store1Sdk.download(fileObjectKey);
}
if (store2Sdk != null) {
store2Sdk.download(fileObjectKey);
}
Try #ActiveProfiles({"env1", "test"}).
Activate multiple profiles using #ActiveProfiles and specify profiles as an array.
this probrom because Store1Repository use #Profile("env1"), when you use #test,this class not invoke. try delete #Profile("env1") of Store1Repository.
if you use #test, both of store1Sdk/store2Sdk don't instanse, try add default instanse.eg:
#Bean
public store2Sdk buildClientStoreDefault() {
return new store2sdk();
}
I'm trying to learn the basics of Spring and have defined this class?
public class Processor {
public Processor() {
this.setup();
}
private void setup(){
//run some setup code
}
public String processString(String str) {
// process the string
return str;
}
}
I want to Spring enable this class so I use a factory bean:
Reading https://www.baeldung.com/spring-factorybean I use:
public class Processor implements FactoryBean<Processor> {
#Override
public Processor getObject() throws Exception {
return new Processor();
}
#Override
public Class<?> getObjectType() {
return Processor.class;
}
}
To Test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ProcessorFactory.class)
public class ProcessorTest {
#Autowired
private Processor processor;
#Test
public void testProcessor() {
//Some tests
}
}
This works as expected.
When I try to use
#Autowired
private Processor processor;
elsewhere in my project I receive compile-time error :
Could not autowire. No beans of 'Processor' type found.
Have I not setup the factory correctly? I should annotate the Processor object to indicate it is to be autowired ? Perhaps this is not a valid use case for Factory ?
In general, factory beans are pretty outdated, first spring versions indeed used this approach, but spring has evolved since that time.
The Factory bean itself should be registered in the spring configuration (the very first versions of spring used xml based configuration because Java Annotation did not exist at that time) so the tutorial contains the XML configuration example. Anyway, that's probably the reason of failure. In the test you should also specify the path to the xml configuration otherwise the factory bean won't be loaded.
You can use these factory beans (they're still supported) but they have the following downsides:
They couple your code to the spring framework
A lot of boilerplate (usually in typical application there can be hundreds of beans, so creating a Factory Bean for each one is an overkill).
So you can:
Instead of using Factory Beans, annotate the Processor with #Component annotation (or more specialized #Service).
Alternatively Use Java configuration:
#Configration
public class MyConfig {
#Bean
public Processor processor() {
// optionally slightly customize the creation of the class if required;
return new Processor();
}
}
I am searching for a way to read and parse a lot of data when the spring boot app is starting and be able to use these data later in other classes.
I started with a class DataRepository.java and annotated it with #Service to be able to inject it later. I'm planning to read the data here and to inject it in any other class I need the data.
But how can I achieve to parse the data just once and at app startup? The spring boot app should only be reachable if the parsing is done.
Your approach with #Service is 100% appropriate.
By default all beans are singletons, so if you parse data on bean creation (in constructor) it will be parsed only once, and this info can be used in other beans by simple injection.
Please note that if during data parsing you have to use other beans, you should be confident that all beans are completely constructed. For that you should use approach proposed by #jreznot:
https://stackoverflow.com/a/51783858/5289288
You can use ContextStartedEvent and handle it:
#Component
public class ContextStartedListener implements ApplicationListener<ContextStartedEvent> {
#Override
public void onApplicationEvent(ContextStartedEvent cse) {
System.out.println("Handling context start event. ");
}
}
See also: https://www.baeldung.com/spring-events
By default all beans in spring context are singletons. Spring guarantees that it will creates a bean just ones during context loading.
For example if you will have few contexts in your application it creates one instance for every context.
If you have just one context you can use these approaches:
initialize data in constructor. Data will initialized and ready to
use just after bean's instance creation.
#Component
public class DataRepository {
public DataRepository() {
... init data
}
}
use #Bean annotation withinit method. Allows you don't stick to Spring in
your data repository and initialize data after all beans were created.
public class DataRepository {
public void init() {
... init data
}
}
#Configuration
public class DataRepositoryConfiguration {
#Bean(initMethod = "init")
public DataRepository dataRepository() {
return new DataRepository();
}
use #Bean annotation and invoke init method. Allows you don't stick to
Spring in your data repository, but #Autowired field will uninitialized.
public class DataRepository {
public void init() {
... init data
}
}
#Configuration
public class DataRepositoryConfiguration {
#Bean
public DataRepository dataRepository() {
DataRepository dr = new new DataRepository();
dr.init();
return dr;
}
}
use #PostConstruct annotation. Initialize data after all beans was
created.
public class DataRepository {
#PostConstruct
public void init() {
... init data
}
}
Exception thrown during initializing will stop Spring's context initializing
You can use PostConstruct on any bean. For example
#Component
class DataLoad {
......
......
#PostConstruct
public void parseData() {
...... do your stuff here.......
}
}
With this the code inside parseData will be called only once. This is a very common way to do things in scenarios like when you want to load some configuration data from database at the start of the application and do it only once. In these cases you can #Autowired the repository class to the same class and use that in your #PostConstruct method and get data
I'm trying to write tests for an application that uses #RefreshScope. I would like to add a test that actually changes out properties and asserts that the application responds correctly. I have figured out how to trigger the refresh (autowiring in RefreshScope and calling refresh(...)), but I haven't figured out a way to modify the properties. If possible, I'd like to write directly to the properties source (rather than having to work with files), but I'm not sure where to look.
Update
Here's an example of what I'm looking for:
public class SomeClassWithAProperty {
#Value{"my.property"}
private String myProperty;
public String getMyProperty() { ... }
}
public class SomeOtherBean {
public SomeOtherBean(SomeClassWithAProperty classWithProp) { ... }
public String getGreeting() {
return "Hello " + classWithProp.getMyProperty() + "!";
}
}
#Configuration
public class ConfigClass {
#Bean
#RefreshScope
SomeClassWithAProperty someClassWithAProperty() { ...}
#Bean
SomeOtherBean someOtherBean() {
return new SomeOtherBean(someClassWithAProperty());
}
}
public class MyAppIT {
private static final DEFAULT_MY_PROP_VALUE = "World";
#Autowired
public SomeOtherBean otherBean;
#Autowired
public RefreshScope refreshScope;
#Test
public void testRefresh() {
assertEquals("Hello World!", otherBean.getGreeting());
[DO SOMETHING HERE TO CHANGE my.property TO "Mars"]
refreshScope.refreshAll();
assertEquals("Hello Mars!", otherBean.getGreeting());
}
}
You could do this (I assume you mistakenly omitted the JUnit annotations at the top of your sample, so I'll add them back for you):
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
public class MyAppIT {
#Autowired
public ConfigurableEnvironment environment;
#Autowired
public SomeOtherBean otherBean;
#Autowired
public RefreshScope refreshScope;
#Test
public void testRefresh() {
assertEquals("Hello World!", otherBean.getGreeting());
EnvironmentTestUtils.addEnvironment(environment, "my.property=Mars");
refreshScope.refreshAll();
assertEquals("Hello Mars!", otherBean.getGreeting());
}
}
But you aren't really testing your code, only the refresh scope features of Spring Cloud (which are already tested extensively for this kind of behaviour).
I'm pretty sure you could have got this from the existing tests for refresh scope as well.
Properties used in the application must be variables annotated with #Value. These variables must belong to a class that is managed by Spring, like in a class with the #Component annotation.
If you want to change the value of the properties file, you can set up different profiles and have various .properties files for each profile.
We should note that these files are meant to be static and loaded once, so changing them programmatically is sort of out of the scope of ther intended use. However, you could set up a simple REST endpoint in a spring boot app that modifies the file on the host's file system (most likely in the jar file you are deploying) and then calls Refresh on the original spring boot app.