SpringBoot之Listener注册到Spring容器中的多种方法

相关文章:
SpringBoot 之AOP切面的使用
SpringBoot之Interceptor拦截器注入使用
SpringBoot之Filter过滤器的实现及排序问题
SpringBoot 之多个过滤器(Filter) ,监听器(Listener),切面(AOP),拦截器(Interceptor)的指定排序问题总结篇
使用Filter,Listener 时无法注入Bean的解决方法

前言

同事项目中需要开一个Listener,正好我做的项目中正好有,所以给他铐了一个过去,结果一会告诉我Listener类没进Listener。
开始的时候使用了最常见的@WebListener注解(我项目就是使用的这个)。然后开始排查原因,起初发现同事项目Application启动器上没有加@ServletComponentScan注解。
加上后发现还是不行。继续找,然后发现可能是因为Listener类不在Application模块中。无法自动注入(可以在@ServletComponentScan加入需要扫描的模块包)
后面百度发现注入Listener还有其他几种方法,我们通过其中一种方法解决了其问题,故在此记录一下。

1、@WebListener注解

@WebListener注解为Servlet3+提供的注解,可以标识一个类为Listener,跟Servlet、Filter差不多

@WebListener
public class AnoContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("@WebListener context 初始化");
    }


    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("@WebListener context 销毁");
    }
}

因为@WebListener注解不是spring的规范,所以为了识别它,需要在启动类上添加注解@ServletComponentScan

@ServletComponentScan
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

@ServletComponentScan
在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码。
注意:如果Application类和Listerner,Servlet,Filter类不在同一包下,则@ServletComponentScan需要添加相应的路径,如Application类在包com.hui.xiao下,则写为@ServletComponentScan(“com.demo.jarvis”)或@ServletComponentScan(“com.demo”)
附上@ServletComponentScan源码:

/**
* Enables scanning for Servlet components ({@link WebFilter filters}, {@link WebServlet
* servlets}, and {@link WebListener listeners}). Scanning is only performed when using an
* embedded web server.
* <p>
* Typically, one of {@code value}, {@code basePackages}, or {@code basePackageClasses}
* should be specified to control the packages to be scanned for components. In their
* absence, scanning will be performed from the package of the class with the annotation.
*
* @author Andy Wilkinson
* @since 1.3.0
* @see WebServlet
* @see WebFilter
* @see WebListener
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ServletComponentScanRegistrar.class)
public @interface ServletComponentScan {


   /**
    * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
    * declarations e.g.: {@code @ServletComponentScan("org.my.pkg")} instead of
    * {@code @ServletComponentScan(basePackages="org.my.pkg")}.
    * @return the base packages to scan
    */
   @AliasFor("basePackages")
   String[] value() default {};


   /**
    * Base packages to scan for annotated servlet components. {@link #value()} is an
    * alias for (and mutually exclusive with) this attribute.
    * <p>
    * Use {@link #basePackageClasses()} for a type-safe alternative to String-based
    * package names.
    * @return the base packages to scan
    */
   @AliasFor("value")
   String[] basePackages() default {};


   /**
    * Type-safe alternative to {@link #basePackages()} for specifying the packages to
    * scan for annotated servlet components. The package of each class specified will be
    * scanned.
    * @return classes from the base packages to scan
    */
   Class<?>[] basePackageClasses() default {};


}

2、普通Bean

第二种使用方式是将Listener当成一个普通的spring bean,spring boot会自动将其包装为ServletListenerRegistrationBean对象

@Component
public class BeanContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("bean context 初始化");
    }


    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("bean context 销毁");
    }
}

3、ServletListenerRegistrationBean

通过java config来主动将一个普通的Listener对象,塞入ServletListenerRegistrationBean对象,创建为spring的bean对象

public class ConfigContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("config context 初始化");
    }


    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("java context 销毁");
    }
}

上面只是一个普通的类定义,下面的bean创建才是关键点:

@Bean
public ServletListenerRegistrationBean configContextListener() {
    ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
    bean.setListener(new ConfigContextListener());
    return bean;
}

ServletListenerRegistrationBean:
RegistrationBean的子类 基于Servlet 3.0+的注册bean的基类。

