1
头图

Author: Xiao Fu Ge
Blog: https://bugstack.cn

Precipitate, share, and grow, so that you and others can gain something! 😄

I. Introduction

complicated, and the big promotion is over before I can figure it out!

Have you experienced 618 and double 11? Did you make a few cents for so many complicated marketing activities when you joined the big promotion? Have you developed a big promotion demand that takes a week to read and understand the gameplay but only uses 3 days? Sometimes the requirements for some products are really too complicated. It is so complicated that development and testing require continuous learning throughout the entire process before you can finally understand why the product is like this. If it is a long-term activity, it may be fine. Cultivate the minds of users! But this whole series of operations such as pull new, help, activate, place an order, apply for insurance, receive coupons, consume, open red envelopes, etc., if it only takes 3 days online, or only 1 day, then TM connects The participating users didn’t figure it out, and the event was over. What kind of good data can you get in the end? For such a complicated process, it is estimated that even wool is not worth it! ! !

The above is just an example. Most of the time, it will not be so disgusting, and the review will not be able to pass! The same principle is used in program design, development and use. If you implement your code logic too scattered, external callers need to call your interface multiple and multiple times when using it. When the message is reached, you can only check the order status by rotating your own interface regularly, and you can only check 10 items each time. If you find too many, you can say no, and so on, anti-human design will give the caller the experience of wanting to do you. .

Therefore, if we can accomplish our purpose, we all hope that the process is as simple as possible, the model is clear, and the service is automatic. This is also reflected in the Spring framework. The popularity of this framework is inseparable from the convenience it can bring, and if we can achieve such convenience, it must be a good thing. Design and implementation.

2. Goal

In fact, by this chapter, we have already implemented all the core content about IOC and AOP, but it is a bit like the early Spring version in use, and needs to be configured in spring.xml one by one. This is still quite different from the actual Spring framework currently used, and this difference is actually built on the core functional logic with less configuration to achieve a more simplified use.

This includes: package scanning registration, the use of annotation configuration, the filling of placeholder attributes, etc., and our goal is to fill in some automated functions on the current core logic, so that everyone can learn this part of the design And implementation, from which I can experience some implementation processes of code logic, and sum up some coding experience.

Three, the plan

First of all, we must consider 🤔. In order to simplify the configuration of the Bean object, so that the registration of the entire Bean object is automatically scanned, then the basic elements required include: scanning path entry, XML parsing and scanning information, and annotating the Bean object that needs to be scanned Mark and scan the Class object to extract the basic information of the Bean registration, assemble the registration information, and register it as a Bean object. Then, with the support of these conditional elements, the registration of the Bean object can be completed by customizing the annotation and configuring the scan path. In addition, by the way, solve the knowledge points of placeholder properties in a configuration. For example, you can ${token} . Then this operation needs to use BeanFactoryPostProcessor, because it can handle after all BeanDefinitions are loaded. Before instantiating the Bean object, it provides a mechanism modify the BeanDefinition properties. This part of the content is implemented for the subsequent integration of such content into the automated configuration processing. The overall design structure is as follows:

Combined with the life cycle of the bean, package scanning is nothing more than scanning specific annotated classes, extracting the relevant information of the class, and assemble it into a BeanDefinition and register it in the container.

The operation of parsing the <context:component-scan /> tag in the XmlBeanDefinitionReader, scanning the class to assemble the BeanDefinition and then registering it in the container is implemented in ClassPathBeanDefinitionScanner#doScan.

  • Automatic scanning registration is mainly to scan the classes with custom annotations, extract the class information during the xml loading process, and assemble the BeanDefinition to register it in the Spring container.
  • So we will use <context:component-scan /> configure the package path and parse it in XmlBeanDefinitionReader and do the corresponding processing. The processing here will include scanning of classes, obtaining annotation information, etc.
  • Finally, it also includes a part of BeanFactoryPostProcessor , because we need to complete the loading of the placeholder configuration information, so we need to use the BeanFactoryPostProcessor. After all BeanDefinitions are loaded and before instantiating the Bean object, modify the attribute information of the BeanDefinition. this part of 1610751ac4bc14 also prepares for the subsequent processing of the placeholder configuration to the annotation

Fourth, realize

1. Engineering structure

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

project source code : public account "bugstack wormhole stack", reply: Spring column, get the complete source code

