Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSR-352 support does not work for custom infrastructure [BATCH-2290] #1312

Closed
spring-projects-issues opened this issue Aug 9, 2014 · 13 comments

Comments

@spring-projects-issues
Copy link
Collaborator

Tobias Flohre opened BATCH-2290 and commented

I am trying to integrate JSR-352 support in our existing Spring Batch infrastructure (existing JobRepository, JobExplorer, TaskExecutor, TransactionManager and so on) and I'm stuck.
The JsrJobOperator has a constructor to pass in the dependencies, but when using the start-method afterwards, baseContext is null (it's only initialized in the other constructor), and I get an error regarding the bean creation of the jobExplorer.
I want to use my existing ApplicationContext as baseContext. If I don't miss something, that's not possible.

Here the exception:


javax.batch.operations.JobStartException: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'batchPropertyPostProcessor': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.batch.core.jsr.launch.support.BatchPropertyBeanPostProcessor.setBatchPropertyContext(org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext); nested exception is org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [jobExplorer] for bean with name 'scopedTarget.jobExplorer' defined in null; nested exception is java.lang.ClassNotFoundException: jobExplorer
	at org.springframework.batch.core.jsr.launch.JsrJobOperator.start(JsrJobOperator.java:615)
	at de.codecentric.batch.web.JobOperationsController.launch(JobOperationsController.java:171)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)
	at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:689)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:938)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
	at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:110)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
	at org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration$ApplicationContextHeaderFilter.doFilterInternal(EndpointWebMvcAutoConfiguration.java:280)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
	at org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration$MetricsFilter.doFilterInternal(MetricFilterAutoConfiguration.java:89)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
	at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:683)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1720)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1679)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'batchPropertyPostProcessor': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.batch.core.jsr.launch.support.BatchPropertyBeanPostProcessor.setBatchPropertyContext(org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext); nested exception is org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [jobExplorer] for bean with name 'scopedTarget.jobExplorer' defined in null; nested exception is java.lang.ClassNotFoundException: jobExplorer
	at org.springframework.batch.core.jsr.configuration.support.SpringAutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(SpringAutowiredAnnotationBeanPostProcessor.java:262)
	at org.springframework.batch.core.jsr.configuration.support.JsrAutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(JsrAutowiredAnnotationBeanPostProcessor.java:30)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1185)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)
	at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:232)
	at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:618)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:467)
	at org.springframework.batch.core.jsr.launch.JsrJobOperator.start(JsrJobOperator.java:613)
	... 52 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.batch.core.jsr.launch.support.BatchPropertyBeanPostProcessor.setBatchPropertyContext(org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext); nested exception is org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [jobExplorer] for bean with name 'scopedTarget.jobExplorer' defined in null; nested exception is java.lang.ClassNotFoundException: jobExplorer
	at org.springframework.batch.core.jsr.configuration.support.SpringAutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(SpringAutowiredAnnotationBeanPostProcessor.java:575)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
	at org.springframework.batch.core.jsr.configuration.support.SpringAutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(SpringAutowiredAnnotationBeanPostProcessor.java:259)
	... 64 common frames omitted
Caused by: org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [jobExplorer] for bean with name 'scopedTarget.jobExplorer' defined in null; nested exception is java.lang.ClassNotFoundException: jobExplorer
	at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1325)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:594)
	at org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch(AbstractBeanFactory.java:526)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:387)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:354)
	at org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors(BeanFactoryUtils.java:187)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1002)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:960)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:858)
	at org.springframework.batch.core.jsr.configuration.support.SpringAutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(SpringAutowiredAnnotationBeanPostProcessor.java:532)
	... 66 common frames omitted
Caused by: java.lang.ClassNotFoundException: jobExplorer
	at org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedWebappClassLoader.loadClass(TomcatEmbeddedWebappClassLoader.java:75)
	at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1571)
	at org.springframework.util.ClassUtils.forName(ClassUtils.java:247)
	at org.springframework.beans.factory.support.AbstractBeanDefinition.resolveBeanClass(AbstractBeanDefinition.java:395)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doResolveBeanClass(AbstractBeanFactory.java:1346)
	at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1317)
	... 75 common frames omitted


Affects: 3.0.1

Referenced from: commits 4861d94, 92e551e, c104693, d3410c9

1 votes, 3 watchers

@spring-projects-issues
Copy link
Collaborator Author

Tobias Flohre commented

A suggestion how to solve this:

baseContext doesn't need to be static. When used with the ServiceLoader mechanism, it will get a reference to a static ApplicationContext, so we don't win that much if baseContext itself is static as well.
So if baseContext isn't static, we could make the JsrJobOperator implement ApplicationContextAware and set the baseContext through it.
Consequence: when JsrJobOperator is used with BatchRuntime, it doesn't belong to any ApplicationContext itself, so ApplicationContextAware doesn't have an effect and baseContext will have the reference to the static ApplicationContext. When used inside an ApplicationContext, that ApplicationContext will become the baseContext for the Job-Contexts.

What do you think?

