SpringBoot的@Configuration注解

Posted zwgitOne123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot的@Configuration注解相关的知识,希望对你有一定的参考价值。

  本文主要讲述SpringBoot的@Configuration注解。

一.POJO类的声明

  例如有两个pojo类,分别是User和Pet

  User类的声明如下:

public class User 

    private String name;
    private Integer age;

    public User()

    

    public User(String name, Integer age) 
        this.name = name;
        this.age = age;
    

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public Integer getAge() 
        return age;
    

    public void setAge(Integer age) 
        this.age = age;
    

    @Override
    public String toString() 
        return "User" +
                "name=\'" + name + \'\\\'\' +
                ", age=" + age +
                \'\';
    

  Pet类的声明如下:

public class Pet 

    private String name;
    private String clasz;

    public Pet() 
    

    public Pet(String name, String clasz) 
        this.name = name;
        this.clasz = clasz;
    

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public String getClasz() 
        return clasz;
    

    public void setClasz(String clasz) 
        this.clasz = clasz;
    

    @Override
    public String toString() 
        return "Pet" +
                "name=\'" + name + \'\\\'\' +
                ", clasz=\'" + clasz + \'\\\'\' +
                \'\';
    

二.在Spring的xml中配置组件

  1.pojo无依赖关系,bean.xml的声明如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user01" class="com.atguigu.boot.pojo.User">
        <property name="name" value="tom"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="pet01" class="com.atguigu.boot.pojo.Pet">
        <property name="name" value="maomi"></property>
        <property name="clasz" value="cat"></property>
    </bean>
</beans>

  2.pojo有依赖关系,user类依赖pet类

  User类声明如下:

public class User 

    private String name;
    private Integer age;
    private Pet pet;

    public User()

    

    public User(String name, Integer age) 
        this.name = name;
        this.age = age;
    

    public Pet getPet() 
        return pet;
    

    public void setPet(Pet pet) 
        this.pet = pet;
    

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public Integer getAge() 
        return age;
    

    public void setAge(Integer age) 
        this.age = age;
    

    @Override
    public String toString() 
        return "User" +
                "name=\'" + name + \'\\\'\' +
                ", age=" + age +
                ", pet=" + pet +
                \'\';
    

  bean.xml的声明如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user01" class="com.atguigu.boot.pojo.User">
        <property name="name" value="tom"></property>
        <property name="age" value="18"></property>
        <property name="pet" ref="pet01"></property>
    </bean>

    <bean id="pet01" class="com.atguigu.boot.pojo.Pet">
        <property name="name" value="maomi"></property>
        <property name="clasz" value="cat"></property>
    </bean>
</beans>

三.SpringBoot的@Configuration配置组件

  @Configuration注解作用在类上,声明该类是配置组件类;

  @Bean注解作用在配置组件类的方法上,声明该方法是ioc容器的组件。

  POJOConfig组件配置类声明如下:

/**
 * @Configuration 标识配置组件的类
 * @Bean 标识配置对象的方法
 */
@Configuration
public class POJOConfig 
    
    @Bean
    public User user01()
        return new User("张三",19);
    

    @Bean
    public User user02()
        return new User("张三",19);
    

    @Bean
    public Pet pet01()
        return new Pet("tom","cat");
    

  在MainApp启动类中验证注册的组件是否唯一。

