【问题标题】:Spring @Value annotation in @Controller class not evaluating to value inside properties file@Controller 类中的 Spring @Value 注释未评估属性文件中的值
【发布时间】:2012-08-07 02:23:02
【问题描述】:

我是 Spring 的新手,并尝试使用带有 @Controller 注释注释的控制器内部的 @Value("${loginpage.message}") 注释注入一个带有值的字符串,并且我的字符串的值被评估为字符串 "${loginpage.message}" 和不是我的属性文件中的内容。

下面是我的控制器,带有我要注入的字符串“消息”。

@Controller
public class LoginController extends BaseController {
    @Value("${loginpage.message}")
    private String message;

    @RequestMapping("/")
    public String goToLoginPage(Model model) {
        model.addAttribute("message", message);

        return "/login";
    }
}

我的应用程序上下文如下所示:

<context:property-placeholder location="classpath:properties/application.properties" />

<context:annotation-config />

<context:component-scan base-package="com.me.application" />

我的属性文件有一行:

loginpage.message=this is a test message

Spring 必须在某个时候获取该值,因为每当我将 @Value("${loginpage.message}") 更改为不在属性文件中的值(如 @Value("${notInPropertiesFile}"))时,都会出现异常。

【问题讨论】:

  • Chris 的回答应该可以确保属性文件在项目类路径中,如果它要在外部战争中,那么应该在应用程序上下文中使用属性占位符。它应该可以工作。

标签: spring properties


【解决方案1】:

我很抱歉问这个显而易见的问题,但是你怎么知道@Value 注释不起作用? Spring工作方式的问题之一是Bean的预处理是在Bean构建完成之后进行的。

因此,如果您使用调试器在构造函数中检查 Bean,您将看不到正在设置的字段。您可以在 Bean 中添加一个名为 audit() 的方法,并使用 @PostConstruct 对其进行注释,如果您在其中放置日志语句,在其上放置断点,您应该会看到带有 @Value 值的字段。

如果您这样做了,但仍然看不到 @Value 字段,那么您甚至可能还没有扫描 Bean。一个你认为实现 Bean 的类仍然是一个 Java 类,它可以被实例化,并且如果它没有被预处理,它的字段将被分配为 null。

为了确保您的 Bean 正在被扫描,类应该至少具有 @Component,并且您需要将类的包添加到 @ComponentScan。

@ComponentScan(basePackages = { "com.example.springboot", "org.bilbo.baggins" })

如果您没有 main() 方法的源代码,通常可以在其中找到@ComponentScan,那么您可以在同一个包中添加一个@Configuration 类,然后在其中添加一个@ComponentScan。

在此示例中,我将 @ComponentScan 作为注释掉的行放在错误的位置(它应该替换 @ImportResources)。

package com.example.springboot;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

// @ComponentScan(basePackages = { "com.example.springboot", "org.bilbo.baggins" })
@Configuration
@ImportResource({"classpath*:applicationContext.xml"})
public class Configurer {
}

我这样做是为了展示如何使用 XML 文件:applicationContext.xml。这包含一个组件扫描并创建一个 Bean。

(注意:只扫描一个包,component-scan 好像是累积的。)

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/sc
hema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/
beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema
/context/spring-context.xsd">

    <context:annotation-config />

    <context:component-scan base-package="org.bilbo.baggins" />
          <bean id="applicationProperties"
                      class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
                    <property name="location" value="classpath:application.properties" />
          </bean>
</beans>

在 XML 文件中构建一个 bean 很有用,这样您就可以列出它并证明您已经加载了 XML 文件。你可以使用String[] beanNames = ctx.getBeanDefinitionNames();方法列出beans

