29

I have been using Dependency Injection in Spring for some time now, and I understand how it works and what are some pros and cons of using it. However, when I'm creating a new class I often wonder - Should this class be managed by Spring IOC Container?

And I don't want to talk about differences between @Autowired annotation, XML configuration, setter injection, constructor injection, etc. My question is a general one.

Let's say we have a Service with a Converter:

@Service
public class Service {

    @Autowired
    private Repository repository;

    @Autowired
    private Converter converter;

    public List<CarDto> getAllCars() {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
}

@Component
public class Converter {

    public CarDto mapToDto(List<Car> cars) {
        return new ArrayList<CarDto>(); // do the mapping here
    }
}

Clearly, the converter doesn't have any dependencies, so it's not necessary for it to be autowired. But for me it seems better as autowired. Code is cleaner and easy to test. If I write this code without DI, the service will look like that:

@Service
public class Service {

    @Autowired
    private Repository repository;

    public List<CarDto> getAllCars() {
        List<Car> cars = repository.findAll();
        Converter converter = new Converter();
        return converter.mapToDto(cars);
    }
}

Now it's much more difficult to test it. Moreover, new converter will be created for every convert operation, even though it's always in the same state, which seems like an overhead.

There are some well known patterns in Spring MVC: Controllers using Services and Services using Repositories. Then, if Repository is autowired (which it usually is), then Service has to be autowired too. And this is quite clear. But when do we use @Component annotation? If you have some static util classes (like converters, mappers) - do you autowire them?

Do you try to make all classes autowired? Then all the class dependencies are easy to inject (once again, easy to understand and easy to test). Or do you try to autowire only when it's absolutely necessary?

I've spent some time looking for some general rules on when to use autowiring, but I couldn't find any specific tips. Usually, people talk about "do you use DI? (yes/no)" or "what type of dependency injection do you prefer", which doesn't answer my question.

I would be grateful for any tips concerning this topic!

  • 3
    +1 for essentially "When is it going too far?" – Andy Hunt Mar 10 '14 at 12:28
  • Check out this question and this article – Laiv Nov 21 '16 at 14:41
7

Agree with @ericW's comment, and just want to add remember you can use initializers to keep your code compact:

@Autowired
private Converter converter;

or

private Converter converter = new Converter();

or, if the class really has no state at all

private static final Converter CONVERTER = new Converter();

One of the key criteria for whether Spring should instantiate and inject a bean is, is that bean so complicated that you want to mock it in testing? If so, then inject it. For example, if converter makes a round trip to any external system, then make it a component instead. Or if the return value has a big decision tree with dozens of possible variations based on the input, then mock it. And so forth.

You've already done a good job of rolling up that functionality and encapsulating it, so now it's just a question of whether it's complex enough to be considered a separate "unit" for testing.

5

I don't think you have to @Autowired all your classes, it should be depends on the real usage, for your scenarios it should be better use static method instead of @Autowired. I haven't see any benefit use @Autowired for those simple utils class, and that will absolutely increase the Spring container cost if not use it properly.

2

My rule of thumb is based on something that you alredy said: testability. Ask yourself "Can I unit test it easily?". If the answer is yes then, in absence of any other reason I would be OK with it. So, if yo develop the unit test at the same time you are developing you will save a lot of pain.

The only potential problem is that if the converter fails your service test will also fail. Some people would say that you should mock the converter's results in unit tests. This way you would be able to identify errors fasters. But it has a price: you have to mock all the converters results when the real converter could have done the work.

I also assume that there is no reason at all to use different dto Converters.

0

TL;DR: A hybrid approach of autowiring for DI, and constructor forwarding for DI can simplify the code you presented.

I've been looking at similar questions due to some weblogic with spring framework startup errors / complications involving @autowired bean initialization dependencies. I have begun mixing in another DI approach: constructor forwarding. It requires conditions like you present ("Clearly, the converter doesn't have any dependencies, so it's not necessary for it to be autowired."). Nevertheless, I like a lot for the flexibility and it is still pretty straight forward.

@Service
public class Service {

    @Autowired
    private Repository repository;

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        Converter converter = new Converter();
        return getAllCars(converter);
    }
}

or even as a rif off Rob's answer

@Service
public class Service {

    @Autowired
    private Repository repository;

    private final Converter converter = new Converter(); // static if safe for that

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        return getAllCars(converter);
    }
}

It may not require a public interface change but I would. Still the

public List<CarDto> getAllCars(Converter converter) { ... }

could be made protected or private to scope down for test purposes / extension only.

The key point is the DI is optional. A default is provided, which can be overridden in a straight forward manner. It has it's weakness as the number of fields increase, but for 1, maybe 2 fields, I'd prefer this under the following conditions:

  • Very small number of fields
  • Default is almost always used (DI is a test seam) or Default is almost never used (stop gap) because I like consistency, so this is part of my decision tree, not a true design constraint

Last point ( a bit OT, but related to how we decide what / where @autowired) : the converter class, as presented, is utility method (no fields, no constructor, could be static). Maybe the solution would to have some sort of mapToDto() method in the Cars class? That is, push the conversion injection to the Cars definition, where is is likely intimately tied already :

@Service
public class Service {

   @Autowired
   private Repository repository;

   public List<CarDto> getAllCars() {
    return repository.findAll().stream.map(c -> c.mapToDto()).collect(Collectors.toList()));
   }
}
-1

I think a good example of this is something like SecurityUtils or UserUtils. With any Spring app that manages users, you'll need some Util class with a whole bunch of static methods such as:

getCurrentUser()

isAuthenticated()

isCurrentUserInRole(Authority authority)

etc.

and I've never autowired these. JHipster (which I use as a pretty good judge of best practice) doesn't.

-2

We can separate the classes on the basis of the function of that class such that controllers, service, repository,entity. We may use other classes for our logic only not for the purpose of controllers,service etc.,on that occasion we could annotate that classes with @component. This will automatically register that class in the spring container. If it is registered then it will be managed by spring container. Concept of dependency injection and inversion of control can be supplied to that annotated class.

  • 3
    this post is rather hard to read (wall of text). Would you mind editing it into a better shape? – gnat Mar 23 '14 at 6:08

protected by gnat Nov 21 '16 at 14:07

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.