关于java:就想搞明白componentscan-是怎么把Bean都注册到Spring容器的

3次阅读

共计 13881 个字符,预计需要花费 35 分钟才能阅读完成。

作者:小傅哥
博客:https://bugstack.cn

积淀、分享、成长,让本人和别人都能有所播种!😄

一、前言

忒简单,没等搞明确大促都过来了!

你经验过 618双 11吗?你退出过大促时候那么多简单的营销流动赚几毛钱吗?你开发过连读明确玩法都须要一周但只应用 3 天的大促需要吗?有时候对于有些产品的需要真的是太简单了,简单到开发、测试都须要在整个过程中一直的学习最初才可能读懂产品为啥这样的玩,要是一个长期的流动可能也就算了,造就用户心智吗!但这一整套拉新、助力、激活、下单、投保、领券、生产、开红包等等一连串的骚操作下来,如果在线上只用 3 天呢,或者是只用 1 天,那 TM 连参加的用户都没弄明确呢,流动就完结了,最初能打来什么样好的数据呢?对于这样流程简单,预计连羊毛当都看不上!!!

以上只是举个例子,大部分时候并不会搞的这么恶心,评审也是过不去的!而同样的情理用在程序设计开发和应用中也是一样的,如果你把你的代码逻辑实现的过于扩散,让内部调用方在应用的时候,须要调用你的接口多个和屡次,还没有音讯触达,只能定时本人轮训你的接口查看订单状态,每次还只能查 10 条,查多了你说不行,等等反人类的设计,都会给调用方带来要干你的领会。

所以,如果咱们能在实现目标的状况下,都是心愿尽可能流程简略、模式清晰、主动服务。那这在 Spring 的框架中也是有所体现的,这个框架的遍及应用水平和它所能带来的方便性是分不开的,而咱们如果能做到如此的不便,那必定是一种好的设计和实现。

二、指标

其实到本章节咱们曾经把对于 IOC 和 AOP 全副核心内容都曾经实现实现了,只不过在应用上还有点像晚期的 Spring 版本,须要一个一个在 spring.xml 中进行配置。这与理论的目前应用的 Spring 框架还是有蛮大的差异,而这种差异其实都是在外围性能逻辑之上建设的在更少的配置下,做到更简化的应用。

这其中就包含:包的扫描注册、注解配置的应用、占位符属性的填充等等,而咱们的指标就是在目前的外围逻辑上填充一些自动化的性能,让大家能够学习到这部分的设计和实现,从中领会到一些对于代码逻辑的实现过程,总结一些编码教训。

三、计划

首先咱们要思考🤔,为了能够简化 Bean 对象的配置,让整个 Bean 对象的注册都是主动扫描的,那么根本须要的元素包含:扫描门路入口、XML 解析扫描信息、给须要扫描的 Bean 对象做注解标记、扫描 Class 对象摘取 Bean 注册的根本信息,组装注册信息、注册成 Bean 对象。那么在这些条件元素的撑持下,就能够实现出通过自定义注解和配置扫描门路的状况下,实现 Bean 对象的注册。除此之外再顺带解决一个配置中占位符属性的知识点,比方能够通过 ${token} 给 Bean 对象注入进去属性信息,那么这个操作须要用到 BeanFactoryPostProcessor,因为它能够解决 在所有的 BeanDefinition 加载实现后,实例化 Bean 对象之前,提供批改 BeanDefinition 属性的机制 而实现这部分内容是为了后续把此类内容联合到自动化配置解决中。整体设计构造如下图:

联合 bean 的生命周期,包扫描只不过是扫描特定注解的类,提取类的相干信息组装成 BeanDefinition 注册到容器中。