@SpringBootApplication
public class MainApp 

    public static void main(String[] args) 
        // 1.获取ioc容器【应用程序的上下文】
        ConfigurableApplicationContext run = SpringApplication.run(MainApp.class, args);

        // 2.获取上下文所有的bean的name
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) 
            System.out.println(name);
        

        // 3.获取指定类型的bean的对象
        // 只注册了一个User类的对象,ioc容器获取的也只有一个对象
        // 注册了同一个类的多个对象
        User user01 = run.getBean("user01", User.class);
        User user02 = run.getBean("user01", User.class);
        System.out.println(user01 == user02);  // true

        // 4.获取配置组件类的对象
        POJOConfig bean = run.getBean(POJOConfig.class);
        System.out.println(bean);

        User user1 = bean.user01();
        User user2 = bean.user01();
        System.out.println(user1 == user2);

    

  证明user01和user02对象,都是从ioc容器中取出的 id=user01的对象。

  @Configuration中有一个参数proxyBeanMethod,默认为 true,

  这个注解主要有以下两个作用:

  1. 声明一个类为 Spring 配置类。@Configuration 注解告诉 Spring 这是一个配置类,需要在应用程序上下文中注册 bean。通常情况下,配置类中包含了多个 @Bean 方法,这些方法都会返回一个对象,供其他 bean 使用。

  2. 控制 Spring 是否会为 @Bean 方法创建代理对象。当 proxyBeanMethods 设置为 true 时,Spring 会为每个 @Bean 方法创建一个代理对象,这个代理对象会缓存方法的调用结果,从而提高应用程序的性能。当 proxyBeanMethods 设置为 false 时,Spring 不会为 @Bean 方法创建代理对象,而是每次调用 @Bean 方法时都会创建一个新的对象。

  需要注意的是,如果使用了 @Configuration(proxyBeanMethods = true),那么 @Bean 方法必须是非 final 的,因为 Spring 使用 CGLIB 生成子类来实现代理。如果 @Bean 方法是 final 的,那么就无法生成子类,从而导致代理失败。因此,如果你的 @Bean 方法必须是 final 的,就需要将 proxyBeanMethods 设置为 false。

  

 

springboot情操陶冶-@Configuration注解解析

承接前文springboot情操陶冶-SpringApplication(二),本文将在前文的基础上分析下@Configuration注解是如何一步一步被解析的

@Configuration

如果要了解与明白@SpringBootApplication的工作机制,必须了解@Configuration的注解应用。因为前者依赖后者,此处看下源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

	/**
	 * Explicitly specify the name of the Spring bean definition associated
	 * with this Configuration class. If left unspecified (the common case),
	 * a bean name will be automatically generated.
	 * <p>The custom name applies only if the Configuration class is picked up via
	 * component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}.
	 * If the Configuration class is registered as a traditional XML bean definition,
	 * the name/id of the bean element will take precedence.
	 * @return the suggested component name, if any (or empty String otherwise)
	 * @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}

根据上面的代码可知,其是Spring框架中最常见的注解@Component的复用类。下面笔者将通过AnnotatedBeanDefinitionReader类对该注解的解析进行详细的解读

1.AnnotatedBeanDefinitionReader

根据前文的描述,我们知道针对main()方法所在的类进行注解解析的是通过BeanDefinitionLoader来操作的,而具体的解析操作其实是
其内部的属性annotatedReader来实现的。可能有点云里雾里,笔者此处就直接进入至对应类看个究竟

构造函数

	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		Assert.notNull(environment, "Environment must not be null");
		this.registry = registry;
		// @Conditional注解表达式解析类
		this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
		// processor接口集合注册
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

由上面可知,在构造函数初始化的过程中,顺便还注册了一发BeanDefinitionRegistryPostProcessor集合,这个接口会在ApplicationContext#refresh()操作中会被统一调用。

AnnotationConfigUtils#registerAnnotationConfigProcessors()

注解配置类的注册,具体配置了哪些processor,笔者此处粗看下

	public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {

		....
		....

		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(4);
		
		// @Configuration注解解析处理类
		if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// @Autowired注解解析处理类
		if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
		}
		
		// @Required注解解析处理类
		if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// @WebServiceDef/@EJB/@Resource/@PostConstruct/@PreDestroy注解解析
		if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// JPA注解解析 
		if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition();
			try {
				def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
						AnnotationConfigUtils.class.getClassLoader()));
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
			}
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		....
		....

		return beanDefs;
	}

笔者此处只关注针对@Configuration注解spring是如何解析的,本文就重点分析ConfigurationClassPostProcessor处理类

2.ConfigurationClassPostProcessor

我们直接去查看其复写的postProcessBeanFactory()方法,里面出现了关键的处理方法

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		int factoryId = System.identityHashCode(beanFactory);
		if (this.factoriesPostProcessed.contains(factoryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + beanFactory);
		}
		this.factoriesPostProcessed.add(factoryId);
		if (!this.registriesPostProcessed.contains(factoryId)) {
			// 关键方法
			processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
		}
		
		// 对bean工厂的含Configuration注解实例进行CGLIB代理
		enhanceConfigurationClasses(beanFactory);
		// 对类型为ImportAware的bean进行额外处理
		beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
	}

笔者此处只追踪processConfigBeanDefinitions()方法,看下对@Configuration注解是如何处理的

ConfigurationClassPostProcessor#processConfigBeanDefinitions()

源码有点长,部分省略

	/**
	 * Build and validate a configuration model based on the registry of
	 * {@link Configuration} classes.
	 */
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();
		// 遍历注册在bean工厂上的所有bean,筛选出未加载过的@Configuration类
		for (String beanName : candidateNames) {
			// 避免重复加载
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
					ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
            // 以当前类循环遍历注解类以查找有无@Configuration注解
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		// 无@Configuration注解bean则直接返回
		if (configCandidates.isEmpty()) {
			return;
		}

		....
		....

		// 解析@Configuration
		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();

			...

			candidates.clear();
			// 检查是否含有新的bean没有被解析
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				...
			}
		}
		while (!candidates.isEmpty());

		...
	}

