web-dev-qa-db-fra.com

Injection de ressort dans Servlet

J'ai donc vu cette question:

injection de dépendance Spring vers une autre instance

et je me demandais si ma méthode fonctionnerait.

1) Déclarer les beans dans mon contexte d'application Spring

    <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) Remplacez la méthode d'initialisation de mon servlet comme indiqué:

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

Cela fonctionnera-t-il ou Spring n'est-il pas encore prêt à livrer des beans à ma servlet à ce stade du déploiement des applications Web? Dois-je faire quelque chose de plus traditionnel comme mettre les haricots dans web.xml?

23
thatidiotguy

Ce que vous essayez de faire fera que chaque Servlet aura sa propre instance ApplicationContext . C'est peut-être ce que vous voulez, mais j'en doute. Un ApplicationContext doit être unique à une application.

La façon appropriée de le faire est de configurer votre ApplicationContext dans un ServletContextListener .

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

        sce.getServletContext().setAttribute("applicationContext", ac);            
    }
    ... // contextDestroyed
}

Maintenant, toutes vos servlets ont accès aux mêmes ApplicationContext via les attributs ServletContext.

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

Je voulais tirer parti de la solution fournie par Sotirios Delimanolis mais en ajoutant un câblage automatique transparent au mélange. L'idée est de transformer des servlets simples en objets compatibles avec les fils automatiques.

J'ai donc créé une classe de servlet abstraite parent qui récupère le contexte Spring, obtient et fabrique automatiquement le câblage et utilise cette fabrique pour câbler automatiquement les instances de servlet (la sous-classe, en fait). Je stocke également l'usine en tant que variable d'instance au cas où les sous-classes en auraient besoin.

Ainsi, le servlet abstrait parent ressemble à ceci:

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

Et une sous-classe de sevlet ressemble à ceci:

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

Notez que la seule chose que EchoServlet doit faire est de déclarer un bean uniquement dans la pratique courante de Spring. La magie se fait dans la méthode init () de la superclasse.

Je ne l'ai pas testé à fond. Mais cela a fonctionné avec un simple bean MyService qui obtient également une propriété câblée automatiquement à partir d'un fichier de propriétés géré par Spring.

Prendre plaisir!


Remarque:

Il est préférable de charger le contexte de l'application avec le propre écouteur de contexte de Spring comme ceci:

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

Ensuite, récupérez-le comme ceci:

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

Seule la bibliothèque Spring-Web doit être importée, pas Spring-mvc.

38
Agustí Sánchez

Jusqu'à présent, les réponses n'ont fonctionné qu'en partie pour moi. En particulier, les classes avec l'annotation @Configuration ont été ignorées et je ne voulais pas utiliser de fichier de configuration xml. Voici ce que j'ai fait pour que l'injection fonctionne uniquement avec une configuration basée sur des annotations Spring (4.3.1):

Démarrez un AnnotationConfigWebApplicationContext dans web.xml sous web-app. En tant que paramètre, vous avez besoin de contextClass et contextConfigLocation (votre classe de configuration annotée):

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

Remplacez ensuite la méthode init dans le servlet. J'utilise une classe abstraite qui étend HttpServlet, donc je n'ai pas à la répéter dans chaque servlet:

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

et enfin j'ai ma configuration principale dans la classe AppConfig mentionnée dans web.xml:

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

Les classes dépendantes sont annotées:

@Component
public class AnnotatedClassToInject

et injecté via le câblage automatique dans ma servlet:

@Autowired
private AnnotatedClassToInject myClass;
8
Arigion

Spring est indépendant du démarrage de Servlet. Juste après que le printemps ait lu le fichier XML des haricots, il sera prêt à livrer les haricots. Donc, juste après la déclaration ci-dessous, les beans sont déjà disponibles

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

Aussi, comme indiqué par @LuiggiMendoza, chaque ApplicationContext créera/maintiendra ses propres beans, il est donc toujours bon de créer ApplicationContextne fois et réutiliser à partir de différents servlets (par opposition à les créer à l'intérieur de la méthode init() d'un Servlet)

1
sanbhat