美文网首页框架原理
spring 源码分析--之注解扫描原理

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

作者: 清风徐来水波不清 | 来源:发表于2019-04-14 22:14 被阅读365次

    前言

    用过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<BeanDefinitionHolder> doScan(String... basePackages) {
            Assert.notEmpty(basePackages, "At least one base package must be specified");
    //创建集合存放合适bean定义信息,可以理解为bean信息
            Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    // 循环出入的包
            for (String basePackage : basePackages) {
    // 集合存放所有符合条件的候选者bean信息,此处findCandidateComponents方法是对符合条件bean过滤
                Set<BeanDefinition> 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<BeanDefinition> 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<BeanDefinition> scanCandidateComponents(String basePackage) {
            Set<BeanDefinition> 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 源码分析--之注解扫描原理

        本文链接:https://www.haomeiwen.com/subject/lxwswqtx.html