RESOLVED - issue BATCH-1164: Putting scope="step" on a listener causes failure
This commit is contained in:
@@ -15,19 +15,15 @@
|
||||
*/
|
||||
package org.springframework.batch.core.listener;
|
||||
|
||||
import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerByAnnotation;
|
||||
import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerForInterface;
|
||||
import static org.springframework.batch.support.MethodInvokerUtils.getParamTypesString;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.support.DefaultPointcutAdvisor;
|
||||
@@ -36,9 +32,7 @@ import org.springframework.batch.support.MethodInvokerUtils;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} implementation that builds a listener based on the
|
||||
@@ -69,8 +63,6 @@ import org.springframework.util.ReflectionUtils;
|
||||
*/
|
||||
public abstract class AbstractListenerFactoryBean implements FactoryBean, InitializingBean {
|
||||
|
||||
private static Log logger = LogFactory.getLog(AbstractListenerFactoryBean.class);
|
||||
|
||||
private Object delegate;
|
||||
|
||||
private Map<String, String> metaDataMap;
|
||||
@@ -101,7 +93,7 @@ public abstract class AbstractListenerFactoryBean implements FactoryBean, Initia
|
||||
invokers.add(getMethodInvokerByName(entry.getValue(), delegate, metaData.getParamTypes()));
|
||||
invokers.add(getMethodInvokerForInterface(metaData.getListenerInterface(), metaData.getMethodName(),
|
||||
delegate, metaData.getParamTypes()));
|
||||
invokers.add(getMethodInvokerByAnnotation(metaData));
|
||||
invokers.add(getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate, metaData.getParamTypes()));
|
||||
if (!invokers.isEmpty()) {
|
||||
invokerMap.put(metaData.getMethodName(), invokers);
|
||||
listenerInterfaces.add(metaData.getListenerInterface());
|
||||
@@ -121,7 +113,12 @@ public abstract class AbstractListenerFactoryBean implements FactoryBean, Initia
|
||||
// create a proxy listener for only the interfaces that have methods to
|
||||
// be called
|
||||
ProxyFactory proxyFactory = new ProxyFactory();
|
||||
proxyFactory.setTarget(delegate);
|
||||
if (delegate instanceof Advised) {
|
||||
proxyFactory.setTargetSource(((Advised) delegate).getTargetSource());
|
||||
}
|
||||
else {
|
||||
proxyFactory.setTarget(delegate);
|
||||
}
|
||||
proxyFactory.setInterfaces(listenerInterfaces.toArray(new Class[0]));
|
||||
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new MethodInvokerMethodInterceptor(invokerMap, ordered)));
|
||||
return proxyFactory.getProxy();
|
||||
@@ -129,50 +126,14 @@ public abstract class AbstractListenerFactoryBean implements FactoryBean, Initia
|
||||
}
|
||||
|
||||
protected abstract ListenerMetaData getMetaDataFromPropertyName(String propertyName);
|
||||
|
||||
|
||||
protected abstract ListenerMetaData[] getMetaDataValues();
|
||||
|
||||
|
||||
protected abstract Class<?> getDefaultListenerClass();
|
||||
|
||||
/**
|
||||
* Create a MethodInvoker from the delegate based on the annotationType.
|
||||
* Ensure that the annotated method has a valid set of parameters.
|
||||
*
|
||||
* @param metaData
|
||||
* @return a MethodInvoker
|
||||
*/
|
||||
protected MethodInvoker getMethodInvokerByAnnotation(final ListenerMetaData metaData) {
|
||||
final Class<? extends Annotation> annotationType = metaData.getAnnotation();
|
||||
MethodInvoker mi = MethodInvokerUtils.getMethodInvokerByAnnotation(annotationType, delegate);
|
||||
if (mi != null) {
|
||||
ReflectionUtils.doWithMethods(delegate.getClass(), new ReflectionUtils.MethodCallback() {
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
|
||||
if (annotation != null) {
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
if (paramTypes.length > 0) {
|
||||
Class<?>[] expectedParamTypes = metaData.getParamTypes();
|
||||
|
||||
String errorMsg = "The method [" + method.getName() + "] on target class ["
|
||||
+ delegate.getClass().getSimpleName() + "] is incompatable with the signature ["
|
||||
+ getParamTypesString(expectedParamTypes) + "] expected for the annotation ["
|
||||
+ metaData.getAnnotation().getSimpleName() + "].";
|
||||
|
||||
Assert.isTrue(paramTypes.length == expectedParamTypes.length, errorMsg);
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
Assert.isTrue(paramTypes[i].equals(expectedParamTypes[i]), errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return mi;
|
||||
}
|
||||
|
||||
protected MethodInvoker getMethodInvokerByName(String methodName, Object candidate, Class<?>... params) {
|
||||
if (methodName != null) {
|
||||
return MethodInvokerUtils.createMethodInvokerByName(candidate, methodName, false, params);
|
||||
return MethodInvokerUtils.getMethodInvokerByName(candidate, methodName, false, params);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@@ -184,16 +145,7 @@ public abstract class AbstractListenerFactoryBean implements FactoryBean, Initia
|
||||
}
|
||||
|
||||
public void setDelegate(Object delegate) {
|
||||
if (delegate instanceof Advised) {
|
||||
try {
|
||||
setDelegate(((Advised) delegate).getTargetSource().getTarget());
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Cannot generate listener for proxy with no target", e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public void setMetaDataMap(Map<String, String> metaDataMap) {
|
||||
@@ -225,24 +177,21 @@ public abstract class AbstractListenerFactoryBean implements FactoryBean, Initia
|
||||
* Convenience method to check whether the given object is or can be made
|
||||
* into a listener.
|
||||
*
|
||||
* @param delegate the object to check
|
||||
* @param target the object to check
|
||||
* @return true if the delegate is an instance of any of the listener
|
||||
* interface, or contains the marker annotations
|
||||
* interface, or contains the marker annotations
|
||||
*/
|
||||
public static boolean isListener(Object delegate, Class<?> listenerType, ListenerMetaData[] metaDataValues) {
|
||||
if (listenerType.isInstance(delegate)) {
|
||||
public static boolean isListener(Object target, Class<?> listenerType, ListenerMetaData[] metaDataValues) {
|
||||
if (listenerType.isInstance(target)) {
|
||||
return true;
|
||||
}
|
||||
if (delegate instanceof Advised) {
|
||||
try {
|
||||
return isListener(((Advised) delegate).getTargetSource().getTarget(), listenerType, metaDataValues);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Error obtaining target for Proxy. Assume not a listener.", e);
|
||||
return false;
|
||||
if (target instanceof Advised) {
|
||||
if (listenerType.isAssignableFrom(((Advised) target).getTargetSource().getTargetClass())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (ListenerMetaData metaData : metaDataValues) {
|
||||
if (MethodInvokerUtils.getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate) != null) {
|
||||
if (MethodInvokerUtils.getMethodInvokerByAnnotation(metaData.getAnnotation(), target) != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.springframework.batch.core.scope.context.StepSynchronizationManager;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ContextFactory} that provides the current
|
||||
* {@link StepContext} as a contxt object.
|
||||
* {@link StepContext} as a context object.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
@@ -33,7 +33,8 @@ public class StepContextFactory implements ContextFactory {
|
||||
}
|
||||
|
||||
public String getContextId() {
|
||||
return (String) StepSynchronizationManager.getContext().getId();
|
||||
StepContext context = StepSynchronizationManager.getContext();
|
||||
return context!=null ? (String) context.getId() : "sysinit";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ public class StepListenerMethodInterceptorTests {
|
||||
public void testMultipleInvokersPerName() throws Throwable{
|
||||
|
||||
Map<String, Set<MethodInvoker>> invokerMap = new HashMap<String, Set<MethodInvoker>>();
|
||||
Set<MethodInvoker> invokers = asSet(MethodInvokerUtils.createMethodInvokerByName(testClass, "method1", false));
|
||||
invokers.add(MethodInvokerUtils.createMethodInvokerByName(testClass, "method2", false));
|
||||
Set<MethodInvoker> invokers = asSet(MethodInvokerUtils.getMethodInvokerByName(testClass, "method1", false));
|
||||
invokers.add(MethodInvokerUtils.getMethodInvokerByName(testClass, "method2", false));
|
||||
invokerMap.put("method1", invokers);
|
||||
interceptor = new MethodInvokerMethodInterceptor(invokerMap);
|
||||
interceptor.invoke(new StubMethodInvocation(TestClass.class.getMethod("method1")));
|
||||
@@ -58,8 +58,8 @@ public class StepListenerMethodInterceptorTests {
|
||||
@Test
|
||||
public void testExitStatusReturn() throws Throwable{
|
||||
Map<String, Set<MethodInvoker>> invokerMap = new HashMap<String, Set<MethodInvoker>>();
|
||||
Set<MethodInvoker> invokers = asSet(MethodInvokerUtils.createMethodInvokerByName(testClass, "method3", false));
|
||||
invokers.add(MethodInvokerUtils.createMethodInvokerByName(testClass, "method3", false));
|
||||
Set<MethodInvoker> invokers = asSet(MethodInvokerUtils.getMethodInvokerByName(testClass, "method3", false));
|
||||
invokers.add(MethodInvokerUtils.getMethodInvokerByName(testClass, "method3", false));
|
||||
invokerMap.put("method3", invokers);
|
||||
interceptor = new MethodInvokerMethodInterceptor(invokerMap);
|
||||
assertEquals(ExitStatus.COMPLETED, interceptor.invoke(new StubMethodInvocation(TestClass.class.getMethod("method3"))));
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -41,12 +42,11 @@ public class MethodInvokerUtils {
|
||||
* @param object to be invoked
|
||||
* @param methodName of the method to be invoked
|
||||
* @param paramsRequired boolean indicating whether the parameters are
|
||||
* required, if false, a no args version of the method will be
|
||||
* searched for.
|
||||
* required, if false, a no args version of the method will be searched for.
|
||||
* @param paramTypes - parameter types of the method to search for.
|
||||
* @return MethodInvoker if the method is found, null if it is not.
|
||||
*/
|
||||
public static MethodInvoker createMethodInvokerByName(Object object, String methodName, boolean paramsRequired,
|
||||
public static MethodInvoker getMethodInvokerByName(Object object, String methodName, boolean paramsRequired,
|
||||
Class<?>... paramTypes) {
|
||||
Assert.notNull(object, "Object to invoke must not be null");
|
||||
Method method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, paramTypes);
|
||||
@@ -94,36 +94,76 @@ public class MethodInvokerUtils {
|
||||
Class<?>... paramTypes) {
|
||||
|
||||
if (cls.isAssignableFrom(object.getClass())) {
|
||||
return MethodInvokerUtils.createMethodInvokerByName(object, methodName, true, paramTypes);
|
||||
return MethodInvokerUtils.getMethodInvokerByName(object, methodName, true, paramTypes);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MethodInvoker from the delegate based on the annotationType.
|
||||
* Ensure that the annotated method has a valid set of parameters.
|
||||
*
|
||||
* @param annotationType the annotation to scan for
|
||||
* @param target the target object
|
||||
* @param expectedParamTypes the expected parameter types for the method
|
||||
* @return a MethodInvoker
|
||||
*/
|
||||
public static MethodInvoker getMethodInvokerByAnnotation(final Class<? extends Annotation> annotationType,
|
||||
final Object target, final Class<?>... expectedParamTypes) {
|
||||
MethodInvoker mi = MethodInvokerUtils.getMethodInvokerByAnnotation(annotationType, target);
|
||||
final Class<?> targetClass = (target instanceof Advised) ? ((Advised) target).getTargetSource()
|
||||
.getTargetClass() : target.getClass();
|
||||
if (mi != null) {
|
||||
ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
|
||||
if (annotation != null) {
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
if (paramTypes.length > 0) {
|
||||
String errorMsg = "The method [" + method.getName() + "] on target class ["
|
||||
+ targetClass.getSimpleName() + "] is incompatable with the signature ["
|
||||
+ getParamTypesString(expectedParamTypes) + "] expected for the annotation ["
|
||||
+ annotationType.getSimpleName() + "].";
|
||||
|
||||
Assert.isTrue(paramTypes.length == expectedParamTypes.length, errorMsg);
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
Assert.isTrue(paramTypes[i].equals(expectedParamTypes[i]), errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return mi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@link MethodInvoker} for the method with the provided annotation
|
||||
* on the provided object. It should be noted that annotations that cannot
|
||||
* be applied to methods (i.e. that aren't annotated with an element type of
|
||||
* METHOD) will cause an exception to be thrown.
|
||||
* on the provided object. Annotations that cannot be applied to methods
|
||||
* (i.e. that aren't annotated with an element type of METHOD) will cause an
|
||||
* exception to be thrown.
|
||||
*
|
||||
* @param annotationType to be searched for
|
||||
* @param candidate to be invoked
|
||||
* @param target to be invoked
|
||||
* @return MethodInvoker for the provided annotation, null if none is found.
|
||||
*/
|
||||
public static MethodInvoker getMethodInvokerByAnnotation(final Class<? extends Annotation> annotationType,
|
||||
final Object candidate) {
|
||||
Assert.notNull(candidate, "class must not be null");
|
||||
Assert.notNull(annotationType, "annotationType must not be null");
|
||||
final Object target) {
|
||||
Assert.notNull(target, "Target must not be null");
|
||||
Assert.notNull(annotationType, "AnnotationType must not be null");
|
||||
Assert.isTrue(ObjectUtils.containsElement(annotationType.getAnnotation(Target.class).value(),
|
||||
ElementType.METHOD), "Annotation [" + annotationType + "] is not a Method-level annotation.");
|
||||
final Class<?> targetClass = (target instanceof Advised) ? ((Advised) target).getTargetSource()
|
||||
.getTargetClass() : target.getClass();
|
||||
final AtomicReference<Method> annotatedMethod = new AtomicReference<Method>();
|
||||
ReflectionUtils.doWithMethods(candidate.getClass(), new ReflectionUtils.MethodCallback() {
|
||||
ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
|
||||
if (annotation != null) {
|
||||
Assert.isNull(annotatedMethod.get(), "found more than one method on target class ["
|
||||
+ candidate.getClass().getSimpleName() + "] with the annotation type ["
|
||||
+ targetClass.getSimpleName() + "] with the annotation type ["
|
||||
+ annotationType.getSimpleName() + "].");
|
||||
annotatedMethod.set(method);
|
||||
}
|
||||
@@ -134,20 +174,20 @@ public class MethodInvokerUtils {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return new SimpleMethodInvoker(candidate, annotatedMethod.get());
|
||||
return new SimpleMethodInvoker(target, annotatedMethod.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link MethodInvoker} for the delegate from a single public
|
||||
* method with the signature provided.
|
||||
* method.
|
||||
*
|
||||
* @param candidate an object to search for an appropriate method
|
||||
* @param target an object to search for an appropriate method
|
||||
* @return a MethodInvoker that calls a method on the delegate
|
||||
*/
|
||||
public static <C, T> MethodInvoker getMethodInvokerForSingleArgument(Object candidate) {
|
||||
public static <C, T> MethodInvoker getMethodInvokerForSingleArgument(Object target) {
|
||||
final AtomicReference<Method> methodHolder = new AtomicReference<Method>();
|
||||
ReflectionUtils.doWithMethods(candidate.getClass(), new ReflectionUtils.MethodCallback() {
|
||||
ReflectionUtils.doWithMethods(target.getClass(), new ReflectionUtils.MethodCallback() {
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
if (method.getParameterTypes() == null || method.getParameterTypes().length != 1) {
|
||||
return;
|
||||
@@ -161,6 +201,6 @@ public class MethodInvokerUtils {
|
||||
}
|
||||
});
|
||||
Method method = methodHolder.get();
|
||||
return new SimpleMethodInvoker(candidate, method);
|
||||
return new SimpleMethodInvoker(target, method);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
<beans:bean id="dummyWriter" class="org.springframework.batch.sample.support.DummyItemWriter"/>
|
||||
|
||||
<beans:bean id="promotionListener" class="org.springframework.batch.core.listener.ExecutionContextPromotionListener">
|
||||
<beans:bean id="promotionListener" class="org.springframework.batch.core.listener.ExecutionContextPromotionListener" scope="step">
|
||||
<beans:property name="keys" value="stepName"/>
|
||||
</beans:bean>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user