【问题标题】:SpringBoot - Property placeholder configuration not working with @Service annotationSpring Boot - 属性占位符配置不适用于 @Service 注释
【发布时间】:2021-01-20 06:05:00
【问题描述】:

我正在尝试使用属性占位符值来定义服务 bean 名称。但是得到错误说没有找到特定名称的bean。我知道问题在于读取属性值,因为在硬编码值时它正在工作。请帮忙,因为我需要从属性文件中读取值。代码如下:sn-p:

application.properties

event.testRequest=TEST_REQUEST

服务类

@Service("${event.testRequest}") // This is not working, getting "No bean named 'TEST_REQUEST' available" error
// @Service("TEST_REQUEST")     // This is working
public class TestRequestExecutor extends DefaultExecutionService {
...
}

另外,为了确认属性值读取正确,我尝试使用@Value("${event.testRequest}") private String value,我得到了预期的值“TEST_REQUEST”。不确定如何将其与 @Service 注释一起使用。

编辑: 为了详细说明将服务 bean 名称外部化的需要,我使用工厂模式来获取基于事件名称(事件名称,例如 Event1、Event2..)的实现。如果事件名称发生更改,则更改将仅发生在属性文件上,而不是使用属性占位符的服务 bean 名称。

    @RestController
    public class RequestProcessController {
    
    @Autowired
    private ExecutorFactory executorFactory;
    ..
    ExecutionService executionService = executorFactory.getExecutionService(request.getEventType());
    executionService.executeRequest(request);
..
}


@Component
public class ExecutorFactory {

private BeanFactory beanFactory;

public ExecutionService getExecutionService(String eventType) {
  return beanFactory.getBean(eventType, DefaultExecutionService.class);
}

这里DefaultExecutionService 有不同的实现,如下所示..

@Service("${event.first}")
public class Event1Executor extends DefaultExecutionService {..}
..
@Service("${event.second}")
public class Event2Executor extends DefaultExecutionService {..}

event.first = Event1
event.second = Event2

所以基本上以后如果Event1名称更新为EventOne,我只需要更新属性文件,而不是服务类。

非常感谢任何帮助!谢谢!

【问题讨论】:

