Interpretation of Spring source code -- Analysis of underlying core concepts

1,BeanDefinition

BeanDefinition represents a Bean definition. There are many attributes in BeanDefinition to describe the characteristics of a Bean. For example:

  • class: indicates the Bean type
  • Scope: indicates the Bean scope, singleton or prototype
  • lazyInit: indicates whether the Bean is lazy loaded
  • initMethodName: indicates the method to be executed during Bean initialization
  • destroyMethodName: indicates the method to be executed when the Bean is destroyed
  • Wait...

The declarative way to define beans is often used in Spring:, @ Bean, @ Component(@Service, @ Controller)

Of course, you can also define beans in an editorial way, that is, directly through BeanDefiniton, such as:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
        beanDefinition.setBeanClass(User.class);
        beanDefinition.setScope("prototype"); // Set scope
        beanDefinition.setInitMethodName("init"); // Set initialization method
        beanDefinition.setLazyInit(true); // Set lazy load
        context.registerBeanDefinition("user",beanDefinition);
        System.out.println(context.getBean("user"));

Bean s defined by declarative transactions and editorial transactions will be resolved into corresponding BeanDefinition objects by Spring and put into the Spring container.

2. BeanDefinitionReader (BeanDefinitionReader)

AnnotatedBeanDefinitionReader

A class can be directly converted to BeanDefinition, and the annotations on the class will be parsed

The annotations it can parse are: @ Conditional, @ Scope, @ Lazy, @ Primary, @ DependsOn, @ Role, @ Description

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        AnnotatedBeanDefinitionReader annotatedBeanDefinitionReader = new AnnotatedBeanDefinitionReader(context);
        //Resolve User.class to BeanDefinition
        annotatedBeanDefinitionReader.register(User.class);
        System.out.println(context.getBean("user"));

XmlBeanDefinitionReader

It can parse tags

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(context);
        int i = xmlBeanDefinitionReader.loadBeanDefinitions("spring.xml");
        System.out.println(context.getBean("user"));

ClassPathBeanDefinitionScanner

ClassPathBeanDefinitionScanner is a scanner, but its function is similar to BeanDefinitionReader. It can scan, scan a package path, and parse the scanned class. If there is @ Component annotation on the scanned class, the class will be parsed as a BeanDefinition.

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
        scanner.scan("com.example");
        System.out.println(context.getBean("user"));

3,BeanFactory

BeanFactory represents a Bean factory, which is responsible for creating beans and providing API s for obtaining beans.

ApplicationContext is a kind of BeanFactory. It is defined in the Spring source code as follows:

ApplicationContext inherits listablebeanfactory and hierarchalbeanfactory, and listablebeanfactory and hierarchalbeanfactory inherit from BeanFactory. Therefore, we can think that ApplicationContext inherits BeanFactory and has all the functions supported by BeanFactory. It also has other functions, such as MessageSource for internationalization, ApplicationEventPublisher for event publishing, EnvironmentCapable for obtaining environment variables, and so on.

In the Spring source code, when we create a new ApplicationContext, the bottom layer will create a new BeanFactory. When using some methods in the ApplicationContext, such as getBean(), the bottom layer calls the getBean method of BeanFactory.

A very important implementation class of the BeanFactory interface is DefaultListableBeanFactory

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
        beanDefinition.setBeanClass(User.class);
        beanFactory.registerBeanDefinition("user",beanDefinition);
        System.out.println(beanFactory.getBean("user"));

Class inheritance implementation structure of DefaultListableBeanFactory