/**
* Base class for Servlet 3.0+ based registration beans.  
*
* @author Phillip Webb
* @since 1.4.0
* @see ServletRegistrationBean
* @see FilterRegistrationBean
* @see DelegatingFilterProxyRegistrationBean
* @see ServletListenerRegistrationBean
*/
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {


   private static final Log logger = LogFactory.getLog(RegistrationBean.class);


   private int order = Ordered.LOWEST_PRECEDENCE;


   private boolean enabled = true;


   @Override
   public final void onStartup(ServletContext servletContext) throws ServletException {
      String description = getDescription();
      if (!isEnabled()) {
         logger.info(StringUtils.capitalize(description)
               + " was not registered (disabled)");
         return;
      }
      register(description, servletContext);
   }


   /**
    * Return a description of the registration. For example "Servlet resourceServlet"
    * @return a description of the registration
    */
   protected abstract String getDescription();


   /**
    * Register this bean with the servlet context.
    * @param description a description of the item being registered
    * @param servletContext the servlet context
    */
   protected abstract void register(String description, ServletContext servletContext);


   /**
    * Flag to indicate that the registration is enabled.
    * @param enabled the enabled to set
    */
   public void setEnabled(boolean enabled) {
      this.enabled = enabled;
   }


   /**
    * Return if the registration is enabled.
    * @return if enabled (default {@code true})
    */
   public boolean isEnabled() {
      return this.enabled;
   }


   /**
    * Set the order of the registration bean.
    * @param order the order
    */
   public void setOrder(int order) {
      this.order = order;
   }


   /**
    * Get the order of the registration bean.
    * @return the order
    */
   @Override
   public int getOrder() {
      return this.order;
   }


}

RegistrationBean类方法说明:

修饰符和类型方法和说明描述
voidaddInitParameter(String name, String value)添加一个init参数,将所有现有参数替换为相同的名称.
protected voidconfigure(Registration.Dynamic registration)配置注册基础设置
Map<String,String>getInitParameters()返回注册初始化参数的可变映射.
protected StringgetOrDeduceName(Object value)推导此注册的名称.
intgetOrder()
booleanisAsyncSupported()返回异步操作是否支持此注册.
booleanisEnabled()启用的标志(默认为true)
voidsetAsyncSupported(boolean asyncSupported)设置异步操作是否支持此注册.
voidsetEnabled(boolean enabled)指示已启用注册的标志.
voidsetInitParameters(Map<String,String> initParameters)设置此注册的初始化参数.
voidsetName(String name)设置此注册的名称.
voidsetOrder(int order)设置执行顺序,值越小,越先执行

SpringBoot 提供的RegistrationBean常用的子类:

  • FilterRegistrationBean 用于注册Filter
  • ServletRegistrationBean 用于注册Servlet
  • ServletListenerRegistrationBean 用于注册Listener

4. ServletContextInitializer

这里主要是借助在ServletContext上下文创建的实际,主动的向其中添加Filter,Servlet, Listener,从而实现一种主动注册的效果

public class SelfContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContextInitializer context 初始化");
    }


    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContextInitializer context 销毁");
    }
}
@Component
public class ExtendServletConfigInitializer implements ServletContextInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        servletContext.addListener(SelfContextListener.class);
    }
}

注意ExtendServletConfigInitializer的主动注册时机,在启动时添加了这个Listenrer,所以它的优先级会是最高

5、多个Listener 指定顺序。

同Filter类似
第一种:通过类名实现排序方式
比如:Listener1MyListener,Listener2MyListener,Listener3MyListener
第二种:ServletListenerRegistrationBean通过设置order进行排序,数值越小越先执行。
比如:


@Bean
public ServletListenerRegistrationBean configContextListener() {
    ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
    bean.setListener(new ConfigContextListener());
    bean.setOrder(1);
    return bean;
}

其他比如@Order注解,Ordered都无法实现排序

6、Listener无法注入Bean解决方法

使用FIlter 或者Listener 无法使用@Autowired 或者@Resource注解。要想使用spring容器中的bean,则需要实现ApplicationContextAware接口的context注入函数, 将其存入静态变量. 通过ApplicationContext进行获取。或者通过WebApplicationContextUtils.getWebApplicationContext 方式获取。

5、总结

项目一般采用前两种即可解决问题,前两种采用注解方式,后面两种相对麻烦。我这里采用了第二种解决方案,第一种是因为项目本身@ServletComponentScan是已经注释掉了,故在这不再去改变它。
参考博文:https://www.cnblogs.com/yihuihui/p/12034522.html

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值