Skip to content

GH-2695: Add proxy option to @EnablePublisher #2701

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,26 +16,19 @@

package org.springframework.integration.aop;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor;
import org.springframework.beans.factory.BeanCreationNotAllowedException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.util.ClassUtils;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;

/**
* Post-processes beans that contain the
* method-level @{@link org.springframework.integration.annotation.Publisher} annotation.
* <p>
* Only one bean instance of this processor can be declared in the application context, manual
* or automatic by thr framework via annotation or XML processing.
*
* @author Oleg Zhurakousky
* @author Mark Fisher
Expand All @@ -46,21 +39,14 @@
* @since 2.0
*/
@SuppressWarnings("serial")
public class PublisherAnnotationBeanPostProcessor extends ProxyConfig
implements BeanPostProcessor, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, Ordered {

private volatile String defaultChannelName;

private volatile PublisherAnnotationAdvisor advisor;
public class PublisherAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
implements BeanNameAware, InitializingBean {

private volatile int order = Ordered.LOWEST_PRECEDENCE;
private String defaultChannelName;

private volatile BeanFactory beanFactory;
private String name;

private volatile ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

private final Set<Class<?>> nonApplicableCache =
Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(256));
private BeanFactory beanFactory;

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

@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}

@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}

public void setOrder(int order) {
this.order = order;
}

@Override
public int getOrder() {
return this.order;
}

