spring boot之org.springframework.boot.context.TypeExcludeFilter
曾经碰到过这样一种情况,想让某个使用了spring 注解的类不被spring扫描注入到spring bean池中,比如下面的类使用了@Component和@ConfigurationProperties("example1.user")自动绑定属性,不想让这个类被注入。
1 package com.github.torlight.sbex; 2 3 import java.io.Serializable; 4 5 import org.springframework.boot.context.properties.ConfigurationProperties; 6 import org.springframework.stereotype.Component; 7 8 @Component 9 @ConfigurationProperties("example1.user") 10 public class User implements Serializable{ 11 12 private static final long serialVersionUID = 6913838730034509179L; 13 14 private String name; 15 16 private Integer age; 17 18 public String getName() { 19 return name; 20 } 21 22 public void setName(String name) { 23 this.name = name; 24 } 25 26 public Integer getAge() { 27 return age; 28 } 29 30 public void setAge(Integer age) { 31 this.age = age; 32 } 33 34 35 }
一开始不想使用BeanPostProcessor接口的实现类来实现这样的功能,想可以借助@SpringBootApplication注解,使用exclude来实现该功能。
1 package com.github.torlight.sbex; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.context.ApplicationContext; 6 import org.springframework.context.annotation.Bean; 7 8 9 /** 10 * Hello world! 11 * 12 */ 13 @SpringBootApplication(exclude={com.github.torlight.sbex.User.class}) 14 public class Example1 { 15 16 17 @Bean 18 public UserAction userAction(){ 19 return new UserAction(); 20 } 21 22 23 public static void main( String[] args ) { 24 25 ApplicationContext context= SpringApplication.run(Example1.class, args); 26 27 ((UserAction)context.getBean(UserAction.class)).sysOutUserInfo(); 28 29 } 30 }
运行程序后,控制台报如下错误:
2018-08-04 13:15:57.079 ERROR 4504 --- [ main] o.s.boot.SpringApplication : Application startup failed org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [com.github.torlight.sbex.Example1]; nested exception is java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes: - com.github.torlight.sbex.User at org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors(ConfigurationClassParser.java:556) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE] at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:185) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE] at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:308) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE] at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:228) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE] at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:270) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE] at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:93) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE] at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:687) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:525) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE] at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) ~[spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693) [spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360) [spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) [spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) [spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) [spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE] at com.github.torlight.sbex.Example1.main(Example1.java:25) [classes/:na] Caused by: java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes: - com.github.torlight.sbex.User at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.handleInvalidExcludes(AutoConfigurationImportSelector.java:193) ~[spring-boot-autoconfigure-1.5.4.RELEASE.jar:1.5.4.RELEASE] at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.checkExcludedClasses(AutoConfigurationImportSelector.java:178) ~[spring-boot-autoconfigure-1.5.4.RELEASE.jar:1.5.4.RELEASE] at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.selectImports(AutoConfigurationImportSelector.java:100) ~[spring-boot-autoconfigure-1.5.4.RELEASE.jar:1.5.4.RELEASE] at org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors(ConfigurationClassParser.java:547) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE] ... 14 common frames omitted
错误提示不能使用exclude={com.github.torlight.sbex.User.class},因为该类并不是被spring boot自动装配的类,类似于RedisAutoConfiguration这样的类。仔细研究一番后,发现可以扩展org.springframework.boot.context.TypeExcludeFilter来实现这一功能。spring boot在执行扫描过程中,会使用TypeExcludeFilter进行过滤。
1 public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware { 2 3 private BeanFactory beanFactory; 4 5 @Override 6 public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 7 this.beanFactory = beanFactory; 8 } 9 10 @Override 11 public boolean match(MetadataReader metadataReader, 12 MetadataReaderFactory metadataReaderFactory) throws IOException { 13 if (this.beanFactory instanceof ListableBeanFactory 14 && getClass().equals(TypeExcludeFilter.class)) { 15 Collection<TypeExcludeFilter> delegates = ((ListableBeanFactory) this.beanFactory) 16 .getBeansOfType(TypeExcludeFilter.class).values(); 17 for (TypeExcludeFilter delegate : delegates) { 18 if (delegate.match(metadataReader, metadataReaderFactory)) { 19 return true; 20 } 21 } 22 } 23 return false; 24 } 25 26 @Override 27 public boolean equals(Object obj) { 28 throw new IllegalStateException( 29 "TypeExcludeFilter " + getClass() + " has not implemented equals"); 30 } 31 32 @Override 33 public int hashCode() { 34 throw new IllegalStateException( 35 "TypeExcludeFilter " + getClass() + " has not implemented hashCode"); 36 } 37 38 }
可以看出,spring boot会加载spring bean池中所有针对TypeExcludeFilter的扩展,并循环遍历这些扩展类调用其match方法。那么思路出来了,只要继承该类并重写match方法,在该方法内部进行相应的处理即可。示例代码如下:
1 public class MyTypeExcludeFilter extends TypeExcludeFilter { 2 3 @Override 4 public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) 5 throws IOException { 6 7 if("com.github.torlight.sbex.User".equals(metadataReader.getClassMetadata().getClassName())){ 8 return true; 9 } 10 11 return false; 12 } 13 14 }
Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled. 2018-08-04 13:27:14.556 ERROR 4964 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPLICATION FAILED TO START *************************** Description: Field user in com.github.torlight.sbex.UserAction required a bean of type 'com.github.torlight.sbex.User' that could not be found. Action: Consider defining a bean of type 'com.github.torlight.sbex.User' in your configuration.
相关示例代码已经上传到github上面,https://github.com/gittorlight/springboot-example