DefaultListableBeanFactory has many functions:

  1. Alias registry: supports alias function. A name can correspond to multiple aliases
  2. BeanDefinitionRegistry: you can register, save, remove, and obtain a BeanDefinition
  3. BeanFactory: bean factory, which can obtain a bean object according to the name, type, or alias of a bean
  4. Singletonbean registry: you can directly register and obtain a singleton Bean
  5. Simpleliasregistry: it is a class that implements the functions defined in the alias registry interface and supports alias functions
  6. ListableBeanFactory: on the basis of BeanFactory, other functions are added to obtain the beanNames of all beandefinitions, the corresponding beanNames according to a type, and the mapping relationship of {type: corresponding Bean} according to a type
  7. Hierarchical BeanFactory: on the basis of BeanFactory, the function of obtaining parent BeanFactory is added
  8. DefaultSingletonBeanRegistry: it is a class that implements the SingletonBeanRegistry interface and has the function of directly registering and obtaining a singleton Bean
  9. ConfigurableBeanFactory: on the basis of hierarchical BeanFactory and SingletonBeanRegistry, it adds settings for parent BeanFactory, class loader (indicating that a class loader can be specified for class loading), Spring EL expression parser (indicating that the BeanFactory can parse EL expression), and type conversion service (indicates that the BeanFactory can be type converted), BeanPostProcessor can be added (indicates that the BeanFactory supports the post processor of a Bean), BeanDefinition can be merged, a Bean can be destroyed, and so on
  10. FactoryBean registrysupport: supports the functions of FactoryBean
  11. . AutowireCapableBeanFactory: it directly inherits BeanFactory. Based on BeanFactory, it supports automatic assembly of beans during the process of creating beans
  12. AbstractBeanFactory: it implements the ConfigurableBeanFactory interface and inherits the FactoryBeanRegistrySupport. This BeanFactory has comprehensive functions, but it cannot automatically assemble and obtain beanNames
  13. ConfigurableListableBeanFactory: inherits ListableBeanFactory, AutowireCapableBeanFactory and ConfigurableBeanFactory
  14. AbstractAutowireCapableBeanFactory: inherits AbstractBeanFactory, implements AutowireCapableBeanFactory and has the function of automatic assembly
  15. DefaultListableBeanFactory: it inherits AbstractAutowireCapableBeanFactory and implements the ConfigurableListableBeanFactory interface and BeanDefinitionRegistry interface. Therefore, DefaultListableBeanFactory has powerful functions

4,ApplicationContext

  1. Hierarchical BeanFactory: it has the function of obtaining the parent BeanFactory
  2. ListableBeanFactory: it has the function of obtaining beanNames
  3. Resourcepattern resolver: a resource loader that can obtain multiple resources (file resources, etc.) at one time
  4. EnvironmentCapable: you can get the runtime environment (the runtime environment function is not set)
  5. ApplicationEventPublisher: has the function of broadcasting events (without the function of adding event listeners)
  6. MessageSource: with internationalization function

ApplicationContext two important implementation classes: AnnotationConfigApplicationContext and ClassPathXmlApplicationContext

AnnotationConfigApplicationContext

ClassPathXmlApplicationContext

internationalization

ApplicationContext has the function of internationalization

    @Bean
    public MessageSource messageSource(){
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("message");
        return messageSource;
    }

With this Bean, you can use the MessageSource wherever you want to internationalize. At the same time, because ApplicationContext also has the function of nationalization, you can use it directly

context.getMessage("test", null, new Locale("en_CN")

Resource loading

ApplicationContext also has the function of resource loading. You can directly use ApplicationContext to obtain the content of a file

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        Resource resource = context.getResource("file://D:\\testaa001.txt");
        System.out.println(resource.contentLength());
        System.out.println(resource.getFilename());

        Resource resource1 = context.getResource("https://www.baidu.com");
        System.out.println(resource1.contentLength());
        System.out.println(resource1.getURL());

        Resource resource2 = context.getResource("classpath:com/example/spring/Test.class");
        System.out.println(resource2.contentLength());
        System.out.println(resource2.getURL());

        Resource[] resources = context.getResources("classpath:com/example/spring/*.class");
        for (Resource resource3 : resources) {
            System.out.println(resource3.contentLength());
            System.out.println(resource3.getFilename());
        }

Operation results:

Get runtime environment

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        Map<String,Object> systemEnvironment = context.getEnvironment().getSystemEnvironment();
        System.out.println(systemEnvironment);

        Map<String,Object> systemProperties = context.getEnvironment().getSystemProperties();
        System.out.println(systemProperties);

        MutablePropertySources propertySources = context.getEnvironment().getPropertySources();
        System.out.println(propertySources);

        System.out.println("---------------------");
        System.out.println(context.getEnvironment().getProperty("NO_PROXY"));
        System.out.println(context.getEnvironment().getProperty("sun.jnu.encoding"));
        System.out.println(context.getEnvironment().getProperty("spring"));

You can add parameters in a properties file to the runtime environment through @ PropertySource("classpath:spring.properties").

Event release

Define an event listener

@Bean
    public ApplicationListener applicationListener(){
        return new ApplicationListener() {
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                System.out.println("An event was received");
            }
        };
    }

