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:
Mark Fisher
2010-08-17 22:18:40 +00:00
parent 3eed02fbb3
commit 929e14a320
3 changed files with 222 additions and 21 deletions

View File

@@ -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;

View File

@@ -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));
}
}
}

View File

@@ -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";
}
}
}