假设我有一个班级Fruit
,它是两个子类 - Apple
和Grape
:
class Fruit {
public void grind() { }
}
class Apple extends Fruit { }
class Grape extends Fruit { }
在spring属性文件中,我有一个属性来决定在启动时注册哪个bean。我一次只能将Apple
或Grape
实例注册为bean。该物业是:
# This can be either apple or grape
app.fruit = apple
在Java配置文件中,我使用String
绑定@Value
属性与此属性,并基于此属性,我将创建适当的实例。我在这里尝试使用工厂模式。所以,我有FruitFactory
这样:
class FruitFactory {
private Map<String, Fruit> map = new HashMap<String, Fruit>();
public FruitFactory() {
map.put("apple", new Apple());
map.put("grape", new Grape());
}
public Fruit getFruit(String fruit) {
return map.get(fruit);
}
}
这是我的弹簧配置类:
class SpringConfig {
@Value("${app.fruit}")
private String fruitType;
@Bean
public FruitFactory fruitFactory() {
return new FruitFactory();
}
@Bean
public Fruit getFruit() {
return fruitFactory().getFruit(fruitType);
}
}
所以,这是我的几个问题:
存储在工厂内地图中的实例是弹簧托管bean吗?实施有什么问题吗?我已经尝试过了,它工作正常,我很困惑这些实例是否真的是春天管理。
我试图以更好的方式实现它,所以当新的水果到来时,我不必修改我的工厂。在途中是在工厂中提供register()
方法,并让所有Fruit
子类调用它。但问题是何时以及如何加载子类?在将实例放入地图之前,我不会使用这些类。任何人都可以提出更好的方法吗?
修改
正如评论和回答中所建议的那样,我尝试使用@Profile
代替工厂模式。但我面临一些问题。这是我的意思:
@Configuration
@Profile("apple")
class AppleProfile {
@Bean
public Fruit getApple() {
return new Apple();
}
}
@Configuration
@Profile("grape")
class GrapeProfile {
@Bean
public Fruit getGrape() {
return new Grape();
}
}
在ServletListener
中,我设置了有效的个人资料:
class MyServletListener implements ServletContextListener {
@Value("${app.fruit}")
private String fruitType;
public void contextInitialized(ServletContextEvent contextEvent) {
// Get Spring Context
WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(contextEvent
.getServletContext());
context.getAutowireCapableBeanFactory().autowireBean(this);
ConfigurableEnvironment configEnvironment = (ConfigurableEnvironment) context.getEnvironment();
logger.debug("0;Setting Active Profile: " + cacheRetrievalMode);
configEnvironment.setActiveProfiles(cacheRetrievalMode);
}
}
这是正确设置活动配置文件,我可以看到。唯一的问题是,侦听器在ContextLoaderListener
之前声明,并且在执行此操作时,已经创建了bean。还有其他选择吗?
答案 0 :(得分:2)
存储在工厂内地图中的实例是否为弹簧 托管bean?
使FruitFactory
成为托管bean
@Bean
public FruitFactory fruitFactory() {
return new FruitFactory();
}
不会创建它引用托管bean的任何对象。但是,这个
@Bean
public Fruit getFruit() {
return fruitFactory().getFruit(fruitType);
}
确实使得一个托管bean返回Fruit
。 @Bean
将方法标记为bean定义和bean工厂(它创建bean)。您返回的对象将由Spring的bean生命周期管理。
实施有什么问题吗?
您正在创建FruitFactory
bean,但同样Fruit
创建FruitFactory
,这似乎很奇怪。你是否打算在应用程序的其他地方注入FruitFactory
?
我试图以更好的方式实施它,以便当一个新的水果 来了,我没有必要修改我的工厂
说真的,你的工厂搞乱了一切。 Spring已经完成了它的工作,还有更多!注释使您的生活更轻松。您可以为@Bean
指定标识符。您可以使用@Qualifier
限定bean(然后使用@Qualifier
限定注入目标)。您可以为应该初始化bean的时间和条件设置@Profile
。
但问题是何时以及如何加载子类?我不会 使用这些类,而不是在将它们的实例放入地图之前。 任何人都可以提出更好的方法吗?
您可以使用bean initMethod
(您指定为@Bean
注释属性)或@PostConstruct
带注释的方法来执行初始化后逻辑。您可以使用这些来注册工厂的豆子,这些工厂已经注入(但是这种设计听起来不对我,你必须向我们展示更多。)
您还应该查看InitializingBean
和FactoryBean
。
要设置活动配置文件,一种可能性是执行以下操作。创建ApplicationContextInitializer
,通过从.properties
文件中读取来设置活动配置文件。你将无法在这里使用@PropertySources
,因为这不是一个bean。
像
这样的东西public class ProfileContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
PropertySource<Map<String, Object>> source = null;
try {
source = new ResourcePropertySource("spring.properties");
String profile = (String) source.getProperty("active.profile");
System.out.println(profile);
ConfigurableEnvironment env = applicationContext.getEnvironment();
env.setActiveProfiles(profile);
} catch (IOException e) {
e.printStackTrace();
}
}
}
您可以在部署描述符中注册它
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>com.yourapp.ProfileContextInitializer</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
创建ContextLoaderListener
后,它会选择并实例化您的类并调用其initialize
方法。这是在刷新WebApplicationContext
之前完成的。
您应该只为活动配置文件设置VM参数,并避免所有这些。