Spring注解编程模型

1.1 元注解

  能声明在其他注解上的注解,典型的例如Spring中的@Compoment,它在@Service,@Repository等注解上都有标注。值得注意的是,元注解并非只限定在Spring的使用场景中,像@Documented,@Inhreited这些Java标准注解,也属于元注解。

1.2 Spring模式注解

  Spring模式注解即@Component派生注解,由于Java语法规定,Annotation不允许继承,因此Spring采用元注解的方式实现注解间的"派生"。

  简单的说,Spring模式注解就被@Component注解标注的注解,例如@Service,@Compository,@Controller

1.2.1 @Component注解的派生性

  @Component作为Spring容器托管的通用模式组件,任何被@Component标注的组件均为组件扫描的候选对象。类似的,凡是被@Component元标注(meta-annotated)的注解,如@Service,当任何组件标注它时,也被视为组件扫描的候选对象。这种性质就是@Component注解的派生性。

1.2.2 @Component派生性原理

  在进行源码分析前,先说明,我并不会对每一行源码都详细的说明,毕竟本本章的重点是说明@Component的派生性原理,而非Spring源码解析,如果对贴出的每一行源码都进行说明,容易迷失重点,所以只会对其中与本章相关的点进行说明

  先从Spring传统的xml配置方式入手,Spring通过context:component-scan/标签配置扫描@Component组件。根据/META-INF/sping.handlers的配置,我们能定位到对应的处理器ContextNameSpaceHandler(具体原理可以参考spring自定义xml配置,这里不多做描述)。

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}
}

  依照源码,Spring框架在解析context:component-scan/时,会生成ComponentScanBeanDefinitionParser类,并调用它的parse()方法。

    @Override
	@Nullable
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
		basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
		String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

		// Actually scan for bean definitions and register them.
		ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
		Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
		registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

		return null;
	}

  这里我们重点看下面这两行代码,这两行代码的的逻辑是生成一个ClassPathBeanDefinitionScanner对象 并扫描basePackges下的类,为满足条件的类生成对应的BeanDefinitionHolder对象,以便接下来注册到Spring容器中。

        ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
		Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);

  首先我们深入到ClassPathBeanDefinitionScanner#doScan的方法中

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			
			...
			
		return beanDefinitions;
	}

  从代码里看到,doScan方法遍历了basePackages,并调用findCandidateComponents方法获取了候选组件。从该方法一直深入下去,我们能看到,

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<>();
		try {
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + this.resourcePattern;
			Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
		    ...
			for (Resource resource : resources) {
		    	...
				if (resource.isReadable()) {
					try {
						MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
						if (isCandidateComponent(metadataReader)) {
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							sbd.setResource(resource);
							sbd.setSource(resource);
							if (isCandidateComponent(sbd)) {
							    ...
								candidates.add(sbd);
							}
						    ...
						}
						...
					}
			    	...
				}
			    ...
			}
		}
		...
		return candidates;
	}

  scanCandidateComponents方法首先处理backagePgae的占位符,将${…}的内容替换为实际的配置值,并将Java pagekage路径中的“.”替换为“/”。假设basePackage是“thinking.in.spring.boot”,那么处理后的packageSearchPath便是“classpath*:thingking/in/spring/boot/**/*.class”,随后以此为参数,获得类资源集合。之后,便会迭代resources,并根据resource获取MetadataReader对象,MetadataReader中包含了类和注解的源信息读取方法,是后续isCandidateComponent方法的判断依据。

  最后,类资源是否满足候选条件就依据isCandidateComponent()方法判断。

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return false;
			}
		}
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}

  候选条件的判断具体由includeFilters和excludeFilters字段决定。那么,includeFilters是在何时初始化的呢?让我们回头到最初的ComponentScanBeanDefinitionParser#parse方法中的。

        ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);

