Bruce
Bruce

Reputation: 3548

accessing the spring context

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

Answers (3)

Paul Grime
Paul Grime

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

DerMiggel
DerMiggel

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

Michal Borek
Michal Borek

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

Related Questions