0

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

应用程序属性

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,我只需要更新属性文件,而不是服务类。

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

4

2 回答 2

1

好的,现在很清楚了。

我认为您可以通过更改实现来实现这种行为:

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

@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 {
...
}

然后你可以创建一个ExecutorFactoryfrom Java Configuration 类,这就是为什么我没有在答案开头的类上放置@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 中的 Map。您可以阅读本文了解技术细节。

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

于 2021-01-20T10:42:02.483 回答
0

你可以尝试一些东西。虽然,我不建议这样做。

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

如果我理解正确,您对此服务有多个实现,您必须根据属性文件中提供的名称选择一个。如果是这样的话,看看这个。如果这些实现依赖于不同的配置文件,看看这个

详细解释后编辑:

我认为实现这一点的最简单方法是注册自己的 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 工厂,BeanDefinitionBuilder如果您想在注册 bean 之前设置其他参数。

于 2021-01-20T08:38:41.723 回答