在 XmlBeanDefinitionReader 中解析 <context:component-scan /> 标签,扫描类组装 BeanDefinition 而后注册到容器中的操作在 ClassPathBeanDefinitionScanner#doScan 中实现。

  • 主动扫描注册次要是扫描增加了自定义注解的类,在 xml 加载过程中提取类的信息,组装 BeanDefinition 注册到 Spring 容器中。
  • 所以咱们会用到 <context:component-scan /> 配置包门路并在 XmlBeanDefinitionReader 解析并做相应的解决。这里的解决会包含对类的扫描、获取注解信息等
  • 最初还包含了一部分对于 BeanFactoryPostProcessor 的应用,因为咱们须要实现对占位符配置信息的加载,所以须要应用到 BeanFactoryPostProcessor 在所有的 BeanDefinition 加载实现后,实例化 Bean 对象之前,批改 BeanDefinition 的属性信息。这一部分的实现也为后续解决对于占位符配置到注解上做筹备

四、实现

1. 工程构造

small-spring-step-13
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework
    │           ├── aop
    │           │   ├── aspectj
    │           │   │   └── AspectJExpressionPointcut.java
    │           │   │   └── AspectJExpressionPointcutAdvisor.java
    │           │   ├── framework 
    │           │   │   ├── adapter
    │           │   │   │   └── MethodBeforeAdviceInterceptor.java
    │           │   │   ├── autoproxy
    │           │   │   │   └── MethodBeforeAdviceInterceptor.java
    │           │   │   ├── AopProxy.java
    │           │   │   ├── Cglib2AopProxy.java
    │           │   │   ├── JdkDynamicAopProxy.java
    │           │   │   ├── ProxyFactory.java
    │           │   │   └── ReflectiveMethodInvocation.java
    │           │   ├── AdvisedSupport.java
    │           │   ├── Advisor.java
    │           │   ├── BeforeAdvice.java
    │           │   ├── ClassFilter.java
    │           │   ├── MethodBeforeAdvice.java
    │           │   ├── MethodMatcher.java
    │           │   ├── Pointcut.java
    │           │   ├── PointcutAdvisor.java
    │           │   └── TargetSource.java
    │           ├── beans
    │           │   ├── factory
    │           │   │   ├── config
    │           │   │   │   ├── AutowireCapableBeanFactory.java
    │           │   │   │   ├── BeanDefinition.java
    │           │   │   │   ├── BeanFactoryPostProcessor.java
    │           │   │   │   ├── BeanPostProcessor.java
    │           │   │   │   ├── BeanReference.java
    │           │   │   │   ├── ConfigurableBeanFactory.java
    │           │   │   │   ├── InstantiationAwareBeanPostProcessor.java
    │           │   │   │   └── SingletonBeanRegistry.java
    │           │   │   ├── support
    │           │   │   │   ├── AbstractAutowireCapableBeanFactory.java
    │           │   │   │   ├── AbstractBeanDefinitionReader.java
    │           │   │   │   ├── AbstractBeanFactory.java
    │           │   │   │   ├── BeanDefinitionReader.java
    │           │   │   │   ├── BeanDefinitionRegistry.java
    │           │   │   │   ├── CglibSubclassingInstantiationStrategy.java
    │           │   │   │   ├── DefaultListableBeanFactory.java
    │           │   │   │   ├── DefaultSingletonBeanRegistry.java
    │           │   │   │   ├── DisposableBeanAdapter.java
    │           │   │   │   ├── FactoryBeanRegistrySupport.java
    │           │   │   │   ├── InstantiationStrategy.java
    │           │   │   │   └── SimpleInstantiationStrategy.java  
    │           │   │   ├── support
    │           │   │   │   └── XmlBeanDefinitionReader.java
    │           │   │   ├── Aware.java
    │           │   │   ├── BeanClassLoaderAware.java
    │           │   │   ├── BeanFactory.java
    │           │   │   ├── BeanFactoryAware.java
    │           │   │   ├── BeanNameAware.java
    │           │   │   ├── ConfigurableListableBeanFactory.java
    │           │   │   ├── DisposableBean.java
    │           │   │   ├── FactoryBean.java
    │           │   │   ├── HierarchicalBeanFactory.java
    │           │   │   ├── InitializingBean.java
    │           │   │   ├── ListableBeanFactory.java
    │           │   │   └── PropertyPlaceholderConfigurer.java
    │           │   ├── BeansException.java
    │           │   ├── PropertyValue.java
    │           │   └── PropertyValues.java 
    │           ├── context
    │           │   ├── annotation
    │           │   │   ├── ClassPathBeanDefinitionScanner.java 
    │           │   │   ├── ClassPathScanningCandidateComponentProvider.java 
    │           │   │   └── Scope.java 
    │           │   ├── event
    │           │   │   ├── AbstractApplicationEventMulticaster.java 
    │           │   │   ├── ApplicationContextEvent.java 
    │           │   │   ├── ApplicationEventMulticaster.java 
    │           │   │   ├── ContextClosedEvent.java 
    │           │   │   ├── ContextRefreshedEvent.java 
    │           │   │   └── SimpleApplicationEventMulticaster.java 
    │           │   ├── support
    │           │   │   ├── AbstractApplicationContext.java 
    │           │   │   ├── AbstractRefreshableApplicationContext.java 
    │           │   │   ├── AbstractXmlApplicationContext.java 
    │           │   │   ├── ApplicationContextAwareProcessor.java 
    │           │   │   └── ClassPathXmlApplicationContext.java 
    │           │   ├── ApplicationContext.java 
    │           │   ├── ApplicationContextAware.java 
    │           │   ├── ApplicationEvent.java 
    │           │   ├── ApplicationEventPublisher.java 
    │           │   ├── ApplicationListener.java 
    │           │   └── ConfigurableApplicationContext.java
    │           ├── core.io
    │           │   ├── ClassPathResource.java 
    │           │   ├── DefaultResourceLoader.java 
    │           │   ├── FileSystemResource.java 
    │           │   ├── Resource.java 
    │           │   ├── ResourceLoader.java
    │           │   └── UrlResource.java
    │           ├── stereotype
    │           │   └── Component.java
    │           └── utils
    │               └── ClassUtils.java
    └── test
        └── java
            └── cn.bugstack.springframework.test
                ├── bean
                │   ├── IUserService.java
                │   └── UserService.java
                └── ApiTest.java

