spring 源码分析--之注解扫描原理

前言

用过springboot的同学肯定知道springboot 的注解开发给我们带来了极大的方便,bean的注入我们可以使用@component、@Service 、@Repository 、@ManageBean(JDK自带注解) 将我们的bean很轻松的交给spring的IOC容器,再也不用写复杂的xml了。

使用注解注入Bean

补充一下@Service 、@Repository 其实相当是@Component的一个别名,我们可以看看@Service 、@Repository 源码


@Service.png

@Repository.png

我们建立一个简单的springboot项目来看一看springboot这些注解的使用,我们新建如下5个类,TestBean0 不使用注解,1-4使用注解

//com.example.demo.ioc.TestBean0
public class TestBean0 {
    public String print(){
        return  "TestBean 0  " ;
    }
}

//com.example.demo.ioc.TestBean1
@Repository
public class TestBean1 {
    public String print(){
        return  "TestBean1 " ;
    }
}
//com.example.demo.ioc.TestBean2
@Service
public class TestBean2 {

    public String print(){
        return  "TestBean2 " ;
    }
}
//com.example.demo.ioc.TestBean3
@Component
public class TestBean3 {
    public String print(){
        return  "TestBean3 " ;
    }
}

//com.example.demo.ioc.TestBean4
@ManagedBean
public class TestBean4 {
    public String print(){
        return  "TestBean4 " ;
    }
}

我们在启动类里通过applicationContext的getBeanDefinitionNames()获取已经注入的所有bean的名称

@ComponentScan("com.example.demo")
public class DemoApplication {

   public static void main(String[] args) {
    ApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
       String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();

   }
}

如下图我们只有加注解的bean被成功注入。


beans.png

自定义注解

在进行源码分析之前我们先了解一下自定义注解,一般我们定义一个注解如下,常用两个元注解,详细解释可参考https://www.cnblogs.com/lyy-2016/p/6288535.html

  • @Target表示注解能作用在什么类型上,比如可以在方法上、类,接口上、参数上;
  • @Retention定义注解的保留策略
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotion {
}

定义好自己的自定义注解如何让我们注解起作用呢?一般上有两种方式,第一使用原生的Java反射获取注解,进行处理,第二种是用过AOP扫描注解,对带注解的方法、类等做增强(当然Aop也是通过反射动态代理实现的)。具体实现本篇文章不做详细实现,感兴趣的可以去百度一下,算了不想买药的话就别百度 了,Google一下或者bing一下吧。

spring的注解扫描

思考

我们也知道自定注解的实现分为两种了,那spring是通过那种实现的,先来看看通过AOP实现,我们讨论的是注解进行Bean注入,那么在扫描之前这个bean肯定是没有被spring进行管理的,因此我们显然不能通过spring的Aop去扫描注解。当然我们要是通过Aspect也是可以实现,但是显然spring这么牛逼的框架怎么会去使用Aspect呢(当然不是说Aspect不好,相信spring开发者初衷肯定是自己实现),那么第二种方式走不通,肯定还是第一种方式了,使用最原生的反射实现。

源码跟踪分析

熟悉spring的同学一定知道spring相关注解的IOC容器的常用实现有AnnotationConfigApplicationContext、AnnotationConfigServletWebServerApplicationContext这两个,其中后者是springmvc的一些注解增强,AnnotationConfigApplicationContext 两者原理基本相同,读者可以自行分析后者源码。大概看一下
AnnotationConfigApplicationContext有哪些实现方法,看源码我们大概发现两类重要的方法,

  • 第一个是registerBean的方法,根据方法名我们很容易知道其实将bean注入容器的入口,
  • 第二方法scan 方法,我们也可以大胆猜测其是spring需要扫描的包路径


    image.png

测试registerBean注册bean

接下来我们来使用下面main方法测试一下

public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(TestBean3.class);
              applicationContext.register(TestBean0.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();

    }

我们可以在看看容器中的beans,如下图


image.png

虽然已经在容器中可以查看这个bean了(细心的同学一定会问这个没有带注解的bean怎么也被注入了,这个问题我们后面源码分析),但是这个bean是没法使用的,熟悉spring源码的同学一定知道,在bean加入到容器中后一个很重要的方式,就是refresh(),因此我们对上述main做简单修改如下

public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(TestBean3.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        applicationContext.refresh();
        applicationContext.getBean(TestBean3.class).print();
    }
测试scan方法

对上述main方法进行修改,我们在com.example.demo.ioc包外新建两个类TestBean5、6 带@Component注解,注意如果是springboot项目,请去掉启动类上面的所有注解(其实不去掉也没有关系,但是为了理解更清楚建议去掉)。


路径.png
public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.scan("com.example.demo.ioc");
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        applicationContext.refresh();//必不可少
        applicationContext.getBean(TestBean3.class).print();
    }

继续对上述代码进行断电调试,查看容器中所有bean,如下图,我们可以看到com.example.demo.ioc包下带注解的类被容器成功注入。


image.png

深入源码

上面一系列只是去验证了注解如何才能被扫描注入,那spring到底是如何扫描,我们现在来一探究竟。
我们以scan()方法为入口进行分析,分析流程图大致如下,颜色深的为主要流程:


spring 注解源码分析.png
  1. 以scan方法为入口,scan作用是指定扫描包,传入包进入扫描,scan方法中掉用 ClassPathBeanDefinitionScanner#scan方法
    public void scan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        this.scanner.scan(basePackages);
    }

2.ClassPathBeanDefinitionScanner顾名思义是扫描classpath下的bean,进入到ClassPathBeanDefinitionScanner#scan的方法,其中我们关心的是doScan(basePackages),此方法猜测应该是进行具体注解扫描

public int scan(String... basePackages) {
// 记录扫描bean数量,后面容器需要用到
        int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 此处进行扫描
        doScan(basePackages);
// 进行注册
        // Register annotation config processors, if necessary.
        if (this.includeAnnotationConfig) {
            AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
        }

        return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
    }
  1. 进入ClassPathBeanDefinitionScanner#doScan 方法分析,此方法作用是返回已注册到容器中的bean信息集合,此方法中findCandidateComponents(basePackage)选取候选者,目测在其中进行了注解扫描操作。
protected Set doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
//创建集合存放合适bean定义信息,可以理解为bean信息
        Set beanDefinitions = new LinkedHashSet<>();
// 循环出入的包
        for (String basePackage : basePackages) {
// 集合存放所有符合条件的候选者bean信息,此处findCandidateComponents方法是对符合条件bean过滤
            Set candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                if (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }
  1. 进入findCandidateComponents(basePackage)分析,正常情况下会进入到else里面执行scanCandidateComponents(basePackage);
public Set findCandidateComponents(String basePackage) {
// 带组件索引处理,一般情况不进入      
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
            return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
        }
        else {
            return scanCandidateComponents(basePackage);
        }
    }

5.继续进入scanCandidateComponents(basePackage)分析,我们可以发现spring其实是将包路径转换为文件绝对路径进行扫描其下面所有*.class文件,并将其解析为自己的资源对象Resource,接着最终要一步,遍历筛选候选组件isCandidateComponent(metadataReader) ,在此操作之前将resource文件通过反射获取到类的元信息,一遍后面进行注解判断需要;

private Set scanCandidateComponents(String basePackage) {
        Set candidates = new LinkedHashSet<>();
        try {
// classpath下对应包下面的所有*.class文件,resolveBasePackage(basePackage) 方法将包名称转换为路径名称,拼接为文件绝对路径
            String 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 {
//此处通过反射获取类元信息
                        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
    // 进行过滤,重要
                    if (isCandidateComponent(metadataReader)) {
                            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                            sbd.setResource(resource);
                            sbd.setSource(resource);
                            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 {
                            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. 在isCandidateComponent(metadataReader)方法里,做了两件事,第一件判断有没有excludeFilters注解(这种注解的bean不被注入),第二件是判断有没有includeFilters注解(这种注解就是@Component这种注解,可以进行注入),其实判断也很简单,返回true的成功候选者,如下图spring 中AnnotationConfigApplicationContext容器只识别@Component和@ManageBean两个注解,这段最核心的部分tf.match(metadataReader, getMetadataReaderFactory())进行判断
    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;
    }
image.png
  1. 猜测这个方法里面应该是通过反射获取到注解进行判断,进入tf.match(metadataReader, getMetadataReaderFactory())分析
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        if (this.matchSelf(metadataReader)) {
            return true;
        } else {
            ClassMetadata metadata = metadataReader.getClassMetadata();
            if (this.matchClassName(metadata.getClassName())) {
                return true;
            } else {
                if (this.considerInherited) {
                    String superClassName = metadata.getSuperClassName();
                    if (superClassName != null) {
                        Boolean superClassMatch = this.matchSuperClass(superClassName);
                        if (superClassMatch != null) {
                            if (superClassMatch) {
                                return true;
                            }
                        } else {
                            try {
                                if (this.match(metadata.getSuperClassName(), metadataReaderFactory)) {
                                    return true;
                                }
                            } catch (IOException var11) {
                                this.logger.debug("Could not read super class [" + metadata.getSuperClassName() + "] of type-filtered class [" + metadata.getClassName() + "]");
                            }
                        }
                    }
                }

                if (this.considerInterfaces) {
                    String[] var12 = metadata.getInterfaceNames();
                    int var13 = var12.length;

                    for(int var6 = 0; var6 < var13; ++var6) {
                        String ifc = var12[var6];
                        Boolean interfaceMatch = this.matchInterface(ifc);
                        if (interfaceMatch != null) {
                            if (interfaceMatch) {
                                return true;
                            }
                        } else {
                            try {
                                if (this.match(ifc, metadataReaderFactory)) {
                                    return true;
                                }
                            } catch (IOException var10) {
                                this.logger.debug("Could not read interface [" + ifc + "] for type-filtered class [" + metadata.getClassName() + "]");
                            }
                        }
                    }
                }

                return false;
            }
        }
    }

8.断点在进入 this.matchSelf(metadataReader) 方法,可见此处应该通过反射获取注解信息和出入的includeFilter注解进行判断

        // 获取注解注解信息
        AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
// 判断是否包含此注解        
return metadata.hasAnnotation(this.annotationType.getName()) || this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName());
    }

总结

通过上面的分析我们大概知道spring扫描注解的流程,其实也是很简单,总结一下就是对某个包或者类进行文件进行扫描读取,反射提取类的元信息,根据元信息的注解和spring需要识别的注解进行比较带spring的注入注解则进行注入。

你可能感兴趣的:(spring 源码分析--之注解扫描原理)