Search code examples
springspring-integrationspring-integration-sftp

SFTP : BeanPostProcessor interfere with @ServiceActivator and @MessagingGateway


It seems BeanPostProcessor interface implementation is having impact on @ServiceActivator. What should be the way to use BeanPostProcessor with @ServiceActivator. Thanks.

Complete logs are available here logs

Following is Java Config used for SFTP -

package com.ftp.example;

import java.io.File;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.sftp.outbound.SftpMessageHandler;
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

import com.jcraft.jsch.ChannelSftp.LsEntry;

@Configuration
@EnableScheduling
@EnableAspectJAutoProxy
@EnableAsync
@IntegrationComponentScan
@EnableIntegration
@EnableBatchProcessing
@PropertySource("file:C:\\DEV\\workspace_oxygen\\ftp-example\\ftp-example.properties")
public class DependencySpringConfiguration {

    private Logger LOG = LoggerFactory.getLogger(DependencySpringConfiguration.class);

    @Value("${project.name}")
    private String applicationName;


    @Value("${${project.name}.ftp.server}")
    private String server;

    @Value("${${project.name}.ftp.port}")
    int port;

    @Value("${${project.name}.ftp.username}")
    private String username;

    @Value("${${project.name}.ftp.password}")
    private String password;

    @Value("${${project.name}.ftp.remote.directory}")
    private String remoteDirectory;

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public ProcessStarter processStarter() {
        return new ProcessStarter();
    }

/*  @Bean
    public LogInjector logInjector() {
        return new LogInjector();
    }*/


    @Bean
    public FTPOutService fTPOutService() {
        return new FTPOutService();
    }


    @Bean
    public SessionFactory<LsEntry> sftpSessionFactory() {
        DefaultSftpSessionFactory sf = new DefaultSftpSessionFactory();
        sf.setHost(server);
        sf.setPort(port);
        sf.setUser(username);
        sf.setPassword(password);
        sf.setAllowUnknownKeys(true);
        return new CachingSessionFactory<LsEntry>(sf);
    }

    @Bean
    @ServiceActivator(inputChannel = "toSftpChannel")
    public MessageHandler handler() {
        SftpMessageHandler handler = new SftpMessageHandler(sftpSessionFactory());
        handler.setRemoteDirectoryExpression(new LiteralExpression(remoteDirectory));
        handler.setFileNameGenerator(new FileNameGenerator() {

            @Override
            public String generateFileName(Message<?> message) {
                return "fileNameToBeFtp.txt";
            }

        });
        return handler;
    }

    @MessagingGateway
    public interface MyGateway {
        @Gateway(requestChannel = "toSftpChannel")
        void sendToSftp(File file);
    }


}

And We are calling gateway object like this while doing SFTP

Main class

public class FtpExample {

    public static String[] ARGS;
    private static final Logger LOG = LoggerFactory.getLogger(FtpExample.class);

    public static void main(String[] args) throws Exception {
        ARGS = args;
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(DependencySpringConfiguration.class);

        ProcessStarter processStarter = ctx.getBean(ProcessStarter.class);
        processStarter.startService();
    }
}

Other classes -

public class ProcessStarter {
    @Inject
    private FTPOutService ftpOutService;

    public void startService() {
        ftpOutService.ftpToBbg();
    }
}



public class FTPOutService {
    private static Logger log = LoggerFactory.getLogger(FTPOutService.class);

    @Inject
    private ApplicationContext appContext;

    public void ftpToBbg() {

        log.info("Starting FTP out process...");
        File file = null;
        try {
            file = new File("C:\\Temp\\log\\debug\\ftp\\priceindex\\for-upload\\ftp-example.txt.REQ");
            MyGateway gateway = appContext.getBean(MyGateway.class);
            gateway.sendToSftp(file);
            log.info("File {} written successfully on remote server", file);
        } catch (Exception e) {
            log.error("Error while uploading file {}", file, e);
        }
    }

}

Above code is working fine unless I am not adding following bean declaration in above defined Java Config -

public LogInjector logInjector() {
            return new LogInjector();
}

Above bean definition is having following implementation -

public class LogInjector implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
        ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                // make the field accessible if defined private
                ReflectionUtils.makeAccessible(field);
                if (field.getAnnotation(Log.class) != null) {
                    if (org.slf4j.Logger.class == field.getType()) {
                        org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(bean.getClass());
                        field.set(bean, log);
                    }  else if (java.util.logging.Logger.class == field.getType()) {
                        java.util.logging.Logger log = java.util.logging.Logger.getLogger(bean.getClass().toString());
                        field.set(bean, log);
                    }
                }
            }
        });
        return bean;
    }
}


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface Log {
}

Once any BeanPostProcessor implementation is added in Java Config, it creates problem and application not able to see toSftpChannel -

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'toSftpChannel' available at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:685) at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1199) 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:88) at org.springframework.integration.support.channel.BeanFactoryChannelResolver.resolveDestination(BeanFactoryChannelResolver.java:45) at org.springframework.integration.gateway.MessagingGatewaySupport.getRequestChannel(MessagingGatewaySupport.java:327) at org.springframework.integration.gateway.MessagingGatewaySupport.send(MessagingGatewaySupport.java:368) at org.springframework.integration.gateway.GatewayProxyFactoryBean.invokeGatewayMethod(GatewayProxyFactoryBean.java:477) at org.springframework.integration.gateway.GatewayProxyFactoryBean.doInvoke(GatewayProxyFactoryBean.java:429) at org.springframework.integration.gateway.GatewayProxyFactoryBean.invoke(GatewayProxyFactoryBean.java:420) 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.$Proxy57.sendToSftp(Unknown Source)


Solution

  • Looks what you have:

    @Bean
    public LogInjector logInjector() {
        return new LogInjector();
    }
    

    If you declare BeanPostProcessors as @Bean you have to specify them with the static modifier: https://docs.spring.io/spring/docs/5.0.0.RELEASE/spring-framework-reference/core.html#beans-factorybeans-annotations

    You may declare @Bean methods as static, allowing for them to be called without creating their containing configuration class as an instance. This makes particular sense when defining post-processor beans, e.g. of type BeanFactoryPostProcessor or BeanPostProcessor, since such beans will get initialized early in the container lifecycle and should avoid triggering other parts of the configuration at that point.