Skip to content

Commit 462dc98

Browse files
artembilangaryrussell
authored andcommitted
GH-2695: Add proxy option to @EnablePublisher (#2701)
* GH-2695: Add proxy option to @EnablePublisher Fixes #2695 To configure a `proxyTargetClass=true` we need declare a `PublisherAnnotationBeanPostProcessor` bean manually, but that may cause a confuse when `@EnablePublisher` is still present. So, target service is proxied twice * Expose `proxyTargetClass` and `order` into the `@EnablePublisher` and `<enable-publisher>` * Refactor `PublisherAnnotationBeanPostProcessor` to extend an `AbstractBeanFactoryAwareAdvisingPostProcessor` to avoid AOP boilerplate code altogether * Add assertion into the `PublisherAnnotationBeanPostProcessor` to be sure that only one of its instance is present in the application context * * Polishing error message and Docs
1 parent cd8cbaa commit 462dc98

File tree

9 files changed

+150
-199
lines changed

9 files changed

+150
-199
lines changed

spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationBeanPostProcessor.java

+31-77
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,26 +16,19 @@
1616

1717
package org.springframework.integration.aop;
1818

19-
import java.util.Collections;
20-
import java.util.Set;
21-
import java.util.concurrent.ConcurrentHashMap;
22-
23-
import org.springframework.aop.framework.Advised;
24-
import org.springframework.aop.framework.ProxyConfig;
25-
import org.springframework.aop.framework.ProxyFactory;
26-
import org.springframework.aop.support.AopUtils;
27-
import org.springframework.beans.BeansException;
28-
import org.springframework.beans.factory.BeanClassLoaderAware;
19+
import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor;
20+
import org.springframework.beans.factory.BeanCreationNotAllowedException;
2921
import org.springframework.beans.factory.BeanFactory;
30-
import org.springframework.beans.factory.BeanFactoryAware;
22+
import org.springframework.beans.factory.BeanNameAware;
3123
import org.springframework.beans.factory.InitializingBean;
32-
import org.springframework.beans.factory.config.BeanPostProcessor;
33-
import org.springframework.core.Ordered;
34-
import org.springframework.util.ClassUtils;
24+
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
3525

3626
/**
3727
* Post-processes beans that contain the
3828
* method-level @{@link org.springframework.integration.annotation.Publisher} annotation.
29+
* <p>
30+
* Only one bean instance of this processor can be declared in the application context, manual
31+
* or automatic by thr framework via annotation or XML processing.
3932
*
4033
* @author Oleg Zhurakousky
4134
* @author Mark Fisher
@@ -46,21 +39,14 @@
4639
* @since 2.0
4740
*/
4841
@SuppressWarnings("serial")
49-
public class PublisherAnnotationBeanPostProcessor extends ProxyConfig
50-
implements BeanPostProcessor, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, Ordered {
51-
52-
private volatile String defaultChannelName;
53-
54-
private volatile PublisherAnnotationAdvisor advisor;
42+
public class PublisherAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
43+
implements BeanNameAware, InitializingBean {
5544

56-
private volatile int order = Ordered.LOWEST_PRECEDENCE;
45+
private String defaultChannelName;
5746

58-
private volatile BeanFactory beanFactory;
47+
private String name;
5948

60-
private volatile ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
61-
62-
private final Set<Class<?>> nonApplicableCache =
63-
Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(256));
49+
private BeanFactory beanFactory;
6450

6551
/**
6652
* Set the default channel where Messages should be sent if the annotation
@@ -73,63 +59,31 @@ public void setDefaultChannelName(String defaultChannelName) {
7359
}
7460

7561
@Override
76-
public void setBeanFactory(BeanFactory beanFactory) {
77-
this.beanFactory = beanFactory;
78-
}
79-
80-
@Override
81-
public void setBeanClassLoader(ClassLoader classLoader) {
82-
this.beanClassLoader = classLoader;
83-
}
84-
85-
public void setOrder(int order) {
86-
this.order = order;
87-
}
88-
89-
@Override
90-
public int getOrder() {
91-
return this.order;
92-
}
93-
94-
@Override
95-
public void afterPropertiesSet() {
96-
this.advisor = new PublisherAnnotationAdvisor();
97-
this.advisor.setBeanFactory(this.beanFactory);
98-
this.advisor.setDefaultChannelName(this.defaultChannelName);
62+
public void setBeanName(String name) {
63+
this.name = name;
9964
}
10065

10166
@Override
102-
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
103-
return bean;
67+
public void setBeanFactory(BeanFactory beanFactory) {
68+
this.beanFactory = beanFactory;
69+
super.setBeanFactory(beanFactory);
70+
PublisherAnnotationAdvisor publisherAnnotationAdvisor = new PublisherAnnotationAdvisor();
71+
publisherAnnotationAdvisor.setBeanFactory(beanFactory);
72+
publisherAnnotationAdvisor.setDefaultChannelName(this.defaultChannelName);
73+
this.advisor = publisherAnnotationAdvisor;
10474
}
10575

10676
@Override
107-
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
108-
Class<?> targetClass = AopUtils.getTargetClass(bean);
109-
110-
// the set will hold records of prior class scans and will contain the bean classes that can not
111-
// be assigned to the Advisor interface and therefore can be short circuited
112-
if (this.nonApplicableCache.contains(targetClass)) {
113-
return bean;
114-
}
115-
116-
if (AopUtils.canApply(this.advisor, targetClass)) {
117-
if (bean instanceof Advised) {
118-
((Advised) bean).addAdvisor(this.advisor);
119-
return bean;
120-
}
121-
else {
122-
ProxyFactory proxyFactory = new ProxyFactory(bean);
123-
// Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
124-
proxyFactory.copyFrom(this);
125-
proxyFactory.addAdvisor(this.advisor);
126-
return proxyFactory.getProxy(this.beanClassLoader);
127-
}
77+
public void afterPropertiesSet() throws Exception {
78+
try {
79+
this.beanFactory.getBean(PublisherAnnotationBeanPostProcessor.class);
12880
}
129-
else {
130-
// cannot apply advisor
131-
this.nonApplicableCache.add(targetClass);
132-
return bean;
81+
catch (NoUniqueBeanDefinitionException ex) {
82+
throw new BeanCreationNotAllowedException(this.name,
83+
"Only one 'PublisherAnnotationBeanPostProcessor' bean can be defined in the application context." +
84+
" Do not use '@EnablePublisher' (or '<int:enable-publisher>') if you declare a" +
85+
" 'PublisherAnnotationBeanPostProcessor' bean definition manually. " +
86+
"Bean names found: " + ex.getBeanNamesFound());
13387
}
13488
}
13589

spring-integration-core/src/main/java/org/springframework/integration/config/EnablePublisher.java

+40-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2018 the original author or authors.
2+
* Copyright 2014-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,11 +23,15 @@
2323
import java.lang.annotation.Target;
2424

2525
import org.springframework.context.annotation.Import;
26+
import org.springframework.core.Ordered;
27+
import org.springframework.core.annotation.AliasFor;
2628

2729
/**
28-
* Provides the registration for the {@link org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor}
30+
* Provides the registration for the
31+
* {@link org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor}
2932
* to allow the use of the {@link org.springframework.integration.annotation.Publisher} annotation.
30-
* In addition the {@code default-publisher-channel} name can be configured as the {@code value} of this annotation.
33+
* In addition the {@code default-publisher-channel} name can be configured as
34+
* the {@link #defaultChannel()} of this annotation.
3135
*
3236
* @author Artem Bilan
3337
*
@@ -40,7 +44,39 @@
4044
public @interface EnablePublisher {
4145

4246
/**
43-
* @return the {@code default-publisher-channel} name.
47+
* Alias for the {@link #defaultChannel()} attribute.
48+
* The {@code default-publisher-channel} name.
49+
* @return the channel bean name.
4450
*/
51+
@AliasFor("defaultChannel")
4552
String value() default "";
53+
54+
/**
55+
* The {@code default-publisher-channel} name.
56+
* @return the channel bean name.
57+
* @since 5.1.3
58+
*/
59+
@AliasFor("value")
60+
String defaultChannel() default "";
61+
62+
/**
63+
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
64+
* to standard Java interface-based proxies.
65+
* @return whether proxy target class or not.
66+
* @since 5.1.3
67+
*/
68+
boolean proxyTargetClass() default false;
69+
70+
/**
71+
* Indicate the order in which the
72+
* {@link org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor}
73+
* should be applied.
74+
* <p>The default is {@link Ordered#LOWEST_PRECEDENCE} in order to run
75+
* after all other post-processors, so that it can add an advisor to
76+
* existing proxies rather than double-proxy.
77+
* @return the order for the bean post-processor.
78+
* @since 5.1.3
79+
*/
80+
int order() default Ordered.LOWEST_PRECEDENCE;
81+
4682
}

spring-integration-core/src/main/java/org/springframework/integration/config/PublisherRegistrar.java

+21-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014 the original author or authors.
2+
* Copyright 2014-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@
3737
/**
3838
* @author Artem Bilan
3939
* @author Gary Russell
40+
*
4041
* @since 4.0
4142
*/
4243
public class PublisherRegistrar implements ImportBeanDefinitionRegistrar {
@@ -48,21 +49,28 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B
4849
Map<String, Object> annotationAttributes =
4950
importingClassMetadata.getAnnotationAttributes(EnablePublisher.class.getName());
5051

51-
String value = (annotationAttributes == null
52-
? (String) AnnotationUtils.getDefaultValue(EnablePublisher.class)
53-
: (String) annotationAttributes.get("value"));
52+
String defaultChannel =
53+
annotationAttributes == null
54+
? (String) AnnotationUtils.getDefaultValue(EnablePublisher.class)
55+
: (String) annotationAttributes.get("defaultChannel");
5456
if (!registry.containsBeanDefinition(IntegrationContextUtils.PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME)) {
5557
BeanDefinitionBuilder builder =
5658
BeanDefinitionBuilder.genericBeanDefinition(PublisherAnnotationBeanPostProcessor.class)
5759
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
5860

59-
if (StringUtils.hasText(value)) {
60-
builder.addPropertyValue("defaultChannelName", value);
61+
if (StringUtils.hasText(defaultChannel)) {
62+
builder.addPropertyValue("defaultChannelName", defaultChannel);
6163
if (logger.isInfoEnabled()) {
62-
logger.info("Setting '@Publisher' default-output-channel to '" + value + "'.");
64+
logger.info("Setting '@Publisher' default-output-channel to '" + defaultChannel + "'.");
6365
}
6466
}
6567

68+
if (annotationAttributes != null) {
69+
Object proxyTargetClass = annotationAttributes.get("proxyTargetClass");
70+
builder.addPropertyValue("proxyTargetClass", proxyTargetClass);
71+
Object order = annotationAttributes.get("order");
72+
builder.addPropertyValue("order", order);
73+
}
6674
registry.registerBeanDefinition(IntegrationContextUtils.PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME,
6775
builder.getBeanDefinition());
6876
}
@@ -71,17 +79,17 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B
7179
registry.getBeanDefinition(IntegrationContextUtils.PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME);
7280
MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
7381
PropertyValue defaultChannelPropertyValue = propertyValues.getPropertyValue("defaultChannelName");
74-
if (StringUtils.hasText(value)) {
82+
if (StringUtils.hasText(defaultChannel)) {
7583
if (defaultChannelPropertyValue == null) {
76-
propertyValues.addPropertyValue("defaultChannelName", value);
84+
propertyValues.addPropertyValue("defaultChannelName", defaultChannel);
7785
if (logger.isInfoEnabled()) {
78-
logger.info("Setting '@Publisher' default-output-channel to '" + value + "'.");
86+
logger.info("Setting '@Publisher' default-output-channel to '" + defaultChannel + "'.");
7987
}
8088
}
81-
else if (!value.equals(defaultChannelPropertyValue.getValue())) {
89+
else if (!defaultChannel.equals(defaultChannelPropertyValue.getValue())) {
8290
throw new BeanDefinitionStoreException("When more than one enable publisher definition " +
83-
"(@EnablePublisher or <annotation-config>)" +
84-
" is found in the context, they all must have the same 'default-publisher-channel' value.");
91+
"(@EnablePublisher or <annotation-config>) is found in the context, " +
92+
"they all must have the same 'default-publisher-channel' attribute value.");
8593
}
8694
}
8795
}

spring-integration-core/src/main/java/org/springframework/integration/config/xml/AnnotationConfigParser.java

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
1616

1717
package org.springframework.integration.config.xml;
1818

19-
import java.util.Collections;
19+
import java.util.HashMap;
2020
import java.util.Map;
2121

2222
import org.w3c.dom.Element;
@@ -40,8 +40,6 @@ public class AnnotationConfigParser implements BeanDefinitionParser {
4040

4141
@Override
4242
public BeanDefinition parse(final Element element, ParserContext parserContext) {
43-
IntegrationRegistrar integrationRegistrar = new IntegrationRegistrar();
44-
4543
StandardAnnotationMetadata importingClassMetadata =
4644
new StandardAnnotationMetadata(Object.class) {
4745

@@ -51,8 +49,13 @@ public Map<String, Object> getAnnotationAttributes(String annotationType) {
5149
Element enablePublisherElement =
5250
DomUtils.getChildElementByTagName(element, "enable-publisher");
5351
if (enablePublisherElement != null) {
54-
return Collections.singletonMap("value",
52+
Map<String, Object> attributes = new HashMap<>();
53+
attributes.put("defaultChannel",
5554
enablePublisherElement.getAttribute("default-publisher-channel"));
55+
attributes.put("proxyTargetClass",
56+
enablePublisherElement.getAttribute("proxy-target-class"));
57+
attributes.put("order", enablePublisherElement.getAttribute("order"));
58+
return attributes;
5659
}
5760
else {
5861
return null;
@@ -65,7 +68,7 @@ public Map<String, Object> getAnnotationAttributes(String annotationType) {
6568

6669
};
6770

68-
integrationRegistrar.registerBeanDefinitions(importingClassMetadata, parserContext.getRegistry());
71+
new IntegrationRegistrar().registerBeanDefinitions(importingClassMetadata, parserContext.getRegistry());
6972

7073
return null;
7174
}

spring-integration-core/src/main/resources/org/springframework/integration/config/spring-integration-5.1.xsd

+24
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,30 @@
3535
</xsd:appinfo>
3636
</xsd:annotation>
3737
</xsd:attribute>
38+
<xsd:attribute name="proxy-target-class" default="false">
39+
<xsd:annotation>
40+
<xsd:documentation>
41+
Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
42+
to standard Java interface-based proxies.
43+
</xsd:documentation>
44+
</xsd:annotation>
45+
<xsd:simpleType>
46+
<xsd:union memberTypes="xsd:boolean xsd:string" />
47+
</xsd:simpleType>
48+
</xsd:attribute>
49+
<xsd:attribute name="order" default="2147483647">
50+
<xsd:annotation>
51+
<xsd:documentation>
52+
Indicate the order in which the
53+
'org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor'
54+
should be applied.
55+
Defaults to 'org.springframework.core.Ordered.LOWEST_PRECEDENCE'.
56+
</xsd:documentation>
57+
</xsd:annotation>
58+
<xsd:simpleType>
59+
<xsd:union memberTypes="xsd:integer xsd:string" />
60+
</xsd:simpleType>
61+
</xsd:attribute>
3862
</xsd:complexType>
3963
</xsd:element>
4064
</xsd:sequence>

spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests-context.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<message-history tracked-components="publishedChannel,input,annotationTestService*"/>
1616

1717
<annotation-config>
18-
<enable-publisher default-publisher-channel="publishedChannel"/>
18+
<enable-publisher default-publisher-channel="publishedChannel" proxy-target-class="true" order="2147483646"/>
1919
</annotation-config>
2020

2121
<channel-interceptor pattern="none">

0 commit comments

Comments
 (0)