diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java index a61f1ded6..cf8396378 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java @@ -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 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 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 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; } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/util/StepContextFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/util/StepContextFactory.java index 3808620cf..664a9c6f3 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/util/StepContextFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/util/StepContextFactory.java @@ -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"; } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerMethodInterceptorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerMethodInterceptorTests.java index 2fa697806..a060aee3e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerMethodInterceptorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerMethodInterceptorTests.java @@ -45,8 +45,8 @@ public class StepListenerMethodInterceptorTests { public void testMultipleInvokersPerName() throws Throwable{ Map> invokerMap = new HashMap>(); - Set invokers = asSet(MethodInvokerUtils.createMethodInvokerByName(testClass, "method1", false)); - invokers.add(MethodInvokerUtils.createMethodInvokerByName(testClass, "method2", false)); + Set 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> invokerMap = new HashMap>(); - Set invokers = asSet(MethodInvokerUtils.createMethodInvokerByName(testClass, "method3", false)); - invokers.add(MethodInvokerUtils.createMethodInvokerByName(testClass, "method3", false)); + Set 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")))); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java index b96caac67..53e18be7b 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java @@ -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 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 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 annotatedMethod = new AtomicReference(); - 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 MethodInvoker getMethodInvokerForSingleArgument(Object candidate) { + public static MethodInvoker getMethodInvokerForSingleArgument(Object target) { final AtomicReference methodHolder = new AtomicReference(); - 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); } } diff --git a/spring-batch-samples/src/main/resources/jobs/skipSampleJob.xml b/spring-batch-samples/src/main/resources/jobs/skipSampleJob.xml index e4a941bb6..4aae7bbca 100644 --- a/spring-batch-samples/src/main/resources/jobs/skipSampleJob.xml +++ b/spring-batch-samples/src/main/resources/jobs/skipSampleJob.xml @@ -64,7 +64,7 @@ - +