【问题标题】:Adding a Pre-constructed Bean to a Spring Application Context将预先构造的 Bean 添加到 Spring 应用程序上下文
【发布时间】:2009-01-30 18:52:31
【问题描述】:

我正在编写一个实现以下方法的类:

public void run(javax.sql.DataSource dataSource);

在此方法中,我希望使用类似于以下的配置文件来构造一个 Spring 应用程序上下文:

<bean id="dataSource" abstract="true" />

<bean id="dao" class="my.Dao">
  <property name="dataSource" ref="dataSource" />
</bean>

是否可以强制 Spring 使用传递给我的方法的 DataSource 对象,只要配置文件中引用了“dataSource”bean 吗?

【问题讨论】:

    标签: java spring


    【解决方案1】:

    我也遇到过同样的情况。由于没有人提出我的解决方案(而且我认为我的解决方案更优雅),我会在这里为后代添加它:-)

    解决方案包括两个步骤:

    1. 创建父 ApplicationContext 并在其中注册您现有的 bean。
    2. 创建子 ApplicationContext(传入父上下文)并从 XML 文件加载 bean

    第 1 步:

    //create parent BeanFactory
    DefaultListableBeanFactory parentBeanFactory = new DefaultListableBeanFactory();
    //register your pre-fabricated object in it
    parentBeanFactory.registerSingleton("dataSource", dataSource);
    //wrap BeanFactory inside ApplicationContext
    GenericApplicationContext parentContext = 
            new GenericApplicationContext(parentBeanFactory);
    parentContext.refresh(); //as suggested "itzgeoff", to overcome a warning about events
    

    第 2 步:

    //create your "child" ApplicationContext that contains the beans from "beans.xml"
    //note that we are passing previously made parent ApplicationContext as parent
    ApplicationContext context = new ClassPathXmlApplicationContext(
            new String[] {"beans.xml"}, parentContext);
    

    【讨论】:

    • 我添加了一个parentContext.refresh() 以克服有关未正确初始化多播应用程序事件的警告。
    • 我真的希望这可以与 AnnotationConfigApplicationContext 一起使用,但有一个问题阻止了它 (jira.springsource.org/browse/SPR-7791)
    【解决方案2】:

    我发现两个 Spring 接口可以用来实现我所需要的。 BeanNameAware 接口允许 Spring 通过调用 setBeanName(String) 方法在应用程序上下文中告诉对象其名称。 FactoryBean 接口告诉 Spring 不要使用对象本身,而是调用 getObject() 方法时返回的对象。把它们放在一起,你会得到:

    public class PlaceholderBean implements BeanNameAware, FactoryBean {
    
        public static Map<String, Object> beansByName = new HashMap<String, Object>();
    
        private String beanName;
    
        @Override
        public void setBeanName(String beanName) {
            this.beanName = beanName;
        }
    
        @Override
        public Object getObject() {
            return beansByName.get(beanName);
        }
    
        @Override
        public Class<?> getObjectType() {
            return beansByName.get(beanName).getClass();
        }
    
        @Override
        public boolean isSingleton() {
            return true;
        }
    
    }
    

    bean 定义现在简化为:

    <bean id="dataSource" class="PlaceholderBean" />
    

    占位符在创建应用程序上下文之前接收其值。

    public void run(DataSource externalDataSource) {
        PlaceholderBean.beansByName.put("dataSource", externalDataSource);
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        assert externalDataSource == context.getBean("dataSource");
    }
    

    似乎一切顺利!

    【讨论】:

    • 你救了我的命!我使用MockServletContext sc = new MockServletContext(); sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, "/applicationContext-test.xml"); new ContextLoader().initWebApplicationContext(sc); 使其通过ContextLoader.getCurrentWebApplicationContext() 工作
    • 其他bean上的dataSource可以@Autowired吗?
    • 这个很好用!基于 Java 的配置示例:@Bean public PlaceholderBean myThirdPartyClassName() { return new PlaceholderBean(); } 在构建 ApplicationContext 之前,调用 PlaceholderBean.beanNames.put("myThirdPartyClassName", myThirdPartyObject);
    【解决方案3】:

    第二种解决方案由于刷新问题导致异常。一种更优雅的方法是将对象添加到上下文中,然后使用 xmlreader 加载 xml 定义。 因此:

     ObjectToBeAddedDynamically objectInst = new ObjectToBeAddedDynamically();
      DefaultListableBeanFactory parentBeanFactory = new DefaultListableBeanFactory();  
      parentBeanFactory.registerSingleton("parameterObject", objectInst);
    
      GenericApplicationContext parentContext = new GenericApplicationContext(parentBeanFactory);
    
      XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(parentContext);
       xmlReader.loadBeanDefinitions(new FileSystemResource("beandefinitions.xml"));
       parentContext.refresh();
    
       ObjectUsingDynamicallyAddedObject userObjectInst= (ObjectUsingDynamicallyAddedObject )parentContext.getBean("userObject");
    

        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="userObject" class="com.beanwiring.ObjectUsingDynamicallyAddedObject"
          >
          <constructor-arg ref="parameterObject" />
    
    </bean>
    
    </beans>
    

    完美运行!

    【讨论】:

    • “第二种解决方案”是指哪个解决方案?答案的顺序随时间而变化,也可以以不同的排序显示。您可以使用解决方案下方的“链接”。
    • 这是 imo 的最佳解决方案。只需要一个应用程序上下文
    【解决方案4】:

    您可以为 DataSource 创建一个包装类,它只是委托给包含的 DataSource

    public class DataSourceWrapper implements DataSource {
    
    DataSource dataSource;
    
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    
    @Override
    public Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
    
    @Override
    public Connection getConnection(String username, String password)
            throws SQLException {
        return dataSource.getConnection(username, password);
    }
    //delegate to all the other DataSource methods
    }
    

    然后在 Spring 上下文文件中声明 DataSourceWrapper 并将其连接到所有 bean。然后在您的方法中获得对 DataSourceWrapper 的引用,并将包装的 DataSource 设置为传入您的方法的那个。

    这一切都在很大程度上取决于加载时 Spring 上下文文件中发生的情况。如果 bean 要求在上下文加载时 DataSource 已经可用,那么您可能必须编写一个 BeanFactoryPostProcessor 在加载时更改 Spring 上下文文件,而不是在加载后执行操作(尽管可能延迟初始化可以解决这个问题)。

    【讨论】:

    • 谢谢!我喜欢你的解决方案,它非常优雅。不幸的是,我们的代码在初始化阶段确实需要数据源,所以这让事情变得更加棘手。但是,您肯定激起了我对 BeanFactoryPostProcessor 接口的兴趣。我得去看看!
    【解决方案5】:

    如果你通过调用“new”来创建一个对象,它就不受 Spring 工厂的控制。

    为什么不让 Spring 将 DataSource 注入对象而不是将其传递给 run()?

    【讨论】:

    • 很遗憾,我们的代码不负责创建相关数据源。因此,它不能定义为 XML 文件中的 bean。数据源是通过另一个安排我们代码执行的框架提供给我们的代码的。
    【解决方案6】:

    有一种更优雅的方式,您可以使用外部 xml 文件并使用文件系统资源加载它,然后将其中配置的 bean 注入您的应用程序上下文中。因此:

    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.support.GenericApplicationContext;
    import org.springframework.core.annotation.Order;
    import org.springframework.core.io.FileSystemResource;
    import org.springframework.stereotype.Service;
    
    @Service
    @Order(-100)
    public class XmlBeanInitializationService implements ApplicationContextAware, InitializingBean {
    
        private ApplicationContext applicationContext;
    
        @Value("${xmlConfigFileLocation}")
        private String xmlConfigFileLocation;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader((BeanDefinitionRegistry)applicationContext);
            reader.loadBeanDefinitions(new FileSystemResource(xmlConfigFileLocation));
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
    
        }
    }
    

    其中 ${xmlConfigFileLocation} 是您的 application.properties 文件中指定的属性,它指向系统中的文件位置,因此:

    xmlConfigFileLocation="your-file-path-anywhere-in-your-system"
    

    您的 xml 文件可能包含:

    <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
            <bean class="com.yourpackage.YourBean1Class"></bean>
            <bean class="com.yourpackage.YourBean2Class"></bean>
            <bean class="com.yourpackage.YourBean3Class"></bean>
    
        </beans>
    

    因此,当您的应用程序启动时,spring 会加载类并将 bean 加载到应用程序上下文中。

    希望这对某人有所帮助。

    【讨论】:

      猜你喜欢
      • 2013-04-12
      • 2010-11-14
      • 2013-12-22
      • 1970-01-01
      • 2011-05-31
      • 2017-08-18
      • 2015-06-24
      相关资源
      最近更新 更多