我在一個Spring的項目中使用shiro搭建權限控制框架。主要經過shiro-spring-boot-web-starter包快速集成Shiro。可是項目沒法啓動,報沒有authorizer的bean的錯誤: ``` No bean named 'authorizer' available ``` 我只好又在本身的Configuration中又配置了Authorizer,才能正常啓動。 @Configuration public class ShiroConfig { @Bean public Authorizer authorizer(){ return new ModularRealmAuthorizer(); } }
可是奇怪的明明athorizer是SecurityManager中一個重要的組件,爲何沒有在shiro starter的Configuration中被聲明爲Bean?一樣的,Authenticator就沒問題?web
咱們在pom文件中聲明瞭shiro-spring-boot-web-starter。就從對應的jar包開始找起。
首先是META-INF中的spring.factories文件。咱們知道spring-boot-starter都是經過在該文件中聲明Configuraion來達到集成自身配置的目的。spring
org.springframework.boot.autoconfigure.EnableAutoConfiguration = \ org.apache.shiro.spring.config.web.autoconfigure.ShiroWebAutoConfiguration,\ org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration
上述聲明瞭兩個Configration:ShiroWebAutoConfiguration和ShiroWebFilterConfiguration。apache
@Configuration @AutoConfigureBefore(ShiroAutoConfiguration.class) @ConditionalOnProperty(name = "shiro.web.enabled", matchIfMissing = true) public class ShiroWebAutoConfiguration extends AbstractShiroWebConfiguration { @Bean @ConditionalOnMissingBean @Override protected AuthenticationStrategy authenticationStrategy() { return super.authenticationStrategy(); } @Bean @ConditionalOnMissingBean @Override protected Authenticator authenticator() { return super.authenticator(); } @Bean @ConditionalOnMissingBean @Override protected Authorizer authorizer() { return super.authorizer(); } @Bean @ConditionalOnMissingBean @Override protected SubjectDAO subjectDAO() { return super.subjectDAO(); } @Bean @ConditionalOnMissingBean @Override protected SessionStorageEvaluator sessionStorageEvaluator() { return super.sessionStorageEvaluator(); } @Bean @ConditionalOnMissingBean @Override protected SubjectFactory subjectFactory() { return super.subjectFactory(); } @Bean @ConditionalOnMissingBean @Override protected SessionFactory sessionFactory() { return super.sessionFactory(); } @Bean @ConditionalOnMissingBean @Override protected SessionDAO sessionDAO() { return super.sessionDAO(); } @Bean @ConditionalOnMissingBean @Override protected SessionManager sessionManager() { return super.sessionManager(); } @Bean @ConditionalOnMissingBean @Override protected SessionsSecurityManager securityManager(List<Realm> realms) { return createSecurityManager(); } @Bean @ConditionalOnMissingBean(name = "sessionCookieTemplate") @Override protected Cookie sessionCookieTemplate() { return super.sessionCookieTemplate(); } @Bean @ConditionalOnMissingBean @Override protected RememberMeManager rememberMeManager() { return super.rememberMeManager(); } @Bean @ConditionalOnMissingBean(name = "rememberMeCookieTemplate") @Override protected Cookie rememberMeCookieTemplate() { return super.rememberMeCookieTemplate(); } @Bean @ConditionalOnMissingBean @Override protected ShiroFilterChainDefinition shiroFilterChainDefinition() { return super.shiroFilterChainDefinition(); } }
這個配置類將Shiro須要的各組件都聲明成了bean,交給容器管理。具體的建立過程都在父類AbstractShiroWebConfiguration。能夠看到確實是有聲明authorizer。可是爲何會找不到呢?是否是其餘的配置文件聲明瞭相似的bean,產生了影響?session
觀察shiro-spring-boot-web-starter的配置文件,能夠看到它又引用了shiro-spring-boot-starter包。shrio-spring-boot-starter又是一個Spring boot starter包,一樣經過它的META-INF文件,能夠知道加入了哪些Configuration:app
org.springframework.boot.autoconfigure.EnableAutoConfiguration = \ org.apache.shiro.spring.boot.autoconfigure.ShiroBeanAutoConfiguration,\ org.apache.shiro.spring.boot.autoconfigure.ShiroAutoConfiguration,\ org.apache.shiro.spring.boot.autoconfigure.ShiroAnnotationProcessorAutoConfiguration org.springframework.boot.diagnostics.FailureAnalyzer = \ org.apache.shiro.spring.boot.autoconfigure.ShiroNoRealmConfiguredFailureAnalyzer
最後一個文件是判斷項目中不存在Realm時,拋出異常。前面是咱們須要關注的配置文件。框架
@Configuration @SuppressWarnings("SpringFacetCodeInspection") @ConditionalOnProperty(name = "shiro.enabled", matchIfMissing = true) public class ShiroAutoConfiguration extends AbstractShiroConfiguration { @Bean @ConditionalOnMissingBean @Override protected AuthenticationStrategy authenticationStrategy() { return super.authenticationStrategy(); } @Bean @ConditionalOnMissingBean @Override protected Authenticator authenticator() { return super.authenticator(); } @Bean @ConditionalOnMissingBean @Override protected Authorizer authorizer() { return super.authorizer(); } @Bean @ConditionalOnMissingBean @Override protected SubjectDAO subjectDAO() { return super.subjectDAO(); } @Bean @ConditionalOnMissingBean @Override protected SessionStorageEvaluator sessionStorageEvaluator() { return super.sessionStorageEvaluator(); } @Bean @ConditionalOnMissingBean @Override protected SubjectFactory subjectFactory() { return super.subjectFactory(); } @Bean @ConditionalOnMissingBean @Override protected SessionFactory sessionFactory() { return super.sessionFactory(); } @Bean @ConditionalOnMissingBean @Override protected SessionDAO sessionDAO() { return super.sessionDAO(); } @Bean @ConditionalOnMissingBean @Override protected SessionManager sessionManager() { return super.sessionManager(); } @Bean @ConditionalOnMissingBean @Override protected SessionsSecurityManager securityManager(List<Realm> realms) { return super.securityManager(realms); } @Bean @ConditionalOnResource(resources = "classpath:shiro.ini") protected Realm iniClasspathRealm() { return iniRealmFromLocation("classpath:shiro.ini"); } @Bean @ConditionalOnResource(resources = "classpath:META-INF/shiro.ini") protected Realm iniMetaInfClasspathRealm() { return iniRealmFromLocation("classpath:META-INF/shiro.ini"); } @Bean @ConditionalOnMissingBean(Realm.class) protected Realm missingRealm() { throw new NoRealmBeanConfiguredException(); } }
大體內容其實和ShiroWebAutoConfiguration很相似,只是ShiroWebAutoConfiguration將一些組件替換成了WEB環境相關的組件。可是ShiroWebAutoConfiguration聲明瞭它的配置要在ShiroAutoConfiguration以前,並且根據ConditionalOnMissingBean的條件,得出Bean的配置應該是以ShiroWebAutoConfiguration中聲明的爲準。可是死馬當活馬醫,配置文件中添加shiro.enabled爲false的條件,再試試。。。果真仍是不行。ide
毫無辦法的辦法就是DEBUG大法。
首先從Configuration中生命的Bean是如何被容器加載的過程入手,找到了ConfigurationClassPostProcessor。一樣是一個PostProcessor,猜測應該是在configuration bean的後置處理中進行了@Bean方法的解析。
主要的處理過程在processConfigBeanDefinition這個方法中,對這個方法作個簡單的說明spring-boot
/** * Build and validate a configuration model based on the registry of * {@link Configuration} classes. */ public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); //獲取registry中的bean definition String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); //bean definition 有configuration的屬性,說明已經被解析處理過 if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } //判斷是不是configuration的bean,是則加入候選 else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // 若是沒有發現候選者,則返回 if (configCandidates.isEmpty()) { return; } // 排序 configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); // Detect any custom bean name generation strategy supplied through the enclosing application context SingletonBeanRegistry sbr = null; if (registry instanceof SingletonBeanRegistry) { sbr = (SingletonBeanRegistry) registry; if (!this.localBeanNameGeneratorSet) { BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR); if (generator != null) { this.componentScanBeanNameGenerator = generator; this.importBeanNameGenerator = generator; } } } if (this.environment == null) { this.environment = new StandardEnvironment(); } // 開始解析configuration 的bean definition ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); // 若是候選者不爲空,則繼續解析 do { // 解析過程 parser.parse(candidates); // 校驗 parser.validate(); // 獲取新解析的config class Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); // 移除掉已經解析過的部分 configClasses.removeAll(alreadyParsed); // 建立reader,添加bean definition if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); candidates.clear(); //若是bean definition數量 大於 候選者的數量,說明有新的bean加入 if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet<>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (String candidateName : newCandidateNames) { //不在舊的candidate中,說明是新加入的 if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); //未被解析的config class,添加到candidates中,等下一輪解析 if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } //更新候選者 candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); } if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { // Clear cache in externally provided MetadataReaderFactory; this is a no-op // for a shared cache since it'll be cleared by the ApplicationContext. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); } }
1)parser.parse後設置斷點,看ConfigurationClassParser是否能將ShiroWebAutoConfiguration中的@Bean正常的解析:ui
能夠看到authorizer確實已經被ShiroWebAutoConfiguration加載。this
2)解析沒問題,那就看加載是否成功:
繼續往下走,看reader.loadBeanDefinitions發生了什麼:
找出ShiroWebAutoConfiguration對應的ConfigurationClass,看到SkippedBeanMethods中有authorizer!!!也就是說雖然解析出了authorizer,可是在加載的時候卻被選擇跳過了。。。
3)問題就變得比較清晰了,找出爲何被跳過的緣由。
順着代碼找到ConfigurationClassBeanDefinitionReader的loadBeanDefinitionsForConfigurationClass的方法,負責處理的BeanMethond的過程是在loadBeanDefitionsForBeanMethod中。
確實在方法開始前,有一個判斷是否須要跳過的條件:
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) { configClass.skippedBeanMethods.add(methodName); return; }
shouldSkip這個方法是根據@Bean上的@Conditional註解,來判斷是否須要加載該Bean。回憶上文咱們的ShiroWebAutoConfiguration中,確實在authorizer的方法上有@ConditionalOnMissingBean的註解。也就是說應該是哪裏聲明authorizer的Bean,致使配置中的Bean沒有被加載。
4)OnBeanCondition.getMatchOutcome():處理@Bean的@Condtional條件,並輸出結果。
最後發現被跳過的緣由居然是:
found beans of type 'org.apache.shiro.authz.Authorizer' authorizer, thirdPartyRealm, userRealm
我自定義的Realm居然和authorizer衝突了。Spring認爲已經有authorizer的bean,而再也不加載配置中的authorizer。
5)爲何Realm和authorizer衝突?原來在獲取相匹配的Bean時候仍是經過容器自己(BeanFactory)的getNamesForType方法:
Set<String> getNamesForType(Class<?> type) { updateTypesIfNecessary(); //便利容器中全部的bean類型,將類型匹配的Type所有返回。注意這裏還用了isAssiginableFrom,所以這裏的查詢類型的子類也會知足 return this.beanTypes.entrySet().stream() .filter((entry) -> entry.getValue() != null && type.isAssignableFrom(entry.getValue())) .map(Map.Entry::getKey) .collect(Collectors.toCollection(LinkedHashSet::new)); }
反觀咱們的Realm對象:AuthorizingRealm實現了Authorizer接口。真相大白。