我在一个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接口。真相大白。