前言
笔者写的spring boot项目的filter,使用@WebFilter不能自定义顺序,使用Bean注入又不能定义拦截路径。一定要定义FilterRegistrationBean的Bean才能同时生效,本章从源码的角度分析为什么。
1. 使用ServletComponentScan启动分析
在SpringBoot启动后,会扫描jar的包
可以看到扫描了ServletComponentScan的包名
在做切面时,扫描包就处理一些逻辑。
class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {
private static final List HANDLERS;
static {
List servletComponentHandlers = new ArrayList<>();
servletComponentHandlers.add(new WebServletHandler());
servletComponentHandlers.add(new WebFilterHandler());
servletComponentHandlers.add(new WebListenerHandler());
HANDLERS = Collections.unmodifiableList(servletComponentHandlers);
}
切面就处理WebFilter了,定义了beanfactory,到时候getbean就通过这种方式
class WebFilterHandler extends ServletComponentHandler {
WebFilterHandler() {
super(WebFilter.class);
}
@Override
public void doHandle(Map attributes, AnnotatedBeanDefinition beanDefinition,
BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterRegistrationBean.class);
builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
builder.addPropertyValue("dispatcherTypes", extractDispatcherTypes(attributes));
builder.addPropertyValue("filter", beanDefinition);
builder.addPropertyValue("initParameters", extractInitParameters(attributes));
String name = determineName(attributes, beanDefinition);
builder.addPropertyValue("name", name);
builder.addPropertyValue("servletNames", attributes.get("servletNames"));
builder.addPropertyValue("urlPatterns", extractUrlPatterns(attributes));
registry.registerBeanDefinition(name, builder.getBeanDefinition());
}
这里定义了WebFilter的生产bean的定义,意思是WebFilter注解通过这种方式生成FilterRegistrationBean。这里唯独没有order的属性注入,这就说明了无论通过注解或者接口都不能定义顺序。笔者通过debug模式强制注入order属性就可以立马生效。
这里扫描要注意
读取文件时,默认有排序,这就是为什么网上说可以按文件名来制定顺序,但是这种是极度不推荐的
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set result) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace("Searching directory [" + dir.getAbsolutePath() +
"] for files matching pattern [" + fullPattern + "]");
}
for (File content : listDirectory(dir)) {
String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
if (!content.canRead()) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
"] because the application is not allowed to read the directory");
}
}
else {
doRetrieveMatchingFiles(fullPattern, content, result);
}
}
if (getPathMatcher().match(fullPattern, currPath)) {
result.add(content);
}
}
}
listDirectory(dir)
排序了,安装文件名称升序排,类似Tomcat加载lib目录也是,在排查问题就需要看说明,当然排序是必要的的,不然不能确定加载顺序,问题会随机发生。
protected File[] listDirectory(File dir) {
File[] files = dir.listFiles();
if (files == null) {
if (logger.isInfoEnabled()) {
logger.info("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
}
return new File[0];
}
Arrays.sort(files, Comparator.comparing(File::getName));
return files;
}
那么这些filter扫描的什么时候生效呢,SCI启动时
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
加载SCI
可以看到最后会统一排一次序,当然每次加载都会排序,排序的依据就是order属性。
由于前面定义了WebFilter,定义了Spring的初始化工厂方式是初始化
FilterRegistrationBean
这是一个SCI的实现。
private List> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class type,
Set> excludes) {
String[] names = beanFactory.getBeanNamesForType(type, true, false);
Map map = new LinkedHashMap<>();
for (String name : names) {
if (!excludes.contains(name) && !ScopedProxyUtils.isScopedTarget(name)) {
T bean = beanFactory.getBean(name, type);
if (!excludes.contains(bean)) {
map.put(name, bean);
}
}
}
List> beans = new ArrayList<>(map.entrySet());
beans.sort((o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getValue(), o2.getValue()));
return beans;
}
这里有排序,就是通过order属性,但是我们的order是默认的,
private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,
ListableBeanFactory beanFactory) {
if (initializer instanceof ServletRegistrationBean) {
Servlet source = ((ServletRegistrationBean>) initializer).getServlet();
addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
}
else if (initializer instanceof FilterRegistrationBean) {
Filter source = ((FilterRegistrationBean>) initializer).getFilter();
addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
}
else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();
addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
}
else if (initializer instanceof ServletListenerRegistrationBean) {
EventListener source = ((ServletListenerRegistrationBean>) initializer).getListener();
addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
}
else {
addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory,
initializer);
}
}
继续,可以看到使用了默认order
注入servlet
protected Dynamic addRegistration(String description, ServletContext servletContext) {
Filter filter = getFilter();
return servletContext.addFilter(getOrDeduceName(filter), filter);
}
protected void configure(FilterRegistration.Dynamic registration) {
super.configure(registration);
EnumSet dispatcherTypes = this.dispatcherTypes;
if (dispatcherTypes == null) {
dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
}
Set servletNames = new LinkedHashSet<>();
for (ServletRegistrationBean> servletRegistrationBean : this.servletRegistrationBeans) {
servletNames.add(servletRegistrationBean.getServletName());
}
servletNames.addAll(this.servletNames);
if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS);
}
else {
if (!servletNames.isEmpty()) {
registration.addMappingForServletNames(dispatcherTypes, this.matchAfter,
StringUtils.toStringArray(servletNames));
}
if (!this.urlPatterns.isEmpty()) {
//此时有拦截路径
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
StringUtils.toStringArray(this.urlPatterns));
}
}
}
至此WebFilter就加载成功了。所有没有加载order属性。
每次加载启动,都会向Tomcat的content注入filter,每次HTTP请就可以使用了,顺序就是前面排好的。
2. 使用Bean注入
Spring Boot的嵌入式Tomcat启动,会通过SCI机制加载web.xml配置相关的类,并启动
参考我的博客嵌入式tomcat的不使用web.xml原理分析
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
拿到SCI的bean
getServletContextInitializerBeans()
通过此方法,加载我们注入bean的filter,可以调式代码发现,很多spring mvc自定义的filter也是这样起作用的。
protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
//重点关注
addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
for (Class> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
addAsRegistrationBean(beanFactory, EventListener.class, (Class) listenerType,
new ServletListenerRegistrationBeanAdapter());
}
}
adapter就是创建方法,本质就是
new FilterRegistrationBean<>(source)
/**
* {@link RegistrationBeanAdapter} for {@link Filter} beans.
*/
private static class FilterRegistrationBeanAdapter implements RegistrationBeanAdapter {
@Override
public RegistrationBean createRegistrationBean(String name, Filter source, int totalNumberOfSourceBeans) {
FilterRegistrationBean bean = new FilterRegistrationBean<>(source);
bean.setName(name);
return bean;
}
}
说明,在Spring Boot中FilterRegistrationBean<>(source)才是filter起作用的原因。继续跟踪
private void addAsRegistrationBean(ListableBeanFactory beanFactory, Class type,
Class beanType, RegistrationBeanAdapter adapter) {
List> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
for (Entry entry : entries) {
String beanName = entry.getKey();
B bean = entry.getValue();
if (this.seen.add(bean)) {
// One that we haven't already seen
//这里创建
RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
//拿到order
int order = getOrder(bean);
//塞进去
registration.setOrder(order);
this.initializers.add(type, registration);
if (logger.isTraceEnabled()) {
logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
+ order + ", resource=" + getResourceDescription(beanName, beanFactory));
}
}
}
}
getOrder方法
private int getOrder(Object value) {
return new AnnotationAwareOrderComparator() {
@Override
public int getOrder(Object obj) {
return super.getOrder(obj);
}
}.getOrder(value);
}
AnnotationAwareOrderComparator,可以看到,先从ordered接口的实现拿,然后在order注解拿
@Override
@Nullable
protected Integer findOrder(Object obj) {
Integer order = super.findOrder(obj);
if (order != null) {
return order;
}
return findOrderFromAnnotation(obj);
}
@Nullable
private Integer findOrderFromAnnotation(Object obj) {
AnnotatedElement element = (obj instanceof AnnotatedElement ? (AnnotatedElement) obj : obj.getClass());
MergedAnnotations annotations = MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY);
Integer order = OrderUtils.getOrderFromAnnotations(element, annotations);
if (order == null && obj instanceof DecoratingProxy) {
return findOrderFromAnnotation(((DecoratingProxy) obj).getDecoratedClass());
}
return order;
}
order注解是有缓存的,应该是Spring扫描之初就缓存了
@Nullable
static Integer getOrderFromAnnotations(AnnotatedElement element, MergedAnnotations annotations) {
if (!(element instanceof Class)) {
return findOrder(annotations);
}
Object cached = orderCache.get(element);
if (cached != null) {
return (cached instanceof Integer ? (Integer) cached : null);
}
Integer result = findOrder(annotations);
orderCache.put(element, result != null ? result : NOT_ANNOTATED);
return result;
}
@Nullable
private static Integer findOrder(MergedAnnotations annotations) {
MergedAnnotation orderAnnotation = annotations.get(Order.class);
if (orderAnnotation.isPresent()) {
return orderAnnotation.getInt(MergedAnnotation.VALUE);
}
MergedAnnotation> priorityAnnotation = annotations.get(JAVAX_PRIORITY_ANNOTATION);
if (priorityAnnotation.isPresent()) {
return priorityAnnotation.getInt(MergedAnnotation.VALUE);
}
return null;
}
缓存拿不到,就注解上取。
使用Java8开始的流排序filter的list
List sortedInitializers = this.initializers.values().stream()
.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
.collect(Collectors.toList());
然后使用order排序
public int compare(@Nullable Object o1, @Nullable Object o2) {
return doCompare(o1, o2, null);
}
private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
boolean p1 = (o1 instanceof PriorityOrdered);
boolean p2 = (o2 instanceof PriorityOrdered);
if (p1 && !p2) {
return -1;
}
else if (p2 && !p1) {
return 1;
}
int i1 = getOrder(o1, sourceProvider);
int i2 = getOrder(o2, sourceProvider);
return Integer.compare(i1, i2);
}
然后在start方法添加filter
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
//启动SCI
beans.onStartup(servletContext);
}
}
addfilter,servlet同理;SCI原理
protected Dynamic addRegistration(String description, ServletContext servletContext) {
Filter filter = getFilter();
return servletContext.addFilter(getOrDeduceName(filter), filter);
}
添加url路径
protected void configure(FilterRegistration.Dynamic registration) {
super.configure(registration);
EnumSet dispatcherTypes = this.dispatcherTypes;
if (dispatcherTypes == null) {
dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
}
Set servletNames = new LinkedHashSet<>();
for (ServletRegistrationBean> servletRegistrationBean : this.servletRegistrationBeans) {
servletNames.add(servletRegistrationBean.getServletName());
}
servletNames.addAll(this.servletNames);
//由于是Spring框架加载bean,new的。所有这个urlpatterns是空的
if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS);
}
else {
if (!servletNames.isEmpty()) {
registration.addMappingForServletNames(dispatcherTypes, this.matchAfter,
StringUtils.toStringArray(servletNames));
}
if (!this.urlPatterns.isEmpty()) {
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
StringUtils.toStringArray(this.urlPatterns));
}
}
}
至此filter设置成功,并且生效中,order是有效的,但是urlpatterns是默认的/*,即拦截所有。
3. FilterRegistrationBean作为Bean注入生效的原因
通过上面的源码分析,我们发现FilterRegistrationBean才是filter生效的根源,这就解释了为什么我们自己注入bean FilterRegistrationBean是可以生效的,而且具有所有filter的功能。FilterRegistrationBean本身实现SCI接口
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
所以SCI直接加载生效的。
总结
通过源码,我们知道了SpringBoot与WebFilter那些order不能生效的原因,以及使用bean方式的filter加载原理,只有默认拦截路径而order生效的原理。处理业务就可以知其所以然。