Automatically load the package in the life cycle of the Bean, scan the class relationship between registered Bean objects and set placeholder attributes, as shown in Figure 14-2

图 14-2

  • From the perspective of the relationship structure of the entire class, there is not much content involved, mainly including the use of ClassPathBeanDefinitionScanner#doScan by the xml parsing class XmlBeanDefinitionReader.
  • In the doScan method, process all the annotated classes under the specified path, disassemble the information of the class: name, scope, etc., and create a BeanDefinition for the registration operation of the Bean object.
  • The PropertyPlaceholderConfigurer currently looks like a separate piece of content, which will be integrated with the auto-loaded Bean object later, that is, you can use placeholders on annotations to configure some property information in the configuration file.

2. Processing placeholder configuration

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;
    }

}
  • Depending on the properties of the BeanFactoryPostProcessor in the life cycle of the Bean, the property information can be changed before the Bean object is instantiated. Therefore, by implementing the BeanFactoryPostProcessor interface, the configuration file is loaded and the configuration in the property file is extracted from the placeholder.
  • In this way, the extracted configuration information can be placed in the attribute configuration, buffer.replace(startIdx, stopIdx + 1, propVal); propertyValues.addPropertyValue

3. Define the interception annotation

cn.bugstack.springframework.context.annotation.Scope

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {

    String value() default "singleton";

}
  • Custom annotations used to configure the scope, so that you can get the scope of the Bean object when you configure the Bean object annotation. but generally use the default singleton

cn.bugstack.springframework.stereotype.Component

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {

    String value() default "";

}
  • Component custom annotations are very familiar to everyone, and are used to configure the Class class. In addition, there are Service and Controller, but all the processing methods are basically the same. Only one Component is shown here.

4. Process object scanning assembly

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;
    }

}
  • First, we need to provide a basePackage=cn.bugstack.springframework.test.bean . Through this method, all Bean objects annotated by @Component can be scanned.

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 is a class that inherits from ClassPathScanningCandidateComponentProvider for specific scanning package processing. After obtaining the scanned class information in doScan, you also need to obtain the scope and class name of the Bean. If the class name is not configured, the acronym is basically abbreviated.

5. Invoke scan in parsing 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);
    }

}
  • About XmlBeanDefinitionReader, after loading the configuration file, the new custom configuration attribute component-scan , and the scanPackage method is called after parsing, which is actually our function in ClassPathBeanDefinitionScanner#doScan.
  • In addition, it should be noted here that in order to facilitate the loading and parsing of xml, XmlBeanDefinitionReader has all been replaced with dom4j for parsing processing.

Five, test

1. Prepare in advance

@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;
    }
}
  • Add a custom annotation @Component("userService") and an attribute information String token to the UserService class. This is to test the package scan and placeholder attributes separately.

2. Property Configuration File

token=RejDlI78hu223Opo983Ds
  • Configure the attribute information of a token here, which is used to obtain it by way of placeholders

3. spring.xml configuration object

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>
  • Load classpath:token.properties set placeholder attribute value ${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>
  • Add component-scan attribute, set the package scan root path

4. Unit test (placeholder)

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

test result

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

Process finished with exit code 0
  • Through the test results, you can see that the token attribute in UserService has been set into the attribute value token.properties

5. Unit Testing (Package Scanning)

@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());
}

test result

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

Process finished with exit code 0
  • It can be seen from this test result that the Bean object can be registered to the Class by using annotations.

Six, summary

  • It can be seen from the realization of the entire content that the current function addition is actually not complicated, and it is all based on the IOC and AOP core to complete the function. These complementary functions are also improving the life cycle of the Bean, making the entire function easier to use.
  • As you continue to implement the various functions of Spring, you can also incorporate some of the functional ideas that you usually use Spring in, such as how Spring dynamically switches data sources, how thread pools provide configuration, although these contents Not the most basic core scope, but it is also very important.
  • Sometimes the content of these types of implementation is more for newcomers, and you can gradually understand it with a little bit of hands. After implementing some slightly more difficult content, in fact, it will not be so difficult to understand later.

Seven, series recommendation


小傅哥
4.7k 声望28.4k 粉丝

CodeGuide | 程序员编码指南 - 原创文章、案例源码、资料书籍、简历模版等下载。