Spring annotations, read properties - java

I have small test project to test Spring annotations:
where in nejake.properties is:
klucik = hodnoticka
and in App.java is:
#Configuration
#PropertySource("classpath:/com/ektyn/springProperties/nejake.properties")
public class App
{
#Value("${klucik}")
private String klc;
public static void main(String[] args)
{
AnnotationConfigApplicationContext ctx1 = new AnnotationConfigApplicationContext();
ctx1.register(App.class);
ctx1.refresh();
//
App app = new App();
app.printIt();
}
private void printIt()
{
System.out.println(klc);
}
}
It should print hodnoticka on console, but prints null - String value is not initialized. My code is bad - at the moment I have no experience with annotation driven Spring. What's bad with code above?

You created the object yourself
App app = new App();
app.printIt();
how is Spring supposed to manage the instance and inject the value?
You will however need
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
to make the properties available. Also, because the App bean initialized for handling #Configuration is initialized before the resolver for #Value, the value field will not have been set. Instead, declare a different App bean and retrieve it
#Bean
public App appBean() {
return new App();
}
...
App app = (App) ctx1.getBean("appBean");

You need to access the property from a Spring bean, and you need to properly wire in the properties. First, add to your config class this:
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() {
PropertySourcesPlaceholderConfigurer props = new PropertySourcesPlaceholderConfigurer();
props.setLocations(new Resource[] { new ClassPathResource("com/ektyn/springProperties/nejake.properties") }); //I think that's its absolute location, but you may need to play around with it to make sure
return props;
}
Then you need to access them from within a Spring Bean. Typically, your config file should not be a bean, so I would recommend you make a separate class, something like this:
#Component //this makes it a spring bean
public class PropertiesAccessor {
#Value("${klucik}")
private String klc;
public void printIt() {
System.out.println(klc);
}
}
Finally, add this to your config to make it find the PropertiesAccessor:
#ComponentScan("com.ektyn.springProperties")
Then you can access the PropertiesAccessor bean from your app context and call its printIt method.

Related

Spring Boot - Property placeholders remain unresolved despite setting values with System.setProperty

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());
}
}

Resolving 'The process engine you are trying to access does not exist'

