Комментарии 18
Конструктор инжектор без мутируюшие методов это ведь про иммутабельность.
И зачем тогда столько слов?
А, понял. Java так не умеет как C#, когда record или record class сразу дают primary constructor с init-only свойствами.
public record Service(IRepository Repo, IConfig Config);
DI работает из коробки, аннотации можно ставить на параметры. Для сервисов вполне годится, не только для DTO
C#
public record UserService(IRepository Repo, ILogger Logger);
// регистрация
services.AddScoped<IRepository, SqlRepository>();
services.AddScoped<ILogger, FileLogger>();
services.AddScoped<UserService>();
// контейнер сам вызовет new UserService(repo, logger)Java
public class UserService {
private final Repository repo;
private final Logger logger;
public UserService(Repository repo, Logger logger) {
this.repo = repo;
this.logger = logger;
}
}
// регистрация через аннотации на классах
@Repository
public class SqlRepository implements Repository {}
@Component
public class FileLogger implements Logger {}
@Service
public class UserService { ... }
// или явно в конфиге
@Configuration
public class AppConfig {
@Bean
public UserService userService(Repository repo, Logger logger) {
return new UserService(repo, logger);
}
}Rust
trait Repository {
fn save(&self, data: &str);
}
trait Logger {
fn log(&self, msg: &str);
}
struct SqlRepository;
impl Repository for SqlRepository {
fn save(&self, data: &str) {}
}
struct FileLogger;
impl Logger for FileLogger {
fn log(&self, msg: &str) {}
}
struct UserService<R: Repository, L: Logger> {
repo: R,
logger: L,
}
fn main() {
let service = UserService {
repo: SqlRepository,
logger: FileLogger,
};
}В шарп самый компактный вариант, в раст самый понятный, джава с танцами
Статья, в общем-то, посвящена тому, что самый компактный вариант (внедрение через поле) отнюдь не является самым лучшим.
Что, так-то понятно. Только начинающие питонисты считают однострочники самым лучшим кодом
Столько слов - потому что LLM-ка столько нагенерила. Проблема высосана из пальца.
Можно всю статью свести к одной табличке
Способ инициализации | Защита от создания невалидные объекты | Вариативность при создании объекта |
Через поля | - | - |
Через методы | - | + |
Через конструкторы | + | + |


Чтобы было меньше сомнений. Так пишешь-пишешь пол дня, а потом ты - ллм)
Думал, этот холивар закончился еще во времена Spring 4. Но статья полезная, особенно аргумент про инварианты.
Еще стоит добавить про старт контекста: конструкторы дают жесткую гарантию в момент поднятия приложения. Field injection позволяет «размазать» инициализацию и получить ошибки в рантайме.
Это всё замечательно, но Hibernate требует no arg конструктор.
Вы абсолютно правы, JPA-сущности (@Entity) требуют конструктор по умолчанию. Но это другой тип класса с другим жизненным циклом, управляемым Hibernate, а не Spring DI. Мы говорим о сервисах, компонентах и репозиториях (@Service, @Component, @Repository), где вся логика работы строится на зависимостях, и их целостность критична.
LLM generated комментарий
Вы так настойчиво продвигаете тезис, что приватное поле инициализируется через reflection, а конструктор - через new, но я вот сильно не уверен в new. Возможны два варианта: или reflection-вызов конструктора, или runtime-кодогенерация, в которой будет new. Я думаю, что используется reflection (я бы так сделал), и это несложно проверить: поставить точку останова в конструкторе и посмотреть стектрейс, но мне безумно лень. Упоминание производительности сразу было смешным: какая блин производительность инжекта, у вас же не миллиарды бинов инжектятся. А имея в виду то, что я в начале сказал, разница в производительности будет даже в пользу reflection.
Но общее моё отношение к теме статьи - а не пофиг ли, как оно инжектится? Много сильных слов: легаси, зомби, невалидное состояние, ну-ну, а это всё точно является проблемой?
Вообще как-будто это всё на поверхности лежит? Интересно какие аргументы в пользу field injection.

Field vs Constructor Injection в Java: ошибка объектного дизайна или вопрос синтаксиса?