4

I have a 3.2.14.RELEASE spring app and am using java config to wire and inject the beans.

In my case i need to

  1. set up a SSH Tunnel
  2. create a DataSource so that it uses the same ssh tunnel session
  3. create a Query class which executes a SQL query using a JDBCTemplate

I've setup my ApplicationContext link this with @DependsOn annotations to link the three beans

package com.b.e.kpireport;

@Configuration
@ComponentScan(basePackages = {"com.b.e.kpireport" })
public class ApplicationContext {

    @Bean(name = "sshTunnel")
    public SSHTunnel getSSHTunnel() {
        return new SSHTunnel();
    }

    @Bean(name = "dataSource" )
    @DependsOn("sshTunnel")
    public DataSource getDataSource() {

        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(jdbcUrl);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        logger.info("getDataSource():"+jdbcUrl+":"+username+"/"+password+":"+driver);
        return dataSource;
    }

    @Bean
    @DependsOn("dataSource")
    public Query getQuery() {
        return new Query();
    }  
}

The Query class looks like

class Query {

    private JdbcTemplate jdbcTemplate;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private SSHTunnel sshTunnel;

    public void runQuery() {
        sshTunnel.openSession();
        jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.execute(sql);
    }

Regardless what i've tried, i see that the dataSource is initialised before the SSHTunnel

Aug 26, 2015 5:20:44 PM com.b.e.kpireport.ApplicationContext getDataSource
INFO: getDataSource():jdbc:mysql://localhost:3006/centstorage:davidobrien/MnBufeuwncv3eR:com.mysql.jdbc.Driver
Aug 26, 2015 5:20:44 PM com.b.e.kpireport.SSHTunnel openSession
INFO: openSession

Any suggestions on how I can ensure the correct bean initialisation order?

emeraldjava
  • 10,894
  • 26
  • 97
  • 170
  • and when you try `public DataSource getDataSource(SSHTunnel tunnel)` - even when you don't need it - does it help? – sodik Aug 26 '15 at 16:43
  • btw: you are opening ssh session only when running query - so it should be after data source is created. – sodik Aug 26 '15 at 16:47
  • @sodik - the ssh session is currently opened first and i can investigate this more but this still doesn't address the order of bean creation in this question. Also which class are you adding the method above to, I assume its the Query.class? – emeraldjava Aug 26 '15 at 16:51
  • I meant `@Bean(name = "dataSource" ) public DataSource getDataSource(SSHTunnel tunnel) ` in your configuration class. – sodik Aug 26 '15 at 17:58

1 Answers1

0

The solution may actually be simple:

@Configuration
public class ApplicationContext {
    @Bean(name = "sshTunnel")
    public SSHTunnel getSSHTunnel() {
        return new SSHTunnel();
    }

    @Bean(name = "dataSource" )
    public DataSource getDataSource() {
         getSHTunnel();
         DriverManagerDataSource dataSource = new DriverManagerDataSource();
         // ...
         return dataSource;
    }

    @Bean
    public Query getQuery() {
        return new Query(getSSHTunnel(), getDataSource());
    }  
}

We use this pattern of calling methods of the configuration class (from within the class) very often. When creating the context, the methods will not be called on you class directly, but on its subclass that Spring creates dynamically. That ensures that even if you call a method (e.g. getSSHTunnel()) multiple times, the corresponding bean is created only once (i.e. only one tunnel will be created).

I also prefer passing dependencies via constructor rather than with @Autowired - it is less magical and easier to test. This is optional though.

I don't know why using @DependsOn doesn't work for you, though. When I tried it locally, it worked (with the same Spring version). In any case, the above is an example how to do without it entirely.


Edit in response to comment:

But in this case you can just delete all the spring annotations since you are not doing any dependency injection. You've basically just got a plain old java object

That is not true. As I said, calling a method on a @Configuration object doesn't call your method directly, but its version overridden by Spring returning the bean according to configuration. This allows you to do proper dependency injection, e.g. in tests like this:

@Configuration 
public class TestDependencies {
    @Bean(name="dataSource")
    public DataSource getDataSource() {
        return new MockDataSource();
    }
}
...
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationContext.class, TestDependencies.class);
context.getBean("dataSource"); // returns MockDataSource

In other words, calling the methods like getDataSource() is in fact equivalent to having the dependency @Autowired or using context.getBean(). This approach doesn't have any pitfalls AFAIK (as long as you don't pass arguments to @Bean methods, otherwise things get a little weird) and we've been using it for a long time .

Community
  • 1
  • 1
Mifeet
  • 12,949
  • 5
  • 60
  • 108
  • But in this case you can just delete all the spring annotations since you are not doing any dependency injection. You've basically just got a plain old java object – emeraldjava Aug 27 '15 at 10:18
  • That's not true, see the edit in my answer, comment was too limiting for the explanation. – Mifeet Aug 28 '15 at 09:17