zoukankan      html  css  js  c++  java
  • springboot情操陶冶-@Conditional和@AutoConfigureAfter注解解析

    承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上阐述@AutoConfigureAfter@Conditional注解的作用与解析

    1.@Conditional

    根据单词来理解,其就是条件的意思。在分析之前我们可以看下其内部源码

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Conditional {
    
    	/**
    	 * All {@link Condition}s that must {@linkplain Condition#matches match}
    	 * in order for the component to be registered.
    	 */
    	Class<? extends Condition>[] value();
    
    }
    

    其作用于类、方法上,且指定的value值必须是org.springframework.context.annotation.Condition的实现类,供条件判断。
    以此为基础而扩展的注解还有@ConditionalBean@ConditionalOnWebApplication@ConditionalOnClass@ConditionalOnMissingBean等等。

    @Conditional注解被解析入口

    那么我们肯定想知道,其中的注解是如何被解析的呢。其实在前文中的ConfigurationClassParser类中,在执行真正的doProcessConfigurationClass()方法前,会执行如下的代码

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    		// 条件判断,满足则直接返回,不进行后续的解析
    		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    			return;
    		}
    
    		....
    
    		// Recursively process the configuration class and its superclass hierarchy.
    		SourceClass sourceClass = asSourceClass(configClass);
    		do {
    			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    		}
    		while (sourceClass != null);
    
    		this.configurationClasses.put(configClass, configClass);
    	}
    

    也就是会执行上述的ConditionEvaluator#shouldSkip()方法,只有条件不满足后才会继续往下执行真正的@Configuration注解解析。

    ConditionEvaluator#shouldSkip()

    废话不多说,直接上源码

    	// metadata为被注解的类元素,返回值为true表明条件满足应该被忽略
    	public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    		// 1.判断类是否含有@Conditional注解,否则直接返回
    		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
    			return false;
    		}
    
    		if (phase == null) {
    			if (metadata instanceof AnnotationMetadata &&
    					ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
    				return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
    			}
    			return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
    		}
    
    		// 2.获取类上所有含有@Conditional注解的value集合(其会递归找寻注解的注解)
    		List<Condition> conditions = new ArrayList<>();
    		for (String[] conditionClasses : getConditionClasses(metadata)) {
    			for (String conditionClass : conditionClasses) {
    				Condition condition = getCondition(conditionClass, this.context.getClassLoader());
    				conditions.add(condition);
    			}
    		}
    		
    		// 3.根据Order来进行排序
    		AnnotationAwareOrderComparator.sort(conditions);
    		
    		// 4.对集合内的condition统一调用matches()方法,一旦遇到条件判断不满足的则返回true对此注解类元素进行忽略
    		for (Condition condition : conditions) {
    			ConfigurationPhase requiredPhase = null;
    			if (condition instanceof ConfigurationCondition) {
    				requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
    			}
    			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
    				return true;
    			}
    		}
    
    		return false;
    	}
    

    具体的代码解释已经按照注释给出了,其实也很简单,读者稍微阅读就能明白了。另外额外的注解比如@ConditionalOnMissingBean等读者可自行去阅读代码分析,笔者此处就不展开了

    2.@AutoConfigureAfter

    @AutoConfigureBefore类同,代表的含义就是自动注入在什么类加载前或者之后。先来看下其内部源码

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.TYPE })
    @Documented
    public @interface AutoConfigureAfter {
    
    	/**
    	 * The auto-configure classes that should have already been applied.
    	 * @return the classes
    	 */
    	Class<?>[] value() default {};
    
    	/**
    	 * The names of the auto-configure classes that should have already been applied.
    	 * @return the class names
    	 * @since 1.2.2
    	 */
    	String[] name() default {};
    
    }
    

    只作用于类上,内部属性name表明beanDefinition的类名;内部属性value表明beanDefinition的类。
    那么其是如何被解析的呢,也是基于前文的ConfigurationClassParser#parse()方法,具体如下

    	public void parse(Set<BeanDefinitionHolder> configCandidates) {
    		this.deferredImportSelectors = new LinkedList<>();
    		// 解析@Configuration注解
    		....
    		// 解析DeferredImportSelector接口类,表面上也就是延迟解析的意思
    		processDeferredImportSelectors();
    	}
    

    笔者此处只关注processDeferredImportSelectors()方法,通过此方法便可察觉到@AutoConfigureAfter等注解的蛛丝马迹

    ConfigurationClassParser#processDeferredImportSelectors()

    直接阅读源码

    	private void processDeferredImportSelectors() {
    		// 1.通过processImport()方法得到DeferredImportSelector接口集合,无则直接返回
    		List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    		this.deferredImportSelectors = null;
    		if (deferredImports == null) {
    			return;
    		}
    		
    		// 2.排序
    		deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
    		// 3.遍历DeferredImportSelector接口集合,获取Group集合类,默认为DefaultDeferredImportSelectorGroup
    		Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
    		Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
    		for (DeferredImportSelectorHolder deferredImport : deferredImports) {
    			// notice this........
    			Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
    			DeferredImportSelectorGrouping grouping = groupings.computeIfAbsent(
    					(group != null ? group : deferredImport),
    					key -> new DeferredImportSelectorGrouping(createGroup(group)));
    			grouping.add(deferredImport);
    			configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
    					deferredImport.getConfigurationClass());
    		}
    		// 4. 遍历Group集合,作用也是调用processImport()方法用于解析@Import
    		for (DeferredImportSelectorGrouping grouping : groupings.values()) {
    			grouping.getImports().forEach(entry -> {
    				ConfigurationClass configurationClass = configurationClasses.get(entry.getMetadata());
    				try {
    					processImports(configurationClass, asSourceClass(configurationClass),
    							asSourceClasses(entry.getImportClassName()), false);
    				}
    				catch (BeanDefinitionStoreException ex) {
    					throw ex;
    				}
    				catch (Throwable ex) {
    					throw new BeanDefinitionStoreException(
    							"Failed to process import candidates for configuration class [" +
    							configurationClass.getMetadata().getClassName() + "]", ex);
    				}
    			});
    		}
    	}
    

    笔者和读者此处只需要关注deferredImport.getImportSelector().getImportGroup()这个方法即可,此处以AutoConfigurationImportSelector.class为例

    AutoConfigurationImportSelector

    首先看下其getImportGroup()方法

    public Class<? extends Group> getImportGroup() {
    		return AutoConfigurationGroup.class;
    	}
    

    再观察下AutoConfigurationGroup此类的selectImports()方法

    		public Iterable<Entry> selectImports() {
    			return sortAutoConfigurations().stream()
    					.map((importClassName) -> new Entry(this.entries.get(importClassName),
    							importClassName))
    					.collect(Collectors.toList());
    		}
    

    关键点来了,就在sortAutoConfigurations()方法,其会通过AutoConfigurationSorter类来对导入的class类进行排序,至于如何排序我们继续往下看

    AutoConfigurationSorter

    排序方法getInPriorityOrder(),我们看下源码

    	public List<String> getInPriorityOrder(Collection<String> classNames) {
    		AutoConfigurationClasses classes = new AutoConfigurationClasses(
    				this.metadataReaderFactory, this.autoConfigurationMetadata, classNames);
    		List<String> orderedClassNames = new ArrayList<>(classNames);
    		// Initially sort alphabetically.首先根据ASCII来进行排序
    		Collections.sort(orderedClassNames);
    		// Then sort by order,再根据Order来进行排序
    		orderedClassNames.sort((o1, o2) -> {
    			int i1 = classes.get(o1).getOrder();
    			int i2 = classes.get(o2).getOrder();
    			return Integer.compare(i1, i2);
    		});
    		// Then respect @AutoConfigureBefore @AutoConfigureAfter
    		orderedClassNames = sortByAnnotation(classes, orderedClassNames);
    		return orderedClassNames;
    	}
    

    可以得出,最关键的排序来自sortByAnnotation()方法,具体就不看了,无非是根据before/after,来对importClassName进行排序得出一个有序的集合。

    1.最后再回到ConfigurationClassParser#processDeferredImportSelectors()方法的最后一段,其会对上述的有序的集合遍历操作processImports()方法,如果对应的class类不存在则会报错,也就满足了AutoConfigureBefore/AutoConfigureAfter的含义。

    2.上述的@AutoConfigureAfter注解解析只作用于META-INFspring.factories文件中EnableAutoConfiguration属性对应的class类集合。
    (v2.0版本以下支持用户使用该注解直接应用自定义类;v2.0版本以上,如果用户也使用了该注解,也需要在META-INFspring.factories配置相应的EnableAutoConfiguration属性)

    小结

    针对@Conditional@AutoConfigureAfter的具体解析可见上文,本文也是对前文的补充。希望读者在阅读此文的同时务必阅读前文方可理解上述的代码含义。同时因为这两个注解具有条件性,所以springboot多用此两注解来相互搭配构建不同条件的依赖部署,对去配置化起到了很大的作用。以WebMvcAutoConfiguration类作为结尾

    @Configuration
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
    		ValidationAutoConfiguration.class })
    public class WebMvcAutoConfiguration {
    }
    
  • 相关阅读:
    用开源项目CropImage实现图片的裁剪(不推荐)
    设定当前视图中所有控件字体的方法
    用开源项目cropper实现对图片中任意部分进行裁剪
    从源码角度一步一步来修改PreferenceActivity界面
    自定义PreferenceActivity和PreferenceFragment的样式
    Eclipse 在线汉化的和修改字体大小、颜色的方法
    用level-list让同一个ImageView根据条件来显示不同的内容
    ClipDrawable属性介绍
    自己用图片做的可旋转、不确定进度的ProgressBar
    Android工具类 DateUtil,可以用它方便的进行日期的操作
  • 原文地址:https://www.cnblogs.com/question-sky/p/9427245.html
Copyright © 2011-2022 走看看