工程源码 公众号「bugstack 虫洞栈」,回复:Spring 专栏,获取残缺源码

在 Bean 的生命周期中主动加载包扫描注册 Bean 对象和设置占位符属性的类关系,如图 14-2

  • 整个类的关系构造来看,其实波及的内容并不多,次要包含的就是 xml 解析类 XmlBeanDefinitionReader 对 ClassPathBeanDefinitionScanner#doScan 的应用。
  • 在 doScan 办法中解决所有指定门路下增加了注解的类,拆解出类的信息:名称、作用范畴等,进行创立 BeanDefinition 好用于 Bean 对象的注册操作。
  • PropertyPlaceholderConfigurer 目前看上去像一块独自的内容,后续会把这块的内容与主动加载 Bean 对象进行整合,也就是能够在注解上应用占位符配置一些在配置文件里的属性信息。

2. 解决占位符配置

cn.bugstack.springframework.beans.factory.PropertyPlaceholderConfigurer

public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor {

    /**
     * Default placeholder prefix: {@value}
     */
    public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";

    /**
     * Default placeholder suffix: {@value}
     */
    public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";

    private String location;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 加载属性文件
        try {DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
            Resource resource = resourceLoader.getResource(location);
            Properties properties = new Properties();
            properties.load(resource.getInputStream());

            String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
            for (String beanName : beanDefinitionNames) {BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);

                PropertyValues propertyValues = beanDefinition.getPropertyValues();
                for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {Object value = propertyValue.getValue();
                    if (!(value instanceof String)) continue;
                    String strVal = (String) value;
                    StringBuilder buffer = new StringBuilder(strVal);
                    int startIdx = strVal.indexOf(DEFAULT_PLACEHOLDER_PREFIX);
                    int stopIdx = strVal.indexOf(DEFAULT_PLACEHOLDER_SUFFIX);
                    if (startIdx != -1 && stopIdx != -1 && startIdx < stopIdx) {String propKey = strVal.substring(startIdx + 2, stopIdx);
                        String propVal = properties.getProperty(propKey);
                        buffer.replace(startIdx, stopIdx + 1, propVal);
                        propertyValues.addPropertyValue(new PropertyValue(propertyValue.getName(), buffer.toString()));
                    }
                }
            }
        } catch (IOException e) {throw new BeansException("Could not load properties", e);
        }
    }

    public void setLocation(String location) {this.location = location;}

}
  • 依赖于 BeanFactoryPostProcessor 在 Bean 生命周期的属性,能够在 Bean 对象实例化之前,扭转属性信息。所以这里通过实现 BeanFactoryPostProcessor 接口,实现对配置文件的加载以及摘取占位符中的在属性文件里的配置。
  • 这样就能够把提取到的配置信息搁置到属性配置中了,buffer.replace(startIdx, stopIdx + 1, propVal); propertyValues.addPropertyValue

