【问题标题】:Spring injection Into ServletSpring 注入到 Servlet
【发布时间】:2013-09-15 17:57:15
【问题描述】:

所以我看到了这个问题:

Spring dependency injection to other instance

想知道我的方法是否可行。

1) 在我的 Spring 应用程序上下文中声明 bean

    <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="initialSize" value="${jdbc.initialSize}" />
        <property name="validationQuery" value="${jdbc.validationQuery}" /> 
        <property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
    </bean>

    <bean id="apiData" class="com.mydomain.api.data.ApiData">
        <property name="dataSource" ref="dataSource" />
        <property name="apiLogger" ref="apiLogger" />
    </bean>

    <bean id="apiLogging" class="com.mydomain.api.data.ApiLogger">
        <property name="dataSource" ref="dataSource" />
    </bean>

2) 覆盖我的 servlet 的 init 方法,如图所示:

    @Override
    public void init(ServletConfig config) throws ServletException {
       super.init(config);

       ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

       this.apiData = (ApiData)ac.getBean("apiData");
       this.apiLogger = (ApiLogger)ac.getBean("apiLogger");
    }

在 Web 应用程序部署的这一点上,这是否可行,或者 Spring 是否还没有准备好将 bean 交付给我的 servlet?我是否必须做一些更传统的事情,比如将豆子放入web.xml

【问题讨论】:

  • 您是否有理由不使用上下文参数和初始化侦听器?从那里您可以从 ServletContext 中查找 ApplicationContext。
  • @bh5k 我正在处理一些实际上具有自定义 servlet 的遗留代码。我以前没有和他们合作过,所以与他们有关的任何事情对我来说都有点陌生。通常我非常依赖 Spring 库来完成所有这些幕后工作。
  • 你应该仍然可以这样做:stackoverflow.com/questions/6451377/…
  • 使用 Context-Listener 加载上下文,然后在 servlet 中查找。
  • 这应该正是你想要做的:stackoverflow.com/questions/6414373/…

标签: java spring servlets dependency-injection


【解决方案1】:

我想利用 Sotirios Delimanolis 提供的解决方案,但在混合中添加了透明的自动装配。 这个想法是将普通的 servlet 转换为可自动连接的对象。

所以我创建了一个父抽象 servlet 类,它检索 Spring 上下文、获取和支持自动装配的工厂,并使用该工厂自动装配 servlet 实例(实际上是子类)。我还将工厂存储为实例变量,以防子类需要它。

所以父抽象servlet看起来像这样:

public abstract class AbstractServlet extends HttpServlet {

    protected AutowireCapableBeanFactory ctx;

    @Override
    public void init() throws ServletException {
        super.init();
        ctx = ((ApplicationContext) getServletContext().getAttribute(
                "applicationContext")).getAutowireCapableBeanFactory();
        //The following line does the magic
        ctx.autowireBean(this);
    }
}

一个 sevlet 子类看起来像这样:

public class EchoServlet extends AbstractServlet {

    @Autowired
    private MyService service;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {
        response.getWriter().println("Hello! "+ service.getMyParam());
    }
}

请注意,EchoServlet 唯一需要做的就是在 Spring 的常见实践中声明一个 bean。神奇的是在超类的 init() 方法中完成的。

我还没有彻底测试过。但它与一个简单的 bean MyService 一起工作,该 bean 还从 Spring 管理的属性文件中自动获取一个属性。

享受吧!


注意:

最好使用 Spring 自己的上下文侦听器加载应用程序上下文,如下所示:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

然后像这样检索它:

WebApplicationContext context = WebApplicationContextUtils
    .getWebApplicationContext(getServletContext());
ctx = context.getAutowireCapableBeanFactory();
ctx.autowireBean(this);

只需要导入spring-web库,不需要spring-mvc。

