Аннотация Value в Spring не внедряет поля java класса

Рейтинг: -1Ответов: 2Опубликовано: 17.08.2023

Вот структура проекта:

Вот структура проекта

Вот SpringConfig:

@Configuration
@ComponentScan(basePackages = {"controlers","database"} )
@EnableWebMvc
public class SpringConfig implements WebMvcConfigurer {
    private final ApplicationContext ApplicationContext;
    @Autowired
    public SpringConfig(ApplicationContext applicationContext) {
        this.ApplicationContext = applicationContext;
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public SpringResourceTemplateResolver templateResolver(){
        SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setApplicationContext(this.ApplicationContext);
        templateResolver.setPrefix("views/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        templateResolver.setOrder(1);
        templateResolver.setCheckExistence(true);
        return templateResolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine(){
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setEnableSpringELCompiler(true);
        templateEngine.setTemplateResolver(templateResolver());
        return templateEngine;
    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry){
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine());
        resolver.setCharacterEncoding("UTF-8");
        registry.viewResolver(resolver);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/web-interface-res/**")
                .addResourceLocations("classpath:/web-interface-res/");

    }


}

В папке database лежит один класс DatabaseConnection, вот, собственно, и он:

@Component
@PropertySource("classpath:config.properties")
public class DatabaseConnection {
    private Connection connection;

    @Value("${db.userName}")
    private String userName;
    @Value("${db.userPassword}")
    private String userPassword;
    @Value("${db.url}")
    private String dbUrl;
    @Value("${db.databaseName}")
    private String dbName;

    public DatabaseConnection() {

        try{
            System.out.println(dbUrl);
            this.connection = DriverManager.getConnection(this.dbUrl,this.dbName,this.userPassword);
            System.out.println("Сойденение устоновлено");
        }catch (SQLException e)
        {
            System.out.println(e.toString());
            System.out.println("Ошибка сойденения с баззой данных");
        }
    }


}

Я пишу обычный web-сайт с БД и решил добавлять некоторые данные через проперти-файл, чтобы в будущем проект было легко поддерживать.

Но тут столкнулся с такой проблемой: в классе DatabaseConnection поля не внедряются из файла проперти, то есть, на строчке

System.out.println(dbUrl);

в консоль выводится null. Не могу понять, как это дело всё исправить. Если требуется дополнительная информация, то я с радостью её добавлю. Также хочу добавить, что если в классе ControlerHello попробовать внедрить, то это получится:

@Controller
@PropertySource("classpath:config.properties")
public class ControlerHello {

    @Value("${db.url}")
    private String url;

    @GetMapping("/hello")
    public String sayHello(){
        System.out.println(url);

        return "hello";
    }

    @GetMapping("/")
    public String showIndexHTML(){
        return "index";
    }
}

То есть, когда мы откроем представление hello, то нам выведется на экран значение из файла проперти. Понять только не могу, почему в DatabaseConnection-то не работает.

Ответы

▲ 2Принят

Содержимое полей, аннотированных @Value становится доступным не сразу в конструкторе (на момент создания объекта Spring ещё не стал его "владельцем" и не может выполнить с ним какие-либо действия).

Созданный бин может считаться инициализированным самое раннее - в методе, который аннотирован @PostConstruct - он вызывается Spring-ом после успешной инициализации свойств бина.

Поэтому предварительный (далеко не лучший) код DatabaseConnection может выглядеть так:

@Component
@PropertySource("classpath:config.properties")
public class DatabaseConnection {
    private Connection connection;

    @Value("${db.userName}")
    private String userName;
    @Value("${db.userPassword}")
    private String userPassword;
    @Value("${db.url}")
    private String dbUrl;
    @Value("${db.databaseName}")
    private String dbName;


    @PostConstruct
    void postConstructActions(){
        try{
            System.out.println(dbUrl);
            this.connection = DriverManager.getConnection(this.dbUrl,this.dbName,this.userPassword);
            System.out.println("Соединение установлено");
        }catch (SQLException e)
        {
            System.out.println(e.toString());
            System.out.println("Ошибка подключения к базе данных");
        }
    }
}

Почему не лучший? Например - в этом коде как раз используется отложенная инициализация некоторых полей (connection). Потенциальный наследник этого класса может не знать об этой особенности и попробует вызвать неинициализированные данные из своего конструктора.
Плюс - в данном случае @Value действует аналогично @Autowired, а его ряд источников считает нежелательным.


Более правильный вариант предполагает внедрение зависимостей через конструктор и, по возможности, использование final-полей:

@Component
@PropertySource("classpath:config.properties")
public class DatabaseConnection {
    private final Connection connection;
    private final String userName;

    private final String userPassword;
    private final String dbUrl;
    private final String dbName;

    public DatabaseConnection(@Value("${db.userName}") String userName, @Value("${db.userPassword}") String userPassword, @Value("${db.url}") String dbUrl, @Value("${db.databaseName}") String dbName) {
            System.out.println(dbUrl);
            this.userName = userName; // а возможно, эти поля не нужно хранить, ведь основная цель - создать подключение
            this.userPassword = userPassword;
            this.dbUrl = dbUrl;
            this.dbName = dbName;
            
            try {
                this.connection = DriverManager.getConnection(this.dbUrl,this.dbName,this.userPassword);
            } catch (SQLException e) {
                throw new RuntimeException(e); // зачем нам запускаться, если подключения к БД нет...
            }
            System.out.println("Соединение установлено");
    }
}

▲ 1

Попробуй добавить над классом DatabaseConnection аннотацию @Component