Spring - Create scheudled tasks from external files - spring

I have a spring boot application that reads configuration files from storage.
I want that for each file - a scheduled task will be created.
The files are read and stored into a Pojo MyFileData and i have a bean that returns List<MyFileData>
I have read about SchedulingConfigurer but I don't understand how i can get the bean value inside the configureTasks method.
#Configuration
public class FirstConfigFile {
#Bean
public List<MyFileData> myFileDataList() {
List<MyFileData> myConfigFiles = new ArrayList();
// read from storage
return myConfigFiles;
}
}
#Configuration
public class SecondConfigFile implements SchedulingConfigurer {
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// how do i get the myFileDataList bean?
for(MyFileData fd : myFileDataList) {
// create scheduled tasks and regiester them in the taskRegistrar
}
}
}
Note: I prefer to keep the #Configuration classes separate.

You can #Autowired that List as it is declared as #Bean.

Related

Spring #Bean - creation of Spring Bean depends on service class name

I use Spring 4.2.8 and I do have the service class below. If this class has the name ScheduleEmailCreateAndSendServiceImpl than everything works fine (method generalEmailMessage is invoked at start time in order to create the Spring Bean)
If I rename this class to EmailCreateAndSendServiceImpl than method generalEmailMessage will not be invoked at start time - does anyone know why?
#Service("emailCreateAndSendService")
public class ScheduleEmailCreateAndSendServiceImpl extends AbstractService
implements EmailService {
protected EmailMessage generalMessage;
#Override
public void createAndSendMessage(String receiver, boolean emailActive, Object[] values) throws BusinessException {
// create and send email
}
#Bean
public EmailMessage generalEmailMessage() {
this.generalMessage = new GeneralEmailInformationMessage();
return generalMessage;
}
}
[EDIT]
with this code it is the same
#Configuration
public #Data class ScheduleGeneralEmailConfiguration {
protected EmailMessage generalMessage;
public ScheduleGeneralEmailConfiguration() {
System.out.println("asdf");
}
#Bean
public EmailMessage generalEmailMessage() {
this.generalMessage = new GeneralEmailInformationMessage();
return generalMessage;
}
}
#Bean annotated methods should be in #Configuration annotated class.
You can also put the #Bean annotated methods in the main class of the Spring Boot application annotated with #SpringBootApplication which encapsulates #Configuration, #EnableAutoConfiguration, and #ComponentScan annotations.
Make sure your #Configuration annotated class is placed in the same package or sub package of the Spring Boot Application class

how to avoid using context.getbean in spring

There have been several arguments around not using ApplicationContext.getBean() to get a bean reference, of which most are based on logic that it violates the principles of Inversion of control.
Is there a way to get reference to prototype scoped bean without calling context.getBean() ?
Consider to use Spring Boot!
Than you can do something like this...
Runner:
#SpringBootApplication
public class Runner{
public static void main(String[] args) {
SpringApplication.run(Runner.class, args);
}
}
Some Controller:
#Controller
public class MyController {
// Spring Boot injecting beans through #Autowired annotation
#Autowired
#Qualifier("CoolFeature") // Use Qualifier annotation to mark a class, if for example
// you have more than one concreate class with differant implementations of some interface.
private CoolFeature myFeature;
public void testFeature(){
myFeature.doStuff();
}
}
Some cool feature:
#Component("CoolFeature") // To identify with Qualifier
public class CoolFeature{
#Autowired
private SomeOtherBean utilityBean;
public void doStuff(){
// use utilityBean in some way
}
}
No XML files to handle.
We can still access context for manual configurations if needed.
Suggested reading:
Spring Boot Reference
Pro Spring Boot
This type of problem can be solved using method injection, which is described in more detail here: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-method-injection
This is the most common approach to create prototype bean:
abstract class MyService {
void doSome() {
OtherService otherService = getOtherService();
}
abstract OtherService getOtherService();
}
#Configuration
class Config {
#Bean
public MyService myService() {
return new MyService() {
OtherService getOtherService() {
return otherService();
}
}
}
#Bean
#Scope("prototype")
public OtherService otherService() {
return new OtherService();
}
}

Properties in Spring Environment