【讨论】:

    【解决方案2】:

    您正在尝试做的事情将使每个Servlet 都有自己的ApplicationContext 实例。也许这就是你想要的,但我对此表示怀疑。 ApplicationContext 对于应用程序应该是唯一的。

    执行此操作的适当方法是将您的ApplicationContext 设置为ServletContextListener

    public class SpringApplicationContextListener implements ServletContextListener {
            @Override
        public void contextInitialized(ServletContextEvent sce) {
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            sce.getServletContext().setAttribute("applicationContext", ac);            
        }
        ... // contextDestroyed
    }
    

    现在您的所有 servlet 都可以通过 ServletContext 属性访问相同的 ApplicationContext

    @Override
    public void init(ServletConfig config) throws ServletException {
       super.init(config);
    
       ApplicationContext ac = (ApplicationContext) config.getServletContext().getAttribute("applicationContext");
    
       this.apiData = (ApiData)ac.getBean("apiData");
       this.apiLogger = (ApiLogger)ac.getBean("apiLogger");
    }
    

    【讨论】:

    • 这看起来正是我想要做的。我将尝试代码并报告。感谢您花时间解释一切。哦,我是否需要向我的 web.xml 或应用程序上下文添加任何内容才能让该侦听器完成其工作?
    • @thatidiotguy 不客气。考虑查看 ContextLoaderListener,这是 Spring-mvc 提供的一个类,它完全符合我刚才描述的功能,等等。
    • 对不起,我编辑了我的评论,我觉得我需要在我的一个 xml 文件中添加一些东西才能让那个监听器工作。不是这样吗?
    • @thatidiotguy 你需要添加一个&lt;listener&gt; 元素。或者使用 servlet 3.0,您可以使用 @WebListener 注释类。
    • 最好让 Spring 自己的 Web 基础架构使用以下 web.xml sn-p 加载应用程序上下文:&lt;context-param&gt; &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt; &lt;param-value&gt;classpath:applicationContext.xml&lt;/param-value&gt; &lt;/context-param&gt; &lt;listener&gt; &lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt; &lt;/listener&gt; 然后使用 WebApplicationContextUtils .getWebApplicationContext(getServletContext()) 检索。这只需要spring-web,不需要spring-mvc。
    【解决方案3】:

    到目前为止,这里的答案只对我部分有用。尤其是带有 @Configuration 注释的类被忽略了,我不想使用 xml 配置文件。这是我为使注入工作单独使用基于 Spring (4.3.1) 注释的设置所做的工作:

    在 web-app 下的 web.xml 中引导 AnnotationConfigWebApplicationContext。作为参数,您需要 contextClass 和 contextConfigLocation (您的注释配置类):

    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
    org.springframework.web.context.support.AnnotationConfigWebApplicationContext
      </param-value>
    </context-param>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.example.config.AppConfig</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    

    然后覆盖servlet中的init方法。我使用了一个扩展 HttpServlet 的抽象类,所以我不必在每个 servlet 中重复它:

    @Configurable
    public abstract class MySpringEnabledServlet extends HttpServlet
    {
      @Override
      public void init(
          ServletConfig config) throws ServletException
      {
        super.init(config);
        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
      }
    [...]
    }
    

    最后我在 web.xml 中提到的 AppConfig 类中有我的主要配置:

    @Configuration
    @ComponentScan(basePackages = "com.example")
    @Import(
    { SomeOtherConfig.class })
    public class AppConfig
    {
    }
    

    依赖类被注释:

    @Component
    public class AnnotatedClassToInject
    

    并在我的 servlet 中通过自动装配注入:

    @Autowired
    private AnnotatedClassToInject myClass;
    

    【讨论】:

      【解决方案4】:

      Spring 独立于 Servlet 启动。在 spring 读取 bean xml 之后,它将准备好交付 bean。所以在下面的语句之后,bean 已经可用了

      ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
      

      正如@LuiggiMendoza 所指出的,每个ApplicationContext 都将创建/维护自己的bean,因此创建ApplicationContext 一次并从不同的servlet 重用它总是好的(而不是在内部创建它们) Servlet 的init() 方法)

      【讨论】:

      • 请注意,这个ApplicationContext 处理的bean 将只对那个servlet 可用。如果您在其他 servlet 中执行此代码,ApiData bean 将是每个 servlet 中完全不同的类实例。
      • 这取决于bean是否被定义为单例。我认为OP关心bean是否可用
      • 我的印象是servlet类会在Spring之前启动,但你是说代码会启动整个过程啊。有时不得不深入研究这些东西的内部运作,这很好。
      • 即使它们被定义为单例,它们也将属于不同的ApplicationContexts,因此是不同的bean。
      • 感谢@LuiggiMendoza 我已经编辑了帖子,希望现在有意义
      猜你喜欢
      • 1970-01-01
      • 2017-05-17
      • 2013-12-08
      • 1970-01-01
      • 2016-05-17
      • 2021-08-29
      • 1970-01-01
      • 2017-09-12
      • 2014-04-25
      相关资源
      最近更新 更多