框架策略级相关|spring boot原理分析(二)(项目内bean的注入)

按照spring boot原理分析(一)规划,第一部分先分析spring boot的环境自动配置、加载所需要的bean是如何实现的。
根据bean的所属项目,spring boot的bean的加载分为两种:一类是项目中定义的、启动类所在包下的bean,比如自定义的@Controller、@Service等;还有一类是依赖包中定义的,比如引入的redis、mybatis、MQ组件等。这两类的加载方式不同,但是都要从@SpringBootApplication注解说起。
spring boot的启动类定义了main函数,同时需要注解@SpringBootApplication(参考原理分析(一)中的项目demo)。@SpringBootApplication是@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解的综合。

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { @AliasFor(annotation = EnableAutoConfiguration.class) Class[] exclude() default {}; @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class[] scanBasePackageClasses() default {}; }

@SpringBootConfiguration比较简单,等同于@Configuration注解,可能只是区分出这是一个spring boot的@Configuration注解。这个注解的作用就是将启动类同时定义为一个由@Configuration注解的Java类配置文件,启动类中可以实现注入bean等,比如可以在启动类中定义:
@Bean public RestTemplate restTemplate() { return restTemplateBuilder.build(); }

就可以注入RestTemplate的bean到IoC容器中。
@ComponentScan注解的作用是扫描启动类的package下所有的bean,这个作用范围是可迭代的,即启动类的所在包的所有子包中的bean(主要是Controller、Service等)也都会被扫描到。所以一般工程的启动类都会放在比较上层的目录下,然后在这个目录下再创建一些比如Controller包、Service包等分别用来存放Controller和Service等类。
另外,这里@ComponentScan带有excludeFilters参数,主要是用来筛选一些不需要注册的bean。@Filter中的FilterType.CUSTOM参数的意思是,自己可以实现TypeFilter接口,完成自定义的过滤。上面主要定义了两个实现类,TypeExcludeFilter.class和AutoConfigurationExcludeFilter.class。TypeExcludeFilter的代码如下:
public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware { private BeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) { Collection delegates = ((ListableBeanFactory) this.beanFactory) .getBeansOfType(TypeExcludeFilter.class).values(); for (TypeExcludeFilter delegate : delegates) { if (delegate.match(metadataReader, metadataReaderFactory)) { return true; } } } return false; } ...... }

其中最重要的是match方法。当扫描到可能需要过滤掉的bean时,会调用TypeExcludeFilter的match方法进行匹配,传入当前扫描类的Class元数据MetadataReader和元数据读取工厂类MetadataReaderFactory。这里的主要作用是使用beanFactory获取所有TypeExcludeFilter的子类,然后调用每个子类的match进行筛选,如果子类match返回true,这里match也会返回true,当前扫描类就不会被注入。主要作用提供一个筛选bean的入口,就是可以通过继承TypeExcludeFilter类,然后重写match方法来过滤掉你不想注入的类。比如如果定义一个Test类的bean,又不想注入或者希望在某些条件满足的情况下注入,可以定义个TypeExcludeFilter的子类
public class MyTypeExcludeFilter extends TypeExcludeFilter {@Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { if("com.Test".equals(metadataReader.getClassMetadata().getClassName())){ return true; } return false; }}

。这样Test bean在@ComponentScan生效时就不会被注入。
AutoConfigurationExcludeFilter是为了过滤已经注入过的bean。
public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware { private ClassLoader beanClassLoader; private volatile List autoConfigurations; @Override public void setBeanClassLoader(ClassLoader beanClassLoader) { this.beanClassLoader = beanClassLoader; } @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader); } private boolean isConfiguration(MetadataReader metadataReader) { return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName()); } private boolean isAutoConfiguration(MetadataReader metadataReader) { return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName()); } protected List getAutoConfigurations() { if (this.autoConfigurations == null) { this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader); } return this.autoConfigurations; }}

这里的match匹配的是由@Configuration注解的和@EnableConfiguration注解的容器,由于这两类容器已经注入过了,所以这里需要筛掉,避免二次注入。
上面的两个注解负责的范围是本项目下的容器的注入,而@EnableAutoConfiguration主要负责的是依赖包中容器的注入,代码逻辑相对比较复杂,放在下一部分单独讲述。
【框架策略级相关|spring boot原理分析(二)(项目内bean的注入)】附:@SpringBootApplication的注解实现中,可以看到每个参数上使用了@AliasFor注解,"alias"在英语中是“别名”的意思,这个注解可以保证注解的输入参数和注解修饰的参数的值保持一致性,比如上面例子中的参数exclude是@EnableAutoConfiguration注解exclued参数(没写代表参数名默认是一样的)的别名,如果这两个参数的输入值不一致,就会报错。还有一种应用场景,比如@RequestMapping注解,其中name、path、value参数代表url,那么如果同时使用这三个参数时就必须赋值是一样的,这时就可以使用@AliasFor保持这三个参数赋值的一致性。

    推荐阅读