When I configure a class that implements DataSource
(for example HikariCP) via XML, it looks something like this:
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close"/>
According to the Spring Reference manual, the equivalent of this in JavaConfig is:
@Bean (destroyMethod = "close")
public DataSource dataSource () {
return new HikariDataSource();
}
Why are we returning the interface type in JavaConfig, especially in this case, where DataSource
doesn't have a close()
method, but the implementation does (and somehow Spring does manage to find the close()
method)?
I couldn't find any difference in the application's behavior between this configuration method to using the implementation's type:
@Bean (destroyMethod = "close")
public HikariDataSource dataSource () {
return new HikariDataSource();
}
Even when considering autowiring this shouldn't be a problem, since both return types will work for a DataSource
instance variable.
So what is the correct type to return (if there is a 'correct' one), and why?
I couldn't find any difference in the application's behavior between
this configuration method to using the implementation's type
You have got it right, there will not be any difference because in both case finally what is returned is new HikariDataSource();
, i.e. object of HikariDataSource
class.
You first case is more scalable because in future you can return some other implementation of DataSource
without changing the return type. Or you can update that method to implement a Factory design pattern to return hell lot of implementations of DataSource
depending upon situation.
So what is the correct type to return (if there is a 'correct' one),
and why?
There is really no right way, it entirely depends upon developer BUT it is always good design to "program to interface". In your case since you are returning something so it would not make much difference but still you should use public DataSource dataSource ()
.
Program to interface is specially useful when you can creating a method which accept some parameter, suppose public void dataSource (DataSource ds)
, in this case you can pass any implementation of DataSource
, so it is a good design because it is scalable.
Read more about program to interface.
Also, I would recommend reading about Factory design pattern, which is a good example of program to interface.
you should return the generic implementation.
This is why:
First of, you are right. If you return the impl, you can still autowire the interface. So you can do the above, and then:
@Autowire Datasource ds;
However, you can not do the reverse. You can't return Datasource and autowire HikariDataSource.
Why is this important?
You don't want your implementation to depend on specific implementations. For example:
You write your Appcode and all of your code depend on HikariDataSource. Since this is a datasource, say you have 400 daos implemented that use this and use an implementation specific detail of that class. Now, if you change that impl, all your clients break. You will have a hell of a time making things work again. And this will repeat itself each time you need to change that (mind you, probs won't happen as often with a datasource).
Now returning the interface or a base class discourages your clients to depend on implementation specific details. You will have no problems switching out your datasource quickly afterwards. Everything will continue to work as long as you implement the datasource interface.
Next: Tests.
You'd want to provide a different thing for your tests (let's say datasource). Do you really want to spin up a database for your tests, while an in memory database is enough?
Now in your DAO test, if you depend on the IMPL, there is no way to test against a generic datasource (InMemoryDataSource). If you return an interface, you can have a different configuration for your tests that simplifies your setup.
I hope this makes sense/helps.
Cheers,
Artur