行业资讯

Spring自动装配之@AutoConfigurationPackage

最近闲来无事想写一篇关于Spring自动装配原理实现的文章,所以自己就简单复习了一下相关源码,然后突然发现一个之前没有注意到的注解:@AutoConfigurationPackage,感觉很有意思,特此记录一下。

它所在的位置是在@SpringBootApplication ->@EnableAutoConfiguration上,具体如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

其实说白点就是被这个注解标记的类所在的包路径在Spring中可以通过被AutoConfigurationPackages.get方法拿到,我们可以做一些第三方组件扫描并交给Spring管理。

在注解中使用了@Import导入了AutoConfigurationPackages.Registrar类,该类实现了ImportBeanDefinitionRegistrar接口,重写了registerBeanDefinitions方法,该方法的作用是注册BasePackages类为beanDefinition,而BasePackages是将@AutoConfigurationPackage注解所在类的所在包路径保存下来,可以通过AutoConfigurationPackages.get()方法获取所有的包名。

这里一个小知识点:ImportBeanDefinitionRegistrar接口是也是spring的扩展点之一,它可以支持我们自己写的代码封装成BeanDefinition对象;实现此接口的类会回调postProcessBeanDefinitionRegistry方法,注册到spring容器中。把bean注入到spring容器不止有 @Service @Component等注解方式;还可以实现此接口,只要@Import导入即可

所以被@AutoConfigurationPackage修饰的类所在的包我们都可以通过AutoConfigurationPackages.get()获取到。

public static List<String> get(BeanFactory beanFactory) {
		try {
      //获取BasePackagesbean,然后调用get方法
			return beanFactory.getBean(BEAN, BasePackages.class).get();
		}
		catch (NoSuchBeanDefinitionException ex) {
			throw new IllegalStateException(
					"Unable to retrieve @EnableAutoConfiguration base packages");
		}
	}

//BasePackages
static final class BasePackages {

		private final List<String> packages;

		private boolean loggedBasePackageInfo;
		//在注册beanDefinition的时候会将参数传进来
		BasePackages(String... names) {
			List<String> packages = new ArrayList<>();
			for (String name : names) {
				if (StringUtils.hasText(name)) {
					packages.add(name);
				}
			}
			this.packages = packages;
		}

//获取所有的被@AutoConfigurationPackage注解所在类的所在包路径
		public List<String> get() {
			if (!this.loggedBasePackageInfo) {
				if (this.packages.isEmpty()) {
					if (logger.isWarnEnabled()) {
						logger.warn("@EnableAutoConfiguration was declared on a class "
								+ "in the default package. Automatic @Repository and "

Spring自动装配之@AutoConfigurationPackage(图1)

+ "@Entity scanning is not enabled."); } } else { if (logger.isDebugEnabled()) { String packageNames = StringUtils .collectionToCommaDelimitedString(this.packages); } } this.loggedBasePackageInfo = true; } return this.packages; } }

而BasePackages作为beanDefinition被注册是在registerBeanDefinitions方法被调用的时候:

SpringApplication.run()
  => refreshContext()
    => EmbeddedWebApplicationContext.refresh()
      => AbstractApplicationContext.invokeBeanFactoryPostProcessors()
        => PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()
          => ConfigurationClassPostProcessor.processConfigBeanDefinitions()
            => ConfigurationClassBeanDefinitionReader.loadBeanDefinitions()
              => loadBeanDefinitionsFromRegistrars()
                => AutoConfigurationPackages$Registrar.registerBeanDefinitions()
                  => AutoConfigurationPackages.register()
Spring自动装配之@AutoConfigurationPackage(图2)

BasePackages作为beanDefinition被注册

说到这里不知道大家有没有发现,这里的@AutoConfigurationPackage和另外一个注解的作用很相似,没错就是@ComponentScan。

@ComponentScan这个注解作用想必大家都很清楚,他是扫描指定包下的所有的Spring提供的例如@Controller、@Service、@Component、@Repository 以及由此衍生出来的一些其他的注解所修饰的类,将其交给IOC容器管理。

这里有一个问题,@ComponentScan只能扫描Spring相关的注解,对于第三方提供的注解,它并不能扫描到。例如我们常用的Mybatis框架里的@Mapper注解,那么Mybatis又如何将@Mapper修饰的类交给Spring的IOC容器管理呢,这里便用到了@AutoConfigurationPackage注解(这里只是演示一种方式,实际我们可以使用@MapperScan进行扫描)。jnty

为了验证这一点,我找到了Mybatis的自动装配类,找到了相关方法JNTY

//该类通过@Import注解被导入
public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    private BeanFactory beanFactory;

    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
			//创建扫描器
      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
      try {
        if (this.resourceLoader != null) {
          scanner.setResourceLoader(this.resourceLoader);
        }
				//这里我们可以看到获取所有被@AutoConfigurationPackage修饰的类所在的包名
        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
        //设置扫描注解
        scanner.setAnnotationClass(Mapper.class);
        scanner.registerFilters();
        //开始扫描指定路径
        scanner.doScan(StringUtils.toStringArray(packages));
      } catch (IllegalStateException ex) {
        logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
      }
    }
  }

	//使用自动装配,导入AutoConfiguredMapperScannerRegistrar类
  @org.springframework.context.annotation.Configuration
  @Import({ AutoConfiguredMapperScannerRegistrar.class })
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration {

    @PostConstruct
    public void afterPropertiesSet() {
      logger.debug("No {} found.", MapperFactoryBean.class.getName());
    }
  }

默认情况下,SpringBoot 项目的启动注解中,实际上已经包含了 @AutoConfigurationPackage,注解具体位置在 @SpringBootApplication

总结一下:

  1. @AutoConfigurationPackage是用来扫描 Bean 的。
  2. @ComponentScan主要用来扫描和 Spring 容器相关的 Bean。而@AutoConfigurationPackage主要用来扫描第三方的 Bean。

jnty有限公司 JNTY JNTY