3. 定义拦挡注解

cn.bugstack.springframework.context.annotation.Scope

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {String value() default "singleton";

}
  • 用于配置作用域的自定义注解,不便通过配置 Bean 对象注解的时候,拿到 Bean 对象的作用域。不过个别都应用默认的 singleton

cn.bugstack.springframework.stereotype.Component

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {String value() default "";

}
  • Component 自定义注解大家都十分相熟了,用于配置到 Class 类上的。除此之外还有 Service、Controller,不过所有的解决形式基本一致,这里就只展现一个 Component 即可。

4. 解决对象扫描拆卸

cn.bugstack.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

public class ClassPathScanningCandidateComponentProvider {public Set<BeanDefinition> findCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();
        Set<Class<?>> classes = ClassUtil.scanPackageByAnnotation(basePackage, Component.class);
        for (Class<?> clazz : classes) {candidates.add(new BeanDefinition(clazz));
        }
        return candidates;
    }

}
  • 这里先要提供一个能够通过配置门路 basePackage=cn.bugstack.springframework.test.bean,解析出 classes 信息的工具办法 findCandidateComponents,通过这个办法就能够扫描到所有 @Component 注解的 Bean 对象了。

cn.bugstack.springframework.context.annotation.ClassPathBeanDefinitionScanner

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {

    private BeanDefinitionRegistry registry;

    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {this.registry = registry;}

    public void doScan(String... basePackages) {for (String basePackage : basePackages) {Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            for (BeanDefinition beanDefinition : candidates) {
                // 解析 Bean 的作用域 singleton、prototype
                String beanScope = resolveBeanScope(beanDefinition);
                if (StrUtil.isNotEmpty(beanScope)) {beanDefinition.setScope(beanScope);
                }
                registry.registerBeanDefinition(determineBeanName(beanDefinition), beanDefinition);
            }
        }
    }

    private String resolveBeanScope(BeanDefinition beanDefinition) {Class<?> beanClass = beanDefinition.getBeanClass();
        Scope scope = beanClass.getAnnotation(Scope.class);
        if (null != scope) return scope.value();
        return StrUtil.EMPTY;
    }

    private String determineBeanName(BeanDefinition beanDefinition) {Class<?> beanClass = beanDefinition.getBeanClass();
        Component component = beanClass.getAnnotation(Component.class);
        String value = component.value();
        if (StrUtil.isEmpty(value)) {value = StrUtil.lowerFirst(beanClass.getSimpleName());
        }
        return value;
    }

}
  • ClassPathBeanDefinitionScanner 是继承自 ClassPathScanningCandidateComponentProvider 的具体扫描包解决的类,在 doScan 中除了获取到扫描的类信息当前,还须要获取 Bean 的作用域和类名,如果不配置类名根本都是把首字母缩写。

5. 解析 xml 中调用扫描

