@Controller 类中的 Spring @Value 注释未评估属性文件中的值

Posted

技术标签:

【中文标题】@Controller 类中的 Spring @Value 注释未评估属性文件中的值【英文标题】:Spring @Value annotation in @Controller class not evaluating to value inside properties file 【发布时间】: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 的回答应该可以确保属性文件在项目类路径中,如果它要在外部战争中,那么应该在应用程序上下文中使用属性占位符。它应该可以工作。 【参考方案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://***.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”,那么问题可能是你有一个应用程序上下文的层次结构,因此你需要属性占位符在多个级别配置。

以上是关于@Controller 类中的 Spring @Value 注释未评估属性文件中的值的主要内容,如果未能解决你的问题,请参考以下文章

Spring常用注解

spring controller方法和jstl

Spring Mvc——Controller中常规方法示例

第九节——spring-mcv快速入门

springboot默认创建的bean是单实例

Spring