Java 将预先构造的 Bean 添加到 Spring 应用程序上下文

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/496711/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 15:22:44  来源:igfitidea点击:

Adding a Pre-constructed Bean to a Spring Application Context

javaspring

提问by Adam Paynter

I am writing a class that implements the following method:

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

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

Within this method, I wish to construct a Spring application context using a configuration file similar to the following:

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

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

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

Is it possible to force Spring to use the DataSource object passed to my method wherever the "dataSource" bean is referenced in the configuration file?

是否可以强制 Spring 在配置文件中引用“dataSource”bean 的任何地方使用传递给我的方法的 DataSource 对象?

采纳答案by Adam Paynter

I discovered two Spring interfaces can be used to implement what I need. The BeanNameAwareinterface allows Spring to tell an object its name within an application context by calling the setBeanName(String)method. The FactoryBeaninterface tells Spring to not use the object itself, but rather the object returned when the getObject()method is invoked. Put them together and you get:

我发现可以使用两个 Spring 接口来实现我所需要的。该BeanNameAware接口允许Spring通过调用来告诉一个对象的应用程序上下文中它的名字setBeanName(字符串)方法。该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;
    }

}

The bean definition is now reduced to:

bean 定义现在简化为:

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

The placeholder receives its value before creating the application context.

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

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

Things appear to be working successfully!

事情似乎成功了!

回答by duffymo

If you create an object by calling "new", it's not under the control of the Spring factory.

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

Why not have Spring inject the DataSource into the object instead of passing it into run()?

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

回答by Patrick

You can create a wrapper class for a DataSourcethat simply delegates to a contained DataSource

您可以为 a 创建一个包装类,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
}

Then in you Spring context file you declare DataSourceWrapperand wire it into all your beans. Then in your method you get a reference to DataSourceWrapper and set the wrapped DataSource to the one passed in to your method.

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

This all working is highly depended on what happens in your Spring context file when its being loaded. If a bean requires the DataSource to already be available when the context loads then you may have to write a BeanFactoryPostProcessorthat alters the Spring context file as it loads, rather then doing things after the load (though perhaps a lazy-init could solve this issue).

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

回答by Neeme Praks

I have been in the exact same situation. As nobody proposed my solution (and I think my solution is more elegant), I will add it here for future generations :-)

我一直处于完全相同的情况。由于没有人提出我的解决方案(而且我认为我的解决方案更优雅),我将在这里添加它以供后代使用:-)

The solution consists of two steps:

解决方案包括两个步骤:

  1. create parent ApplicationContext and register your existing bean in it.
  2. create child ApplicationContext (pass in parent context) and load beans from XML file
  1. 创建父 ApplicationContext 并在其中注册您现有的 bean。
  2. 创建子 ApplicationContext(传入父上下文)并从 XML 文件加载 bean

Step #1:

第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

Step #2:

第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);

回答by ruhsuzbaykus

The second solution causes an exception because of a refresh problem. A more elegant way will be adding objects to the context and then loading xml definitions using the xmlreader. Thus:

第二种解决方案由于刷新问题导致异常。一种更优雅的方法是将对象添加到上下文,然后使用 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");

and

    <?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>

works perfect!

工作完美!

回答by Sohlowmawn

There's a more elegant way in which you can use an external xml file and load it with file system resource then inject beans configured in it into your application context. Thus:

有一种更优雅的方式,您可以使用外部 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;

    }
}

where ${xmlConfigFileLocation} is a property specified in your application.properties file which points to the file location in your system thus:

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

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

and your xml file may contain:

并且您的 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>

thus when your application starts spring loads the class and loads the bean into application context.

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

Hope this helps someone.

希望这可以帮助某人。