@Configuration注解的搜寻策略是根据当前类如果不存在此注解则会根据当前类的注解的注解再搜索,依次类推

由上可知具体的解析类为ConfigurationClassParser,我们关注下它的公有方法parse()

3.ConfigurationClassParser#parse()

源码如下

	public void parse(Set<BeanDefinitionHolder> configCandidates) {
		this.deferredImportSelectors = new LinkedList<>();

		// 对不同类型的bean调用不同的parse负载方法
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}
		// 针对DeferredImportSelector延迟选择类作下处理
		processDeferredImportSelectors();
	}

我们继续对上述的代码作下分析

4.ConfigurationClassParser#doProcessConfigurationClass()

首先分析下parse()方法,内部均会调用如下的代码

	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {

		// 优先遍历其内部类,找寻其被@Bean和@Configuration等下述注解修饰的内部类,对内部类进行注入工厂
		processMemberClasses(configClass, sourceClass);

		// Process any @PropertySource annotations
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
				logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}

		// Process any @ComponentScan annotations
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

		// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), true);

		// Process any @ImportResource annotations
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}

		// Process individual @Bean methods
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		return null;
	}

上述代码阐述了对@PropertySource@ComponentScan@Import@ImportResource@Bean注解的处理,并且会递归遍历父类来进行相同的解析。
笔者下面便针对上述的五个注解分别作下简单的分析

@PropertySource注解

分析前我们先看下@PropertySource注解的代码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {

	/**
	 * Indicate the name of this property source. If omitted, a name will
	 * be generated based on the description of the underlying resource.
	 * @see org.springframework.core.env.PropertySource#getName()
	 * @see org.springframework.core.io.Resource#getDescription()
	 */
	String name() default "";

	/**
	 * Indicate the resource location(s) of the properties file to be loaded.
	 * <p>Both traditional and XML-based properties file formats are supported
	 * &mdash; for example, {@code "classpath:/com/myco/app.properties"}
	 * or {@code "file:/path/to/file.xml"}.
	 * <p>Resource location wildcards (e.g. *&#42;/*.properties) are not permitted;
	 * each location must evaluate to exactly one {@code .properties} resource.
	 * <p>${...} placeholders will be resolved against any/all property sources already
	 * registered with the {@code Environment}. See {@linkplain PropertySource above}
	 * for examples.
	 * <p>Each location will be added to the enclosing {@code Environment} as its own
	 * property source, and in the order declared.
	 */
	String[] value();

	/**
	 * Indicate if failure to find the a {@link #value() property resource} should be
	 * ignored.
	 * <p>{@code true} is appropriate if the properties file is completely optional.
	 * Default is {@code false}.
	 * @since 4.0
	 */
	boolean ignoreResourceNotFound() default false;

	/**
	 * A specific character encoding for the given resources, e.g. "UTF-8".
	 * @since 4.3
	 */
	String encoding() default "";

	/**
	 * Specify a custom {@link PropertySourceFactory}, if any.
	 * <p>By default, a default factory for standard resource files will be used.
	 * @since 4.3
	 * @see org.springframework.core.io.support.DefaultPropertySourceFactory
	 * @see org.springframework.core.io.support.ResourcePropertySource
	 */
	Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;

}

