Asics6789
@Asics6789
Студент 2 курса ФКН ВГУ, основной язык Java

Как правильно внедрять зависимости в классы, зависящие от не-бинов?

Есть класс CommitManager:
@Component
public class CommitManager {
//  ...
    @Autowired
    private final FilePatcher filePatcher;
//  ...
}


А так же класс FilePatcher:
@Component
public class FilePatcher {
    private Path directory;

    public FilePatcher(Path directory) {
        this.directory = directory;
    }

    // ...
}


FilePatcher однозначно хочется внедрять как бин, однако в таком случае встает проблема: FilePatcher зависит от поля directory, которое не может быть внедрено через @Value, т.к. не определено заранее по условию (не может лежать в application.properties). По очевидным причинам Spring не может создать бин FilePatcher через конструктор. Потенцинальное решение - отказаться от конструктора, оставив directory неопределенным изначально, с определением его впоследствие через сеттер, может быть небезопасным.

Есть ли альтернативы, кроме как отпустить идею с определением FilePatcher как бина?
Насколько вообще будет приемлемым код с внедрением в CommitManager ещё 8-10 схожих объектов через @Autowired и внедрением одного (FilePatcher) через оператор new (к тому же без контроля на Singleton)?
Есть ли какие-то конвенции (или хотя бы негласные правила) пересекающиеся с моим вопросом?
  • Вопрос задан
  • 77 просмотров
Пригласить эксперта
Ответы на вопрос 1
Vamp
@Vamp
Есть ли альтернативы, кроме как отпустить идею с определением FilePatcher как бина?

Вы можете определить его как бин чуть позже - в момент, когда вам становится известен directory. FilePatcher надо будет инжектить с аннотацией @Lazy.

Пример

// Обычный класс. Не компонент.
public class FilePatcher {
    private final Path directory;

    public FilePatcher(Path directory) {
        this.directory = directory;
    }

    public void hello() {
        System.out.println(directory);
    }
}

@Component
public class CommitManager {

    private final FilePatcher filePatcher;

    // Spring сгенерирует объект-заглшку. Реальный бин будет запрошен из
    // спринг контекста при первом вызове любого метода объекта-заглушки.
    public CommitManager(@Lazy FilePatcher filePatcher) {
        this.filePatcher = filePatcher;
    }

    public void callFilePatcher() {
        filePatcher.hello();
    }
}

@Component
public class RuntimeSetter {

    private final ApplicationContext ctx;
    private final CommitManager cm;

    public RuntimeSetter(ApplicationContext ctx, CommitManager cm) {
        this.ctx = ctx;
        this.cm = cm;
    }

    @Scheduled(initialDelay = 500, fixedDelay = Long.MAX_VALUE)
    public void set() {
        // Спустя 500 мс становится известен path.
        // В этот момент создаём бин FilePatcher вручную.
        var factory = (BeanDefinitionRegistry) ctx.getAutowireCapableBeanFactory();
        var gbd = new GenericBeanDefinition();
        gbd.setBeanClass(FilePatcher.class);
        var cav = new ConstructorArgumentValues();
        cav.addGenericArgumentValue(Path.of("hello_world"));
        gbd.setConstructorArgumentValues(cav);
        factory.registerBeanDefinition("filePatcher", gbd);
    }


    @Scheduled(initialDelay = 1000, fixedDelay = Long.MAX_VALUE)
    public void run() {
        // Спустя 1000 мс вызываем код, который триггерит резолв lazy бина.
        cm.callFilePatcher();
    }
}



Альтернативно можно не бин создавать, а property. Делается чутка сложнее:

Пример с property

public class CtxInit implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        applicationContext.getEnvironment()
            .getPropertySources()
            .addLast(new PropertySource<String>("pathPropertyResolver") {

                private volatile String path;

                {
                    var t = new Thread(() -> {
                        try {
                            // Спустя 500 мс после старта становится
                            // известен path.
                            Thread.sleep(500);
                            path = "hello_world";
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    });
                    t.start();
                }

                @Override
                public Object getProperty(String name) {
                    // Наш проперти называется myLazyPath.
                    if ("myLazyPath".equals(name)) {
                        return path;
                    }
                    return null;
                }
            });
    }
}

@SpringBootApplication
@EnableScheduling
public class ApplicationMain {
    public static void main(String[] args) {
        var sa = new SpringApplication(ApplicationMain.class);
        sa.addInitializers(new CtxInit());
        sa.run(args);
    }
}

@Component
public class FilePatcher {
    private final Path directory;

    public FilePatcher(@Value("${myLazyPath}") Path directory) {
        this.directory = directory;
    }

    public void hello() {
        System.out.println(directory);
    }
}

@Component
public class CommitManager {

    private final FilePatcher filePatcher;

    public CommitManager(@Lazy FilePatcher filePatcher) {
        this.filePatcher = filePatcher;
    }

    public void callFilePatcher() {
        filePatcher.hello();
    }
}

@Component
public class RuntimeRunner {

    private final CommitManager cm;

    public RuntimeRunner(CommitManager cm) {
        this.cm = cm;
    }

    @Scheduled(initialDelay = 1000, fixedDelay=Long.MAX_VALUE)
    public void run() {
        // Спустя 1000 мс вызываем код, который триггерит резолв lazy бина.
        cm.callFilePatcher();
    }
}

Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы