3
头图

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

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

Chapter Table of Contents (Handwritten Spring to let you know more)

I. Introduction

writing code is to constantly toss from being able to use to being easy to use!

Have you heard of perturbation functions? Have you written about Fibonacci hashes? Have you implemented the Mason rotation algorithm? Why have never heard of these can not write code! No, even if you haven’t heard of it, you can write the same code. For example, the database routing data you implement always falls in 1 database and 1 table, it is not hashed and distributed, and the lottery system you implement is always the largest red envelope of operational configuration. Sending it out increases the operating cost, and the spike system you develop always hangs up 1 second after the start, and the goods cannot be delivered at all.

In addition to some programmers who only treat coding as a bricks-and-mortar to deal with their work, there are also some programmers who are always pursuing the ultimate. can still make money by writing code, so happy! this code farmers will always consider 🤔 there is no better implement the logic to make the code not only can, but also easy to use it? In fact, the pursuit of this point to completion requires a lot of extensible learning and deep mining, so that the system you design can be more comprehensive and can deal with various complex scenarios.

2. Goal

With the support of the current two core functional modules of IOC and AOP, the registration and acquisition of Bean objects can be fully managed, but this way of use always feels like slash and burn is a bit difficult to use. Therefore, in the previous chapter, we solved the need to manually configure the Bean object to the spring.xml file. Instead, it can automatically scan @Component to complete the automatic assembly and registration to the Spring container.

Then after the automatic scanning package registers the Bean object, you need to change the original configuration file through property name="token" configure the attributes and Bean operations to be automatically injected. This is just like we use the @Autowired and @Value annotations in the Spring framework to complete our injection operations on attributes and objects.

Three, the plan

In fact, after we have completed the basic functions of the Bean object, the subsequent functions added are all around the life cycle of the Bean, such as modifying the definition of Bean BeanFactoryPostProcessor, processing the attributes of the Bean must use BeanPostProcessor, and completing individual attribute operations Specially inherit BeanPostProcessor to provide a new interface, because in this way, the marked interface can be judged by instanceof. Therefore, operations on Beans, etc., as well as monitoring Aware and obtaining BeanFactory, all need to be completed in the life cycle of the Bean. So when we design attributes and Bean object injection, we will also use BeanPostProcessor to allow BeanPostProcessor to modify attribute values before setting Bean attributes. The overall design structure is as follows:

  • To handle automatic scan injection, including property injection and object injection, you need to write the property information into the PropertyValues collection before filling the applyPropertyValues This step is equivalent to solving the previous process of configuring properties in spring.xml.
  • In the reading of attributes, it is necessary to rely on the scanning of the annotations configured for the attributes in the class of the Bean object. field.getAnnotation(Value.class); takes out the corresponding attributes in turn and fills in the corresponding configuration information. There is one point here. The configuration information of the attribute needs to depend on the implementation class PropertyPlaceholderConfigurer of BeanFactoryPostProcessor, and write the value to the embeddedValueResolvers collection of AbstractBeanFactory, so that the beanFactory can be used in the attribute filling to obtain the corresponding attribute value
  • Another is about @Autowired's injection of objects. In fact, the only difference between this one and property injection is the acquisition of objects beanFactory.getBean(fieldType) , and the others are no different.
  • When all the properties are set to PropertyValues, the next step is to create the object, property filling, and at this time, the configuration and objects we obtained one by one will be filled into the properties, and automatic injection is realized. Function.

Fourth, realize

1. Engineering structure

small-spring-step-14
└── 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  
    │           │   │   ├── annotation
    │           │   │   │   ├── Autowired.java
    │           │   │   │   ├── AutowiredAnnotationBeanPostProcessor.java
    │           │   │   │   ├── Qualifier.java
    │           │   │   │   └── Value.java
    │           │   │   ├── 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
    │               └── StringValueResolver.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 scan the injected placeholder configuration and the class relationship of the object, as shown in Figure 15-2

图 15-2

  • In the entire class diagram, the class AutowiredAnnotationBeanPostProcessor that implements the interface InstantiationAwareBeanPostProcessor is used as the entry point, which is called by AbstractAutowireCapableBeanFactory during the creation of the Bean object. The property configuration of the entire class contains custom annotations Value , Autowired , Qualifier , and the property values.
  • The slight change here is about the acquisition of attribute value information. When the attribute field of the annotation configuration scans to the information injection, the placeholder is included. The information obtained from the configuration file also includes the Bean object. The Bean object can be obtained directly, but the configuration information needs Add a new set of properties embeddedValueResolvers in AbstractBeanFactory, which is filled into the property set by PropertyPlaceholderConfigurer#postProcessBeanFactory.

