Spring 注入到 Servlet 中

25

我看到了这个问题:

Spring 依赖注入至其他实例

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

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");
    }

这会起作用吗?还是Spring在Web应用程序部署的这一点上还没有准备好向我的servlet提供bean?我是否需要像将bean放入web.xml那样做一些更传统的事情?


你为什么不使用Context-param和初始化监听器呢?从那里,你可以通过ServletContext查找ApplicationContext。 - bh5k
@bh5k 我正在处理一些遗留代码,实际上其中包含一个自定义的servlet。我以前没有接触过它们,所以与它们相关的任何内容对我来说都有点陌生。通常情况下,我会大量依赖Spring库来完成所有这些后台工作。 - thatidiotguy
1
你仍然可以这样做:https://dev59.com/vGw15IYBdhLWcg3wtd0g - bh5k
使用Context-Listener加载您的上下文,然后在servlet中查找它。 - bh5k
这应该是您想要做的事情:https://dev59.com/UWw15IYBdhLWcg3wv-fa - bh5k
这可能会有所帮助:http://www.tugay.biz/2016/03/web-app-with-spring-core-only.html - Koray Tugay
4个回答

43

我想利用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);
    }
}

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

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,并从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。


29
你正在尝试做的事情会让每个 Servlet 都有自己的 ApplicationContext 实例。也许这正是你想要的,但我怀疑。一个 ApplicationContext 应该是应用程序唯一的。
适当的方法是在 ServletContextListener 中设置你的 ApplicationContext
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。请注意保留HTML标签。
@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");
}

1
这看起来正是我想要做的。我会尝试运行代码并回报结果。感谢您花时间解释一切。哦,我需要在我的 web.xml 或应用程序上下文中添加任何内容才能让监听器发挥作用吗? - thatidiotguy
@thatidiotguy 不用谢。考虑一下使用ContextLoaderListener,这是Spring-mvc提供的一个类,可以精确地执行我刚才描述的操作,而且还有更多功能。 - Sotirios Delimanolis
抱歉,我编辑了我的评论,我觉得我需要在我的一个XML文件中添加一些内容才能让那个监听器起作用。这不是这种情况吗? - thatidiotguy
@thatidiotguy 你需要添加一个<listener>元素。或者使用servlet 3.0,你可以在类上注释@WebListener - Sotirios Delimanolis
7
最好让Spring的Web基础架构使用以下web.xml片段加载应用程序上下文:<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> 然后使用 WebApplicationContextUtils.getWebApplicationContext(getServletContext()) 检索。这只需要spring-web,而不是spring-mvc。 - Agustí Sánchez
显示剩余2条评论

8
到目前为止,这里的答案只在一定程度上对我有用。特别是使用@Configuration注释的类被忽略了,而且我不想使用xml配置文件。以下是我所做的,以便仅使用基于Spring(4.3.1)注释的设置来使注入工作:

在web.xml中的web-app下引导一个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;

1

Spring独立于Servlet启动。在读取bean xml之后,它将准备好提供bean。因此,在下面的语句之后,bean已经可用。

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

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


请注意,由此ApplicationContext处理的bean仅在该servlet中可用。如果您在其他servlet中执行此代码,则ApiData bean将在每个servlet中成为完全不同的类实例。 - Luiggi Mendoza
这取决于bean是否被定义为单例。我认为OP担心bean是否可用。 - sanbhat
我原本以为Servlet类会在Spring之前启动,但你说代码会启动整个过程。有时深入了解这些东西的内部工作方式是很好的。 - thatidiotguy
1
即使它们被定义为单例,它们仍将属于不同的“ApplicationContext”,因此是不同的bean。 - Luiggi Mendoza
谢谢@LuiggiMendoza,我已经编辑了帖子,希望现在可以理解。 - sanbhat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接