@Override
public void afterPropertiesSet() {
this.advisor = new PublisherAnnotationAdvisor();
this.advisor.setBeanFactory(this.beanFactory);
this.advisor.setDefaultChannelName(this.defaultChannelName);
public void setBeanName(String name) {
this.name = name;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
super.setBeanFactory(beanFactory);
PublisherAnnotationAdvisor publisherAnnotationAdvisor = new PublisherAnnotationAdvisor();
publisherAnnotationAdvisor.setBeanFactory(beanFactory);
publisherAnnotationAdvisor.setDefaultChannelName(this.defaultChannelName);
this.advisor = publisherAnnotationAdvisor;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> targetClass = AopUtils.getTargetClass(bean);

// the set will hold records of prior class scans and will contain the bean classes that can not
// be assigned to the Advisor interface and therefore can be short circuited
if (this.nonApplicableCache.contains(targetClass)) {
return bean;
}

if (AopUtils.canApply(this.advisor, targetClass)) {
if (bean instanceof Advised) {
((Advised) bean).addAdvisor(this.advisor);
return bean;
}
else {
ProxyFactory proxyFactory = new ProxyFactory(bean);
// Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
proxyFactory.copyFrom(this);
proxyFactory.addAdvisor(this.advisor);
return proxyFactory.getProxy(this.beanClassLoader);
}
public void afterPropertiesSet() throws Exception {
try {
this.beanFactory.getBean(PublisherAnnotationBeanPostProcessor.class);
}
else {
// cannot apply advisor
this.nonApplicableCache.add(targetClass);
return bean;
catch (NoUniqueBeanDefinitionException ex) {
throw new BeanCreationNotAllowedException(this.name,
"Only one 'PublisherAnnotationBeanPostProcessor' bean can be defined in the application context." +
" Do not use '@EnablePublisher' (or '<int:enable-publisher>') if you declare a" +
" 'PublisherAnnotationBeanPostProcessor' bean definition manually. " +
"Bean names found: " + ex.getBeanNamesFound());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,11 +23,15 @@
import java.lang.annotation.Target;

import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AliasFor;

/**
* Provides the registration for the {@link org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor}
* Provides the registration for the
* {@link org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor}
* to allow the use of the {@link org.springframework.integration.annotation.Publisher} annotation.
* In addition the {@code default-publisher-channel} name can be configured as the {@code value} of this annotation.
* In addition the {@code default-publisher-channel} name can be configured as
* the {@link #defaultChannel()} of this annotation.
*
* @author Artem Bilan
*
Expand All @@ -40,7 +44,39 @@
public @interface EnablePublisher {

/**
* @return the {@code default-publisher-channel} name.
* Alias for the {@link #defaultChannel()} attribute.
* The {@code default-publisher-channel} name.
* @return the channel bean name.
*/
@AliasFor("defaultChannel")
String value() default "";

/**
* The {@code default-publisher-channel} name.
* @return the channel bean name.
* @since 5.1.3
*/
@AliasFor("value")
String defaultChannel() default "";

/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies.
* @return whether proxy target class or not.
* @since 5.1.3
*/
boolean proxyTargetClass() default false;

/**
* Indicate the order in which the
* {@link org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor}
* should be applied.
* <p>The default is {@link Ordered#LOWEST_PRECEDENCE} in order to run
* after all other post-processors, so that it can add an advisor to
* existing proxies rather than double-proxy.
* @return the order for the bean post-processor.
* @since 5.1.3
*/
int order() default Ordered.LOWEST_PRECEDENCE;

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -37,6 +37,7 @@
/**
* @author Artem Bilan
* @author Gary Russell
*
* @since 4.0
*/
public class PublisherRegistrar implements ImportBeanDefinitionRegistrar {
Expand All @@ -48,21 +49,28 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B
Map<String, Object> annotationAttributes =
importingClassMetadata.getAnnotationAttributes(EnablePublisher.class.getName());

String value = (annotationAttributes == null
? (String) AnnotationUtils.getDefaultValue(EnablePublisher.class)
: (String) annotationAttributes.get("value"));
String defaultChannel =
annotationAttributes == null
? (String) AnnotationUtils.getDefaultValue(EnablePublisher.class)
: (String) annotationAttributes.get("defaultChannel");
if (!registry.containsBeanDefinition(IntegrationContextUtils.PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME)) {
BeanDefinitionBuilder builder =
BeanDefinitionBuilder.genericBeanDefinition(PublisherAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

if (StringUtils.hasText(value)) {
builder.addPropertyValue("defaultChannelName", value);
if (StringUtils.hasText(defaultChannel)) {
builder.addPropertyValue("defaultChannelName", defaultChannel);
if (logger.isInfoEnabled()) {
logger.info("Setting '@Publisher' default-output-channel to '" + value + "'.");
logger.info("Setting '@Publisher' default-output-channel to '" + defaultChannel + "'.");
}
}

if (annotationAttributes != null) {
Object proxyTargetClass = annotationAttributes.get("proxyTargetClass");
builder.addPropertyValue("proxyTargetClass", proxyTargetClass);
Object order = annotationAttributes.get("order");
builder.addPropertyValue("order", order);
}
registry.registerBeanDefinition(IntegrationContextUtils.PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME,
builder.getBeanDefinition());
}
Expand All @@ -71,17 +79,17 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B
registry.getBeanDefinition(IntegrationContextUtils.PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME);
MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
PropertyValue defaultChannelPropertyValue = propertyValues.getPropertyValue("defaultChannelName");
if (StringUtils.hasText(value)) {
if (StringUtils.hasText(defaultChannel)) {
if (defaultChannelPropertyValue == null) {
propertyValues.addPropertyValue("defaultChannelName", value);
propertyValues.addPropertyValue("defaultChannelName", defaultChannel);
if (logger.isInfoEnabled()) {
logger.info("Setting '@Publisher' default-output-channel to '" + value + "'.");
logger.info("Setting '@Publisher' default-output-channel to '" + defaultChannel + "'.");
}
}
else if (!value.equals(defaultChannelPropertyValue.getValue())) {
else if (!defaultChannel.equals(defaultChannelPropertyValue.getValue())) {
throw new BeanDefinitionStoreException("When more than one enable publisher definition " +
"(@EnablePublisher or <annotation-config>)" +
" is found in the context, they all must have the same 'default-publisher-channel' value.");
"(@EnablePublisher or <annotation-config>) is found in the context, " +
"they all must have the same 'default-publisher-channel' attribute value.");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,7 +16,7 @@

package org.springframework.integration.config.xml;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

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

@Override
public BeanDefinition parse(final Element element, ParserContext parserContext) {
IntegrationRegistrar integrationRegistrar = new IntegrationRegistrar();

StandardAnnotationMetadata importingClassMetadata =
new StandardAnnotationMetadata(Object.class) {

Expand All @@ -51,8 +49,13 @@ public Map<String, Object> getAnnotationAttributes(String annotationType) {
Element enablePublisherElement =
DomUtils.getChildElementByTagName(element, "enable-publisher");
if (enablePublisherElement != null) {
return Collections.singletonMap("value",
Map<String, Object> attributes = new HashMap<>();
attributes.put("defaultChannel",
enablePublisherElement.getAttribute("default-publisher-channel"));
attributes.put("proxyTargetClass",
enablePublisherElement.getAttribute("proxy-target-class"));
attributes.put("order", enablePublisherElement.getAttribute("order"));
return attributes;
}
else {
return null;
Expand All @@ -65,7 +68,7 @@ public Map<String, Object> getAnnotationAttributes(String annotationType) {

};

integrationRegistrar.registerBeanDefinitions(importingClassMetadata, parserContext.getRegistry());
new IntegrationRegistrar().registerBeanDefinitions(importingClassMetadata, parserContext.getRegistry());

return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,30 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="proxy-target-class" default="false">
<xsd:annotation>
<xsd:documentation>
Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
to standard Java interface-based proxies.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="order" default="2147483647">
<xsd:annotation>
<xsd:documentation>
Indicate the order in which the
'org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor'
should be applied.
Defaults to 'org.springframework.core.Ordered.LOWEST_PRECEDENCE'.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:integer xsd:string" />
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<message-history tracked-components="publishedChannel,input,annotationTestService*"/>

<annotation-config>
<enable-publisher default-publisher-channel="publishedChannel"/>
<enable-publisher default-publisher-channel="publishedChannel" proxy-target-class="true" order="2147483646"/>
</annotation-config>

<channel-interceptor pattern="none">
Expand Down
Loading