2. Fill the read attribute to the container

defines the interface for parsing strings

cn.bugstack.springframework.util.StringValueResolver

public interface StringValueResolver {

    String resolveStringValue(String strVal);

}
  • The interface StringValueResolver is an interface for parsing string operations

Fill string

public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            // 加载属性文件
            DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
            Resource resource = resourceLoader.getResource(location);
            
            // ... 占位符替换属性值、设置属性值

            // 向容器中添加字符串解析器,供解析@Value注解使用
            StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(properties);
            beanFactory.addEmbeddedValueResolver(valueResolver);
            
        } catch (IOException e) {
            throw new BeansException("Could not load properties", e);
        }
    }

    private class PlaceholderResolvingStringValueResolver implements StringValueResolver {

        private final Properties properties;

        public PlaceholderResolvingStringValueResolver(Properties properties) {
            this.properties = properties;
        }

        @Override
        public String resolveStringValue(String strVal) {
            return PropertyPlaceholderConfigurer.this.resolvePlaceholder(strVal, properties);
        }

    }

}
  • In the class PropertyPlaceholderConfigurer that parses the property configuration, the most important thing is actually the operation of this line of code beanFactory.addEmbeddedValueResolver(valueResolver) which writes the property value into the embeddedValueResolvers of AbstractBeanFactory.
  • Here is a description, embeddedValueResolvers is a newly added collection of AbstractBeanFactory class List<StringValueResolver> embeddedValueResolvers String resolvers to apply eg to annotation attribute values

3. Custom attribute injection annotation

custom annotations, Autowired, Qualifier, Value

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
public @interface Autowired {
}

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {

    String value() default "";

}  

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {

    /**
     * The actual value expression: e.g. "#{systemProperties.myProp}".
     */
    String value();

}
  • Three annotations are also very common in our daily use of Spring, injecting objects and injecting properties, and Qualifier is generally used in conjunction with Autowired.

4. Scan custom annotations

cn.bugstack.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor

public class AutowiredAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {

    private ConfigurableListableBeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }

    @Override
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        // 1. 处理注解 @Value
        Class<?> clazz = bean.getClass();
        clazz = ClassUtils.isCglibProxyClass(clazz) ? clazz.getSuperclass() : clazz;

        Field[] declaredFields = clazz.getDeclaredFields();

        for (Field field : declaredFields) {
            Value valueAnnotation = field.getAnnotation(Value.class);
            if (null != valueAnnotation) {
                String value = valueAnnotation.value();
                value = beanFactory.resolveEmbeddedValue(value);
                BeanUtil.setFieldValue(bean, field.getName(), value);
            }
        }

        // 2. 处理注解 @Autowired
        for (Field field : declaredFields) {
            Autowired autowiredAnnotation = field.getAnnotation(Autowired.class);
            if (null != autowiredAnnotation) {
                Class<?> fieldType = field.getType();
                String dependentBeanName = null;
                Qualifier qualifierAnnotation = field.getAnnotation(Qualifier.class);
                Object dependentBean = null;
                if (null != qualifierAnnotation) {
                    dependentBeanName = qualifierAnnotation.value();
                    dependentBean = beanFactory.getBean(dependentBeanName, fieldType);
                } else {
                    dependentBean = beanFactory.getBean(fieldType);
                }
                BeanUtil.setFieldValue(bean, field.getName(), dependentBean);
            }
        }

        return pvs;
    }

}
  • AutowiredAnnotationBeanPostProcessor is a class and operation method that implements the interface InstantiationAwareBeanPostProcessor and is used to process the attribute information before setting the attribute operation after the Bean object is instantiated. Only when the BeanPostProcessor interface is implemented can you have the opportunity to process the initialization information in the life cycle of the Bean
  • The core method postProcessPropertyValues is mainly used to process properties annotated with @Value and @Autowired in the class, and to extract and set property information.
  • One thing to note here is that because we use CglibSubclassingInstantiationStrategy in the AbstractAutowireCapableBeanFactory class to create the class, we need to determine whether to create an object for CGlib in AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues, otherwise the class information cannot be obtained correctly. ClassUtils.isCglibProxyClass(clazz) ? clazz.getSuperclass() : clazz;

5. Call attribute injection in the life cycle of Bean

cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {

    private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
        Object bean = null;
        try {
            // 判断是否返回代理 Bean 对象
            bean = resolveBeforeInstantiation(beanName, beanDefinition);
            if (null != bean) {
                return bean;
            }
            // 实例化 Bean
            bean = createBeanInstance(beanDefinition, beanName, args);
            // 在设置 Bean 属性之前,允许 BeanPostProcessor 修改属性值
            applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition);
            // 给 Bean 填充属性
            applyPropertyValues(beanName, bean, beanDefinition);
            // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法
            bean = initializeBean(beanName, bean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }

        // 注册实现了 DisposableBean 接口的 Bean 对象
        registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);

        // 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPE
        if (beanDefinition.isSingleton()) {
            registerSingleton(beanName, bean);
        }
        return bean;
    }

    /**
     * 在设置 Bean 属性之前,允许 BeanPostProcessor 修改属性值
     *
     * @param beanName
     * @param bean
     * @param beanDefinition
     */
    protected void applyBeanPostProcessorsBeforeApplyingPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) {
        for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {
            if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor){
                PropertyValues pvs = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessPropertyValues(beanDefinition.getPropertyValues(), bean, beanName);
                if (null != pvs) {
                    for (PropertyValue propertyValue : pvs.getPropertyValues()) {
                        beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
                    }
                }
            }
        }
    }  

    // ...
}
  • The AbstractAutowireCapableBeanFactory#createBean method has this newly added method call, which is to allow the BeanPostProcessor to modify the attribute value before setting the Bean attribute applyBeanPostProcessorsBeforeApplyingPropertyValues
  • Then in the applyBeanPostProcessorsBeforeApplyingPropertyValues method, the first step is to obtain the injected BeanPostProcessor collection and filter out the implementation classes that inherit the interface InstantiationAwareBeanPostProcessor.
  • The last is to call the corresponding postProcessPropertyValues method and set the property value information circularly, beanDefinition.getPropertyValues().addPropertyValue(propertyValue);

Five, test

1. Prepare in advance

Configure Dao

@Component
public class UserDao {

    private static Map<String, String> hashMap = new HashMap<>();

    static {
        hashMap.put("10001", "小傅哥,北京,亦庄");
        hashMap.put("10002", "八杯水,上海,尖沙咀");
        hashMap.put("10003", "阿毛,香港,铜锣湾");
    }

    public String queryUserName(String uId) {
        return hashMap.get(uId);
    }

}
  • Configure the class with an annotation @Component that automatically scans registered Bean objects, and then inject this class into UserService.

annotation is injected into UserService

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

    @Value("${token}")
    private String token;

    @Autowired
    private UserDao userDao;

    public String queryUserInfo() {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return userDao.queryUserName("10001") + "," + token;
    }    

    // ...
}
  • There are two types of injections, one is placeholder injection attribute information @Value("${token}") , and the other is injection object information @Autowired

2. Property Configuration File

token.properties

token=RejDlI78hu223Opo983Ds

spring.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>

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

</beans>
  • The scan attribute information and the automatic scan package path range are configured in spring.xml.

3. Unit Testing

@Test
public void test_scan() {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
    IUserService userService = applicationContext.getBean("userService", IUserService.class);
    System.out.println("测试结果:" + userService.queryUserInfo());
}
  • During unit testing, a class can be completely tested and injected into the Spring container, and this attribute information can also be automatically scanned and filled.

test result

测试结果:小傅哥,北京,亦庄,RejDlI78hu223Opo983Ds

Process finished with exit code 0

  • From the test results, we can see that our usage method has been passed, there are automatic scanning classes, and annotation injection properties. This is more and more like using the Spring framework.

Six, summary

  • From the perspective of the implementation content of the entire annotation information scanning injection, we have been focusing on processing in the life cycle of the Bean, just like BeanPostProcessor is used to modify the extension point of the newly instantiated Bean object, and the interface methods provided can be used to process the Bean Processing operations are performed before and after the object is instantiated. Sometimes you need to do some differentiated control, so you need to inherit the BeanPostProcessor interface and define a new interface InstantiationAwareBeanPostProcessor so that you can distinguish the operations of different extension points.
  • For example, the interface is judged by instanceof, and the annotation is obtained by Field.getAnnotation(Value.class);, which are equivalent to some identifying information on the class, so that these function points can be found in some ways for processing. Therefore, these features can also be used in our daily development and design components.
  • When you think about integrating your implementation into the life cycle of a Bean that has been subdivided, you will find that its design is so good that it allows you to be you at any point in time and on any aspect of initialization. Need to expand or change, this is also the flexibility we pursue when doing program design.

Seven, series recommendation


小傅哥
4.7k 声望28.4k 粉丝

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