I have a Spring Boot project that is going to be using Quartz to manage the running of some scripts. The project layout is as follows:
scheduler
|
|__scheduler-api
| |
| |__quartz-bean
|
|__scheduler-composition
|
|__service-to-schedule-quartz-jobs-using-quartz-bean
The api module is a Spring Boot application where the quartz bean lives. The composition module is where my services live that will be used to add jobs and triggers to Quartz. The problem I am running into is that my Quartz bean is not accessible from the composition module, therefore I am not able to schedule jobs in my service like I'd want to. My Quartz bean is defined as follows:
#Configuration
class QuartzScheduler {
#Autowired
private val applicationContext: ApplicationContext? = null
#Autowired
private val databaseConfiguration: DatabaseConfiguration? = null
#Bean
fun springBeanJobFactory(): SpringBeanJobFactory {
val jobFactory = AutoWiringSpringBeanJobFactory()
jobFactory.setApplicationContext(applicationContext!!)
return jobFactory
}
#Bean
#Throws(SchedulerException::class)
fun scheduler(#Qualifier("schedulerFactoryBean") factory: SchedulerFactoryBean): Scheduler {
val scheduler = factory.scheduler
scheduler.start()
return scheduler
}
#Bean
#Throws(IOException::class)
fun schedulerFactoryBean(): SchedulerFactoryBean {
val factory = SchedulerFactoryBean()
factory.setDataSource(databaseConfiguration!!.dataSource())
factory.setJobFactory(springBeanJobFactory())
factory.setQuartzProperties(quartzProperties())
return factory
}
#Throws(IOException::class)
fun quartzProperties(): Properties {
val propertiesFactoryBean = PropertiesFactoryBean()
propertiesFactoryBean.setLocation(ClassPathResource("/quartz.properties"))
propertiesFactoryBean.afterPropertiesSet()
return propertiesFactoryBean.getObject()!!
}
}
A couple things I've tried include moving the Quarts bean to the composition module, but then it doesn't have access to the database configuration it needs. I also tried importing the api module into the composition module but it created a circular dependency. Can someone help me access the Quartz bean from my composition module? I'm new to Spring Boot so I am not really sure where I am going wrong or what my options are even. Thanks!
Edit
My service looks like this:
class QuartzService {
#Autowired
private var quartzScheduler: QuartzScheduler? = null
fun upsertJob(job: JobEntity) {
var jobExists = quartzScheduler!!.scheduler().checkExists(JobKey.jobKey(job.id.toString()))
if (!jobExists) {
quartzScheduler!!.scheduler().addJob(
newJob().ofType(EnqueueJob::class.java).storeDurably().withIdentity(JobKey.jobKey(job.id.toString())).build(),
true
)
}
}
}
The error that appears is that the type QuartzScheduler cannot be found (my QuartzScheduler class from scheduler-api)
I had a couple of problems going on. First, my Quartz service was auto wiring the scheduler improperly. I changed it to this:
class QuartzService {
#Autowired
private lateint var scheduler: Scheduler
fun upsertJob(job: JobEntity) {
var jobExists = scheduler.checkExists(JobKey.jobKey(job.id.toString()))
if (!jobExists) {
scheduler.addJob(
newJob().ofType(EnqueueJob::class.java).storeDurably().withIdentity(JobKey.jobKey(job.id.toString())).build(),
true
)
}
}
}
Next, I had to change the class that was using the Quartz service to auto wire the service, I accidentally just instantiated it as a normal object:
#Autowired
private lateinit var quartzService: QuartzService
Thanks everyone for the help!
Related
I have the following question regarding the structure of my application architecture.
Assume my application consists of the following components
First of all a #ConfigurationProperties, which initializes my needed properties (here only a type to select a provider).
In addition, a bean of the type "Provider" is to be registered at this point depending on the specified type. This type is implemented as an interface and has two concrete implementations in this example (ProviderImplA & ProviderImplB).
#Configuration
#Profile("provider")
#ConfigurationProperties(prefix = "provider")
#ConditionalOnProperty(prefix = "provider", name = ["type"])
class ProviderConfiguration {
lateinit var type: String
#Bean(name = ["provider"])
fun provider(): Provider {
return when (type) {
"providerA" -> ProviderImplA()
"providerB" -> ProviderImplB()
}
}
}
Next, only the two concrete implementation of the interface.
class ProviderImplA: Provider {
#Autowired
lateinit var serviceA: ServiceA
}
class ProviderImplB: Provider {
#Autowired
lateinit var serviceA: ServiceA
#Autowired
lateinit var serviceB: ServiceB
#Autowired
lateinit var serviceC: ServiceC
}
And last but not least the interface itself.
interface Provider{
fun doSomething()
}
Now to the actual problem or better my question:
Because my concrete implementations (ProviderImplA and ProviderImplB) are no valid defined beans (missing annotation e.g. #Component), but they have to use their own #Service components, it is not possible to use #Autworie at this point. I would like to avoid different profiles/configurations if possible, therefore the initialization by property. How can I still use the individual #Service's within my implementations and still create the provider beans manually, depending on the configuration (only one provider exists at runtime)? Maybe you have other suggestions or improvements?
When instantiating objects directly, spring cannot control its life cycle, so you must create an #Bean for each 'Provider'
#Bean(name = ["providerA"])
#Lazy
fun providerA(): Provider {
return ProviderImplA()
}
#Bean(name = ["providerB"])
#Lazy
fun providerB(): Provider {
return ProviderImplB()
}
IdentityProviderConfiguration class
IdentityProviderConfiguration {
var context: ApplicationContext
...
fun provider(): Provider {
return when (type) {
"providerA" -> context.getBean("providerA")
"providerB" -> context.getBean("providerB")
}
}
}
I'm trying to get an application working in Spring-boot, but I'm running into injections errors. I have a #Service with a few #Autowire Classes. The classes our just POJO with a public setDatSource method that I need to set the DataSource via runtime. See below:
#Bean
#Qualifier("datasetDao")
public com.lexi.dao.core.DatasetDAO getDatasetDao() throws NamingException {
DatasetDAOImpl ds = new DatasetDAOImpl();
ds.setDataSource(createAuthReadDataSoure());
return ds;
}
#Bean
public LicenseDAO getLicenseDao() throws NamingException {
LicenseDAOImpl ds = new LicenseDAOImpl();
ds.setReadDataSource(createOnlineDSReadDataSoure());
ds.setWriteDataSource(createOnlineDSWriteDataSoure());
ds.setDistribDataSource(createAuthReadDataSoure());
return ds;
}
I have a Service define as this:
#Service
public class LicenseService {
#Autowired
#Qualifier("datasetDao")
private DatasetDAO datasetDao;
#Autowired
private LicenseDAO licenseDao;
However when I run the application I get this:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field datasetDao in com.wk.online.services.LicenseService required a single bean, but 3 were found:
- createAuthReadDataSoure: defined by method 'createAuthReadDataSoure' in com.wk.online.ws.OnlineWsApplication
- createOnlineDSReadDataSoure: defined by method 'createOnlineDSReadDataSoure' in com.wk.online.ws.OnlineWsApplication
- createOnlineDSWriteDataSoure: defined by method 'createOnlineDSWriteDataSoure' in com.wk.online.ws.OnlineWsApplication
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
I tried to add a #Qualifier but that didn't seem to jive wiht Spring. What am I missing, I been at this for a while and figured i'm doing something very stupid.
When defining the bean, you need to specify name, not qualifier, qualifier annotation should be used where you autowire it:
#Bean(name = "datasetDao")
public com.lexi.dao.core.DatasetDAO getDatasetDao() throws NamingException {
DatasetDAOImpl ds = new DatasetDAOImpl();
ds.setDataSource(createAuthReadDataSoure());
return ds;
}
Do you have #Bean annotation on following methods in OnlineWsApplication class?
createAuthReadDataSoure
createOnlineDSReadDataSoure
createOnlineDSWriteDataSoure
If yes get rid of them.
Full code of OnlineWsApplication would be very useful to invastigate it.
In the bean definition, instead of
#Bean
#Qualifier("datasetDao")
Try using the following:
#Bean(name="datasetDao")
I'm running a PoC around replacing bean injection at runtime after a ConfigurationProperties has changed. This is based on spring boot dynamic configuration properties support as well summarised here by Dave Syer from Pivotal.
In my application I have a simple interface implemented by two different concrete classes:
#Component
#RefreshScope
#ConditionalOnExpression(value = "'${config.dynamic.context.country}' == 'it'")
public class HelloIT implements HelloService {
#Override
public String sayHello() {
return "Ciao dall'italia";
}
}
and
#Component
#RefreshScope
#ConditionalOnExpression(value = "'${config.dynamic.context.country}' == 'us'")
public class HelloUS implements HelloService {
#Override
public String sayHello() {
return "Hi from US";
}
}
application.yaml served by spring cloud config server is:
config:
name: Default App
dynamic:
context:
country: us
and the related ConfigurationProperties class:
#Configuration
#ConfigurationProperties (prefix = "config.dynamic")
public class ContextHolder {
private Map<String, String> context;
Map<String, String> getContext() {
return context;
}
public void setContext(Map<String, String> context) {
this.context = context;
}
My client app entrypoint is:
#SpringBootApplication
#RestController
#RefreshScope
public class App1Application {
#Autowired
private HelloService helloService;
#RequestMapping("/hello")
public String hello() {
return helloService.sayHello();
}
First time I browse http://locahost:8080/hello endpoint it returns "Hi from US"
After that I change country: us in country: it in application.yaml in spring config server, and then hit the actuator/refresh endpoint ( on the client app).
Second time I browse http://locahost:8080/hello it stills returns "Hi from US" instead of "ciao dall'italia" as I would expect.
Is this use case supported in spring boot 2 when using #RefreshScope? In particular I'm referring to the fact of using it along with #Conditional annotations.
This implementation worked for me:
#Component
#RefreshScope
public class HelloDelegate implements HelloService {
#Delegate // lombok delegate (for the sake of brevity)
private final HelloService delegate;
public HelloDelegate(
// just inject value from Spring configuration
#Value("${country}") String country
) {
HelloService impl = null;
switch (country) {
case "it":
this.delegate = new HelloIT();
break;
default:
this.delegate = new HelloUS();
break;
}
}
}
It works the following way:
When first invocation of service method happens Spring creates bean HelloDelegate with configuration effective at that moment; bean is put into refresh scope cache
Because of #RefreshScope whenever configuration is changed (country property particularly in this case) HelloDelegate bean gets cleared from refresh scope cache
When next invocation happens, Spring has to create bean again because it does not exist in cache, so step 1 is repeated with new country property
As far as I watched the behavior of this implementation, Spring will try to avoid recreating RefreshScope bean if it's configuration was untouched.
I was looking for more generic solution of doing such "runtime" implementation replacement when found this question. This implementation has one significant disadvantage: if delegated beans have complex non-homogeneous configuration (e.g. each bean has it's own properties) code becomes lousy and therefore unsafe.
I use this approach to provide additional testability for artifacts. So that QA would be able to switch between stub and real integration without significant efforts. I would strongly recommend to avoid using such approach for business functionality.
Is there any way to load a class marked with #ConfigurationProperties without using a Spring Context directly? Basically I want to reuse all the smart logic that Spring does but for a bean I manually instantiate outside of the Spring lifecycle.
I have a bean that loads happily in Spring (Boot) and I can inject this into my other Service beans:
#ConfigurationProperties(prefix="my")
public class MySettings {
String property1;
File property2;
}
See the spring docco for more info http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-external-config-command-line-args
But now I need to access this bean from a class that is created outside of Spring (by Hibernate). The class is created so early in the app startup process that Spring Boot has not yet made the application context available through the classic lookup helper methods or roll-my-own static references.
So I instead want to do something like:
MySettings mySettings = new MySettings();
SpringPropertyLoadingMagicClass loader = new SpringPropertyLoadingMagicClass();
loader.populatePropertyValues(mySettings);
And have MySettings end up with all its values loaded, from the command line, system properties, app.properties, etc. Is there some class in Spring that does something like this or is it all too interwoven with the application context?
Obviously I could just load the Properties file myself, but I really want to keep Spring Boot's logic around using command line variables (e.g. --my.property1=xxx), or system variables, or application.properties or even a yaml file, as well as its logic around relaxed binding and type conversion (e.g. property2 is a File) so that it all works exactly the same as when used in the Spring context.
Possible or pipe dream?
Thanks for your help!
I had the same "issue".
Here is how I solved it in SpringBoot version 1.3.xxx and 1.4.1.
Let's say we have the following yaml configuration file:
foo:
apis:
-
name: Happy Api
path: /happyApi.json?v=bar
-
name: Grumpy Api
path: /grumpyApi.json?v=grrr
and we have the following ConfigurationProperties:
#ConfigurationProperties(prefix = "foo")
public class ApisProperties {
private List<ApiPath> apis = Lists.newArrayList();
public ApisProperties() {
}
public List<ApiPath> getApis() {
return apis;
}
public static class ApiPath {
private String name;
private String path;
public String getName() {
return name;
}
public void setName(final String aName) {
name = aName;
}
public String getPath() {
return path;
}
public void setPath(final String aPath) {
path = aPath;
}
}
}
Then, to do the "magic" things of Spring Boot programmatically (e.g. loading some properties in a static method), you can do:
private static ApisProperties apiProperties() {
try {
ClassPathResource resource;
resource = new ClassPathResource("/config/application.yml");
YamlPropertiesFactoryBean factoryBean;
factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setSingleton(true); // optional depends on your use-case
factoryBean.setResources(resource);
Properties properties;
properties = factoryBean.getObject();
MutablePropertySources propertySources;
propertySources = new MutablePropertySources();
propertySources.addLast(new PropertiesPropertySource("apis", properties));
ApisProperties apisProperties;
apisProperties = new ApisProperties();
PropertiesConfigurationFactory<ApisProperties> configurationFactory;
configurationFactory = new PropertiesConfigurationFactory<>(apisProperties);
configurationFactory.setPropertySources(propertySources);
configurationFactory.setTargetName("foo"); // it's the same prefix as the one defined in the #ConfigurationProperties
configurationFactory.bindPropertiesToTarget();
return apisProperties; // apiProperties are fed with the values defined in the application.yaml
} catch (BindException e) {
throw new IllegalArgumentException(e);
}
}
Here's an update to ctranxuan's answer for Spring Boot 2.x. In our situation, we avoid spinning up a Spring context for unit tests, but do like to test our configuration classes (which is called AppConfig in this example, and its settings are prefixed by app):
public class AppConfigTest {
private static AppConfig config;
#BeforeClass
public static void init() {
YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setResources(new ClassPathResource("application.yaml"));
Properties properties = factoryBean.getObject();
ConfigurationPropertySource propertySource = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(propertySource);
config = binder.bind("app", AppConfig.class).get(); // same prefix as #ConfigurationProperties
}
}
The "magic" class you are looking for is PropertiesConfigurationFactory. But I would question your need for it - if you only need to bind once, then Spring should be able to do it for you, and if you have lifecycle issues it would be better to address those (in case they break something else).
This post is going into similar direction but extends the last answer with also validation and property placeholder resolutions.
Spring Boot Binder API support for #Value Annotations
#Value annotations in ConfigurationPropertys don't seem to bind properly though (at least if the referenced values are not part of the ConfigurationProperty's prefix namespace).
import org.springframework.boot.context.properties.bind.Binder
val binder = Binder.get(environment)
binder.bind(prefix, MySettings.class).get
I have 5 controllers and i would like to register an InitBinder to all of them.
I know i can add this code to each of them.
#InitBinder
public void initBinder(WebDataBinder binder)
{
binder.registerCustomEditor(StringWrapper.class, new StringWrapperEditor());
}
But i would like to define it only once (even create a bean of StringWrapperEditor and use it instead of creating new every time.)
I searched SO and some other places but didn't find any answear.
Is it even possible?
Im using spring 3.1.1 with java 1.6.
Though the initial question was about Spring 3.1, the following might be useful for those who use newer Spring versions.
One possible option is to move your #InitBinder to #ControllerAdvice, for example
#ControllerAdvice
class InitBinderControllerAdvice {
#InitBinder
fun initBinder(dataBinder: WebDataBinder) {
dataBinder.registerCustomEditor(
MLQueryOutputFormat::class.java,
StringToMLQueryOutputFormat()
)
dataBinder.registerCustomEditor(
IDatabaseOps.SortDirection::class.java,
StringToSortDirection()
)
}
}
Regarding ConfigurableWebBindingInitializer, even though it's quite a powerful thing, it requires additional configuration in terms of validation and etc. So pay attention to detail once implementing it. For instance, the following code does the job as per InitBinder, but lacks setting a Validator. As a result, the validation of the rest controller param annotated with #Validated didn't work:
#Configuration
class WebMvcConfig {
#Bean
fun configurableWebBindingInitializer(): ConfigurableWebBindingInitializer {
val initializer = ConfigurableWebBindingInitializer()
initializer.propertyEditorRegistrars = arrayOf(
PropertyEditorRegistrar {
it.registerCustomEditor(
MLQueryOutputFormat::class.java,
StringToMLQueryOutputFormat()
)
}, PropertyEditorRegistrar {
it.registerCustomEditor(
IDatabaseOps.SortDirection::class.java,
StringToSortDirection()
)
}
)
return initializer
}
}
To add validation, one could do the following:
#Bean
fun configurableWebBindingInitializer(
#Qualifier("defaultValidator") validator: Validator
): ConfigurableWebBindingInitializer {
val initializer = ConfigurableWebBindingInitializer()
initializer.validator = validator
...
Implement a PropertyEditorRegistrar which registers all your custom PropertyEditors. Then in your configuration add a ConfigurableWebBindingInitializer which you hookup with the created PropertyEditorRegistrar and hook it to your HandlerAdapter.
public class MyPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(StringWrapper.class, new StringWrapperEditor());
}
}
If you have a <mvc:annotation-driven /> tag in your configuration, the problem is that with this tag you cannot add the WebBindingInitializer to the adapter next to that there is already a ConfigurableWebBindingInitializer added to the pre-configured HandlerAdapter. You can use a BeanPostProcessor to proces and configure the bean.
public class MyPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
if (bean instanceof RequestMappingHandlerAdapter) {
WebBindingInitializer wbi = ((RequestMappingHandlerAdapter) bean).getWebBindingInitializer();
if (wbi == null) {
wbi = new ConfigurableWebBindingInitializer();
((RequestMappingHandlerAdapter) bean).setWebBindingInitializer(wbi);
}
if (wbi instanceof ConfigurableWebBindingInitializer) {
((ConfigurableWebBindingInitializer) wbi).setPropertyEditorRegistrar(new MyPropertyEditorRegistrar());
}
}
}
}
Requires a bit of work but it is doable. You could also implement your own WebBindingInitializer.
If you don't have the tag you can simply manually configure a RequestMappingHandlerAdapter and wire everything together.
Links
PropertyEditorRegistrar javadoc
ConfigurableWebBindingInitializer javadoc
Reference Guide link