相关文章:
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类方法说明:
修饰符和类型 | 方法和说明 | 描述 |
---|---|---|
void | addInitParameter(String name, String value) | 添加一个init参数,将所有现有参数替换为相同的名称. |
protected void | configure(Registration.Dynamic registration) | 配置注册基础设置 |
Map<String,String> | getInitParameters() | 返回注册初始化参数的可变映射. |
protected String | getOrDeduceName(Object value) | 推导此注册的名称. |
int | getOrder() | |
boolean | isAsyncSupported() | 返回异步操作是否支持此注册. |
boolean | isEnabled() | 启用的标志(默认为true) |
void | setAsyncSupported(boolean asyncSupported) | 设置异步操作是否支持此注册. |
void | setEnabled(boolean enabled) | 指示已启用注册的标志. |
void | setInitParameters(Map<String,String> initParameters) | 设置此注册的初始化参数. |
void | setName(String name) | 设置此注册的名称. |
void | setOrder(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