然后我们再看下处理方法ConfigurationClassParser#processPropertySource()

	private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
		String name = propertySource.getString("name");
		if (!StringUtils.hasLength(name)) {
			name = null;
		}
		String encoding = propertySource.getString("encoding");
		if (!StringUtils.hasLength(encoding)) {
			encoding = null;
		}
		String[] locations = propertySource.getStringArray("value");
		Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
		boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");

		Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
		PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
				DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));

		for (String location : locations) {
			try {
				// if resource has ${},use environment to resolve it 
				String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
				// via DefaultResourceLoader to resolve file grammer or classpath grammer
				Resource resource = this.resourceLoader.getResource(resolvedLocation);
				// store into environment
				addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
			}
			catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
				// Placeholders not resolvable or resource not found when trying to open it
				if (ignoreResourceNotFound) {
					if (logger.isInfoEnabled()) {
						logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
					}
				}
				else {
					throw ex;
				}
			}
		}
	}

针对上述代码作下小结

  1. 属性value,支持"classpath:"和"file://"方式加载,可指定多个资源路径,以,分隔。并且支持properties/xml后缀

  2. 属性ignoreResourceNotFound,是否忽略找不到的资源,默认为false,即会对找不到的资源抛出异常

  3. 支持在文件路径添加${}表达式,其会被Enviroment环境的对应属性所解析

  4. @PropertySource主要用于加载外部文件资源,最终加载的资源会被保存至Enviroment环境的Map集合中,可通过Environment#getProperty()方法获取

@ComponentScan

@Component注解扫描类,先看下注解的本身源码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

	/**
	 * Alias for {@link #basePackages}.
	 * <p>Allows for more concise annotation declarations if no other attributes
	 * are needed &mdash; for example, {@code @ComponentScan("org.my.pkg")}
	 * instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
	 */
	@AliasFor("basePackages")
	String[] value() default {};


	@AliasFor("value")
	String[] basePackages() default {};

	Class<?>[] basePackageClasses() default {};


	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;


	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;


	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

	/**
	 * Controls the class files eligible for component detection.
	 * <p>Consider use of {@link #includeFilters} and {@link #excludeFilters}
	 * for a more flexible approach.
	 */
	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

	/**
	 * Indicates whether automatic detection of classes annotated with {@code @Component}
	 * {@code @Repository}, {@code @Service}, or {@code @Controller} should be enabled.
	 */
	boolean useDefaultFilters() default true;


	Filter[] includeFilters() default {};

	/**
	 * Specifies which types are not eligible for component scanning.
	 * @see #resourcePattern
	 */
	Filter[] excludeFilters() default {};

	/**
	 * Specify whether scanned beans should be registered for lazy initialization.
	 * <p>Default is {@code false}; switch this to {@code true} when desired.
	 * @since 4.1
	 */
	boolean lazyInit() default false;


	/**
	 * Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters
	 * include filter} or {@linkplain ComponentScan#excludeFilters exclude filter}.
	 */
	@Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {

		/**
		 * The type of filter to use.
		 * <p>Default is {@link FilterType#ANNOTATION}.
		 * @see #classes
		 * @see #pattern
		 */
		FilterType type() default FilterType.ANNOTATION;

		/**
		 * Alias for {@link #classes}.
		 * @see #classes
		 */
		@AliasFor("classes")
		Class<?>[] value() default {};

		@AliasFor("value")
		Class<?>[] classes() default {};


		String[] pattern() default {};

	}

}

此处注解的配置就和spring的<context:component-scan>配置一样,本文就不延伸了