@spring-projects-issues
Copy link
Collaborator Author

Michael Minella commented

The Spring Batch implementation of JSR-352 was never intended for you to wire up your own JsrJobOperator. The spec states that you are to get a handle of it via BatchRuntime so on the JSR side, that is all that is supported right now. I know there is an issue with the JSR to make that more DI friendly, but I'm unaware of it's state. We do provide the ability to override the components in the BaseContext if you like.

That being said, you are correct in that baseContext within the JsrJobOperator does not need to be static since the ContextSingletonBeanFactoryLocator addresses the need to only load that context once. However, looking at this, I'm not even sure baseContext is needed if you're using DI (if you're wiring up the JsrJobOperator yourself, you probably have the other required items in your context anyways). Because of that, I'm wondering if, instead of making JsrJobOperator ApplicationContextAware if we should just add baseContext as the parent conditionally on if it exists in the first place (aka you're taking the BatchRuntime.getJobOperator() route) in both the start and restart methods.

However, to re-iterate, if you are looking to conform to JSR-352, the entry point for batch jobs is via BatchRuntime.getJobOperator(). Not following that does not conform with JSR-352 and therefore cannot guarantee backwards compatibility with future versions of the spec.

@spring-projects-issues
Copy link
Collaborator Author

Tobias Flohre commented

I would guess that batchContext needs a parent to get the batch infrastructure from. And that would be my main ApplicationContext. I will try it out to confirm that.

Yes, you're right, Spring Batch implements the spec, so this is more a feature request than a bug. But for me it's quite a standard use case: customers that are already using Spring Batch and are now looking for a way to implement JSR-352 jobs. And I know, if I don't use the BatchRuntime for getting a JobOperator, I'm not spec-compliant, but I don't like the BatchRuntime-thing anyway ;-). And everything from the point where I have the JobOperator is spec-compliant.

@spring-projects-issues
Copy link
Collaborator Author

Michael Minella commented

To be 100% honest, I'd ask those customers what they are looking to get out of JSR-352 in the first place if they are already Spring Batch users. However, that's a topic for another day...

@spring-projects-issues
Copy link
Collaborator Author

Tobias Flohre commented

I think it's more about having options.

Anyway, I just checked:
If the batchContext doesn't get a parent, the exception from the description is thrown when the batchContext is refreshed. If I use my ApplicationContext with the batch infrastructure as parent, the job runs fine.

@spring-projects-issues
Copy link
Collaborator Author

Tobias Flohre commented

For reference: I subclassed the JsrJobOperator to make it work for me, here it is: https://github.com/codecentric/spring-boot-starter-batch-web/blob/master/src/main/java/de/codecentric/batch/jsr352/CustomJsrJobOperator.java.

@spring-projects-issues
Copy link
Collaborator Author

Michael Minella commented

Upon reflection, one thing we could do to allow for a bit more freedom and still being spec compliant is to allow the user to specify via a JVM property the location of a base context. Currently we only allow the ability to override it at a job level, but I don't see why we couldn't allow a global one to be set at the JVM level. Thoughts?

@spring-projects-issues
Copy link
Collaborator Author

Michael Minella commented

Added the ability to override the base context configuration on a global scale: 4861d94

@spring-projects-issues
Copy link
Collaborator Author

Tobias Flohre commented

Sorry for replying a little late. I still don't see what's not spec-compliant in the solution I provided here: https://github.com/codecentric/spring-boot-starter-batch-web/blob/master/src/main/java/de/codecentric/batch/jsr352/CustomJsrJobOperator.java.
When using BatchRuntime.getJobOperator() it's 100% spec-compliant. And if you want to use it with an existing ApplicationContext it works fine as well. For me that's not against the spec, it extends the functionality of the spec for convenience. You do that with specs all the time.
Your solution still just points to more configuration, not an instantiated, existing ApplicationContext.
However, I can live with my custom implementation, so if you want to keep this ticket closed, keep it closed.

@spring-projects-issues
Copy link
Collaborator Author

Michael Minella commented

Re-evaluating the way the manually wired instance works based on comments by Tobias Flohre.

@spring-projects-issues
Copy link
Collaborator Author

Tobias Flohre commented

My implementation also adds some listeners to jobs, which of course is not part of this issue. The two things you need to do is:

  1. remove static from baseContext
  2. implement ApplicationContextAware and set the baseContext in setApplicationContext
    This way baseContext will be set up like before when used with BatchRuntime.getJobOperator, because the JobOperator itself is no Spring bean and setApplicationContext will never be called. But if you instantiate the JsrJobOperator yourself in a Spring configuration, the surrounding ApplicationContext will become the baseContext for the JobOperator.

@spring-projects-issues
Copy link
Collaborator Author

Michael Minella commented

Tobias Flohre, I've already removed the static from baseContext in this commit: c104693. The part I was reconsidering was I had not implemented ApplicationContextAware before and I think you've sold me on doing so.

@spring-projects-issues
Copy link
Collaborator Author

Michael Minella commented

Made JsrJobOperator ApplicationContextAware.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant