Retrieve spring-batch job bean definitions - spring

Is there a way to retrieve the job beans declared within this location: classpath*/META-INF/spring/batch/jobs/*.xml ?
Tried the code below but I was not able to retrieve them.
#Autowired
private ApplicationContext applicationContext;
public void sometMethod() {
AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();
String[] strings = ((BeanDefinitionRegistry) beanFactory).getBeanDefinitionNames();
}

I was able to able to merge the applicationContext and the job bean definitions with the following changes:
#Autowired
private ApplicationContext applicationContext;
public void sometMethod() {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"classpath:META-INF/spring/batch/jobs/*.xml"}, applicationContext);
AutowireCapableBeanFactory beanFactory = context.getAutowireCapableBeanFactory();
String[] strings = ((BeanDefinitionRegistry) beanFactory).getBeanDefinitionNames();
}

Related

How to get the Bean package from ConfigurableApplicationContext

Using a interface like ConfigurableApplicationContext, it is possible to retrieve the list of Beans running in the Spring DI container, but I would like to know what Beans come from the User Space and what Beans comes from the Spring Boot / Spring Boot Starters.
#TestConfiguration
static class BeanInventoryConfiguration {
#Autowired
private ConfigurableApplicationContext applicationContext;
record BeanInventory(List<String> beans) {}
#Bean
public BeanInventory getBeanInventory(ConfigurableApplicationContext applicationContext) {
String[] allBeanNames = applicationContext.getBeanDefinitionNames();
return new BeanInventory(Arrays.stream(allBeanNames).toList());
}
}
Does exist a way to return the package where the Bean is located?
If I know the package, I could filter in a easy way.
Reviewing the Javadoc from Spring, I didnt find a way:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ConfigurableApplicationContext.html
Many thanks in advance
POC: https://github.com/jabrena/spring-boot-http-client-poc/blob/main/src/test/java/ms/info/ms/BeanInventoryTests.java
I found a solution for it:
#TestConfiguration
public class BeanInventory {
#Autowired
private ConfigurableApplicationContext applicationContext;
public record BeanInfo(String name, String pkg) {}
private final List<BeanInfo> beans = new ArrayList<>();
#PostConstruct
private void after() {
final String[] beanNames = applicationContext.getBeanDefinitionNames();
for (String beanName : beanNames) {
final Object beanObject = applicationContext.getBean(beanName);
Class<?> targetClass = AopUtils.getTargetClass(beanObject);
if (AopUtils.isJdkDynamicProxy(beanObject)) {
Class<?>[] proxiedInterfaces = AopProxyUtils.proxiedUserInterfaces(beanObject);
Assert.isTrue(proxiedInterfaces.length == 1, "Only one proxied interface expected");
targetClass = proxiedInterfaces[0];
}
beans.add(new BeanInfo(beanName, targetClass.getPackageName()));
}
}
public List<BeanInfo> getBeans() {
return beans;
}
}
Further information here:
https://github.com/spring-projects/spring-framework/issues/29973#event-8527246281
Note: Many thanks to Simon Basle

how to convert BeanFactory to ApplicationContext

I have some code like this:
#Slf4j
#Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor, PriorityOrdered {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
ListableBeanFactory listableBeanFactory = factory;
ApplicationContext applicationContext = (ApplicationContext) listableBeanFactory;
AbstractApplicationContext context = (AbstractApplicationContext) applicationContext;
ConfigurableEnvironment environment = context.getEnvironment();
AbstractEnvironment abstractEnvironment = (AbstractEnvironment) environment;
abstractEnvironment.getProperty("business.bean.dependsOn");
// how to gain ApplicationContext
//.........
}
I want to obtain some config value from classpath:application.properties
and only ApplicationContext could getEnvironment.
how to gain ApplicationContext?
The ApplicationContext object can be obtained by implementing the ApplicationContextAware interface.
The Value annotation gets the attributes in application.properties.

How to autowired in quartz?

Previously I had set it up to autowired in a quartz job.
Note here.
But, the autowired of the job inner class will fail.
My job code example is here.
public class MyJob extends QuartzJobBean {
#Autowired
private Hello hello; //<--- this is suceess!
#Override
public void executeInternal(JobExecutionContext context) {
//...
Do do = new Do();
do.doSomething();
//...
}
}
Do.java
public class Do {
#Autowired
priavte Do2 do2; // <---- ***this is null !***
//...
}
Why is this happening?
How do I solve it and what concepts should I know more?
Quartz jobs are not ran in the same context as spring so autowired objects become null within the same class. You have to make Quartz Spring aware.
First add sprint-context-support
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
Then create a Application Context Holder that is Application Context Aware
#Component
public final class ApplicationContextHolder extends SpringBeanJobFactory implements ApplicationContextAware {
private static ApplicationContext context;
private transient AutowireCapableBeanFactory beanFactory;
#Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
beanFactory = ctx.getAutowireCapableBeanFactory();
context = ctx;
}
#Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
public static ApplicationContext getContext() {
return context;
}
}
Then you can create your Quartz Scheduler Configuration Class
#Configuration
public class QuartzSchedulerConfiguration {
#Autowired
private ApplicationContext applicationContext;
/**
* Create the job factory bean
* #return Job factory bean
*/
#Bean
public JobFactory jobFactory() {
ApplicationContextHolder jobFactory = new ApplicationContextHolder();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
/**
* Create the Scheduler Factory bean
* #return scheduler factory object
*/
#Bean
public SchedulerFactoryBean schedulerFactory() {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setAutoStartup(true);
factory.setSchedulerName("My Scheduler");
factory.setOverwriteExistingJobs(true);
factory.setJobFactory(jobFactory());
return factory;
}
}
Now this will place your quartz scheduler in the same context as Spring, so you can now create a SchedulerService class.
#Service
public class SchedulerService {
#Autowired
private SchedulerFactoryBean schedulerFactory;
private Scheduler scheduler;
/**
* Initialize the scheduler service
*/
#PostConstruct
private void init() {
scheduler = schedulerFactory.getScheduler();
}
}
now you can populate this class with methods to create your schedules using the scheduler object and when the task is triggered the class that extends Job will be context aware with spring and the autowired objects will no longer be null
to address the follow up question implement the ApplicationContextHolder component then autowire it into your SchedulerConfig class
#Autowire
ApplicationContextHolder holder
#Bean
// injecting SpringLiquibase to ensure liquibase is already initialized and created the quartz tables:
public JobFactory jobFactory(SpringLiquibase springLiquibase) {
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(holder);
return jobFactory;
}

Why #Inject is not working when implement BeanFactoryPostProcessor

I have some web app with simple task:
public class CustomTask {
private final Logger logger = LoggerFactory.getLogger(getClass());
#Inject
private CustomDao customDao;
#Override
#Transactional(propagation = Propagation.REQUIRED, timeout = 600)
public int run() {
customDao.doSomething();
}
}
now it work with no problem. But now I want to add implementation of BeanFactoryPostProcessor (implements BeanFactoryPostProcessor)to this task and override this method:
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
logger.info(beanFactory.getClass() + "xxxxxxxxxx");
logger.info("The factory contains the followig beans:");
String[] beanNames = beanFactory.getBeanDefinitionNames();
for (int i = 0; i < beanNames.length; ++i)
logger.info(beanNames[i]);
}
but now then I want to do something with customDao it throw NullPointException and I dont know why because in logger I see that this bean is registred. Can you explain me why this exception occurs and how should I fix it ?
This will not work because #Inject (just like #Autowired) is detected by AutowiredAnnotationBeanPostProcessor which is a BeanPostProcessor. BeanFactoryPostProcessors are instantiated BEFORE any other bean in the application context. So, when your post processor is created, the infrastructure that detects #Inject annotation isn't even created and cannot act on your bean factory post processor.
See the api docs for Autowired annotation where there is a note about the restriction for BeanFactoryPostProcessors.
You could make your task to implement BeanFactoryAware and inject the bean yourself:
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.customDao = beanFactory.getBean(CustomDao.class);
}

How to use the appicationcontextaware in java class

I need to load the applicationcontext in java class in which the applicationcontextaware bean is defined. I need to access the other beans inside the applicationcontext.xml using the applicationcontextaware. I dont want to load the context using
ClassPathXmlApplicationContext("applicationContext.xml");
I need to access the beans inside the applicationContext like this
ApplicationContextAccess.getInstance().getApplicationContext.getbean("BeanName");
Applicationcontextacess implemented as singleton class:
public class ApplicationContextAccess implements ApplicationContextAware {
private ApplicationContext applicationContext = null;
private static ApplicationContextAccess applicationContextAccess=null;
private ApplicationContextAccessor() {
}
public static synchronized ApplicationContextAccess getInstance() {
if(applicationContextAccess == null)
{
applicationContextAccess = new ApplicationContextAccess();
}
return applicationContextAccess;
}
public void ApplicationContext getApplicationContext() {
return applicationContext;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
applicationContext = applicationContext;
}
}
I need to access the beans inside the applicationContext like this ApplicationContextAccess.getInstance().getApplicationContext.getbean("BeanName");
But I have a doubt how the getApplicationContext loads the applicationContext.xml........?

Resources