i'm running embedded camunda engine in my application. Now i would like to run second camunda engine with cockpit on different container with the same database. What i did is basically copy-paste of my main applciation configuration only switched dependency from camunda-bpm-spring-boot-starter to camunda-bpm-spring-boot-starter-webapp. I can acess cockpits main page but i'm immediately prompted The process engine you are trying to access does not exist and i don't understand why? On startup i can see that mySpringProcessEngineConfiguration bean is created as well as ProcessEngineFactoryBean bean.
However:
BpmPlatform.getProcessEngineService().getProcessEngineNames();
returns empty set.
Could you please have a look and point my mistake?
main app class:
#SpringBootApplication
public class CamundaCockpitApplication {
public static void main(String[] args) {
SpringApplication.run(CamundaCockpitApplication.class, args);
BpmPlatform.getProcessEngineService().getProcessEngineNames();
}
Camunda confing:
#Configuration
#RequiredArgsConstructor
public class EngineConfiguration {
private final DataSource dataSource;
private final PlatformTransactionManager transactionManager;
private final ResourcePatternResolver resourcePatternResolver;
#Bean
public SpringProcessEngineConfiguration springProcessEngineConfiguration() {
SpringProcessEngineConfiguration springConfiguration = new SpringProcessEngineConfiguration();
springConfiguration.setDataSource(dataSource);
springConfiguration.setTransactionManager(transactionManager);
springConfiguration.setDatabaseSchemaUpdate("false");
springConfiguration.setJobExecutorActivate(false);
springConfiguration.setHistory("full");
springConfiguration.setJdbcBatchProcessing(false);
return springConfiguration;
}
#Bean
public ProcessEngineFactoryBean processEngineFactoryBean() {
ProcessEngineFactoryBean engine = new ProcessEngineFactoryBean();
engine.setProcessEngineConfiguration(springProcessEngineConfiguration());
return engine;
}
}
You need to add #EnableProcessApplication annotation to your main class.
see https://docs.camunda.org/manual/7.9/user-guide/spring-boot-integration/process-applications/

SpringBoot - Register a bean before #Component's get scanned

I have a component Login that depends on ValidatorService. ValidatorService is being injected/autowired in the Login constructor. ValidationServiceImpl is provided by an external API, so I can't just annotate it as #Service.
#Component
class Login {
#Autowire
public Login (ValidatorService validator) {
}
}
#SpringBootApplication
public class Starter {
public static void main(String[] args)
{
SpringApplication.run(Starter.class, args);
}
}
I'm looking for a way to register ValidatorService as a bean before #Components get scanned. Is there a way to get ApplicationContext instance before starting the application?
SpringBoot 2.0.4.RELEASE
UPDATE
I need to pass a validationId that I'll get from main(args) to this external API.
public static void main(String[] args) {
String validationId = args[0];
ValidatorService service = ExternalValidationAPI.getValidationServiceImp(validationId);
}
You should be able to declare it as a bean as such in one of your configuration classes:
#Bean
public ValidatorService validatorService(){
return new ValidatorServiceImpl();
}
This will then autowire in the ValidatorService implementation class at the point it is needed. This method needs to go in an #Configuration class (your Starter class is one).
There's a good example of how to do this here.
I believe you can solve your problem with the help of the #Configurable annotation.
Annotate your Login class with #Configurable instead of #Componenet, and when the ValidatorService object becomes available, you can initiate the Login object with it.
You need to define a ValidationService bean :
#Configuration
public class ValidationServiceConfig {
#Bean
public ValidationService validationService(#Value("${validationId}") String validationId) {
return new ValidationServiceImpl(validationId);
}
}
and run the program this way : java -jar program.jar --validationId=xxx
I solved my problem creating a Configuration class and declaring a Bean to handle the instantiation of the external service that will be injected later (as some people have suggested). In order to retrieve the program arguments I autowired DefaultApplicationArguments to retrieve program arguments with getSourceArgs():
#Configuration
public class ValidatorConfig {
#Autowired
DefaultApplicationArguments applicationArguments;
#Bean
public ValidatorService validatorService()
{
String validationId = applicationArguments.getSourceArgs()[0];
return ExternalValidationAPI.getValidationServiceImp(validationId);
}

Injecting library class as dependencies in spring project

I have multiple library classes in my project which need to be injected into a service class. This is the error statement for IntegrationFactory class:
Consider defining a bean of type 'com.ignitionone.service.programmanager.integration.IntegrationFactory' in your configuration.
This error is coming on almost every injection where this library class is injected.
I have already added the Library package in #ComponentScan, but, as it is read-only file, I can not annotate the library class. I came to know from some answer here that Spring can not inject classes which it does not manage. This library is not built on spring.
I have tried to create a #Bean method which returns the IntegrationFactory(class in question) in the class where #Inject is used, but this too does not seem to work.
How can this be done, preferably without creating a stub/copy class?
This is EngagementServiceImpl class snippet:
#Inject
public EngagementServiceImpl(EngagementRepository engagementRepository,
#Lazy IntegrationFactory integrationFactory, TokenRepository tokenRepository,
EngagementPartnerRepository engagementPartnerRepository, MetricsService metricsService) {
this.engagementRepository = engagementRepository;
this.integrationFactory = integrationFactory;
this.tokenRepository = tokenRepository;
this.engagementPartnerRepository = engagementPartnerRepository;
this.metricsService = metricsService;
}
This is injection part:
#Autowired
private EngagementService engagementService;
This is ConfigClass:
#Configuration
public class ConfigClass {
#Bean
public IntegrationFactory getIntegrationFactory(){
Map<String, Object> globalConfig = new HashMap<>();
return new IntegrationFactory(globalConfig);
}
#Bean
#Primary
public EntityDataStore getEntityDataStore(){
EntityModel entityModel = Models.ENTITY;
return new EntityDataStore(this.dataSource(), entityModel );
}
#ConfigurationProperties(prefix = "datasource.postgres")
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.build();
}
}
You need to add your bean definitions in a configuration class.
#Configuration
public class ServiceConfig {
#Bean
public IntegrationFactory getIntegrationFactory(){
// return an IntegrationFactory instance
}
}
Then you have to make sure your #Configuration class gets detected by Spring, either by having it within your scanned path or by manually importing it via #Import from somewhere withing you scanned path. An example of #Import, considering you are using Spring Boot.
#Import(ServiceConfig.class)
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Hope this helps!
Your Bean IntegrationFactory can't be found, as it is not annotated with any Spring stereotype and therefore not recognized by the component scan.
As you have multiple options to provide an instance of your class to the application context, read the Spring documentation (which also includes samples) to find out which one fits you the most:
https://docs.spring.io/spring/docs/5.1.0.RELEASE/spring-framework-reference/core.html#beans-java-basic-concepts
One Option would be to create a factory which provides an instance of your class to the application context, like it is stated in the documentation:
#Configuration
public class AppConfig {
#Bean
public IntegrationFactory myIntegrationFactory() {
return new IntegrationFactory();
}
}
Do not forget to add the Configuration to the application context.

Spring Boot - Use Application Listener

After starting my spring boot application I want to start an customer process like creating required folders, files, etc. For that I'm using ApplicationListener<ApplicationReadyEvent>. This works like expected. But I'm building my spring application context with SpringApplicationBuilder. Every child notifies that the application is started correctly. So my customer post-process startes even more than one time.
#SpringBootApplication
#EnableConfigurationProperties(value = {StorageProperties.class})
#EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplicationBuilder parentBuilder
= new SpringApplicationBuilder(Application.class);
parentBuilder.child(Config1.class)
.properties("server.port:1443")
...
.run(args);
parentBuilder.child(Config2.class)
.properties("server.port:2443")
...
.run(args);
}
}
My first idea was, that I can create manuelly a new Bean with #Bean in Config1 for my Event-Listener. But I was not able to overhand the configuration file StorageProperties.class, which is necessary for this class.
Because the Listener has an constructor based dependency injection:
private final Path mPathTo;
public AfterStart(StorageProperties prop) {
this.mPathTo = Paths.get(prob.getPath());
}
How can I be able to start the listener just once per start?
For everyone who is interested in this question. This solution worked for me:
public void onApplicationEvent(ApplicationReadyEvent e) {
if (e.getApplicationContext().getParent == null) {
System.out.println("******************************");
System.out.println("Post-process begins.");
System.out.println("******************************");
}
}

Categories