深入该行代码,我们能看到,这里会调用ClassPathBeanDefinitionScanner的构造方法

 public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
			Environment environment, @Nullable ResourceLoader resourceLoader) {

		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		this.registry = registry;

		if (useDefaultFilters) {
			registerDefaultFilters();
		}
		setEnvironment(environment);
		setResourceLoader(resourceLoader);
	}

  并最终会调用registerDefaultFilters()方法,在该方法中,会给includeFilters字段增添一个包含@Component类型信息的AnnotationTypeFilter实例。

	@SuppressWarnings("unchecked")
	protected void registerDefaultFilters() {
		this.includeFilters.add(new AnnotationTypeFilter(Component.class));
		ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
			logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
		}
		catch (ClassNotFoundException ex) {
			// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
		}
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
			logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
		}
		catch (ClassNotFoundException ex) {
			// JSR-330 API not available - simply skip.
		}
	}

  因此,ClassPathBeanDefinitionScanner类会扫描标注所有@Compontent以及@Compontent派生注解标注的类为Spring候选组件。

  在SpringBoot或者高版本的Spring中,支持通过注解@ComponentScan的方式标注组件扫描。原理和xml配置是一样的。在ApplicationContext的AbstractApplicationContext#refresh()方法中,会调用postProcessBeanFactory()方法,

	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
		    ...
			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}
	    	...
		}
	}

  在AbstractApplicationContext的子类中,该方法会重载,已AnnotationConfigServletWebApplicationContext为例

	public AnnotationConfigServletWebApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

	@Override
	protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		super.postProcessBeanFactory(beanFactory);
		if (!ObjectUtils.isEmpty(this.basePackages)) {
			this.scanner.scan(this.basePackages);
		}
		if (!this.annotatedClasses.isEmpty()) {
			this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
		}
	}

  可以看到,最终还是调用了ClassPathBeanDefinitionScanner#scan方法

1.2.3 @Component多层次派生性质

  Spring4.0.0之后的版本,@Component派生性开始具备多层次,多层次的表现在于,@A标注了@B,@Component标注了@A,那么@B同样拥有@Component的性质。在4.0.0之前的版本,@Component只能支持单层次或者两层的派生性,这其中的差别主要是由于metadataReader的内部的实现差异造成的。

  前文有所提到,MetadataReader中包含了类和注解的源信息读取方法,在老版本中,MetadataReader内部实现只查找一层或两层元注解信息。而从4.0.0版本开始,则采用了递归的方式获取所有的上层元注解。这时,Spring才开始真正支持@Component多层次派生性。具体在这里不在赘述,大家感兴趣可以自己翻看源码。

1.3 组合注解

  所谓的组合注解,指的是某个注解被"元标注"了一个或多个其他注解,其目的是将这些注解行为合并为单个自定义注解。简单举例来说,@TransactionalService标注了@Transctional和@Service注解,那么,@TransactionalService就同时具备了这两个注解语义。

1.4 Spring注解属性别名和覆盖

1.4.1.隐式覆盖

  较低层次的注解属性会覆盖同名的高层次注解属性,如何理解较低层次呢?已@Service为例,@Compontant元标注了@Service,@Service就是较低的层次,@Compontent就是高层次,所以@Service的注解属性会覆盖同名的@Compoent注解属性。

1.4.2.显示覆盖

  有隐式覆盖就有显式覆盖,显示覆盖就是@AliasFor提供的属性覆盖能力。

  简单介绍下@AliasFor标签有几种使用方式。

  • 在同一个注解内显示使用
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
 
    @AliasFor("path")
    String[] value() default {};
 
    @AliasFor("value")
    String[] path() default {};
 
    //...
}

  示例代码中,value和path就是互为别名,注意互为别名的属性属性值类型,默认值,都是相同的,互为别名的注解必须成对出现,比如value属性添加了@AliasFor(“path”),那么path属性就必须添加@AliasFor(“value”),互为别名的属性必须定义默认值。

  • 显示的覆盖元注解中的属性
...
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
)
...
public @interface SpringBootApplication {
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
    ...
}

  已SpringBootApplication为例,@ComponentScan是@SpringBootApplication的源注解,
@SpringBootApplication分别指定了scanBasePackages和scanBasePackageClasses为注解源ComponentScan的basePackages和basePackageClasses的别名

  • 在一个注解中隐式声明别名
 @ContextConfiguration
 public @interface MyTestConfig {
 
    @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
    String[] value() default {};
 
    @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
    String[] groovyScripts() default {};
 
    @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
    String[] xmlFiles() default {};
 }

  可以看到,在MyTestConfig注解中,为value,groovyScripts,xmlFiles都定义了别名@AliasFor(annotation = ContextConfiguration.class, attribute = “locations”),所以,其实在这个注解中,value、groovyScripts和xmlFiles也互为别名,这个就是所谓的在统一注解中的隐式别名方式;

  • 别名的传递

  简单理解,如果A是B的别名,并且B是C的别名,那么A是C的别名;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值