基于Java的配置和工厂模式

时间:2014-03-18 06:31:40

标签: java spring factory-pattern

假设我有一个班级Fruit,它是两个子类 - AppleGrape

class Fruit {
    public void grind() { }
}

class Apple extends Fruit { }
class Grape extends Fruit { }

在spring属性文件中,我有一个属性来决定在启动时注册哪个bean。我一次只能将AppleGrape实例注册为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。还有其他选择吗?

1 个答案:

答案 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带注释的方法来执行初始化后逻辑。您可以使用这些来注册工厂的豆子,这些工厂已经注入(但是这种设计听起来不对我,你必须向我们展示更多。)

您还应该查看InitializingBeanFactoryBean


要设置活动配置文件,一种可能性是执行以下操作。创建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参数,并避免所有这些。

相关问题