Spring Integration application does not define cha

2019-05-26 11:37发布

问题:

I started to use Spring Integration in a project at work. Everything was looking fine and running smoothly on my local dev environment (when executed from Eclipse).

However, when I tried to deploy to our dev/staging environment I got some issues related with the definition of the Spring Integration channels.

After a couple of hours completely clueless (blaming external dependencies, our development/staging environment setup etc etc), I came to realize that I would get exactly the same issue whenever I tried to execute my application as a packaged jar (on my local machine)

I did a small "sample" application without any other dependencies in order to reproduce this issue. Once again everything works fine from eclipse but whenever executed as a packaged jar the following exception was thrown:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'gatewayChannel' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:687)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1207)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:284)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.integration.support.channel.BeanFactoryChannelResolver.resolveDestination(BeanFactoryChannelResolver.java:89)
    at org.springframework.integration.support.channel.BeanFactoryChannelResolver.resolveDestination(BeanFactoryChannelResolver.java:46)
    at org.springframework.integration.gateway.MessagingGatewaySupport.getRequestChannel(MessagingGatewaySupport.java:344)
    at org.springframework.integration.gateway.MessagingGatewaySupport.send(MessagingGatewaySupport.java:385)
    at org.springframework.integration.gateway.GatewayProxyFactoryBean.invokeGatewayMethod(GatewayProxyFactoryBean.java:481)
    at org.springframework.integration.gateway.GatewayProxyFactoryBean.doInvoke(GatewayProxyFactoryBean.java:433)
    at org.springframework.integration.gateway.GatewayProxyFactoryBean.invoke(GatewayProxyFactoryBean.java:424)
    at org.springframework.integration.gateway.GatewayCompletableFutureProxyFactoryBean.invoke(GatewayCompletableFutureProxyFactoryBean.java:65)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    at com.sun.proxy.$Proxy24.send(Unknown Source)
    at com.test.App$RealApp.doThings(App.java:52)
    at com.test.App.main(App.java:62)

Bellow you can find the code of my sample application and the pom that I used to build my packaged jar.

@ComponentScan
@EnableIntegration
@IntegrationComponentScan
@Configuration
public class App {

  @MessagingGateway
  public interface GatewayApp {

    @Gateway(requestChannel = "gatewayChannel")
    void send(String string);
  }

  @Bean
  public IntegrationFlow inboud() {
    return IntegrationFlows.from("gatewayChannel")
        .handle(System.out::println)
        .get();
  }

  @Component
  public class RealApp {
    @Autowired
    private GatewayApp gateway;

    public void doThings() {
      gateway.send("yeee");
    }
  }

  @SuppressWarnings("resource")
  public static void main(String[] args) {

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(App.class);
    context.refresh();
    context.getBean(RealApp.class).doThings();
  }
}

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>sprint-integration</groupId>
    <artifactId>integration</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>integration</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>4.3.14.RELEASE</spring.version>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.20.1</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <createDependencyReducedPom>true</createDependencyReducedPom>
                    <createSourcesJar>false</createSourcesJar>

                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <finalName>yo-service</finalName>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>

        <!-- Spring Integration -->
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-core</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-amqp</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-java-dsl</artifactId>
            <version>1.2.3.RELEASE</version>
        </dependency>
    </dependencies>
</project>

Note: I think this issue might have exaclty the same cause as the one reported in Spring integration bootstrap - intellij in debug works, packaged jar does not

回答1:

My best guess is the shade plugin is having some effect on the order in which BeanPostProcessors are run.

Does the app work if you explicitly define the channel...

@Bean
public MessageChannel gatewayChannel() {
    return new DirectChannel();
}

...?

If so, that would be a smoking gun. In that case, the next step would be to get a DEBUG log for org.springframework in both environments and compare the bean definition/creation/post processing logs.

If you can post a complete (simple) example that exhibits the problem, we can take a look.

EDIT

The problem is the shade plugin does not merge the META-INF/spring.factories files, so we lose the entry from the JAVA DSL and hence don't process any IntegrationFlows...

From the Uber jar we just have the core file...

org.springframework.integration.config.IntegrationConfigurationInitializer=\
org.springframework.integration.config.GlobalChannelInterceptorInitializer,\
org.springframework.integration.config.IntegrationConverterInitializer,\
org.springframework.integration.config.IdempotentReceiverAutoProxyCreatorInitializer

and so are missing the additional initializer from the DSL jar...

org.springframework.integration.config.IntegrationConfigurationInitializer=\
org.springframework.integration.dsl.config.DslIntegrationConfigurationInitializer

Hence it works with 5.0.1 because the DSL is now part of core.

Spring Boot solves this problem by nesting the jars instead of extracting all the classes.

EDIT2

Here's another work-around, if you can't move to Spring Integration 5. Add this bean to your application...

@Bean
public static BeanFactoryPostProcessor dslInitializer() {
    return new BeanFactoryPostProcessor() {

        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) throws BeansException {
            new DslIntegrationConfigurationInitializer().initialize(bf);
        }

    };
}

(Notice static).