Reputation: 3548
Something that confuses me slightly with spring. I understand that one of its main functions is to promote modularity and loose coupling by providing a way for modular components (classes) to be wired together without having knowledge of other modules' implementations, only the contracts they expose.
However, in practice, I sometimes find myself in the following situation.
One class has to get the spring application context. Let's say that you do that in main();
So you load up spring in main, and then hopefully the rest of your objects are now nicely injected as beans and can interact without being tightly coupled.
But what if one of those objects has a method that every time it's called, it needs to create a fresh bean? That object now needs a reference to the spring application context. But now it's no longer really a pojo, as it's coupled to spring?
I guess in practice, you create a global object that contains the spring application context for easy access by classes that need to do this, but it still seems a bit messy and counter to the spring philosophy of loose coupling?
Is this just one of those things? It's not perfect but it's still a lot better than having your classes tightly coupled?
EDIT: And if you use a global object to hold your spring application context, how do you unit test the class that accesses it? Do you write your global holder so that you can parameterize it with a test context. Alternatively if you don't use a global object, but rather inject a spring context into classes that need it, that means that you need to keep the chain of injecting spring going down through your code, or you'll reach a point where you want a non-springified object to create a new bean, but have no reference to spring?
Upvotes: 2
Views: 215
Reputation: 15104
Here's a contrived example of how a factory/service could help. It uses @Inject
, but the principle applies to @Autowired
, etc.
The Spring docs for testing are here.
Imagine an IsSummerService
, that provides a single method to tell you if it is summer 'now':
public interface IsItSummerService {
public boolean isItSummerNow();
}
A simple first impl could be as follows. This impl uses the new
keyword to manage its Date
dependency, and @Inject
to manage the summer month range. The Date
dependency here is similar to your create a fresh bean.
import java.util.*;
import javax.inject.Inject;
public class IsItSummerServiceImpl1 implements IsItSummerService {
@Inject int startMonth = Calendar.JUNE;
@Inject int endMonth = Calendar.SEPTEMBER;
public boolean isItSummerNow() {
return DateUtils.isDateBetweenMonths(new Date(), startMonth, endMonth);
}
}
A second impl could use DI to inject a NowService
(or factory). This service/factory will take away the use of new
, and pass the responsibility to create a fresh bean to the service/factory.
import java.util.*;
import javax.inject.Inject;
public class IsItSummerServiceImpl2 implements IsItSummerService {
@Inject NowService nowService;
@Inject int startMonth = Calendar.JUNE;
@Inject int endMonth = Calendar.SEPTEMBER;
public boolean isItSummerNow() {
return DateUtils.isDateBetweenMonths(nowService.now(), startMonth, endMonth);
}
}
NowService:
import java.util.Date;
public interface NowService {
public Date now();
}
So now how hard are each to test? Wherever you see 'fake injection' this can be achieved with Spring and test contexts.
The tests for IsItSummerServiceImpl2
are obviously better and more flexible.
import java.text.*;
import java.util.*;
import org.junit.*;
import static org.junit.Assert.*;
public class IsItSummerServiceTest {
@Test
public void testIsItSummerImpl1() {
IsItSummerService s = new IsItSummerServiceImpl1();
assertFalse(s.isItSummerNow());
// Test passes for May in the UK
// But what about when 'now' changes?
// We can't control the 'now' value effectively to test.
// assertFalse could pass/fail unpredictably.
// (Well, it actually *is* predictable when it passes/fails of course!)
}
@Test
public void testIsItSummerImpl2() {
// You would keep these tests separate, but for the sake of brevity.
IsItSummerService s = new IsItSummerServiceImpl2();
// Fake injection - set values directly. Use mocks/stubs/whatever.
((IsItSummerServiceImpl2)s).nowService = fakeNowService("01/01/2013");
assertFalse(s.isItSummerNow());
((IsItSummerServiceImpl2)s).nowService = fakeNowService("01/07/2013");
assertTrue(s.isItSummerNow());
}
@Test
public void testIsItSummerInSouthernHemisphere() {
// You would keep these tests separate, but for the sake of brevity.
IsItSummerService s = new IsItSummerServiceImpl2();
// Fake injection - set values directly. Use mocks/stubs/whatever.
((IsItSummerServiceImpl2)s).startMonth = Calendar.DECEMBER;
((IsItSummerServiceImpl2)s).endMonth = Calendar.MARCH;
((IsItSummerServiceImpl2)s).nowService = fakeNowService("01/11/2013");
assertFalse(s.isItSummerNow());
((IsItSummerServiceImpl2)s).nowService = fakeNowService("01/12/2013");
assertTrue(s.isItSummerNow());
((IsItSummerServiceImpl2)s).nowService = fakeNowService("01/03/2013");
assertTrue(s.isItSummerNow());
((IsItSummerServiceImpl2)s).nowService = fakeNowService("01/04/2013");
assertFalse(s.isItSummerNow());
}
static class FakeNowService implements NowService {
Date now;
public FakeNowService(Date now) {
this.now = now;
}
public Date now() {
return now;
}
}
Date parseDate(String dateStr) {
try {
// UK date format
return new SimpleDateFormat("dd/MM/yyyy").parse(dateStr);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
NowService fakeNowService(String dateStr) {
return new FakeNowService(parseDate(dateStr));
}
}
So to bring this back to your question, by diverting object creation from the new
keyword we have made the app more testable.
Using new
is undesirable if there is a possibility you want to change the object creation process at a later date. Once new
has been used then that's pretty much that. But with a factory you can alter the create process to your heart's content.
Upvotes: 3
Reputation: 493
I am not totally sure if I understand your problem correctly, but I think the definition of bean scopes could be a solution for you.
I assume that the objects your method needing a fresh bean with every call belong to already use Spring's DI mechanism to access the bean. In this case it might do the trick to use the prototype scope for the respective bean to force a new creation at every injection - see http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/beans.html#beans-factory-scopes-prototype for details.
Upvotes: 0
Reputation: 4634
The situation which you describe (creating objects in each method invocation) is usually connected to creating business model objects and it is perfectly fine to create such objects using "new".
Spring IOC (Dependency Injection) is important for non-model objects, so in practice you won't need Spring context to create objects in specific method, because this created class is not a dependency.
Upvotes: 0