Then publish an event

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        context.publishEvent("aaa");

Type conversion

1. Type conversion tool provided in JDK: PropertyEditor

public class StringToUser extends PropertyEditorSupport implements PropertyEditor {

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        User user = new User();
        user.setName(text);
        this.setValue(user);
    }
}
StringToUser stringToUser = new StringToUser();
stringToUser.setAsText("Xiao Ming");
User user = (User) stringToUser.getValue();
System.out.println(user);

Register the propertyEditor with Spring

    @Bean
    public CustomEditorConfigurer customEditorConfigurer() {
        CustomEditorConfigurer customEditorConfigurer = new CustomEditorConfigurer();
        Map<Class<?>, Class<? extends PropertyEditor>> propertyEditorMap = new HashMap<>();
        // Indicates that the StringToUserPropertyEditor can convert a String into a User type. In the Spring source code, if the current
        //If the object is String and the required type is User, the PropertyEditor will be used for type conversion
        propertyEditorMap.put(User.class, StringToUser.class);
        customEditorConfigurer.setCustomEditors(propertyEditorMap);
        return customEditorConfigurer;
    }

Suppose you have the following Bean

@Component
public class UserService {

    @Value("Small ming")
    private User user;

    public void test(){
        System.out.println(user.getName());
    }
}


2. The type conversion service ConversionService provided in Spring is more powerful than the property editor

Write a class to implement ConditionalGenericConverter and override the methods in it

public class StringToUserConverter implements ConditionalGenericConverter {
    @Override
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
        return sourceType.getType().equals(String.class) && targetType.getType().equals(User.class);
    }

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
        return Collections.singleton(new ConvertiblePair(String.class,User.class));
    }

    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        User user = new User();
        user.setName((String) source);
        return user;
    }
}
DefaultConversionService conversionService = new DefaultConversionService();
        conversionService.addConverter(new StringToUserConverter());
        User user = conversionService.convert("Xiao Ming",User.class);
        System.out.println(user.getName());


Register ConversionService with Spring

@Bean
    public ConversionServiceFactoryBean conversionService(){
        ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
        conversionServiceFactoryBean.setConverters(Collections.singleton(new StringToUserConverter()));
        return conversionServiceFactoryBean;
    }

3. TypeConverter integrates the functions of PropertyEditor and ConversionService and is used internally in Spring

SimpleTypeConverter typeConverter = new SimpleTypeConverter();
typeConverter.registerCustomEditor(User.class,new StringToUser());
User user = typeConverter.convertIfNecessary("Xiao Mingming",User.class);
System.out.println(user.getName());


OrderComparator

OrderComparator is a comparator provided by Spring. It can be used to compare values according to the @ Order annotation or implement the Ordered interface, so as to sort.

public class A implements Ordered {
    @Override
    public int getOrder() {
        return 3;
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName();
    }
}
public class B implements Ordered {
    @Override
    public int getOrder() {
        return 2;
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName();
    }
}
 A a = new A();
 B b = new B();
 OrderComparator comparator = new OrderComparator();
 System.out.println(comparator.compare(a,b));

List list = new ArrayList<>();
list.add(a);
list.add(b);
list.sort(comparator);
System.out.println(list);


Spring also provides a subclass of OrderComparator: annotationawarerodecomparator, which supports @ order to specify the order value.

@Order(3)
public class A {

