欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

spring ioc的包的扫描(基于注解)

发布时间:2025/3/15 编程问答 28 豆豆
生活随笔 收集整理的这篇文章主要介绍了 spring ioc的包的扫描(基于注解) 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

一、包扫描初始步骤源码分析

AnnotationConfigApplicationContext aac =new AnnotationConfigApplicationContext("com.mydemo");

AnnotationConfigApplicationContext可以实现基于Java的配置类(包括各种注解)加载Spring的应用上下文。避免使用application.xml进行配置。相比XML配置,更加便捷。

  • 主要类结构图:
  • 主要类介绍:
  • 已GenericApplicationContext——通用应用上下文,内部持有一个DefaultListableBeanFactory实例,这个类实现了BeanDefinitionRegistry接口,可以在它身上使用任意的bean definition读取器。典型的使用案例是:通过BeanFactoryRegistry接口注册bean definitions,然后调用refresh()方法来初始化那些带有应用上下文语义(ApplicationContextAware)的bean,自动探测(BeanFactoryPostProcessor)等。

    BeanDefinitionRegistry——用于持有像RootBeanDefinition和 ChildBeanDefinition实例的bean definitions的注册表接口。DefaultListableBeanFactory实现了这个接口,因此可以通过相应的方法向beanFactory里面注册bean。GenericApplicationContext内置一个DefaultListableBeanFactory实例,它对这个接口的实现实际上是通过调用这个实例的相应方法实现的。

    AbstractApplicationContext——ApplicationContext接口的抽象实现,没有强制规定配置的存储类型,仅仅实现了通用的上下文功能。这个实现用到了模板方法设计模式,需要具体的子类来实现其抽象方法。自动通过registerBeanPostProcessors()方法注册BeanFactoryPostProcessor, BeanPostProcessor和ApplicationListener的实例用来探测bean factory里的特殊bean

    AnnotationConfigRegistry——注解配置注册表。用于注解配置应用上下文的通用接口,拥有一个注册配置类和扫描配置类的方法。

  • 构造函数
  • //默认构造函数,初始化一个空容器,容器不包含任何 Bean 信息,需要在稍后通过调用其register()//方法注册配置类,并调用refresh()方法刷新容器,触发容器对注解Bean的载入、解析和注册过程public AnnotationConfigApplicationContext() {this.reader = new AnnotatedBeanDefinitionReader(this);this.scanner = new ClassPathBeanDefinitionScanner(this);} public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {super(beanFactory);this.reader = new AnnotatedBeanDefinitionReader(this);this.scanner = new ClassPathBeanDefinitionScanner(this);}//最常用的构造函数,通过将涉及到的配置类传递给该构造函数,以实现将相应配置类中的Bean自动注册到容器中public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {//调用无参构造函数,初始化AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScannerthis();register(annotatedClasses);refresh();}//该构造函数会自动扫描以给定的包及其子包下的所有类,并自动识别所有的Spring Bean,将其注册到容器中public AnnotationConfigApplicationContext(String... basePackages) {//初始化ClassPathBeanDefinitionScanner和AnnotatedBeanDefinitionReaderthis();//step1//扫描包、注册beanscan(basePackages);//step2refresh();//step3}
  • 主要属性:
  • 1.AnnotatedBeanDefinitionReader——BeanDefinition解析器用来解析带注解的bean
    2.ClassPathBeanDefinitionScanner——bean的扫描器,用来扫描类
    3.注册解析传入的配置类(使用类配置的方式进行解析)
    4.调用容器的refresh方法初始化容器

    这里我们用的是最后一种构造函数,即传入一个包路径。

  • IoC 之 构造函数初始化
  • //第一步,调用本类的无参构造函数 public AnnotationConfigApplicationContext() {this.reader = new AnnotatedBeanDefinitionReader(this);this.scanner = new ClassPathBeanDefinitionScanner(this);} //第二步,然后初始化AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner//先看ClassPathBeanDefinitionScanner的构造函数 public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {this(registry, true);}//继续跟踪下去,最后调用的是这个方法: public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,Environment environment, @Nullable ResourceLoader resourceLoader) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");//为容器设置加载Bean定义的注册器this.registry = registry;//是否使用默认过滤规则if (useDefaultFilters) {registerDefaultFilters();}//设置环境setEnvironment(environment);//为容器设置资源加载器setResourceLoader(resourceLoader);}

    这里面最主要的是registerDefaultFilters()方法,初始化spring扫描默认过滤规则,对应@ComponentScan注解,如果没有自定义规则,就初始化默认过滤规则。这里调用的是ClassPathScanningCandidateComponentProvider类中的registerDefaultFilters()方法如下:

    //向容器注册过滤规则 @SuppressWarnings("unchecked") protected void registerDefaultFilters() {//向要包含的过滤规则中添加@Component注解类//@Service和@Controller都是Component,因为这些注解都添加了@Component注解this.includeFilters.add(new AnnotationTypeFilter(Component.class));//获取当前类的类加载器ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();try {//向要包含的过滤规则添加JavaEE6的@ManagedBean注解this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));logger.debug("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 {//向要包含的过滤规则添加@Named注解this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");}catch (ClassNotFoundException ex) {// JSR-330 API not available - simply skip.} }

    两个关键变量:

    //includeFilters表示要包含的注解,即只有包含includeFilters中的注解,才会被扫描 private final List<TypeFilter> includeFilters = new LinkedList<>(); // excludeFilters表示要排除的注解,即包含excludeFilters中的注解不会被扫描 private final List<TypeFilter> excludeFilters = new LinkedList<>();

    在这个方法中,includeFilters集合中添加了@Component、JavaEE6的@ManagedBean和JSR-330的@Named注解 而excludeFilters集合没做任何变动,即没有要排除的注解

    总结:
    所以默认规则就是,只要包含了@Component、JavaEE6的@ManagedBean和JSR-330的@Named这3个注解中的任意一个,就会被扫描

    二、包扫描的具体过程

    先看以下源码
    AnnotationConfigApplicationContext类:

    //该构造函数会自动扫描以给定的包及其子包下的所有类,并自动识别所有的Spring Bean,将其注册到容器中public AnnotationConfigApplicationContext(String... basePackages) {//初始化this();//扫描包、注册beanscan(basePackages);refresh();}

    上文我们分析了this()方法,会去初始化AnnotatedBeanDefinitionReader读取器(BeanDefinition解析器用来解析带注解的bean)和ClassPathBeanDefinitionScanner扫描器(bean的扫描器 用来扫描类),并初始化扫描过滤规则。此次我们跟踪scan(basePackages)方法:
    一直跟踪下去,发现调用了ClassPathBeanDefinitionScanner类中的scan()方法

    //调用类路径Bean定义扫描器入口方法public int scan(String... basePackages) {//获取容器中已经注册的Bean个数int beanCountAtScanStart = this.registry.getBeanDefinitionCount();//启动扫描器扫描给定包doScan(basePackages);// Register annotation config processors, if necessary.//注册注解配置(Annotation config)处理器if (this.includeAnnotationConfig) {AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);}//返回注册的Bean个数return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);}

    可以看到主要是doScan(basePackages)方法实现了扫描的逻辑,我们继续跟踪进去看下:

    //类路径Bean定义扫描器扫描给定包及其子包protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");//创建一个集合,存放扫描到Bean定义的封装类Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();//遍历扫描所有给定的包for (String basePackage : basePackages) {//调用父类ClassPathScanningCandidateComponentProvider的方法//扫描给定类路径,获取符合条件的Bean定义Set<BeanDefinition> candidates = findCandidateComponents(basePackage);//遍历扫描到的Beanfor (BeanDefinition candidate : candidates) {//获取@Scope注解的值,即获取Bean的作用域ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);//为Bean设置作用域 candidate.setScope(scopeMetadata.getScopeName());//为Bean生成名称String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);//如果扫描到的Bean不是Spring的注解Bean,则为Bean设置默认值,//设置Bean的自动依赖注入装配属性等if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}//如果扫描到的Bean是Spring的注解Bean,则处理其通用的Spring注解if (candidate instanceof AnnotatedBeanDefinition) {//处理注解Bean中通用的注解,在分析注解Bean定义类读取器时已经分析过AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}//根据Bean名称检查指定的Bean是否需要在容器中注册,或者在容器中冲突if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);//根据注解中配置的作用域,为Bean应用相应的代理模式definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);//向容器注册扫描到的BeanregisterBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}

    这一大段代码基本上就是spring扫描识别注解,并注册Bean到IOC容器中的代码。在第10行有一个findCandidateComponents(basePackage)方法,这个方法里就是具体的扫描逻辑。
    ClassPathScanningCandidateComponentProvider类:

    //扫描给定类路径的包public Set<BeanDefinition> findCandidateComponents(String basePackage) {//spring5.0开始 索引 开启的话生成文件META-INF/spring.components 后面加载直接从本地文件读取(一般不建议开启 spring.index.ignore=true)if (this.componentsIndex != null && indexSupportsIncludeFilters()) {return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);}else {return scanCandidateComponents(basePackage);}}

    有一个if判断,默认走else里scanCandidateComponents(basePackage)方法。

    private Set<BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {//补全扫描路径,扫描所有.class文件 classpath*:com/mydemo/**/*.classString packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;//定位资源Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}if (resource.isReadable()) {try {//通过ASM获取class元数据,并封装在MetadataReader元数据读取器中MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);//判断该类是否符合@CompoentScan的过滤规则//过滤匹配排除excludeFilters排除过滤器(可以没有),包含includeFilter中的包含过滤器(至少包含一个)。if (isCandidateComponent(metadataReader)) {//把元数据转化为 BeanDefinitionScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setResource(resource);sbd.setSource(resource);//判断是否是合格的bean定义if (isCandidateComponent(sbd)) {if (debugEnabled) {logger.debug("Identified candidate component class: " + resource);}//加入到集合中candidates.add(sbd);}else {//不合格 不是顶级类、具体类if (debugEnabled) {logger.debug("Ignored because not a concrete top-level class: " + resource);}}}else {//不符@CompoentScan过滤规则if (traceEnabled) {logger.trace("Ignored because not matching any filter: " + resource);}}}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);}}else {if (traceEnabled) {logger.trace("Ignored because not readable: " + resource);}}}}catch (IOException ex) {throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);}return candidates;}

    这里就是主要的扫描逻辑,代码中的注释已经说的很清楚了。
    主要过程:

    1.根据包路径,扫描所有.class文件
    2.根据包路径,生成.class对应的Resource对象
    3.通过ASM获取class元数据,并封装在MetadataReader元数据读取器中
    4.判断该类是否符合过滤规则
    5.判断该类是否为独立的类、具体的类
    6.加入到集合中

    详细看下过滤的方法 isCandidateComponent(metadataReader)

    //判断元信息读取器读取的类是否符合容器定义的注解过滤规则//@CompoentScan的过滤规则支持5种 (注解、类、正则、aop、自定义)protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {//如果读取的类的注解在排除注解过滤规则中,返回falsefor (TypeFilter tf : this.excludeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return false;}}//如果读取的类的注解在包含的注解的过滤规则中,则返回turefor (TypeFilter tf : this.includeFilters) {//判断当前类的注解是否match规则if (tf.match(metadataReader, getMetadataReaderFactory())) {//是否有@Conditional注解,进行相关处理return isConditionMatch(metadataReader);}}//如果读取的类的注解既不在排除规则,也不在包含规则中,则返回falsereturn false;}

    接着跟踪 tf.match()方法AbstractTypeHierarchyTraversingFilter类

    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)throws IOException {// This method optimizes avoiding unnecessary creation of ClassReaders// as well as visiting over those readers.//检查当前类的注解是否符合规律规则if (matchSelf(metadataReader)) {return true;}//check 类名是否符合规则ClassMetadata metadata = metadataReader.getClassMetadata();if (matchClassName(metadata.getClassName())) {return true;}//如果有继承父类if (this.considerInherited) {String superClassName = metadata.getSuperClassName();if (superClassName != null) {// Optimization to avoid creating ClassReader for super class.Boolean superClassMatch = matchSuperClass(superClassName);if (superClassMatch != null) {if (superClassMatch.booleanValue()) {return true;}}else {// Need to read super class to determine a match...try {if (match(metadata.getSuperClassName(), metadataReaderFactory)) {return true;}}catch (IOException ex) {logger.debug("Could not read super class [" + metadata.getSuperClassName() +"] of type-filtered class [" + metadata.getClassName() + "]");}}}}//如果有实现接口if (this.considerInterfaces) {for (String ifc : metadata.getInterfaceNames()) {// Optimization to avoid creating ClassReader for super classBoolean interfaceMatch = matchInterface(ifc);if (interfaceMatch != null) {if (interfaceMatch.booleanValue()) {return true;}}else {// Need to read interface to determine a match...try {if (match(ifc, metadataReaderFactory)) {return true;}}catch (IOException ex) {logger.debug("Could not read interface [" + ifc + "] for type-filtered class [" +metadata.getClassName() + "]");}}}}return false;}

    这里面最主要的是 matchSelf(metadataReader) 方法 AnnotationTypeFilter类

    protected boolean matchSelf(MetadataReader metadataReader) {//获取注解元数据AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();//check 注解及其派生注解中是否包含@Component//获取当前类的注解 metadata.hasAnnotation @Controller//获取当前类的注解及其派生注解 metadata.hasAnnotation @Controller包含的@Component\@Documented等等return metadata.hasAnnotation(this.annotationType.getName()) ||(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));}

    在这段代码中,可以解决我们之前的疑惑“Spring是怎么发现@Configuration、@Controller、@Service这些注解修饰的类的?”
    原来@Configuration、@Controller、@Service这些注解其实都是@Component的派生注解,我们看这些注解的代码会发现,都有@Component注解修饰。而spring通过metadata.hasMetaAnnotation()方法获取到这些注解包含@Component,所以都可以扫描到。


    然后我们再看回 scanCandidateComponents(basePackage)方法,接下来有一个 isCandidateComponent(sbd)方法,如下:

    //这个方法的作用是,判断该类是否为 //顶层的类(没有父类或静态内部类) //具体的类(不是抽象类或接口)protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {AnnotationMetadata metadata = beanDefinition.getMetadata();return (metadata.isIndependent() && (metadata.isConcrete() ||(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));}

    至此,ClassPathBeanDefinitionScanner类中的doScan(basePackages)方法中的findCandidateComponents(basePackage)方法已经结束了,包扫描也结束了,已经把扫描到的类存入到了集合中,结下来就是解析注册Bean的过程了。
    总结
    通过这一节,我们可以回答之前的一些问题了:

    • Spring是怎么发现@Bean、@Controller、@Service这些注解修饰的类的?
      通过 matchSelf(metadataReader)方法,判断这些注解中是否包含@Component
    • @CompoentScan注解是怎么起作用的?
      通过 isCandidateComponent(metadataReader)方法过滤

    文章转自1:
    文章转自2:

    总结

    以上是生活随笔为你收集整理的spring ioc的包的扫描(基于注解)的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。