DI на основе сеттера (setter) выполняется путем вызова контейнером сеттеров для бинов после вызова конструктора без аргументов или статического фабричного метода без аргументов для создания экземпляра бина.

В следующем примере показан класс, зависимость в который может быть внедрена исключительно через сеттер. Данный класс является обычным Java-классом. Это POJO (простой объект языка Java), который не зависит от конкретных интерфейсов контейнера, базовых классов или аннотаций.

Java
public class SimpleMovieLister {
    // SimpleMovieLister зависит от MovieFinder
    private MovieFinder movieFinder;
    // сеттер, позволяющий контейнеру Spring внедрить MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    // бизнес-логика, которая фактически использует внедренный MovieFinder, опущена...
}
Kotlin
class SimpleMovieLister {
    // свойство с отложенной инициализацией, которое позволяет контейнеру Spring  внедрить MovieFinder
    lateinit var movieFinder: MovieFinder
    // бизнес-логика, которая фактически использует внедренный MovieFinder, опущена...
}

ApplicationContext поддерживает DI на основе конструктора и на основе сеттера для бинов, которыми он управляет. Он также поддерживает DI на основе сеттера после того, как некоторые зависимости уже внедрены с помощью подхода с использованием конструктора. Вы настраиваете зависимости в виде BeanDefinition, которые используете вместе с экземплярами PropertyEditor для преобразования свойств из одного формата в другой. Однако большинство пользователей Spring работают с этими классами не напрямую (то есть программно), а с XML-определениями bean, аннотированными компонентами (то есть классами, помеченными аннотациями @Component, @Controller и так далее) или методами, помеченными аннотацией @Bean, в классах, помеченных аннотацией @Configuration, на основе Java. Эти источники затем внутренне преобразуются в экземпляры BeanDefinition и используются для загрузки всего экземпляра IoC-контейнера Spring.

DI на основе конструктора или на основе сеттера?

Поскольку можно смешивать DI на основе конструктора и DI на основе сеттера, хорошим практическим правилом является использование конструкторов для обязательных зависимостей, а сеттеров или методов конфигурации - для необязательных зависимостей. Обратите внимание, что использование аннотации @Required на сеттере может быть осуществлено для того, чтобы превратить свойство в требуемую зависимость; однако предпочтительнее использовать внедрение через конструктор с программной проверкой аргументов.

Команда Spring в целом поддерживает внедрение через конструктор, поскольку это позволяет реализовать компоненты приложения в виде неизменяемых объектов и гарантирует, что необходимые зависимости не будут null. Более того, компоненты, внедряемые через конструктор, всегда возвращаются клиентскому (вызывному) коду в полностью инициализированном состоянии. В качестве примечания: присутствие большого количества аргументов конструктора - это проблемный код с душком (code smell), что говорит о том, что класс, вероятно, слишком перегружен и подлежит рефакторингу для лучшего распределения задач.

Внедрение через сеттер следует по большей части использовать только для необязательных зависимостей, которым можно присвоить разумные значения по умолчанию в классе. В противном случае, проверка на not-null должна выполняться везде, где в коде используется зависимость. Одним из преимуществ внедрения через сеттер является то, что устанавливающие методы делают объекты данного класса пригодными для последующей реконфигурации или повторного внедрения. Поэтому управление через JMX MBeans является адекватным примером использования внедрения через сеттер.

Используйте стиль DI, наиболее подходящий для конкретного класса. Иногда, если речь идет о сторонних классах, для которых у вас нет исходного кода, выбор делается за вас. Например, если сторонний класс не открывает какие-либо устанавливающие методы, тогда внедрение через конструктор может быть единственной доступной формой внедрения зависимостей.