INT-1343, INT-1355 @Publisher can now be used as a meta-annotation, and when the "channel" is not available at method-level, a class-level annotation will now be checked next, and only if neither are present will it fall back to the default channel of the advisor.
This commit is contained in:
@@ -152,10 +152,12 @@ public class MethodAnnotationExpressionSource implements ExpressionSource {
|
||||
|
||||
public String getChannelName(Method method) {
|
||||
String channelName = this.getAnnotationValue(method, this.channelAttributeName, String.class);
|
||||
if (channelName == null) {
|
||||
channelName = this.getAnnotationValue(method.getDeclaringClass(), this.channelAttributeName, String.class);
|
||||
}
|
||||
return (StringUtils.hasText(channelName) ? channelName : null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getAnnotationValue(Method method, String attributeName, Class<T> expectedType) {
|
||||
T value = null;
|
||||
for (Class<? extends Annotation> annotationType : this.annotationTypes) {
|
||||
@@ -165,18 +167,40 @@ public class MethodAnnotationExpressionSource implements ExpressionSource {
|
||||
throw new IllegalStateException(
|
||||
"method [" + method + "] contains more than one publisher annotation");
|
||||
}
|
||||
Object valueAsObject = (attributeName == null) ? AnnotationUtils.getValue(annotation)
|
||||
: AnnotationUtils.getValue(annotation, attributeName);
|
||||
if (valueAsObject != null) {
|
||||
if (expectedType.isAssignableFrom(valueAsObject.getClass())) {
|
||||
value = (T) valueAsObject;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("expected type [" + expectedType.getName() +
|
||||
"] for attribute '" + attributeName + "' on publisher annotation [" +
|
||||
annotationType + "], but actual type was [" + valueAsObject.getClass() + "]");
|
||||
}
|
||||
value = this.getAnnotationValue(annotation, attributeName, expectedType);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private <T> T getAnnotationValue(Class<?> clazz, String attributeName, Class<T> expectedType) {
|
||||
T value = null;
|
||||
for (Class<? extends Annotation> annotationType : this.annotationTypes) {
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(clazz, annotationType);
|
||||
if (annotation != null) {
|
||||
if (value != null) {
|
||||
throw new IllegalStateException(
|
||||
"class [" + clazz + "] contains more than one publisher annotation");
|
||||
}
|
||||
value = this.getAnnotationValue(annotation, attributeName, expectedType);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getAnnotationValue(Annotation annotation, String attributeName, Class<T> expectedType) {
|
||||
T value = null;
|
||||
Object valueAsObject = (attributeName == null) ? AnnotationUtils.getValue(annotation)
|
||||
: AnnotationUtils.getValue(annotation, attributeName);
|
||||
if (valueAsObject != null) {
|
||||
if (expectedType.isAssignableFrom(valueAsObject.getClass())) {
|
||||
value = (T) valueAsObject;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("expected type [" + expectedType.getName() +
|
||||
"] for attribute '" + attributeName + "' on publisher annotation [" +
|
||||
annotation.annotationType() + "], but actual type was [" + valueAsObject.getClass() + "]");
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -17,20 +17,27 @@
|
||||
package org.springframework.integration.aop;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
|
||||
import org.springframework.aop.ClassFilter;
|
||||
import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.support.AbstractPointcutAdvisor;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aop.support.ComposablePointcut;
|
||||
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
|
||||
import org.springframework.aop.support.annotation.AnnotationClassFilter;
|
||||
import org.springframework.aop.support.annotation.AnnotationMethodMatcher;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.integration.context.BeanFactoryChannelResolver;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An advisor that will apply the {@link MessagePublishingInterceptor} to any
|
||||
@@ -79,8 +86,8 @@ public class PublisherAnnotationAdvisor extends AbstractPointcutAdvisor implemen
|
||||
private Pointcut buildPointcut() {
|
||||
ComposablePointcut result = null;
|
||||
for (Class<? extends Annotation> publisherAnnotationType : this.publisherAnnotationTypes) {
|
||||
Pointcut cpc = new AnnotationMatchingPointcut(publisherAnnotationType, true);
|
||||
Pointcut mpc = new AnnotationMatchingPointcut(null, publisherAnnotationType);
|
||||
Pointcut cpc = new MetaAnnotationMatchingPointcut(publisherAnnotationType, true);
|
||||
Pointcut mpc = new MetaAnnotationMatchingPointcut(null, publisherAnnotationType);
|
||||
if (result == null) {
|
||||
result = new ComposablePointcut(cpc).union(mpc);
|
||||
}
|
||||
@@ -91,4 +98,91 @@ public class PublisherAnnotationAdvisor extends AbstractPointcutAdvisor implemen
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static class MetaAnnotationMatchingPointcut implements Pointcut {
|
||||
|
||||
private final ClassFilter classFilter;
|
||||
|
||||
private final MethodMatcher methodMatcher;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new MetaAnnotationMatchingPointcut for the given annotation type.
|
||||
* @param classAnnotationType the annotation type to look for at the class level
|
||||
* @param checkInherited whether to explicitly check the superclasses and
|
||||
* interfaces for the annotation type as well (even if the annotation type
|
||||
* is not marked as inherited itself)
|
||||
*/
|
||||
private MetaAnnotationMatchingPointcut(Class<? extends Annotation> classAnnotationType, boolean checkInherited) {
|
||||
this.classFilter = new AnnotationClassFilter(classAnnotationType, checkInherited);
|
||||
this.methodMatcher = MethodMatcher.TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new MetaAnnotationMatchingPointcut for the given annotation type.
|
||||
* @param classAnnotationType the annotation type to look for at the class level
|
||||
* (can be <code>null</code>)
|
||||
* @param methodAnnotationType the annotation type to look for at the method level
|
||||
* (can be <code>null</code>)
|
||||
*/
|
||||
private MetaAnnotationMatchingPointcut(
|
||||
Class<? extends Annotation> classAnnotationType, Class<? extends Annotation> methodAnnotationType) {
|
||||
|
||||
Assert.isTrue((classAnnotationType != null || methodAnnotationType != null),
|
||||
"Either Class annotation type or Method annotation type needs to be specified (or both)");
|
||||
|
||||
if (classAnnotationType != null) {
|
||||
this.classFilter = new AnnotationClassFilter(classAnnotationType);
|
||||
}
|
||||
else {
|
||||
this.classFilter = ClassFilter.TRUE;
|
||||
}
|
||||
|
||||
if (methodAnnotationType != null) {
|
||||
this.methodMatcher = new MetaAnnotationMethodMatcher(methodAnnotationType);
|
||||
}
|
||||
else {
|
||||
this.methodMatcher = MethodMatcher.TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ClassFilter getClassFilter() {
|
||||
return this.classFilter;
|
||||
}
|
||||
|
||||
public MethodMatcher getMethodMatcher() {
|
||||
return this.methodMatcher;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class MetaAnnotationMethodMatcher extends AnnotationMethodMatcher {
|
||||
|
||||
private final Class<? extends Annotation> annotationType;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new AnnotationClassFilter for the given annotation type.
|
||||
* @param annotationType the annotation type to look for
|
||||
*/
|
||||
private MetaAnnotationMethodMatcher(Class<? extends Annotation> annotationType) {
|
||||
super(annotationType);
|
||||
this.annotationType = annotationType;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public boolean matches(Method method, Class targetClass) {
|
||||
if (AnnotationUtils.getAnnotation(method, this.annotationType) != null) {
|
||||
return true;
|
||||
}
|
||||
// The method may be on an interface, so let's check on the target class as well.
|
||||
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
|
||||
return (specificMethod != method &&
|
||||
(AnnotationUtils.getAnnotation(specificMethod, this.annotationType) != null));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ package org.springframework.integration.aop;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -38,16 +43,17 @@ public class PublisherAnnotationAdvisorTests {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
context.registerSingleton("testChannel", QueueChannel.class);
|
||||
context.registerSingleton("testChannel", QueueChannel.class);
|
||||
context.registerSingleton("testMetaChannel", QueueChannel.class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void returnValue() {
|
||||
public void annotationAtMethodLevel() {
|
||||
PublisherAnnotationAdvisor advisor = new PublisherAnnotationAdvisor();
|
||||
advisor.setBeanFactory(context);
|
||||
QueueChannel testChannel = context.getBean("testChannel", QueueChannel.class);
|
||||
advisor.setDefaultChannel(testChannel);
|
||||
ProxyFactory pf = new ProxyFactory(new TestBeanImpl());
|
||||
ProxyFactory pf = new ProxyFactory(new AnnotationAtMethodLevelTestBeanImpl());
|
||||
pf.addAdvisor(advisor);
|
||||
TestBean proxy = (TestBean) pf.getProxy();
|
||||
proxy.test();
|
||||
@@ -56,6 +62,48 @@ public class PublisherAnnotationAdvisorTests {
|
||||
assertEquals("foo", message.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void annotationAtClassLevel() {
|
||||
PublisherAnnotationAdvisor advisor = new PublisherAnnotationAdvisor();
|
||||
advisor.setBeanFactory(context);
|
||||
QueueChannel testChannel = context.getBean("testChannel", QueueChannel.class);
|
||||
ProxyFactory pf = new ProxyFactory(new AnnotationAtClassLevelTestBeanImpl());
|
||||
pf.addAdvisor(advisor);
|
||||
TestBean proxy = (TestBean) pf.getProxy();
|
||||
proxy.test();
|
||||
Message<?> message = testChannel.receive(0);
|
||||
assertNotNull(message);
|
||||
assertEquals("foo", message.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metaAnnotationAtMethodLevel() {
|
||||
PublisherAnnotationAdvisor advisor = new PublisherAnnotationAdvisor();
|
||||
advisor.setBeanFactory(context);
|
||||
QueueChannel testMetaChannel = context.getBean("testMetaChannel", QueueChannel.class);
|
||||
ProxyFactory pf = new ProxyFactory(new MetaAnnotationAtMethodLevelTestBeanImpl());
|
||||
pf.addAdvisor(advisor);
|
||||
TestBean proxy = (TestBean) pf.getProxy();
|
||||
proxy.test();
|
||||
Message<?> message = testMetaChannel.receive(0);
|
||||
assertNotNull(message);
|
||||
assertEquals("foo", message.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metaAnnotationAtClassLevel() {
|
||||
PublisherAnnotationAdvisor advisor = new PublisherAnnotationAdvisor();
|
||||
advisor.setBeanFactory(context);
|
||||
QueueChannel testMetaChannel = context.getBean("testMetaChannel", QueueChannel.class);
|
||||
ProxyFactory pf = new ProxyFactory(new MetaAnnotationAtClassLevelTestBeanImpl());
|
||||
pf.addAdvisor(advisor);
|
||||
TestBean proxy = (TestBean) pf.getProxy();
|
||||
proxy.test();
|
||||
Message<?> message = testMetaChannel.receive(0);
|
||||
assertNotNull(message);
|
||||
assertEquals("foo", message.getPayload());
|
||||
}
|
||||
|
||||
|
||||
static interface TestBean {
|
||||
|
||||
@@ -64,13 +112,48 @@ public class PublisherAnnotationAdvisorTests {
|
||||
}
|
||||
|
||||
|
||||
static class TestBeanImpl implements TestBean {
|
||||
static class AnnotationAtMethodLevelTestBeanImpl implements TestBean {
|
||||
|
||||
@Publisher
|
||||
@Publisher(channel="testChannel")
|
||||
public String test() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Publisher(channel="testChannel")
|
||||
static class AnnotationAtClassLevelTestBeanImpl implements TestBean {
|
||||
|
||||
public String test() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Publisher(channel="testMetaChannel")
|
||||
public @interface TestMetaPublisher {
|
||||
}
|
||||
|
||||
|
||||
static class MetaAnnotationAtMethodLevelTestBeanImpl implements TestBean {
|
||||
|
||||
@TestMetaPublisher
|
||||
public String test() {
|
||||
return "foo";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@TestMetaPublisher
|
||||
static class MetaAnnotationAtClassLevelTestBeanImpl implements TestBean {
|
||||
|
||||
public String test() {
|
||||
return "foo";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user