再看下对应的解析类ComponentScanAnnotationParser

	public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
		// if useDefaultFilters true,@Repository/@Service/@Controller will be also considered to resolve
		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
				componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
		
		Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
		boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
		scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
				BeanUtils.instantiateClass(generatorClass));

		ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
		if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
			scanner.setScopedProxyMode(scopedProxyMode);
		}
		else {
			Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
			scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
		}
		
		// the resourcePattern of files.default to **/**.class
		scanner.setResourcePattern(componentScan.getString("resourcePattern"));
		
		// includeFilters and excludeFilters configuration
		for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
				scanner.addIncludeFilter(typeFilter);
			}
		}
		for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
				scanner.addExcludeFilter(typeFilter);
			}
		}

		// if lazyInit is true,the beanDefination will be loading when first to use
		boolean lazyInit = componentScan.getBoolean("lazyInit");
		if (lazyInit) {
			scanner.getBeanDefinitionDefaults().setLazyInit(true);
		}

		Set<String> basePackages = new LinkedHashSet<>();
		String[] basePackagesArray = componentScan.getStringArray("basePackages");
		for (String pkg : basePackagesArray) {
			String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
			Collections.addAll(basePackages, tokenized);
		}
		// load concret class\'s packageName
		for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
			basePackages.add(ClassUtils.getPackageName(clazz));
		}
		
		// be aware of this. when basePackages and basePackageClasses property are not configurated,it will load current annotated class\'s packageName 
		if (basePackages.isEmpty()) {
			basePackages.add(ClassUtils.getPackageName(declaringClass));
		}

		scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
			@Override
			protected boolean matchClassName(String className) {
				return declaringClass.equals(className);
			}
		});
		return scanner.doScan(StringUtils.toStringArray(basePackages));
	}

具体的解析过程笔者就不展开了,对上述的代码稍微作下小结

  1. 属性useDefaultFilters,默认为true。表明支持也对@Repository/@Service/@Controller注解进行扫描

  2. 属性resourcePattern,默认为**/**.class。代表扫描何种模式的文件

  3. 属性basePackagesbasePackageClasses,代表扫描的包名列表。如果都没有指定的话,其会以@ComponentScan注解的当前类所在的包名作为扫描路径

  4. @ComponentScan注解类默认是不扫描所注解的当前类的

@Import

此注解用于引入相应的类来进行多元的解析,扩展性比较好。我们可以看下其源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
	 * or regular component classes to import.
	 */
	Class<?>[] value();

}