【讨论】:

    【解决方案2】:

    我在我的 spring 项目中遇到了类似的问题,但特别是 spring BATCH one。我最初构建我的配置如下

    @Configuration
    public class BatchConfig   
    {
      @Bean
      public Job job(@Autowired Step stepMulti, @Autowired Step stepMultiDiff,  @Autowired Step stepMultiPolling
            ){
    
        Job job = jobBuilders.get("job")
                    .start(init0())
                        .on("POLLING").to(stepMultiPolling)
                    .from(init0()).on("*").to(stepMulti).next(stepMultiDiff).end()
                    .build();
        return job;
      }
    
      @Bean
      public Step init0(){
        return stepBuilders.get("init0")
                .tasklet(new MyDecider())
                .build();
      }
    
      ...
    }
    

    MyDecider 如下所示

    public class MyDecider implements StepExecutionListener , Tasklet{ 
    
      @Autowired ThreadPoolTaskScheduler taskScheduler;
      @Value("${read.chunk.size}") private Integer pagesize;
    
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
        return RepeatStatus.FINISHED;
    }
    
    @Override
    public ExitStatus afterStep(StepExecution exe) {
        String type = exe.getJobParameters().getString("mode");
    
        log.info("SPRING BATCH props:");
        log.info("    READ chunk size:  {}", pagesize);
    
    
        if (StringUtils.equals(type, "send")) {
            log.info("MODE batch SENDING...");
    
            if (taskScheduler !=null) taskScheduler.shutdown();
            else log.info("      Not able to stop scheduler (is null)");
    
            return new ExitStatus("SEND");
        } else  {
            log.info("MODE batch POLLING...");
            return new ExitStatus("POLLING");
        } 
    
    }
    

    但是这样既没有连接taskScheduler,也没有注入pagesize;两者都为空。感谢鲍里斯的回答,经过一番尝试,我将 BatchConfig 更改为以下完美工作

    ...
    
    @Bean
    public Step init0(){
        return stepBuilders.get("init0")
                .tasklet(decider())
                .build();
    }
    
    @Bean
    public Tasklet decider() {
        return new MyDecider();
    }
    
    ...
    

    原因:让 MyDecider 构造更接近 BatchConfig 中的 Bean 注释(decider() 之一),让 spring 明白必须正确注入 MyDecider,其值在 application.property 值中找到,并与使用的 TaskScheduler 连接(因为我也尝试过激活 SpringScheduler,但如果 jar 启动选项是“发送”,我想关闭它)。

    注意:使用选项 mode="send" 春季批处理作业采用 stepMulti 而不是 stepMultiPolling,因为 MyDecider 退出状态是 SEND 而不是 POLLING;但这只是本主题之外的解释,所以我跳过更多细节。

    希望这个春季批次案例可以对某人有所帮助!

    【讨论】:

      【解决方案3】:

      如果您使用 @Value 注释,则需要使用 PropertySourcePlaceHolder,因为它可以从属性文件中提取值。如果您使用的是 java config base,则需要创建一个像这样的 bean

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

      或者,如果您使用基于 xml,则相应地声明 bean。

      【讨论】:

        【解决方案4】:

        您可以先@Autowire Environment,然后再environment.getProperty("name")。 见https://stackoverflow.com/a/15562319/632293

        【讨论】:

          【解决方案5】:

          是的,我在 Spring 3 中遇到了同样的问题。它似乎在控制器中不起作用。 为了解决这个问题,我使用 @Service 创建了另一个 bean 并将其注入到控制器中。 它确实对我有用。希望这对我花了一整天时间弄清楚的人有所帮助。

          【讨论】:

          • 您好,我尝试了您的解决方案,但我的服务遇到了同样的问题,该服务需要什么特别的吗?
          【解决方案6】:

          问题好像已经问过了Spring 3.0.5 doesn't evaluate @Value annotation from properties

          Web 应用程序根目录和 servlet 应用程序上下文之间的区别是 Spring 中最容易混淆的来源之一,请参阅 Difference between applicationContext.xml and spring-servlet.xml in Spring Framework

          来自@Valuejavadoc:

          请注意,@Value 注释的实际处理是由一个 豆后处理器

          来自Spring documentation:

          BeanPostProcessor 接口的范围是每个容器。这仅在您使用容器层次结构时才相关。如果您在一个容器中定义一个 BeanPostProcessor,它只会在该容器中的 bean 上工作。在一个容器中定义的 Bean 不会由另一个容器中的 BeanPostProcessor 进行后处理,即使两个容器属于同一层次结构。

          【讨论】:

          • 谢谢,成功了。我必须将&lt;context:property-placeholder location="classpath:properties/application.properties" /&gt; 添加到dispatcher-servlet.xml 并且它现在能够从属性文件中读取。
          • 如果有人试图在类构造函数中访问这些属性,请记住你不能,因为它们还没有被连接。您应该改为实现 InitializingBean::afterPropertiesSet
          • 这也适用于将属性值注入 Spring MVC 拦截器,例如 HandlerInterceptorAdaptor 的子类。
          • @Chris 感谢您的提示。一直把我的头发拉到那个上面。
          • 谢谢!我希望我能更快地找到这个答案。重复一遍,如果你的实例成员用@Value("${key}") 注释在运行时被设置为未解析的“${key}”,那么问题可能是你有一个应用程序上下文的层次结构,因此你需要属性占位符在多个级别配置。
          猜你喜欢
          • 2011-07-13
          • 2011-05-07
          • 1970-01-01
          • 1970-01-01
          • 2012-09-26
          • 1970-01-01
          • 1970-01-01
          • 2017-07-09
          • 2022-06-22
          相关资源
          最近更新 更多