5

在 Web 应用程序中,我们真的不需要做 ..

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml");
ctx.getBean("beanId");

因为一般的做法是加载上下文文件并使用 web.xml 中的 ContextLoaderServlet 注入所有具有依赖关系的 bean,如下所示。

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/spring-context.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- or use the ContextLoaderServlet instead of the above listener
<servlet>
  <servlet-name>context</servlet-name>
  <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>
--> 

但是,在没有容器的独立 Java 应用程序中,我最终会执行ctx.getBean("xyz"); . 有没有一种干净的方法可以做到这一点,在网上找不到一个例子。

我检查了..简单的 Spring,将 ClasspathApplicationContext 用于独立应用程序,如何重用?,其中谈到使用SingletonBeanFactoryLocator,但它最终使用 context.getBean()。

我还查看了ServiceLocatorFactoryBean,但这又是通过使用代理来按需获取 bean。

我正在寻找一种解决方案来从我的独立 java 应用程序的 main() 程序加载上下文文件(所有 bean),这样我就不想按需获取 bean。

示例代码:

public interface IReader {
    public String read();
}

public class TextFileReader implements IReader {

    private StringBuilder builder = null;
    private Scanner scanner = null;

    public TextFileReader(String fileName) throws FileNotFoundException {
        scanner = new Scanner(new File(fileName));
        builder = new StringBuilder();
    }

    public String read() {
        while (scanner.hasNext()) {
            builder.append(scanner.next());
            builder.append(",");
        }
        return builder.toString();
    }
}



 public class SpringNoConextDataReaderClient {

    private IReader reader = null;

    public void setReader(TextFileReader reader) {
        this.reader = reader;
    }

    private String fetchDataOne() {
        return reader.read();
    }

    private String fetchDataTwo() {
        return reader.read();
    }

    public static void main(String[] args) {

        final ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
        String fetchedData = context.getBean(SpringNoConextDataReaderClient.class).fetchDataOne(); // <-- reader is injected as TextFileReader in fetchDataOne which reads the file

        SpringNoConextDataReaderClient client = new SpringNoConextDataReaderClient();
        client.fetchDataOne(); // <--  reader is null and throws NPE, probably its lifetime ended with previous call?

        System.out.println("Example 1.1: Got data without context: " + fetchDataOne);
    }

}

弹簧上下文.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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="reader" class="com.himalay.spring.core.basic.readers.TextFileReader">
        <constructor-arg value="src/main/resources/data.txt" />
    </bean>

    <bean id="springNoConextDataReaderClient" class="com.himalay.spring.core.basic.SpringNoConextDataReaderClient">
        <property name="reader"><ref bean = "reader" /></property>
    </bean>

</beans>

谢谢你。

4

1 回答 1

8

在独立应用程序中,您需要创建ApplicationContext自己的实例并使用它来加载至少一个 bean。但是您加载的一个 bean 可以使用所有的 Spring 魔法@Autowired等,并且不需要再使用getBean了。因此,您可以拥有一个加载使用的引导 bean,getBean然后让这个 bean 执行其他所有操作。就像是:

@Component
public class Main
{
    @Autowired
    protected MyDependencyClass2 someClass1;
    @Autowired
    protected MyDependencyClass2 someClass2;
    // ...
    // or if you need an entity manager
    @PersistenceContext
    protected EntityManager em;
    // etc.

    protected void mainInternal(String[] args)
        throws Exception
    {
        // do everything here
        // all dependencies are initialized
        // ...
    }

    public static void main(String[] args)
        throws Exception
{
        // Bootstrap Spring and let it create and configure beans.
        final ApplicationContext context =
            new ClassPathXmlApplicationContext("spring-context.xml");
        context.getBean(Main.class).mainInternal(args);
    }
}

注意:通常使用带参数的getBean(Class)getBean(String,Class)变体更安全Class<T>


如果您只是调用new Main(),则依赖项不会被初始化。Spring 不知道您使用创建的new实例,只知道它自己创建的实例。这是 Spring 的一个关键概念。它不仅创建类的实例,还管理与其他 bean 的依赖关系,可以使用方面等处理创建的实例。这在您自己创建的实例上是不可能的new.

这里的重点是,如果您将所有代码从main移至mainInternal,则所有必需的依赖项都将被初始化。不仅Main如此,还有它的依赖关系、它们的依赖关系等等等等。因此,如果您的应用程序是使用 Spring 正确构建的,并且如果它只使用 Spring 特性(例如@Autowired)管理依赖关系,那么您将获得一个类似于您在一个网络应用程序。

因此,在这种情况下,正确的过程是:将所有 bean 所需的应用程序启动依赖项制作成Main. 它们将与所有依赖项一起被初始化,您可以安全地使用它们mainInternal或任何它调用的东西。


编辑:评论你的例子。正如我所解释的,Spring 只管理它创建的对象,而不是您使用创建的对象new。在您的示例中,您使用

SpringNoConextDataReaderClient client = new SpringNoConextDataReaderClient();

所以client不会由 Spring 管理,也不会设置或解析它的依赖项。可以这样想:如果你自己创建了一个对象,Spring 怎么会知道呢?

另外,您的示例设计得不好。Spring 的主要思想是使用控制反转原理管理程序组件并将它们连接在一起。在大多数情况下,此类程序组件意味着是在应用程序的整个生命周期中存在的单例对象。(也可能有生命周期较短的组件,例如一个 HTTP 请求或一个 HTTP 会话范围,但这超出了本问题的范围。)重要的是,此类单例组件一旦它们就不应更改其内部状态'被初始化。

另一方面,Spring 并不意味着管理您的数据对象,例如IReader. IReader不是程序的组件,它是您创建、从文件中读取并在之后处理的对象。更好的设计是:

  • 有一个为您提供IReader按需的单例 bean,例如

    public class TextFileReaderProvider {
        public IReader createReader() { ... }
    }
    
  • 将此提供程序连接到SpringNoConextDataReaderClientlike

    public class SpringNoConextDataReaderClient {
        @Autowired
        protected TextFileReaderProvider readerProvider;
    
        public SomeResult doMyComputation() {
            IReader r = readerProvider.createReader();
            try {
                // compute the result
                return theResult;
            } finally {
                r.close();
            }
        }
    }
    

    (或者不是@Autowired在 XML 中手动配置依赖项)。

  • main中,让 Spring 为您提供一个实例SpringNoConextDataReaderClient并调用doMyComputation()它。

这样的设计会导致您将软件分成不同的组件,这些组件可以在整个应用程序中重复使用,并且不会出现并发问题。

于 2012-08-15T15:16:30.947 回答