【问题标题】:@Autowired in ServletContextListenerServletContextListener 中的 @Autowired
【发布时间】:2013-07-15 13:58:35
【问题描述】:

我有一个类 InitApp

@Component
public class InitApp implements ServletContextListener {

@Autowired
ConfigrationService weatherConfService;

/** Creates a new instance of InitApp */
public InitApp() {
   }

public void contextInitialized(ServletContextEvent servletContextEvent) {
    System.out.println(weatherConfService);
   }
public void contextDestroyed(ServletContextEvent servletContextEvent) {
   }
}

和 web.xml 中的监听器:

    <listener>
        <listener-class>com.web.Utils.InitApp</listener-class>
    </listener>

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

confService 打印 --> null 什么问题?

【问题讨论】:

  • 您应该在 xml Spring 配置中定义 ConfigurationService,正如 Nambari 指出的那样,或者您应该将 component-scan 放在包含 ConfigurationService 的包上,并使用 @Component@Service

标签: java spring spring-mvc


【解决方案1】:

当我遇到同样的问题时,我想到了几个想法。

第一个是使用 Spring utils 从侦听器中的 Spring 上下文中检索 bean:

例如:

@WebListener
public class CacheInitializationListener implements ServletContextListener {

    /**
     * Initialize the Cache Manager once the application has started
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        CacheManager cacheManager = WebApplicationContextUtils.getRequiredWebApplicationContext(
                sce.getServletContext()).getBean(CacheManager.class);
        try {
            cacheManager.init();
        } catch (Exception e) {
            // rethrow as a runtime exception
            throw new IllegalStateException(e);
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // TODO Auto-generated method stub

    }
}

如果你只有一两个豆子,这很好用。否则会变得乏味。另一种选择是显式调用 Spring 的 Autowire 实用程序:

@WebListener
public class CacheInitializationListener implements ServletContextListener {

    @Autowired
    private CacheManager cacheManager;

    /**
     * Initialize the Cache once the application has started
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
        try {
            cacheManager.init();
        } catch (Exception e) {
            // rethrow as a runtime exception
            throw new IllegalStateException(e);
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // TODO Auto-generated method stub

    }
}

这两个解决方案的警告是,必须先加载 Spring 上下文,然后才能工作。鉴于无法使用@WebListener 定义侦听器顺序,请确保在web.xml 中定义 Spring ContextLoaderListener 以强制首先加载它(Web 描述符中定义的侦听器在由定义的侦听器之前加载注释)。

【讨论】:

    【解决方案2】:

    通过声明

    <listener>
      <listener-class>com.web.Utils.InitApp</listener-class>
    </listener>
    

    在你的 web.xml 中,你告诉你的容器初始化并注册一个 InitApp 的实例。因此,该对象不是由 Spring 管理的,您不能 @Autowired 向其中添加任何内容。

    如果您的应用程序上下文设置为组件扫描com.web.Utils 包,那么您将拥有一个未注册为容器的侦听器的InitApp bean。因此,即使您的其他 bean 将是 @Autowired,servlet 容器也不会命中它。

    这就是权衡。

    如果您使用带有 ServletContainerInitializerWebApplicationInitializer 的 servlet 3.0 的编程配置,则可以解决此问题。你不会使用@Autowired,你只会使用设置实例的setter/getter。

    这是一个例子

    class SpringExample implements WebApplicationInitializer {
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            WebApplicationContext context = ...;
    
            SomeBean someBean = context.getBean(SomeBean.class);
            CustomListener listener = new CustomListener();
            listener.setSomeBean(someBean);
    
            // register the listener instance
            servletContext.addListener(listener);
    
            // then register DispatcherServlet and ContextLoaderListener, as appropriate
            ...
        }
    }
    
    class CustomListener implements ServletContextListener {
        private SomeBean someBean;
    
        public SomeBean getSomeBean() {
            return someBean;
        }
    
        public void setSomeBean(SomeBean someBean) {
            this.someBean = someBean;
        }
    
        @Override
        public void contextInitialized(ServletContextEvent sce) {
           // do something with someBean
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent sce) {
        }
    }
    

    请注意,Spring 有一些非常复杂的 WebApplicationInitializer 自定义实现。您真的不需要自己直接实现它。这个想法保持不变,只是在继承层次结构中更深。

    【讨论】:

    • 你能提供一个解决方法的例子吗?我很难理解@Configuration bean 如何将某些东西注入到由容器管理的 bean 中。 @Config bean 如何获得对容器 bean 的引用?
    • @EricB。使用ServletContainerInitializerWebApplicationInitializer,您正在自己初始化和注册ServletContextListener。由于您还可以访问 Spring WebApplicationContext(s),因此您可以从中检索 bean 并将其交给侦听器。
    • @EricB。 (如果你想要一个完整的例子,你必须在稍后再给我。)
    • 当然 - 我可以使用 WebApplicationContextUtils.getRequiredWebApplicationContext 之类的东西来检索应用程序上下文,然后从中获取 bean,但我不确定这是否是您的意思。我从您的帖子中了解到,它可以通过 Spring 配置类来完成,并将 spring bean 推送到侦听器中(而不是让侦听器从 Spring 上下文中拉出 bean)。
    • 好的 - 在这种情况下,如果您有时间可以给我一个示例说明您的想法,我很想看看您如何在 Spring 中操作侦听器 bean。
    【解决方案3】:
    @WebListener
    public class StartupListener implements ServletContextListener {
    
        @Autowired
        private MyRepository repository;
    
        @Override
        public void contextDestroyed(ServletContextEvent event) {
        }
    
        @Override
        public void contextInitialized(ServletContextEvent event) {
            AutowireCapableBeanFactory autowireCapableBeanFactory = WebApplicationContextUtils.getRequiredWebApplicationContext(event.getServletContext()).getAutowireCapableBeanFactory();
            autowireCapableBeanFactory.autowireBean(this);
    
            repository.doSomething();
        }
    }
    

    【讨论】:

      【解决方案4】:

      正如其他人所说,此侦听器通过 web servlet(tomcat) 上下文(不是 Spring 容器)进行观察,并收到有关 servlet 启动/关闭的通知。

      由于它是由 Spring 容器外部的 servlet 创建的,它不受 Spring 管理,因此不可能使用 @Autowire 成员。

      如果您将 bean 设置为托管 @Component,则 Spring 将创建实例,并且侦听器不会向外部 servlet 注册。

      你不能两全其美..

      一种解决方案是删除 Spring 注释并从 Spring 应用程序上下文中手动检索您的成员并以这种方式设置您的成员。

          public class InitApp implements ServletContextListener {
      
              private ConfigrationService weatherConfService;
      
              private static ApplicationContext   applicationContext  = new ClassPathXmlApplicationContext("classpath:web-context.xml");
      
              @Override
              public void contextInitialized(ServletContextEvent servletContextEvent) {
                  weatherConfService = applicationContext.getBean(ConfigrationService.class);
                  System.out.println(weatherConfService);
              }
      
              @Override
              public void contextDestroyed(ServletContextEvent servletContextEvent) {
              }
          }
      

      【讨论】:

        【解决方案5】:

        使用当前版本的 Spring Boot 2,您还可以将 Servlet、过滤器和侦听器注册为 Spring bean,并正常使用自动装配的组件:

        将 Servlet、过滤器和侦听器注册为 Spring Beans

        作为 Spring bean 的任何 Servlet、Filter 或 servlet *Listener 实例都注册到嵌入式容器。如果您想在配置期间引用 application.properties 中的值,这会特别方便。

        更多信息在这里Register @WebListeners in a way that allows them to register servlets and filters

        这意味着您只需将ServletContextListener 注释为@Comonent

        由于 Spring Boot 2.4 使用 @WebListener 不再起作用,在 release notes 中提到。

        此更改的副作用是 Servlet 容器现在创建了 WebListener 的实例,因此不能再使用诸如 @Autowired 的依赖注入。在这种情况下,应该使用@Component。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-06-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-12-12
          • 2013-08-24
          相关资源
          最近更新 更多