cn.bugstack.springframework.beans.factory.xml.XmlBeanDefinitionReader

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException, DocumentException {SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);
        Element root = document.getRootElement();

        // 解析 context:component-scan 标签,扫描包中的类并提取相干信息,用于组装 BeanDefinition
        Element componentScan = root.element("component-scan");
        if (null != componentScan) {String scanPath = componentScan.attributeValue("base-package");
            if (StrUtil.isEmpty(scanPath)) {throw new BeansException("The value of base-package attribute can not be empty or null");
            }
            scanPackage(scanPath);
        }
       
        // ... 省略其余
            
        // 注册 BeanDefinition
        getRegistry().registerBeanDefinition(beanName, beanDefinition);
    }

    private void scanPackage(String scanPath) {String[] basePackages = StrUtil.splitToArray(scanPath, ',');
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(getRegistry());
        scanner.doScan(basePackages);
    }

}
  • 对于 XmlBeanDefinitionReader 中次要是在加载配置文件后,解决新增的自定义配置属性 component-scan,解析后调用 scanPackage 办法,其实也就是咱们在 ClassPathBeanDefinitionScanner#doScan 性能。
  • 另外这里须要留神,为了能够不便的加载和解析 xml,XmlBeanDefinitionReader 曾经全副替换为 dom4j 的形式进行解析解决。

五、测试

1. 当时筹备

@Component("userService")
public class UserService implements IUserService {

    private String token;

    public String queryUserInfo() {
        try {Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {e.printStackTrace();
        }
        return "小傅哥,100001,深圳";
    }

    public String register(String userName) {
        try {Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {e.printStackTrace();
        }
        return "注册用户:" + userName + "success!";
    }

    @Override
    public String toString() {return "UserService#token = {" + token + "}";
    }

    public String getToken() {return token;}

    public void setToken(String token) {this.token = token;}
}
  • 给 UserService 类增加一个自定义注解 @Component("userService") 和一个属性信息 String token。这是为了别离测试包扫描和占位符属性。

2. 属性配置文件

token=RejDlI78hu223Opo983Ds
  • 这里配置一个 token 的属性信息,用于通过占位符的形式进行获取

3. spring.xml 配置对象

spring-property.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context">

    <bean class="cn.bugstack.springframework.beans.factory.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:token.properties"/>
    </bean>

    <bean id="userService" class="cn.bugstack.springframework.test.bean.UserService">
        <property name="token" value="${token}"/>
    </bean>

</beans>
  • 加载 classpath:token.properties 设置占位符属性值 ${token}

spring-scan.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context">

    <context:component-scan base-package="cn.bugstack.springframework.test.bean"/>

</beans>
  • 增加 component-scan 属性,设置包扫描根门路

4. 单元测试(占位符)

@Test
public void test_property() {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring-property.xml");
    IUserService userService = applicationContext.getBean("userService", IUserService.class);
    System.out.println("测试后果:" + userService);
}

测试后果

测试后果:UserService#token = {RejDlI78hu223Opo983Ds}

Process finished with exit code 0
  • 通过测试后果能够看到 UserService 中的 token 属性曾经通过占位符的形式设置进去配置文件里的 token.properties 的属性值了。

5. 单元测试(包扫描)

@Test
public void test_scan() {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring-scan.xml");
    IUserService userService = applicationContext.getBean("userService", IUserService.class);
    System.out.println("测试后果:" + userService.queryUserInfo());
}

测试后果

测试后果:小傅哥,100001,深圳

Process finished with exit code 0
  • 通过这个测试后果能够看进去,当初应用注解的形式就能够让 Class 注册实现 Bean 对象了。

六、总结

  • 通过整篇的内容实现能够看进去,目前的性能增加其实曾经不简单了,都是在 IOC 和 AOP 外围的根底上来补全性能。这些补全的性能也是在欠缺 Bean 的生命周期,让整个性能应用也越来越容易。
  • 在你一直的实现着 Spring 的各项性能时,也能够把本人在平时应用 Spring 的一些性能想法融入进来,比方像 Spring 是如何动静切换数据源的,线程池是怎么提供配置的,这些内容尽管不是最根底的外围范畴,但也十分重要。
  • 可能有些时候这些类实现的内容对新人来说比拟多,能够一点点入手实现逐渐了解,在把一些略微较有难度的内容实现后,其实前面也就没有那么难了解了。

七、系列举荐

  • 13 年毕业,用两年工夫从外包走进互联网大厂
  • 工作两三年了,整不明确架构图都画啥?
  • 面试现场:小伙伴美团一面的分享和剖析(含解答)
  • LinkedList 插入速度比 ArrayList 快?你确定吗?
  • Netty+JavaFx 实战:仿桌面版微信聊天
正文完
 0