I'm trying to create a custom property placeholder.
At my Spring Configuration Class I have this:
#Configuration
#Import(ConfigurationClass2.class)
public class ConfigurationClass1 {
#Bean
#Lazy(false)
public static PropertySourcesPlaceholderConfigurer settings() throws IOException {
// custom load of my properties file
// legacy configuration, comes from a SVN repository
// I need to download it and then do some logic and finally
// load it.
}
}
At another configuration class I want to access some properties using the Environment, like this:
#Configuration
#EnableScheduling
public class ConfigurationClass2 {
#Autowired
private Environment env;
#Bean
public DataSource dataSource() {
String jdbcUrl = env.getProperty('jdbc.url');
.... // jdbcUrl is null!!!!
}
#Scheduled(cron = "${cronExpression}")
public void worker() {
}
}
I don't want to use #Value or #PropertySource.
I need to load this properties manually and access this values programmatically!
How can I solve this?

Inject test beans into main method

I'm using JavaConfig to manage and wire Spring beans into my Java app. The Java application is a main method - and basically runs as a batch job, invoked via a bash file. Is there a way that I can use a different (test) config in my main method?
public static void main(String[] args) {
final ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
// do Stuff
}
I have used the following annotations successfully before in my test classes:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { TestConfig.class })
, but this does not work for "main" applications. Short of passing in the Spring context to use as an argument, not sure what I can do here. Thanks
You should be able to use profiles in your actual config class to do what you want as well.
By setting the desired Profile you can "inject" the different beans you want.
Your ApplicationConfig might look like:
#Configuration
#Import({
JndiDataConfig.class,
TestDataConfig.class,
)
public class ApplicationConfig {
...
where TestDataConfig looks (in part) like:
#Configuration
#Profile("test")
public class TestDataConfig {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
and where JndiDataConfig looks like:
#Configuration
#Profile("production")
public class JndiDataConfig {
#Bean
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}

Custom spring property source does not resolve placeholders in #Value

I'm trying to build a Spring 3.1 PropertySource which reads its values from Zookeeper nodes. For connecting to Zookeeper I am using Curator from Netflix.
For that I've built a custom property source which reads the value of a property from Zookeeper and returns it. This works fine when I am resolving the property like this
ZookeeperPropertySource zkPropertySource = new ZookeeperPropertySource(zkClient);
ctx.getEnvironment().getPropertySources().addLast(zkPropertySource);
ctx.getEnvironment().getProperty("foo"); // returns 'from zookeeper'
However, when I try to instantiate a bean which has a field with an #Value annotation then this fails:
#Component
public class MyBean {
#Value("${foo}") public String foo;
}
MyBean b = ctx.getBean(MyBean.class); // fails with BeanCreationException
This problem has most likely nothing to do with Zookeeper but with the way I'm registering the property sources and creating the beans.
Any insight is highly appreciated.
Update 1:
I'm creating the app context from an XML file like this:
public class Main {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.registerShutdownHook();
}
}
The class which connects to Zookeeper is a #Component.
#Component
public class Server {
CuratorFramework zkClient;
public void connectToZookeeper() {
zkClient = ... (curator magic) ...
}
public void registerPropertySource() {
ZookeeperPropertySource zkPropertySource = new ZookeeperPropertySource(zkClient);
ctx.getEnvironment().getPropertySources().addLast(zkPropertySource);
ctx.getEnvironment().getProperty("foo"); // returns 'from zookeeper'
}
#PostConstruct
public void start() {
connectToZookeeper();
registerPropertySource();
MyBean b = ctx.getBean(MyBean.class);
}
}
Update 2
This seems to work when I'm using XML-less configuration, i.e. #Configuration, #ComponentScan and #PropertySource in combination with an AnnotationConfigApplicationContext. Why doesn't it work with a ClassPathXmlApplicationContext?
#Configuration
#ComponentScan("com.goleft")
#PropertySource({"classpath:config.properties","classpath:version.properties"})
public class AppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Answering to your Update 2: This does not work with your original configuration(registering a PropertySource using #PostConstruct) because the PropertySource is being registered very late, by this time your target bean has already been constructed and initialized.
Typically the injection of the placeholders happens via a BeanFactoryPostProcessor which is very early in the Spring lifecycle(beans have not been created at this stage) and if a PropertySource is registered at that stage, then placeholders should be resolved.
The best approach though is to use a ApplicationContextInitializer, get a handle on the applicationContext and to register the propertySource there:
public class CustomInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext> {
public void initialize(ConfigurableWebApplicationContext ctx) {
ZookeeperPropertySource zkPropertySource = new ZookeeperPropertySource(zkClient);
ctx.getEnvironment().getPropertySources().addFirst(zkPropertySource);
}
}

Resources