曾經碰到過這樣一種情況,想讓某個使用了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