    @Override
    public String toString() {
        return this.getClass().getSimpleName();
    }
}
@Order(2)
public class B{

    @Override
    public String toString() {
        return this.getClass().getSimpleName();
    }
}
A a = new A();
B b = new B();
AnnotationAwareOrderComparator comparator = new AnnotationAwareOrderComparator();
System.out.println(comparator.compare(a,b));

List list = new ArrayList<>();
list.add(a);
list.add(b);
list.sort(comparator);
System.out.println(list);

5,BeanPostProcessor

BeanPostProcess represents Bena's post processor. We can define one or more beanpostprocessors

@Component
public class WlBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if("userService".equals(beanName)){
            System.out.println("Before initialization");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if("userService".equals(beanName)){
            System.out.println("After initialization");
        }
        return bean;
    }
}


A BeanPostProcessor can make additional user-defined logic before and after the initialization of any Bean. We can judge the beanName for targeted processing (for a Bean or a department Bean). We can interfere with the process of Spring creating beans by defining BeanPostProcessor.

6,BeanFactoryPostProcessor

Beanfactoryprocessor represents the post processor of a Bean factory, which is actually similar to BeanPostProcessor.

BeanPostProcessor is the creation process of interference Bean, and beanfactorypost processor is the creation process of interference BeanFactory.

@Component
public class WlBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("machining beanFactory");
    }
}

7,FactoryBean

Through FactoryBean, we can create a Bean ourselves

@Component
public class WlFactoryBean implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        UserService userService = new UserService();
        return userService;
    }

    @Override
    public Class<?> getObjectType() {
        return UserService.class;
    }
}

Through the above code, we create a UserService object, which is also a Bean. However, the Bean of UserService created in this way will only be initialized, and other Spring life cycle steps will not go through, such as dependency injection. The difference between the object generated by @ Bean and the Bean is that the Bean defined by @ Bean will go through the complete Bean life cycle.

8. ExcludeFilter and IncludeFilter

These two filters are used for filtering during Spring scanning. ExcludeFilter means exclude Filter and IncludeFilter means include Filter

The following configuration means that the UserService class is excluded when scanning all classes under the com.example package. Even if it is annotated with @ Component, it will not become a Bean

@ComponentScan(value = "com.example",
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
                classes = UserService.class)})
@Configuration
public class AppConfig {
}

The following configuration will be scanned into a Bean even if there is no @ Component annotation on the UserService class

@ComponentScan(value = "com.example",
        includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
                classes = UserService.class)})
@Configuration
public class AppConfig {
}

FilterType is divided into:

  1. ANNOTATION: indicates whether an ANNOTATION is included
  2. ASSIGNABLE_TYPE: indicates whether it is a class
  3. AspectJ: indicates whether it conforms to an AspectJ expression
  4. REGEX: indicates whether it conforms to a regular expression
  5. CUSTOM: CUSTOM

In Spring's scanning logic, an AnnotationTypeFilter will be added to includeFilters by default, which means that by default, beans with @ Component annotation on the class will be considered during Spring scanning.

9,MetadataReader,ClassMetadata, AnnotationMetadata

In Spring, you need to parse class information, such as class name, methods in class and annotations on class. These can be called class metadata. Therefore, Spring abstracts class metadata and provides some tool classes.

MetadataReader represents the metadata reader of the class. The default implementation class is SimpleMetadataReader. ASM technology used by SimpleMetadataReader to parse classes.

SimpleMetadataReaderFactory simpleMetadataReaderFactory = new SimpleMetadataReaderFactory();
        //Construct a MetadataReader
        MetadataReader metadataReader = simpleMetadataReaderFactory.getMetadataReader("com.example.spring.UserService");
        //Get a ClassMetadata and get the class name
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        System.out.println(classMetadata.getClassName());
        //Get an AnnotationMetadata and get the annotation information on the class
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        for (String annotationType : annotationMetadata.getAnnotationTypes()) {
            System.out.println(annotationType);
        }

Tags: Java Spring Back-end

Posted on Wed, 10 Nov 2021 18:36:14 -0500 by kratsg