再看下具体的解析方法processImports()

	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
		// if current sourceClass has no @Import annotation.return directly
		if (importCandidates.isEmpty()) {
			return;
		}

		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		else {
			this.importStack.push(configClass);
			try {
				// recursively
				for (SourceClass candidate : importCandidates) {
					// 1. aim to process ImportSelector interface
					if (candidate.isAssignable(ImportSelector.class)) {
						Class<?> candidateClass = candidate.loadClass();
						ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
						// configurate common properties
						ParserStrategyUtils.invokeAwareMethods(
								selector, this.environment, this.resourceLoader, this.registry);
						// store DeferredImportSelector interface
						if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
							this.deferredImportSelectors.add(
									new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
						}
						else {
							// recursively process @Import based on importClassNames
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
							processImports(configClass, currentSourceClass, importSourceClasses, false);
						}
					}
					// 2. aim to process ImportBeanDefinitionRegistrar interface
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
						ParserStrategyUtils.invokeAwareMethods(
								registrar, this.environment, this.resourceLoader, this.registry);
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					else {	
						// 3. aim to process it as an @Configuration class
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						processConfigurationClass(candidate.asConfigClass(configClass));
					}
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
			finally {
				this.importStack.pop();
			}
		}
	}

针对上述的代码作下小结

  1. @Import注解中的属性value,支持的类型有ImportSelector.classImportBeanDefinitionRegistrar.class接口和普通的@Configuration注解类

  2. ImportSelector.class接口的selectImports()方法用于引入更多的筛选类以满足不同注解的解析。这利于扩展,用户可自定义去实现

  3. ImportBeanDefinitionRegistrar接口的registerBeanDefinitions()方法主要用于注册特定的beanDefinition。这也利于扩展,用户可自定义去实现

  4. @Import注解对于带有@Configuration的普通类则会再次执行本文开头的方法递归解析

  5. @Import注解的最终目的也是为了将特定的bean注册至spring上下文的bean工厂中

@ImportResource

根据注释描述,其类似于spring中的<import/>标签,用于引入外部的<beans>配置。具体的笔者就不阐述了,有兴趣的读者可自行分析。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ImportResource {

	@AliasFor("locations")
	String[] value() default {};

	/**
	 * Resource locations from which to import.
	 * <p>Supports resource-loading prefixes such as {@code classpath:},
	 * {@code file:}, etc.
	 * <p>Consult the Javadoc for {@link #reader} for details on how resources
	 * will be processed.
	 * @since 4.2
	 * @see #value
	 * @see #reader
	 */
	@AliasFor("value")
	String[] locations() default {};

	/**
	 * {@link BeanDefinitionReader} implementation to use when processing
	 * resources specified via the {@link #value} attribute.
	 * <p>By default, the reader will be adapted to the resource path specified:
	 * {@code ".groovy"} files will be processed with a
	 * {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader GroovyBeanDefinitionReader};
	 * whereas, all other resources will be processed with an
	 * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader XmlBeanDefinitionReader}.
	 * @see #value
	 */
	Class<? extends BeanDefinitionReader> reader() default BeanDefinitionReader.class;

}

@Bean

此注解主要用于对类的方法上,用于将指定的方法返回的值包装成BeanDefinition,并注入至spring中的bean工厂中。具体笔者也不分析了,有兴趣的读者可自行分析

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {

	/**
	 * Alias for {@link #name}.
	 * <p>Intended to be used when no other attributes are needed, for example:
	 * {@code @Bean("customBeanName")}.
	 * @since 4.3.3
	 * @see #name
	 */
	@AliasFor("name")
	String[] value() default {};


	@AliasFor("value")
	String[] name() default {};


	Autowire autowire() default Autowire.NO;

	/**
	 * The optional name of a method to call on the bean instance during initialization.
	 * Not commonly used, given that the method may be called programmatically directly
	 * within the body of a Bean-annotated method.
	 * <p>The default value is {@code ""}, indicating no init method to be called.
	 * @see org.springframework.beans.factory.InitializingBean
	 * @see org.springframework.context.ConfigurableApplicationContext#refresh()
	 */
	String initMethod() default "";


	String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;

}

小结

根据上述的代码描述我们基本了解@Configuration注解是如何被解析的,读者只需要依次阅读下来基本就明白了。大致逻辑如下

  1. 获取bean工厂里的所有beanDefinition,对含有@Configuration注解进行筛选得到ConfigurationCandidate集合

  2. 遍历ConfigurationCandidate集合,对@Configuration注解进行解析,其中包括@Bean@Import@PropertySource@ImportResource@ComponentScan注解的解析。其中bean注册顺序为 内部类>@ComponentScan>@Import导入的类>@Bean>@ImportResource>ImportBeanDefinitionRegistrar,但也是有前提的,前提就是带有@Conditional注解的条件判断通过方可

  3. 对Import导入的类型为DeferredImportSelector进行解析,其中涉及@AutoConfigureBefore的注解排序解析。一般是用于springboot的自带配置类解析,其的bean解析顺序是最靠后

  4. 通过ConfigurationClassBeanDefinitionReader类对上述@Import导入的类、@Bean注解的类方法、@ImportResource注解的类均注册至bean工厂中

  5. 对于@Import导入的非ImportBeanDefinitionRegistrar/ImportSelector实现类,即使其上面没有@Configuration注解其也会被注入至bean工厂;有则可能会被重载

基于上述的解读,那么@SpringBootApplication注解的解读就迫在眉睫了,里面肯定蕴含了一些玄机等待我们去发现。

以上是关于SpringBoot的@Configuration注解的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot的@Configuration注解

java springboot activemq的@configuration类

springboot之additional-spring-configuration-metadata.json自定义提示

Springboot@Configuration和@Bean详解

springboot整合redis

springboot整合redis