  • 您不能在@Component 注释中使用占位符(@Service 就是其中之一)`。在我的书中动态分配名称也没有任何意义。
  • 你能详细说明为什么你真的需要这样做吗?为了正确的依赖注入解析规则,spring 需要引用 spring bean 的名称。我从来没有看到需要让它们动态化。我相信如果你能提出问题,还有其他方法可以解决这个问题......
  • @MarkBramnik 我们有不同的实现。我们需要将其外部化以从属性文件中读取,因为每个占位符的名称(这些是事件名称)将来可能会有所不同,并且它将在属性文件中更新。因此,无需更改代码,我们仍然可以使用现有代码,因为服务正在引用属性值。希望清楚。
  • 不是很清楚,所以你有不同的 DefaultExecutionService 实现,对吧?假设它可以工作,你在哪里使用解析的bean名称,我的意思是,如果spring能够从配置文件中解析@Service(“someService”)或@Service(“anotherService”),你在哪里使用字符串“someService ”或“另一个服务”在应用程序中?如果您只需要加载许多实现中的一种,则可以使用 \@ConditionalOnProperty 代替,在某些其他情况下,使用 \@Profile 可能很方便(虽然在引擎盖下是相同的),但它与您的技术不同问...
  • @MarkBramnik 请查看我帖子中的编辑部分。我已经详细解释过了。如果还不清楚,请告诉我。

标签: spring-boot property-placeholder


【解决方案1】:

好的,现在很清楚了。

我认为你可以通过改变实现来实现这样的行为:

您不需要在 ExecutorFactory 中使用 bean factory,而是考虑创建以下实现:

@AllArgsConstructor // note, its not a component - I'll use @Configuration
public class ExecutorFactory {
   private final Map<String, DefaultExecutionService> executorByEventName;

   public DefaultExecutorService  getExecutionService(String eventType) {
        return executorByEventName.get(eventType);
   }

现在创建这样的地图很棘手,需要不同的方法:

不要在执行器服务的实现中使用属性解析,而是采用某种“静态标识”方式,它可以是另一个注释,也可以是限定符,甚至是静态 bean 名称。在此示例中,我将使用基于限定符的方法,因为在我看来它最容易展示/实现。

@Service
@Qualifier("evt1")
public class TestRequestExecutor1 extends DefaultExecutionService {
...
}

@Service
@Qualifier("evt2")
public class TestRequestExecutor2 extends DefaultExecutionService {
...
}

然后你可以从Java Configuration类创建一个ExecutorFactory,这就是为什么我没有把@Component/@Service注释放在答案开头的ExecutorFactory类上。

@Configuration
public class MyConfiguration {
    @Bean
    public ExecutorFactory executorFactory(Map<String, DefaultExecutorService> 
        allServicesByQualifierName, MyConfigurationProperties config) {
        Map<String, DefaultExecutorService> map = new HashMap<>();
        allServicesByQualifierName.forEach((qualifierName, serviceBean) -> {
             String actualEventName = config.getMappedEventName(qualifierName);
             map.put(actualEventName, serviceBean);
        });
        return new ExecutorFactory(map);    
    }
} 

首先,我在这里使用了 spring 的一个特性,它允许将字符串映射注入您的界面(在本例中为 DefaultExecutorService)。 Spring 将注入以下地图:

evt1 --> bean of type TestRequestExecutor1 
evt2 --> bean of type TestRequestExecutor2

然后我访问应该支持获取所有事件的方法的配置。这可以通过不同的方式实现,可能最自然的方式是使用@ConfigurationProperties 注解并将application.yaml 的事件映射映射到Java 中的映射。您可以阅读this article 了解技术详情。

作为旁注,虽然我使用了@Configuration 方法,因为它对我来说看起来更清晰,但可以在ExecutorFactory 上使用@Service,但是我展示的类似逻辑将是作为执行器工厂的一部分(构造函数或构造后方法),您仍然可以将 bean 名称映射到实际 bean 并将配置属性注入构造函数,由您决定

【讨论】:

  • 感谢您的详细说明。我会看看你的方法,看看效果如何。
【解决方案2】:

有些东西你可以试试。不过,我不建议这样做。

尝试创建一个BeanNameGenerator 并使用beanNameGenerator(BeanNameGenerator beanNameGenerator) 方法将其提供给SpringApplicationBuilder。如果您好奇,这里是默认实现的link

如果我理解正确,您有多个实现该服务的方法,您必须根据属性文件中提供的名称选择一个。如果是这种情况,请查看this。如果这些实现依赖于不同的配置文件,请查看this

详细解释后编辑:

我认为实现这一点的最简单方法是注册您自己的 bean。所以从你的执行者中删除 @Service 注释。然后,使用DefaultListableBeanFactory为执行者注册自己的BeanDefinition

代码如下所示:

@Value("${event.first}")
String event1;

DefaultListableBeanFactory context = .. //Get BeanFactory

GenericBeanDefinition gbd = new GenericBeanDefinition();
gbd.setBeanClass(Event1Executor.class);
gbd.getPropertyValues().addPropertyValue("someProperty", "someValue");

context.registerBeanDefinition(event1, gbd);
Event1Executor bean = (Event1Executor) context.getBean(event1);

您可以可能使用BeanFactoryAware 获取bean 工厂,如果您想在注册bean 之前设置其他参数,则使用BeanDefinitionBuilder

【讨论】:

  • 请看编辑部分,我已经详细解释过了。
猜你喜欢
  • 2014-07-16
  • 2017-09-15
  • 2011-07-28
  • 2014-08-31
  • 2016-02-22
  • 1970-01-01
  • 1970-01-01
  • 2014-03-23
  • 2018-07-04
相关资源
最近更新 更多