Study Notes: Spring Framework Source Code Part.1 - Basics

Learning video link: https://www.bilibili.com/video/BV1zd4y1L7YD


Preface

If you are a beginner. When you first start reading the Spring source code, you will definitely find it particularly difficult. Because there are so many new things involved. But this just shows that Spring source code is a treasure trove.


Chapter 1 Bean Metadata

1. Review the bean injection method

1、xml

<bean id="user" class="com.ydlclass.User" scope="prototype" autowire="byType" init-method="init" depends-on="a,b" >
    <property name="name" value="jerry"/>
    <property name="age" value="18"/>
</bean>

2. Annotation

@Controller
public class UserController {
    
    }
@Service
@DependsOn(value = {
    
    "xxx", "yyyy"})
public class UserService {
    
    }
@Repository
public class UserDao {
    
    }
@Compoment
@DependsOn(value = {
    
    "xxx", "yyyy"})
public class RedisTemplate {
    
    }

3. Configuration class

@Configuration
public class UserConfiguration {
    
    
    @Bean("user")
    public User user(){
    
    
        User user = new User();
        user.setName("lily");
        user.setAge(20);
        return user;
    }
}

4. @Import annotation

public class MySelector implements ImportSelector {
    
    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    
    
        return new String[]{
    
    "com.ydlclass.UserService","com.ydlclass.UserDao"};
    }
}

@Configuration
@Import(MySelector.class)
public class UserConfiguration {
    
    }

2. Detailed explanation of BeanDefiniiton

When Spring constructs a bean, it is impossible to do new, assign values, and complete method calls as we like when we actively construct it. He needs to summarize the vastly different classes into a [unified descriptive] language. Spring provides an interface BeanDefintion to unify the metadata for describing beans.
Bean metadata is usually data that we configure using xml or annotations. The first step when starting our Spring container is to load the configuration data. These metadata will be loaded into memory and saved in a map in the form of beanDefinitions. .

A BeanDefiniiton probably stores the following information:

  1. Defines the corresponding relationship between id, alias and Bean (BeanDefinitionHolder)
  2. Specific factory method (Class type), including the return type of the factory method and the Method object of the factory method
  3. Constructor, constructor parameter type
  4. Bean class object
  5. Scope, whether to lazy load, etc.

The following is the structure diagram of the BeanDefiniiton class. No class diagram is used here. I think it looks better to use such a diagram here:
Insert image description here

Different beanDefinition sources may have different implementations. Currently, our most commonly used implementation is the GenericBeanDefinition implementation class.

Little knowledge: Generic (general, universal), almost all implementations starting with this word are universal implementations of Spring.

Interface: The interface is used for top-level design and dependency management. The dependence between interfaces determines the coupling between businesses. The interface is the abstraction of capabilities. Abstract
class: implements the core and shared methods of the interface layer. The template method design pattern involves the use of abstract classes, which is reflected in Spring

1. Understand BeanDefinition

The BeanDefinition interface defines a large number of constants and methods:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    
    

	// 常量标志一个bean的作用范围
	String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
	String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;

    // 设置父BeanDefinition,可以只对有父子关系的bean
	void setParentName(@Nullable String parentName);
	String getParentName();
    // bean的类的全限定名
	void setBeanClassName(@Nullable String beanClassName);
	String getBeanClassName();

	void setScope(@Nullable String scope);
	String getScope();

	void setLazyInit(boolean lazyInit);
	boolean isLazyInit();

    // 设置依赖性,被依赖的bean会优先创建
	void setDependsOn(@Nullable String... dependsOn);
	String[] getDependsOn();
	// 是否允许自动装配
	void setAutowireCandidate(boolean autowireCandidate);
	boolean isAutowireCandidate();

    // 设置是否主要bean
	void setPrimary(boolean primary);
	boolean isPrimary();

	// 工厂bean和工厂方法
	void setFactoryBeanName(@Nullable String factoryBeanName);
	String getFactoryBeanName();
	void setFactoryMethodName(@Nullable String factoryMethodName);
	String getFactoryMethodName();

	ConstructorArgumentValues getConstructorArgumentValues();
	default boolean hasConstructorArgumentValues() {
    
    
		return !getConstructorArgumentValues().isEmpty();
	}

    // 使用setter注入时的key-value对,都保存在这里
	MutablePropertyValues getPropertyValues();
	default boolean hasPropertyValues() {
    
    
		return !getPropertyValues().isEmpty();
	}

	// @since 5.1初始化方法和销毁方法
	void setInitMethodName(@Nullable String initMethodName);
	String getInitMethodName();
	void setDestroyMethodName(@Nullable String destroyMethodName);
	String getDestroyMethodName();

	// 为bean设置角色
	void setRole(int role);
	int getRole();

	// bean的描述
	void setDescription(@Nullable String description);
	String getDescription();

	// 返回此bean定义的可解析类型,基于bean类或其他特定元数据。
    // 这通常在运行时合并bean定义上完全解决但不一定是在配置时定义实例上。
	ResolvableType getResolvableType();
	boolean isSingleton();
	boolean isPrototype();
	boolean isAbstract();
}

2、AbstractBeanDefinition

This class implements the common core methods (which is also the role of abstract classes), and provides default values ​​for some member variables:

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
		implements BeanDefinition, Cloneable {
    
    

	// 定义一些常量
	public static final String SCOPE_DEFAULT = "";
	public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;
	public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
	public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
    // ...还有很多

	// 初始化默认值
	private volatile Object beanClass;
	private String scope = SCOPE_DEFAULT
	private boolean autowireCandidate = true;
	private boolean primary = false;
	// ...还有很多

	// 构造器
	protected AbstractBeanDefinition() {
    
    
		this(null, null);
	}

	// 指定构造器参数和属性参数
	protected AbstractBeanDefinition(@Nullable ConstructorArgumentValues cargs, @Nullable MutablePropertyValues pvs) {
    
    
		this.constructorArgumentValues = cargs;
		this.propertyValues = pvs;
	}

	// 使用深拷贝创建一个新的
	protected AbstractBeanDefinition(BeanDefinition original) {
    
    
		
	}

	// 复制一个bean的定义到当前bean,通常父子bean合并时可用
	public void overrideFrom(BeanDefinition other) {
    
    
		
	}

	// ...此处省略其他的方法实现
}

3、GenericBeanDefinition

The implementation of this class is relatively simple and provides methods for setting parent-child relationships and constructing instances. This class and its subclasses are the most commonly used BeanDefinitions in the current version:

public class GenericBeanDefinition extends AbstractBeanDefinition {
    
    

   @Nullable
   private String parentName;

   public GenericBeanDefinition() {
    
    
      super();
   }

   // 通过深拷贝创建一个bean
   public GenericBeanDefinition(BeanDefinition original) {
    
    
      super(original);
   }

   @Override
   public void setParentName(@Nullable String parentName) {
    
    
      this.parentName = parentName;
   }

   @Override
   @Nullable
   public String getParentName() {
    
    
      return this.parentName;
   }

   @Override
   public AbstractBeanDefinition cloneBeanDefinition() {
    
    
      return new GenericBeanDefinition(this);
   }
}

At this point, we can write the following test case:

@Test
public void testGenericBeanDefinition(){
    
    
    GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
    beanDefinition.setBeanClassName("com.ziang.User");
    // 此处类似setter注入的描述
    MutablePropertyValues propertyValues = new MutablePropertyValues();
    propertyValues.addPropertyValue("name","lily");
    propertyValues.addPropertyValue("age",12);
    beanDefinition.setPropertyValues(propertyValues);
}

Note: This test case is extremely simple, but it can describe all classes in the form of strings.

Let's test another bean with an inheritance relationship:


public class Dog {
    
    
    private String color;
    private Integer age;
}

public class TeddyDog extends Dog{
    
    
    private String name;
}

xml can be defined as follows:

<bean id="dog" class="com.ydlclass.Dog">
    <property name="color" value="white"/>
    <property name="age" value="3"/>
</bean>

<bean id="teddyDog" class="com.ydlclass.TeddyDog" parent="dog">
    <property name="name" value="小红"/>
</bean>

If it is defined manually as follows:

@Test
public void testRootBeanDefinition() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    
    
    RootBeanDefinition dog = new RootBeanDefinition();
    dog.setBeanClassName("com.ydlclass.Dog");
    BeanMetadataAttribute color = new BeanMetadataAttribute("color","white");
    BeanMetadataAttribute age = new BeanMetadataAttribute("age","3");
    dog.addMetadataAttribute(color);
    dog.addMetadataAttribute(age);

    // 子Definition的创建需要依赖父Definition
    ChildBeanDefinition teddy = new ChildBeanDefinition("dog");
    teddy.setBeanClassName("com.ydlclass.TeddyDog");
    BeanMetadataAttribute name = new BeanMetadataAttribute("name","doudou");
    teddy.addMetadataAttribute(name);
}

GenericBeanDefinition can replace the above content in many scenarios, but due to historical and other reasons, RootBeanDefinition still exists and is very important. Later normalization processing still requires converting or merging different BeanDefinitions into one RootBeanDefinition:

  • RootBeanDefinition and AbstractBeanDefinition are complementary. RootBeanDefinition defines more attributes based on AbstractBeanDefinition.
  • RootBeanDefinition cannot have a parent BeanDefinition and can be used in conjunction with ChildBeanDefinition to build a parent-child relationship (beans can be inherited).
  • The most commonly used BeanDefinition at present is the implementation of GenericBeanDefinition and its subclasses. GenericBeanDefinition is very powerful and can easily build a parent-child relationship independently.
  • Sometimes for unified calling, different BeanDefinitions can be merged, copied, etc.
    // 转换的
    GenericBeanDefinition definition = new GenericBeanDefinition(teddy);
    // 合并的
    definition.overrideFrom(dog);
    

3. BeanDefinition register

With unified and standard metadata, we can manage it in a unified way, which requires a container to store it. Of course, we can use a collection class like map. Of course, spring does the same thing. It provides us with an interface BeanDefinitionRegistry. As long as you implement this interface, you will have the ability to register beanDefinition.

The BeanDefinitionRegistry interface is as follows:

public interface BeanDefinitionRegistry extends AliasRegistry {
    
    

	// 注册一个BeanDefinition
	void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException;

	void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

	BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

	boolean containsBeanDefinition(String beanName);

	String[] getBeanDefinitionNames();

	int getBeanDefinitionCount();

	boolean isBeanNameInUse(String beanName);

}

A bean can have an id, and multiple names or aliases. AliasRegistry provides us with the ability to register aliases:

public interface AliasRegistry {
    
    
	void registerAlias(String name, String alias);
	void removeAlias(String alias);
	boolean isAlias(String name);
	String[] getAliases(String name);
}

Spring provides us with a super simple implementation of SimpleBeanDefinitionRegistry, as follows:

public class SimpleBeanDefinitionRegistry extends SimpleAliasRegistry implements BeanDefinitionRegistry {
    
    

	// 维持一个map用来保存beanDefinition,就这么简单
	private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);

    // 对beanDefinition的增删查改
	@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {
    
    
		Assert.hasText(beanName, "'beanName' must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");
		this.beanDefinitionMap.put(beanName, beanDefinition);
	}
	@Override
	public void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
    
    
		if (this.beanDefinitionMap.remove(beanName) == null) {
    
    
			throw new NoSuchBeanDefinitionException(beanName);
		}
	}
	@Override
	public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
    
    
		BeanDefinition bd = this.beanDefinitionMap.get(beanName);
		if (bd == null) {
    
    
			throw new NoSuchBeanDefinitionException(beanName);
		}
		return bd;
	}
	@Override
	public boolean containsBeanDefinition(String beanName) {
    
    
		return this.beanDefinitionMap.containsKey(beanName);
	}
	@Override
	public String[] getBeanDefinitionNames() {
    
    
		return StringUtils.toStringArray(this.beanDefinitionMap.keySet());
	}
	@Override
	public int getBeanDefinitionCount() {
    
    
		return this.beanDefinitionMap.size();
	}
	@Override
	public boolean isBeanNameInUse(String beanName) {
    
    
		return isAlias(beanName) || containsBeanDefinition(beanName);
	}

}

Here we write test cases:

@Test
public void testRegistryByJava(){
    
    

    // 定义一个注册器,用来注册和管理BeanDefinition
    BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();

    // 代码方式创建
    GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
    beanDefinition.setBeanClassName("com.ydlclass.User");
    MutablePropertyValues propertyValues = new MutablePropertyValues();
    propertyValues.addPropertyValue("name","lily");
    propertyValues.addPropertyValue("age",12);
    beanDefinition.setPropertyValues(propertyValues);
    // 进行注册
    registry.registerBeanDefinition("user",beanDefinition);

    logger.info("The beanClassName is {}.",beanDefinition.getBeanClassName());
}

4. Load BeanDefinition

Of course, it is impossible for us to manually write the corresponding BeanDefinition for each class. The metadata still needs to be obtained from xml or annotations or configuration classes. Spring also provides us with the corresponding tools.

1. Read xml configuration file

This class completes the reading of BeanDefinition by parsing xml, and registers the BeanDefinition it parsed into a register:

@Test
public void testRegistryByXml(){
    
    

    // 定义一个注册器,用来注册和管理BeanDefinition
    BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();

    // 通过xml文件加载
    XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(registry);
    xmlReader.loadBeanDefinitions("classpath:spring.xml");

    logger.info(Arrays.toString(registry.getBeanDefinitionNames()));
}

The principle will be introduced later.

2. Load annotated beans

@Test
public void testRegistryByAnnotation() {
    
    
    // 定义一个注册器,用来注册和管理BeanDefinition
    BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();

    // 通过配置文件加载
    AnnotatedBeanDefinitionReader annoReader = new AnnotatedBeanDefinitionReader(registry);
    annoReader.register(User.class);
    logger.info(Arrays.toString(registry.getBeanDefinitionNames()));
}

3. Read configuration class

ConfigurationClassBeanDefinitionReader can read configuration classes, but this class does not allow us to use it. This class provides the following methods:

private void loadBeanDefinitionsForConfigurationClass
private void registerBeanDefinitionForImportedConfigurationClass
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod)

It will encapsulate the read metadata into: ConfigurationClassBeanDefinition.

4. Class path scanning

@Test
public void testRegistryByScanner() {
    
    
    // 定义一个注册器,用来注册和管理BeanDefinition
    BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();

    // 通过扫描包的方式
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
    scanner.scan("com.ydlclass");
    logger.info(Arrays.toString(registry.getBeanDefinitionNames()));
}

5. Package scanning process

Whether it is package scanning or other methods, we have several ways to parse a class:

  1. Load a class into memory, obtain the Class object, and obtain metadata through reflection
  2. Directly manipulate bytecode files (.class) and read metadata within the bytecode

There is no doubt that spring chose the second option.

Reason : The second performance is better than the first one. The first one will load all the scanned classes into the heap memory, which will undoubtedly waste space and increase the number of GCs. The second one can be loaded on demand based on metadata.

Let’s take the doScan method of package scanning as an example (ClassPathBeanDefinitionScanner class):

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    
    
    // BeanDefinitionHolder持有 BeanDefinition实例和名字以及别名
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    for (String basePackage : basePackages) {
    
    
        // 这里是具体的扫描过程,找出全部符合过滤器要求的BeanDefinition
        // 返回的BeanDefinition的实际类型为ScannedGenericBeanDefinition
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        // 根据不同的bean类型做统一处理,如附默认值等
        // 因为有些数据我们并没有配置,需要这里做默认处理
        
        for (BeanDefinition candidate : candidates) {
    
    
            // 如果存在,则解析@Scope注解,为候选bean设置代理的方式ScopedProxyMode,XML属性也能配置:scope-resolver、scoped-proxy,可以指定代理方式jdk或者cglib
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            
            // 首先从注解中获取bean的名字,如果没有
            // 使用beanName生成器beanNameGenerator来生成beanName
            // 在注解中的bean的默认名称和xml中是不一致的
            // 注解中如果没有指定名字本质是通过ClassUtil 的 getShortName 方法获取的
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
  
            // 将进一步设置应用于给定的BeanDefinition,使用AbstractBeanDefinition的一些默认属性值
            //设置autowireCandidate属性,即XML的autowire-candidate属性,IoC学习的时候就见过该属性,默认为true,表示该bean支持成为自动注入候选bean
            if (candidate instanceof AbstractBeanDefinition) {
    
     postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            
            // 如果bean定义是AnnotatedBeanDefinition类型,ScannedGenericBeanDefinition同样属于AnnotatedBeanDefinition类型
            if (candidate instanceof AnnotatedBeanDefinition) {
    
    
 // 4 处理类上的其他通用注解:@Lazy, @Primary, @DependsOn, @Role, @Description
           AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            // 检查给定的 beanName,确定相应的bean 定义是否需要注册或与现有bean定义兼容
            if (checkCandidate(beanName, candidate)) {
    
    
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                // 根据proxyMode属性的值,判断是否需要创建scope代理,一般都是不需要的
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

We can take a look at one of the most important methods:

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    
    
    if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
    
    
        // Spring5的新特性,直接从"META-INF/spring.components"组件索引文件中加载符合条件的bean,避免了包扫描,用于提升启动速度
        // Spring5升级的其中一个重点就提升了注解驱动的启动性能,"META-INF/spring.components"这个文件类似于一个“组件索引”文件,我们将需要加载的组件(beean定义)预先的以键值对的样式配置到该文件中,当项目中存在"META-INF/spring.components"文件并且文件中配置了属性时,Spring不会进行包扫描,而是直接读取"META-INF/spring.components"中组件的定义并直接加载,从而达到提升性能的目的。
        return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
    }
    else {
    
    
        return scanCandidateComponents(basePackage);
    }
}

We can add the following dependencies to automatically generate partial indexes:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-indexer</artifactId>
    <version>6.0.3</version>
</dependency>

The compiled file is as follows:

Insert image description here

Of course, we are more concerned about the scanCandidateComponents method:

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    
    
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
    
    
        // 生成完整的资源解析路径
        // com.ydlclass -> classpath*:com/ydlclass/**/*.class
        // 关于资源解析的内容会在后边的课程单独讲
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + '/' + this.resourcePattern;
       
        // 加载所有路径下的资源,我们看到前缀是"classpath*",因此项目依赖的jar包中的相同路径下资源都会被加载进来
        // Spring会将每一个定义的字节码文件加载成为一个Resource资源(包括内部类都是一个Resource资源)
        // 此处是以资源(流)的方式加载(普通文件),而不是将一个类使用类加载器加载到jvm中。
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        boolean traceEnabled = logger.isTraceEnabled();
        boolean debugEnabled = logger.isDebugEnabled();
        
        // 遍历所有的资源文件
        for (Resource resource : resources) {
    
    
            String filename = resource.getFilename();
            // 此处忽略CGLIB生成的代理类文件,这个应该不陌生
            if (filename != null && filename.contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
    
    
                continue;
            }
            if (traceEnabled) {
    
    
                logger.trace("Scanning " + resource);
            }
            try {
    
    
                // getMetadataReader方法会生成一个元数据读取器
                // 我们的例子中是SimpleMetadataReader
                MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

                // 检查读取到的类是否可以作为候选组件,即是否符合TypeFilter类型过滤器的要求
                // 使用IncludeFilter。就算目标类上没有@Component注解,它也会被扫描成为一个Bean
                // 使用ExcludeFilter,就算目标类上面有@Component注解也不会成为Bean
                if (isCandidateComponent(metadataReader)) {
    
    
                    // 构建一个ScannedGenericBeanDefinition
                    ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                    sbd.setSource(resource);
                    if (isCandidateComponent(sbd)) {
    
    
                        if (debugEnabled) {
    
    
                            logger.debug("Identified candidate component class: " + resource);
                        }
                        candidates.add(sbd);
                    } 
                }
            }       
        }
    }
    return candidates;
}

In the source code above, we see that the real example of reading class files is simpleMetadataReader. Spring uses the [read+visitor] method to read bytecodes. Read is responsible for exposing the interface, and visitor is responsible for the actual reading work:

final class SimpleMetadataReader implements MetadataReader {
    
    

	SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
    
    
		SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader);
        // 这里是核心,一个reader需要结合一个visitor
		getClassReader(resource).accept(visitor, PARSING_OPTIONS);
		this.resource = resource;
        // 元数据都是visitor的能力,典型的访问者设计模式
		this.annotationMetadata = visitor.getMetadata();
	}
	
	// 通过资源获取一个ClassReader
	private static ClassReader getClassReader(Resource resource) throws IOException {
    
    
		try (InputStream is = resource.getInputStream()) {
    
    
			try {
    
    
				return new ClassReader(is);
			}
		}
	}
    
    // 提供了通用能力
	@Override
	public ClassMetadata getClassMetadata() {
    
    
		return this.annotationMetadata;
	}
	@Override
	public AnnotationMetadata getAnnotationMetadata() {
    
    
		return this.annotationMetadata;
	}
}

The SimpleAnnotationMetadataReadingVisitor class uses a lot of asm content. It can be seen that when spring reads metadata, it directly reads the content of the class file instead of obtaining it through reflection after loading. We list some of the individual properties and methods and can roughly spy on them. One or two:

final class SimpleAnnotationMetadataReadingVisitor extends ClassVisitor {
    
    

    // 访问一个内部类的方法
	@Override
	public void visitInnerClass(String name, @Nullable String outerName, String innerName, int access) {
    
    
        // ...省略
	}

    // 访问注解的方法
	@Override
	@Nullable
	public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
    
    
		// ...省略
	}

    // 访问方法的方法
	@Override
	@Nullable
	public MethodVisitor visitMethod(
		// ...省略
	}

     // ...省略
}

Some classes are still used, as follows:
Insert image description here
These classes encapsulate the metadata of annotations and classes, providing simpler access methods, which is very simple.

Let’s write a simple example to take a look:

@Test
public void testAsm() throws IOException {
    
    
    Resource resource = new ClassPathResource("com/ydlclass/User.class");
    ClassReader classReader = new ClassReader(resource.getInputStream());
    logger.info(classReader.getClassName());
    // 缺少visitor的reader能力优先,我们只做几个简单的实现
    // visitor实现相对复杂,我们没有必要去学习
    // classReader.accept(xxxVisitor);

    // 返回的对应的常量池的偏移量+1
    // 0-3 cafebaba  4-7 主次版本号 8-9 第一个是10+1
    // 二进制可以使用bined插件查看
    logger.info("The first item is {}.",classReader.getItem(1));
    logger.info("The first item is {}.",classReader.getItem(2));
    // 00 3A 这是字节码文件看到的,
    // 常量池的计数是 1-57 0表示不引用任何一个常量池项目
    logger.info("The first item is {}.",classReader.getItemCount());

    // 通过javap -v  .\User.class   class文件访问标志
    // flags: (0x0021) ACC_PUBLIC, ACC_SUPER   十进制就是33
    // ACC_SUPER   0x00 20	是否允许使用invokespecial字节码指令的新语义.
    // ACC_PUBLIC  0x00 01	是否为Public类型
    logger.info("classReader.getAccess() is {}",classReader.getAccess());

}

Different register methods form different BeanDefinition subclasses:
Insert image description here


Chapter 2 Basic Tools

1. Introspection API

1. What is introspection?

Introspection (IntroSpector) is a default processing method for Bean class properties and events in the Java language. Related APIs often appear in Spring's source code, so we need to understand it.

JavaBean is a special class that is mainly used to transfer data information. The methods in this class are mainly used to access private fields, and the method names conform to certain naming rules. It sounds somewhat similar to reflection. In fact, the introspection mechanism is also implemented through reflection.

Compared with introspection, reflection is more powerful. It can map various components in Java classes into corresponding Java classes in the running state. It can dynamically obtain all attributes and dynamically call any method. The emphasis is on the running state.

The commonly used APIs for introspection and reflection are as follows:
Insert image description hereIn Java introspection, the above classes are basically used.

The general method of introspection api is to obtain the BeanInfo information of an object through the getBeanInfo method of the Introspector class, and then obtain the property descriptor (PropertyDescriptor) through BeanInfo. Through this property descriptor, you can obtain the corresponding access point of a certain property. Value/assignment methods, and then we can call these methods through the reflection mechanism, which is the introspection mechanism.

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

@Test
public void testIntrospect1() throws IntrospectionException {
    
    
    BeanInfo beanInfo = Introspector.getBeanInfo(User.class, Object.class);
    PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
    for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
    
    
        logger.info("{}",propertyDescriptor.getPropertyType());
        logger.info("{}",propertyDescriptor.getReadMethod());
        logger.info("{}",propertyDescriptor.getWriteMethod());
    }
}

// 2.操纵bean的指定属性:age
@Test
public void testIntrospect2() throws Exception {
    
    
    User user = new User();
    PropertyDescriptor pd = new PropertyDescriptor("age", User.class);
    // 得到属性的写方法,为属性赋值
    Method method = pd.getWriteMethod();
    method.invoke(user, 24);
    // 获取属性的值
    method = pd.getReadMethod();
    System.out.println(method.invoke(user, null));
}

The famous BeanUtils can operate beans more simply and elegantly:

@Test
public void testBeanUtil() throws Exception {
    
    
    User user = new User();
    // 赋值
    BeanUtils.setProperty(user,"name","tom");
    BeanUtils.setProperty(user,"age",10);
    logger.info("user->{}",user);
    // 获取值
    logger.info("the user's name is ->{}.",BeanUtils.getProperty(user,"name"));
}

2. Stronger reflection tools

In spring, in addition to introspection-related APIs, we may also see more reflective APIs. Of course, spring also encapsulates the complexity of the native API to make it easier to use.

Spring provides us with the powerful reflection tool BeanWrapper. The following example shows how this class is instantiated with BeanDefinition:

1. Creation of bean

@Test
public void testCreate() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    
    
    // 1、通过任意形式捕获beanDefinition
    GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
    beanDefinition.setBeanClassName("com.ydlclass.User");
    MutablePropertyValues propertyValues = new MutablePropertyValues();
    propertyValues.addPropertyValue("name","lily");
    propertyValues.addPropertyValue("age",12);
    beanDefinition.setPropertyValues(propertyValues);

    // 2、通过权限定名称获得Class
    Class<?> aClass = Class.forName(beanDefinition.getBeanClassName());

    // 3、使用BeanWrapper包裹实例,使其更方便使用反射方法
    BeanWrapper beanWrapper = new BeanWrapperImpl(aClass);
    beanWrapper.setPropertyValues(beanDefinition.getPropertyValues());
    Object bean = beanWrapper.getWrappedInstance();
    logger.info("The bean is [{}]",bean);
}

We can see that BeanWrapperImpl only needs a Class to be very friendly in combination with beanDefinition for construction and assignment. It does not need to obtain the constructor for instantiation through complex reflection and obtain the field object for assignment. Of course, this is only due to the api encapsulation. The principles are still the same.

2. Batch construction

We can use the following method to perform batch construction:

@Test
public void testBatchCreate() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    
    
    // 1、通过任意形式捕获beanDefinition
    BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
    XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(registry);
    xmlReader.loadBeanDefinitions("classpath:spring.xml");

    // 2、通过反射实例化
    String[] definitionNames = registry.getBeanDefinitionNames();
    for (String definitionName : definitionNames) {
    
    
        BeanDefinition beanDefinition = registry.getBeanDefinition(definitionName);
        String beanClassName = beanDefinition.getBeanClassName();
        Class<?> aClass = Class.forName(beanClassName);
        Constructor<?> constructor = aClass.getConstructor();
        Object bean = constructor.newInstance();

        // 3、使用BeanWrapper包裹实例,使其更方便使用反射方法
        BeanWrapper beanWrapper = new BeanWrapperImpl(bean);
        beanWrapper.setPropertyValues(beanDefinition.getPropertyValues());
        bean = beanWrapper.getWrappedInstance();
        System.out.println(bean);
    }
}

It seems to be counterproductive. An exception is thrown at this time, which means that a TypedStringValue type data cannot be converted into an Integer, and there is no suitable converter:

org.springframework.beans.TypeMismatchException: Failed to convert property value of type 'org.springframework.beans.factory.config.TypedStringValue' to required type 'java.lang.Integer' for property 'age';

Let’s press this issue first and explain it later. Then let’s look at a few commonly used classes:

3、ResolvableType

This class can encapsulate Java types, provide access to super class types, interfaces and generic parameters, as well as the ability to finally resolve to classes. This is a very common class, and it can simplify the call to the reflection API extremely conveniently. This class has a very high usage rate in spring.

ResolvableType can be obtained from a field, method parameter, method return type, or class. Most methods on this class will themselves return a ResolvableType to facilitate chaining of calls.

The official case is as follows:

private HashMap<Integer, List<String>> myMap;
public void example() {
    
    
    ResolvableType t = ResolvableType.forField(getClass().getDeclaredField("myMap"));
    t.getSuperType(); // AbstractMap<Integer, List<String>>
    t.asMap(); // Map<Integer, List<String>>
    t.getGeneric(0).resolve(); // Integer  // 获取泛型
    t.getGeneric(1).resolve(); // List
    t.getGeneric(1); // List<String>
    //第二个泛型,里面的泛型,即List<String>里面的String
    t.resolveGeneric(1, 0); // String
}

We can also write test cases to test it. You won’t be able to remember it just by watching it:

@Test
public void testTypeResolvableType() throws NoSuchFieldException {
    
    
    ResolvableType type = ResolvableType.forField(DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects"));
    // 获取类型
    logger.info(type.getType().getTypeName());
    // 获取泛型
    logger.info(Arrays.toString(type.getGenerics()));
    logger.info(Arrays.toString(type.getInterfaces()));
    logger.info(Arrays.toString(type.resolveGenerics()));
    // 获取来源
    Class<?> resolve = type.resolve();
    logger.info(type.getRawClass().getName());
}

Resolvable: parsable, decomposable. This word often appears in spring. Such as ResolvableAttribute, ResolvableType, registerResolvableDependency, we will learn more when we encounter them later.

3. Type conversion

All the data we collect from xml is [string], but the member variables in the actual class may be numbers, arrays, sets, or complex reference data types, so spring provides us with powerful conversion services (conversionService interface).

1. Conversion service

The ConversionService interface is very simple. It can determine whether conversion can be performed based on the source type and target type, and perform the conversion:

public interface ConversionService {
    
    
	boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);

	boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

	@Nullable
	<T> T convert(@Nullable Object source, Class<T> targetType);

	 // 将给定的{@code source}转换为指定的{@code targetType}。
	Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
}

We might as well take a look at the source code of DefaultConversionService. More core functions are implemented in the container parent class. When constructing an instance, it will pass in a large number of available converters by default:

public class DefaultConversionService extends GenericConversionService {
    
    

	@Nullable
	private static volatile DefaultConversionService sharedInstance;

	public DefaultConversionService() {
    
    
        // 添加大量的默认的转换器
		addDefaultConverters(this);
	}
    // 类似单例的获取方式
	public static ConversionService getSharedInstance() {
    
    
		DefaultConversionService cs = sharedInstance;
		if (cs == null) {
    
    
			synchronized (DefaultConversionService.class) {
    
    
				cs = sharedInstance;
				if (cs == null) {
    
    
					cs = new DefaultConversionService();
					sharedInstance = cs;
				}
			}
		}
		return cs;
	}

	// 添加适合大多数环境的转换器
	public static void addDefaultConverters(ConverterRegistry converterRegistry) {
    
    
		addScalarConverters(converterRegistry);
		addCollectionConverters(converterRegistry);

		converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
		converterRegistry.addConverter(new StringToTimeZoneConverter());
		converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
		converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());

		//...还有好多
	}

	// 增加通用的转换器,例如集合、数组、对象等
	public static void addCollectionConverters(ConverterRegistry converterRegistry) {
    
    
		ConversionService conversionService = (ConversionService) converterRegistry;

		converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService));
		converterRegistry.addConverter(new CollectionToArrayConverter(conversionService));

		converterRegistry.addConverter(new StringToCollectionConverter(conversionService));

		converterRegistry.addConverter(new CollectionToObjectConverter(conversionService));
		converterRegistry.addConverter(new ObjectToCollectionConverter(conversionService));

        //...还有好多
	}

    // 新增标量的转化器,主要是字符串数字类型
	private static void addScalarConverters(ConverterRegistry converterRegistry) {
    
    
		converterRegistry.addConverterFactory(new NumberToNumberConverterFactory());
		converterRegistry.addConverterFactory(new StringToNumberConverterFactory());
		converterRegistry.addConverter(Number.class, String.class, new ObjectToStringConverter());
		converterRegistry.addConverter(new StringToPropertiesConverter());
		converterRegistry.addConverter(new PropertiesToStringConverter());
		converterRegistry.addConverter(new StringToUUIDConverter());
        
        //...还有好多
	}
}

2. Write a converter independently

In the previous example, our exception said that TypedStringValue cannot be converted to Integer. TypedStringValue is a wrapper of String by Spring. The specific value is stored in TypedStringValue. Our DefaultConversionService does not have a default converter. Of course, Spring is actually converting It will be processed when the time comes. We can also write our own converters for TypedStringValueToInteger and TypedStringValueToString:

@Test
public void testBatchCreateByBeanWrapper() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    
    
	// 1.通过任意形式捕获beanDefinition
	BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
	XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(registry);
	xmlReader.loadBeanDefinitions("classpath:spring.xml");

	// 2.通过反射实例化
	for (String definitionName : registry.getBeanDefinitionNames()) {
    
    
		BeanDefinition beanDefinition = registry.getBeanDefinition(definitionName);
		String beanClassName = beanDefinition.getBeanClassName();
		Constructor<?> constructor = Class.forName(beanClassName).getConstructor();
		Object bean = constructor.newInstance();

		// 3.注册类型转换器:由于xml配置中的字符串会被转化为TypedStringValue类型,
		// 		而TypedStringValue无法自由转化为其他类型(如Integer、String),所以需要自行定义转化器并注册入转化服务
		DefaultConversionService conversionService = new DefaultConversionService();
		conversionService.addConverter(new Converter<TypedStringValue, Integer>() {
    
    
			@Override
			public Integer convert(@NonNull TypedStringValue source) {
    
    
				return Integer.valueOf(Objects.requireNonNull(source.getValue()));
			}
		});
		conversionService.addConverter(new Converter<TypedStringValue, String>() {
    
    
			@Override
			public String convert(@NonNull TypedStringValue source) {
    
    
				return source.getValue();
			}
		});

		// 4.使用BeanWrapper包裹实例,使其更方便使用反射方法
		BeanWrapper beanWrapper = new BeanWrapperImpl(bean);
		beanWrapper.setConversionService(conversionService);
		beanWrapper.setPropertyValues(beanDefinition.getPropertyValues());
		bean = beanWrapper.getWrappedInstance();
		logger.info(bean.toString());
	}
}

3. Converter source code

All registered converters will be stored in Converters. The structure of this class is relatively complex:

private static class Converters {
    
    
    // 存取通用的转换器,并不限定转换类型,一般用于兜底
    private final Set<GenericConverter> globalConverters = new CopyOnWriteArraySet<>();
    // 指定了类型对,对应的转换器们的映射关系。
    // ConvertiblePair:表示一对,包含sourceType和targetType
    // ConvertersForPair:这一对对应的转换器们(因为能处理一对类型转换可能存在多个转换器),内部使用一个双端队列Deque来存储,保证顺序
    private final Map<ConvertiblePair, ConvertersForPair> converters = new ConcurrentHashMap<>(256);

    public void add(GenericConverter converter) {
    
    
        // 获得他的类型对儿
        Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
        if (convertibleTypes == null) {
    
    
			// 如果没有限定转换类型,添加到globalConverters
            this.globalConverters.add(converter);
        }
        else {
    
    
            // 如果已经存在转换类型,我们写的都在这里
            for (ConvertiblePair convertiblePair : convertibleTypes) {
    
    
                // 找到与之匹配的加进去,这里是个链表
                getMatchableConverters(convertiblePair).add(converter);
            }
        }
    }
    
    @Nullable
    public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
    
    
        // 搜索完整的类型层次结构,父类--->
        // 比如想要搜索【虎猫 -> 老虎】,但如过虎猫有父类(猫)
        // 我们还需检索【猫 -> 老虎】
        List<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType());
        List<Class<?>> targetCandidates = getClassHierarchy(targetType.getType());
        for (Class<?> sourceCandidate : sourceCandidates) {
    
    
            for (Class<?> targetCandidate : targetCandidates) {
    
    
                // 所有的类型都要匹配
                ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
                // 找到一个就返回
                GenericConverter converter = getRegisteredConverter(sourceType, targetType, convertiblePair);
                if (converter != null) {
    
    
                    return converter;
                }
            }
        }
        return null;
    }

    @Nullable
    private GenericConverter getRegisteredConverter(TypeDescriptor sourceType,
TypeDescriptor targetType, ConvertiblePair convertiblePair) {
    
    

        // 根据convertiblePair获取ConvertersForPair
        ConvertersForPair convertersForPair = this.converters.get(convertiblePair);
        if (convertersForPair != null) {
    
    
            GenericConverter converter = convertersForPair.getConverter(sourceType, targetType);
            if (converter != null) {
    
    
                return converter;
            }
        }
        // 检查是否能匹配兜底的全局转换器
        for (GenericConverter globalConverter : this.globalConverters) {
    
    
            if (((ConditionalConverter) globalConverter).matches(sourceType, targetType)) {
    
    
                return globalConverter;
            }
        }
        return null;
    }
   
}

ConvertiblePair: represents a pair, including sourceType and targetType

final class ConvertiblePair {
    
    
    private final Class<?> sourceType;
    private final Class<?> targetType;
}

ConvertersForPair: This pair of corresponding converters (because there may be multiple converters that can handle a pair of type conversions), is internally stored using a double-ended queue Deque to ensure order. Its structure is as follows:
Insert image description here

private static class ConvertersForPair {
    
    
    // 内部维护的队列
    private final Deque<GenericConverter> converters = new ConcurrentLinkedDeque<>();

    public void add(GenericConverter converter) {
    
    
        this.converters.addFirst(converter);
    }

    @Nullable
    public GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
    
    
        for (GenericConverter converter : this.converters) {
    
    
            // 此处表明,如果我们有特殊的需求,还可以实现ConditionalGenericConverter,实现特殊的匹配规则,连边中的converter可以有不同的匹配规则,
            // 当然通常情况下会返回第一个
            if (!(converter instanceof ConditionalGenericConverter genericConverter) ||
                genericConverter.matches(sourceType, targetType)) {
    
    
                return converter;
            }
        }
        return null;
    }
}

4、PropertyEditor

You may also see articles in some places using PropertyEditor for type converters. This class is an interface defined in java.beans. Its function is similar to the content of this chapter. It is a converter used before spring 3.0, so we will not use it. Viewed:
Insert image description here

Use the following method to add it in BeanWapper: beanWrapper.registerCustomEditor(xxxxPropertyEditor);
those who are interested can learn by themselves.

4. Resource acquisition

Spring's [Resource] interface is located in the [org.springframework.core.io] package, which abstracts the ability to access resources. An overview of the [Resource] interface is provided below. Spring itself uses the Resource interface extensively, which we have touched before.

public interface Resource extends InputStreamSource {
    
    
    boolean exists();
    boolean isReadable();
    boolean isOpen();
    boolean isFile();
    URL getURL() throws IOException;
    URI getURI() throws IOException;
    File getFile() throws IOException;
    ReadableByteChannel readableChannel() throws IOException;
    long contentLength() throws IOException;
    long lastModified() throws IOException;
    Resource createRelative(String relativePath) throws IOException;
    String getFilename();
    String getDescription();
}

1. Implementation of built-in Resource

Spring includes several built-in Resource implementations, as follows:

  • UrlResource: UrlResource wraps java.net.URL and can be used to access any object that needs to be accessed through a URL, such as files, HTTPS targets, FTP targets, etc. All URLs are represented by a standardized string, so that different types of URLs can be represented using appropriate standardized prefixes. This includes ' file: ' for accessing file system paths, ' https: ' for accessing resources via https protocol, ' ftp: ' for accessing resources via ftp, etc.
  • ClassPathResource: This class represents the resources that should be obtained from the [class path]. It uses the thread context class loader, the given class loader, or the given class load resource.
  • FileSystemResource: This is a Resource implementation for java.io, which can easily operate system files.
  • InputStreamResource: Resource implementation of the given InputStream. It should be used only when no specific resource implementation is applicable.
  • ByteArrayResource: This is a resource implementation for a given byte array.

We write the following three test cases to simply learn to perceive:

@Test
public void testUrl() throws IOException {
    
    
    Resource resource = new UrlResource("https://dldir1.qq.com/qqfile/qq/PCQQ9.7.0/QQ9.7.0.28921.exe");
    FileOutputStream fos = new FileOutputStream("\\" + resource.getFilename());
    // 该工具包需要引入commons-io
    IOUtils.copy(resource.getInputStream(), fos);
}
@Test
public void testFileSystem() throws IOException {
    
    
    Resource resource = new FileSystemResource("\\spring\\spring.xml");
    byte[] buffer = new byte[1024 * 100];
    int offset = IOUtils.read(resource.getInputStream(), buffer);
    logger.info(new String(buffer, 0, offset));
}
@Test
public void testClassPath() throws IOException {
    
    
    Resource resource = new ClassPathResource("spring.xml");
    byte[] buffer = new byte[1024 * 100];
    int offset = IOUtils.read(resource.getInputStream(), buffer);
    logger.info(new String(buffer, 0, offset));
}

2. xml parsing

Here we continue to delve deeper into the previous source code and review our previous process of loading beanDefinition:

  1. Loading .class file resources is not the same concept as class loading.
  2. Load xml file

First let’s look at a little knowledge point:

  • Prefix [classpath:*]: Only files will be found in the class path under the target, usually matching a resource.
  • Prefix [classpath*:]: not only includes the class path under the target, but also includes the jar file (the class path under the target) for search. It can match multiple resources. There are many such scenarios, such as implementing springboot automatic assembly. Most of the jar packages will have spring.factories files.Insert image description here

Spring provides us with the springPathMatchingResourcePatternResolver class. This tool can flexibly help us obtain the corresponding Resource instance. It provides the getResource and getResources methods for us to use:

public Resource getResource(String location) {
    
    
    Assert.notNull(location, "Location must not be null");
	// 留给我们的扩展的协议解析器,如自定义
    for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
    
    
        Resource resource = protocolResolver.resolve(location, this);
        if (resource != null) {
    
    
            return resource;
        }
    }

    if (location.startsWith("/")) {
    
    
        return getResourceByPath(location);
    }
    // classpath:
    else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
    
    
        return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
    } else {
    
    
        try {
    
    
            // 尝试将位置解析为URL…
            URL url = ResourceUtils.toURL(location);
            // 检测是不是file:  vfsfile:打头的
            return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
        }
    }
}

Let's write a test case as follows:

@Test
public void testPathMatchingResourcePatternResolver() throws IOException {
    
    
	PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

	Resource httpResource = resourcePatternResolver.getResource("https://leetcode.cn/problems/permutation-sequence/");
	// https://leetcode.cn/problems/permutation-sequence/
	logger.info(httpResource.getURI().toString()); 

	Resource classPathResource = resourcePatternResolver.getResource("classpath:spring.xml");
	// file:/Users/ziang.zhang/dreamPointer/JavaSpace/JavaProjects/spring-framework-source/spring-framework-source/spring-ziang-demo/build/resources/test/spring.xml
	logger.info(classPathResource.getURI().toString()); 
	
	Resource fileResource = resourcePatternResolver.getResource("file:/Users/ziang.zhang/Downloads/chrome/dcf57c3fb1a448bba392e5b1f0cd302f.png");
	// file:/Users/ziang.zhang/Downloads/chrome/dcf57c3fb1a448bba392e5b1f0cd302f.png
	logger.info(fileResource.getURI().toString()); 

	Resource[] resources = resourcePatternResolver.getResources("classpath*:META-INF/spring.factories");
	for (Resource resource : resources) {
    
    
		// jar:file:/Users/ziang.zhang/dreamPointer/JavaSpace/JavaProjects/spring-framework-source/spring-framework-source/spring-beans/build/libs/spring-beans-5.2.23.BUILD-SNAPSHOT.jar!/META-INF/spring.factories
		logger.info(resource.getURI().toString());
	}
}

Of course, we can also call the getResources method to obtain all files with the same name under the class path:

We can write test cases as follows:

public void testMoreFile() throws IOException {
    
    
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    Resource[] resources = resolver.getResources("classpath*:/META-INF/spring.factories");
    for (Resource resource : resources) {
    
    
        System.out.println(resource.getURI());
    }
}

The results are as follows:
Insert image description here
For the source code, we start with the following method:

xmlReader.loadBeanDefinitions("classpath:spring.xml");
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    
    
    ResourceLoader resourceLoader = getResourceLoader();
    
    if (resourceLoader instanceof ResourcePatternResolver) {
    
    
        try {
    
    
            // 此方法会返回一个以上可用的Resource实现,根据前缀特征
            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
            int count = loadBeanDefinitions(resources);
            
            return count;
        }
    }
    else {
    
    
        // 只能加载单个资源的绝对URL。
        Resource resource = resourceLoader.getResource(location);
        int count = loadBeanDefinitions(resource);
        if (actualResources != null) {
    
    
            actualResources.add(resource);
        }
        if (logger.isTraceEnabled()) {
    
    
            logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
        }
        return count;
    }
}

Back in our doLoadBeanDefinitions method:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
    
    

    try {
    
    
        Document doc = doLoadDocument(inputSource, resource);
        int count = registerBeanDefinitions(doc, resource);
        if (logger.isDebugEnabled()) {
    
    
            logger.debug("Loaded " + count + " bean definitions from " + resource);
        }
        return count;
    }
}

Continue to enter the core method loadDocument:

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
    
    
		// ...其他省略
		return builder.parse(inputSource);
}

At this point, the xml file becomes a Document object, and there is no need to read the following content. The analysis uses not dom4j but the org.w3c.dom related API that comes with jdk.

The following is the part that parses the dom:

Let's go back to doLoadBeanDefinitions and take another look at the registerBeanDefinitions method, which is where the DOM is registered as a beanDefinition.

int count = registerBeanDefinitions(doc, resource);

All the way into the core method, the following is exactly how to solve each first-level tag, such as import, bean, alias, etc.:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    
    
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
    
    
        importBeanDefinitionResource(ele);
    }
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
    
    
        processAliasRegistration(ele);
    }
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
    
    
        processBeanDefinition(ele, delegate);
    }
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
    
    
        // recurse
        doRegisterBeanDefinitions(ele);
    }
}

We follow up on a method of parsing bean tags. If we follow up, we can find the following code:

@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
			Element ele, String beanName, @Nullable BeanDefinition containingBean) {
    
    
	this.parseState.push(new BeanEntry(beanName));

	// 获取className
	String className = null;
	if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
    
    
		className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
	}
	String parent = null;
	if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
    
    
		parent = ele.getAttribute(PARENT_ATTRIBUTE);
	}

	try {
    
    
		// 创建bean的定义
		AbstractBeanDefinition bd = createBeanDefinition(className, parent);

        // 解析属性标签scope、lazy-init等
		parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
		bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

        // 解析<meta key="" value=""/>
		parseMetaElements(ele, bd);
        // <lookup-method></lookup-method>
		parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        // <replaced-method></replaced-method>
		parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

		// 构造注入的元素
		parseConstructorArgElements(ele, bd);
        // setter注入的元素
		parsePropertyElements(ele, bd);
		// 解析qualifier
		parseQualifierElements(ele, bd);

		bd.setResource(this.readerContext.getResource());
		bd.setSource(extractSource(ele));
		return bd;
	}
	catch (ClassNotFoundException ex) {
    
    
		error("Bean class [" + className + "] not found", ele, ex);
	}
	catch (NoClassDefFoundError err) {
    
    
		error("Class that bean class [" + className + "] depends on not found", ele, err);
	}
	catch (Throwable ex) {
    
    
		error("Unexpected failure during bean definition parsing", ele, ex);
	}
	finally {
    
    
		this.parseState.pop();
	}

	return null;
}

Seeing this, we already know how to load the xml file.

5. Environmental abstraction

Spring provides the Environment interface, which is an abstraction of the environment and is integrated in the container. It simulates two key aspects of the application environment, namely profiles and properties.

A profile is a beanDifination configuration of a given name, logically grouped, and is only registered with the container if the given profile is activated.

Properties play an important role in almost all applications. They are a collection of key-values, which may come from various sources: properties files, JVM system properties, system environment variables, JNDI, servlet context Parameters, specific [Properties] objects, "Map" objects, etc. The role of the Environment object related to properties is to provide users with a convenient service interface for configuring property sources and parsing properties from there.

1、properties

The environment object in Spring provides search operations for [properties]. Let's take a look at the following example:

@Test
public void testMoreEnvProperties() throws IOException {
    
    
    ApplicationContext ctx = new GenericApplicationContext();
    Environment env = ctx.getEnvironment();
    boolean containsMyProperty = env.containsProperty("JAVA_HOME");
    logger.info("Does my environment contain the 'JAVA_HOME' property? {}", containsMyProperty);
}

Of course, we can also debug and observe the env variables:
Insert image description hereIn the previous code snippet, we saw whether the [JAVA_HOME] attribute exists in the spring env. To answer this question, the Environment object performs a search for [PropertySource] in propertySources.

The concept of PropertySource appears here again. It is a simple abstraction of any [key-value pair source]. Spring's StandardEnvironment configures two basic PropertySource objects:

(1) A collection representing JVM system properties ("System.getProperties()")

@Test
public void testSystemProperties()  {
    
    
    Properties properties = System.getProperties();
    Set<Map.Entry<Object, Object>> entries = properties.entrySet();
    for (Map.Entry<Object,Object> entry:entries){
    
    
        System.out.println(entry);
    }
}

Insert image description here(2) A setting representing system environment variables (System.getenv()")

@Test
public void testSystemEnv()  {
    
    
    Map<String, String> env = System.getenv();
    Set<Map.Entry<String, String>> entries = env.entrySet();
    for (Map.Entry<String,String> entry:entries){
    
    
        System.out.println(entry);
    }
}

Its value is as follows, partially intercepted:
Insert image description hereThe source code of StandardEnvironment is as follows. By default, these two propertySources will be added to env:

public class StandardEnvironment extends AbstractEnvironment {
    
    

    /** System environment property source name: {@value}. */
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

    /** JVM system properties property source name: {@value}. */
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
    
    
        propertySources.addLast(
            new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(
            new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }
}

Of course, the entire mechanism is configurable. We can integrate custom attribute sources into this search. To do this, we can instantiate our own [PropertySource] and add it to the 'propertiessources' collection of the current 'Environment', as follows:

public class MyPropertySource extends PropertySource<String> {
    
    
    public MyPropertySource(String name) {
    
    
        super(name);
    }

    @Override
    public String getProperty(String name) {
    
    
        // 这里可以是源自任何逻辑的键值对来源,
        // 可以从properties文件中检索,也可以是数据库检索,等等
        return "hello";
    }
}

The test examples are as follows:

@Test
public void testStandardEnvironment()  {
    
    
    StandardEnvironment env = new StandardEnvironment();
    MutablePropertySources sources = env.getPropertySources();
    sources.addFirst(new MyPropertySource("my-source"));
}

The [@PropertySource] annotation provides a convenient declarative mechanism for adding [PropertySource] to Spring's [Environment].

Given a properties file named app, enter the content, such as [teacherName=itnanls], and write the following configuration class:

@Configuration
@PropertySource("classpath:app.properties")
public class EnvPropertiesConfig {
    
    
}

The test examples are as follows:

@Test
public void testEnvironment() {
    
    
	AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(EnvPropertiesConfig.class);
	logger.info(applicationContext.getEnvironment().getProperty("my.name"));
	logger.info(applicationContext.getEnvironment().getProperty("my.age"));
}

2、Profiles

Profiles provide a mechanism in the core container that allows different beans to be registered in different environments. The word "environment" means different things to different users,

  • Use an in-memory data source in development, or look it up from JNDI in production.
  • Register custom bean implementations for Customer A and Customer B deployments.

Consider the first use case in a real application, which requires a "data source". In a test environment, the configuration might look like this:

@Bean
public DataSource dataSource() {
    
    
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("classpath:com/bank/config/sql/schema.sql")
        .addScript("classpath:com/bank/config/sql/test-data.sql")
        .build();
}

Now consider how to deploy this application into a production environment, assuming that the application's data source is registered in the JNDI directory of the production application server. Our 'dataSource' bean now looks like this:

@Bean
public DataSource dataSource() throws Exception {
    
    
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}

Key point : The question is how to switch between using these two data sources depending on the current environment?

Of course, we can use @Profile.

The [@Profile] annotation allows you to indicate when one or more beans are injected in which Profile is activated. Using the previous example, we can rewrite the dataSource configuration as follows:

@Configuration
@Profile("development")
public class StandaloneDataConfig {
    
    
    @Bean
    public DataSource dataSource() {
    
    
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
@Configuration
@Profile("production")
public class JndiDataConfig {
    
    
    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
    
    
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

@Profile can also be declared at the method level, containing only a configuration class for a specific bean (for example, for an alternative variant of a specific bean), as in the following example:

@Configuration
public class AppConfig {
    
    
    @Bean("dataSource")
    @Profile("development") 
    public DataSource standaloneDataSource() {
    
    
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }

    @Bean("dataSource")
    @Profile("production") 
    public DataSource jndiDataSource() throws Exception {
    
    
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

3. Activate a configuration

Now that we have updated the configuration, we still need to instruct Spring which configuration file is active. If we start our sample application now, we will see that a NoSuchBeanDefinitionException is thrown because the container cannot find the Spring bean named dataSource.

There are several ways to activate a configuration file, but the most direct way is to execute it programmatically through the [Environment] API available in [ApplicationContext]. The following example shows how to do this:

@Test
public void testProfile(){
    
    
    // 创建容器
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    // 激活环境
    context.getEnvironment().setActiveProfiles("development");
    // 扫包
    context.scan("com.ydlclass.datasource");
    //  刷新
    context.refresh();
    // 使用
    DataSource bean = context.getBean(DataSource.class);
    logger.info("{}",bean);
}

In addition, you can also declaratively activate the environment [active] attribute through spring.profiles, which can be specified in 'web.xml' through system environment variables, JVM system properties, and servlet context parameters.

Note that profiles are not an "either/or" proposition. You can activate multiple profiles at once. Programmatically, you can provide multiple profile names to the 'setActiveProfiles()' method, which accepts the 'String...' variable argument. The following example activates multiple profiles:

Add startup parameters:

-Dspring.profiles.active="profile1,profile2"

Programming method:

ctx.getEnvironment().setActiveProfiles("profile1""profile2");

It will be simpler in spring-boot, so we won’t go into details here:

spring.profiles.active=dev

6. Publish and subscribe

1 Introduction

Spring provides us with event multicaster, which can easily implement the publish and subscribe model:

multicast: Also called multicast, multicast or groupcast. Refers to delivering information to a group of destination addresses at the same time. Its use of policies is the most efficient because messages only need to be delivered once on each network link, and messages are only replicated when a link forks.

Insert image description here
When spring starts, the following bean will be injected by default: simpleApplicationEventMulticaster. We can use this class to easily implement the event mechanism. This is a typical [Observer Design Pattern]:

@Test
public void testEventMulticaster() {
    
    
		SimpleApplicationEventMulticaster caster = new SimpleApplicationEventMulticaster();
		caster.addApplicationListener(new EmailListener());
		caster.addApplicationListener(new EmailListener());
		caster.addApplicationListener(new MessageListener());
		caster.multicastEvent(new OrderEvent("FYI, check your order!"));
	}

The result is as follows:

Insert image description here

2. Source code reading

There are many complex situations in this. For example, is the listener an instance that we directly register manually, or is it a bean in the spring factory? If the bean is a singleton or a prototype?

Because the source code is relatively complex, we need to look at it together with the following figure:

Figure 1:
Insert image description here
Figure 2 shows how caching is performed after the listener is called and executed:
Insert image description here

SimpleApplicationEventMulticaster source code is as follows:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    
    

   // 定义一个线程池,事件被触发时可由他来实现事件,默认为null
   private Executor taskExecutor;

   // 这个用来捕获listener执行过程中产生的异常
   // 需要这用 赋值caster.setErrorHandler(new XxxErrorHandler())
   private ErrorHandler errorHandler;
   private volatile Log lazyLogger;


   public SimpleApplicationEventMulticaster() {
    
    }
   public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
    
    
      setBeanFactory(beanFactory);
   }
      
   public void setErrorHandler(@Nullable ErrorHandler errorHandler) {
    
    
      this.errorHandler = errorHandler;
   }

   @Nullable
   protected ErrorHandler getErrorHandler() {
    
    
      return this.errorHandler;
   }

   //设置一个自定义执行器(线程池)来调用每个侦听器。
   public void setTaskExecutor(@Nullable Executor taskExecutor) {
    
    
      this.taskExecutor = taskExecutor;
   }
   
   @Nullable
   protected Executor getTaskExecutor() {
    
    
      return this.taskExecutor;
   }
    
   // 广播一个事件
   @Override
   public void multicastEvent(ApplicationEvent event) {
    
    
      multicastEvent(event, resolveDefaultEventType(event));
   }

   // 广播一个事件的具体实现
   @Override
   public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    
    
      ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
      Executor executor = getTaskExecutor();
      // 核心一:getApplicationListeners(event, type)稍后看
      // 他的重点是如何设计的缓存
      // 获取所有与event事件匹配的listener并调用核心方法onApplicationEvent
      for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    
    
          // 如果你设置了线程池他会将任务丢给线程池
          if (executor != null) {
    
    
              // 核心二:调用Listener的方法invokeListener
            executor.execute(() -> invokeListener(listener, event));
         }
         // 否则就以单线程的方式运行
         else {
    
    
            invokeListener(listener, event);
         }
      }
   }

   // 调用listener的方法
   protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    
    
      // ErrorHandler可以保存Listener在执行过程中产生的异常
      // 其默认为null,我们可以独立设置
      ErrorHandler errorHandler = getErrorHandler();
      if (errorHandler != null) {
    
    
         try {
    
    
            doInvokeListener(listener, event);
         }
         catch (Throwable err) {
    
    
            // 将执行listener时产生放入errorHandler
            errorHandler.handleError(err);
         }
      }
      else {
    
    
         // 负责直接调用
         doInvokeListener(listener, event);
      }
   }

   @SuppressWarnings({
    
    "rawtypes", "unchecked"})
   private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    
    
      try {
    
    
         // 调用方法
         listener.onApplicationEvent(event);
      }
      // 捕获类型转化异常
      catch (ClassCastException ex) {
    
    
         String msg = ex.getMessage();
         if (msg == null || matchesClassCastMessage(msg, event.getClass()) ||
            (event instanceof PayloadApplicationEvent && matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))  ) {
    
    
            // 可能是lambda定义的侦听器,我们无法为其解析泛型事件类型
            // 让我们抑制异常。
            Log loggerToUse = this.lazyLogger;
            if (loggerToUse == null) {
    
    
               loggerToUse = LogFactory.getLog(getClass());
               this.lazyLogger = loggerToUse;
            }
            if (loggerToUse.isTraceEnabled()) {
    
    
               loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
            }
         } else {
    
    
            throw ex;
         }
      }
   }

   private boolean matchesClassCastMessage(String classCastMessage, Class<?> eventClass) {
    
    
      // 在Java 8上,消息以类名“Java .lang”开始。字符串不能强制转换…
      if (classCastMessage.startsWith(eventClass.getName())) {
    
    
         return true;
      }
      // 在Java 11中,消息以“class…”开头,也就是class . tostring ()
      if (classCastMessage.startsWith(eventClass.toString())) {
    
    
         return true;
      }
      // 在Java 9上,用于包含模块名称的消息:" Java .base/ Java .lang. lang. xml "。字符串不能强制转换…”
      int moduleSeparatorIndex = classCastMessage.indexOf('/');
      if (moduleSeparatorIndex != -1 && classCastMessage.startsWith(eventClass.getName(), moduleSeparatorIndex + 1)) {
    
    
         return true;
      }
      // 假设一个不相关的类转换失败……
      return false;
   }
}

There are more implementations in its parent class, including getApplicationListeners(event, type):

public abstract class AbstractApplicationEventMulticaster
    implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
    
    

    // 真正保存listener的地方,他保存了传入的listener实例和容器里的监听器bean的名字
    // 其结构如图1
    private final DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever();

    // 这个集合是对已调用的event和listener的缓存
    // ListenerCacheKey保存了一组event和source
    // CachedListenerRetriever保存了已注册的、容器的单例bean、容器的非单例beanName
    // 非单例的bean只能缓存name,实例会消亡
    final Map<ListenerCacheKey, Cached  Retriever> retrieverCache = new ConcurrentHashMap<>(64);

    @Nullable
    private ClassLoader beanClassLoader;

    @Nullable
    private ConfigurableBeanFactory beanFactory;

    // 以编程的方式添加监听器
    @Override
    public void addApplicationListener(ApplicationListener<?> listener) {
    
    
        // 这个过程可能存在线程安全的问题,比如一线线程
        synchronized (this.defaultRetriever) {
    
    
            // 因为你的Aop可能影响到该listener
            // 我们需要将代理对象从defaultRetriever中删除,因为我们并不需要
            Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
            if (singletonTarget instanceof ApplicationListener) {
    
    
                this.defaultRetriever.applicationListeners.remove(singletonTarget);
            }
            // 添加原生的监听器到defaultRetriever
            this.defaultRetriever.applicationListeners.add(listener);
            // 清理缓存
            this.retrieverCache.clear();
        }
    }

    // 直接以bean的方式将listenerBeanName添加到defaultRetriever
    @Override
    public void addApplicationListenerBean(String listenerBeanName) {
    
    
        synchronized (this.defaultRetriever) {
    
    
            this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);
            // 清理缓存
            this.retrieverCache.clear();
        }
    }

    // 获得所有的bean的实例
    // 第一部分:编程式的bean的实例
    // 第二部分:容器的bean,进行实例化
    protected Collection<ApplicationListener<?>> getApplicationListeners() {
    
    
        synchronized (this.defaultRetriever) {
    
    
            return this.defaultRetriever.getApplicationListeners();
        }
    }

    // ...省略掉大量雷同的crud(增删查改)代码

    // 我们看一个比较有难度的方法,该方法根据事件获取满足条件的listeners
    protected Collection<ApplicationListener<?>> getApplicationListeners(
        ApplicationEvent event, ResolvableType eventType) {
    
    
        // 获得事件源(那个类中发布的时间(如OrderService类发布是orderEvent))
        // source就是OrderService,event就是orderEvent
        Object source = event.getSource();
        Class<?> sourceType = (source != null ? source.getClass() : null);
        // 通过事件类型和源类型创建缓存key
        // 第二次请求就避免了再次检索
        ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

        // 定义新的Listener缓存器,他是作为retrieverCache的value
        CachedListenerRetriever newRetriever = null;

        // 核心:快速检查缓存中的内容
        CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);
        if (existingRetriever == null) {
    
    
            // 满足条件,则在existingRetriever缓存一个新的条目
            // key->ListenerCacheKey(eventType和sourceType)
            // value->CachedListenerRetriever(保存了与key匹配的listeners)
            if (this.beanClassLoader == null ||(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
                                                (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
    
    
                // 创建一个新的缓存器,目前里边为空
                newRetriever = new CachedListenerRetriever();
                // 将这个key和检索器放入整体缓存map中
                existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
                // 添加失败,则将newRetriever置空
                // 多线程下可能会添加失败,两个线程同时添加,只能有一个成功
                if (existingRetriever != null) {
    
    
                    // 将new的缓存器置空,复用其他线程创建的
                    newRetriever = null;  
                }
            }
        }
        // 如果缓存命中(缓存中存在这一对key-value)
        if (existingRetriever != null) {
    
    
            Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
            // 将缓存中的Listeners全部取出,返回
            if (result != null) {
    
    
                return result;
            }
            // 缓存命中没有拿到结果,此时result为null
            // 这种一般出现在多线程情况,有一个线程已经创建了这个缓存器,但是还没有机会赋值
            // 当前线程又拿到了这个缓存器,那我们就继续
        }

        // 该方法会为我们当前根据key过滤合适的listeners,并缓存器赋值
        return retrieveApplicationListeners(eventType, sourceType, newRetriever);
    }

    // 该方法会给传入的newRetriever赋值,检索过程相对复杂
    // 这个方法会过滤满足条件的Listeners,并将过滤后的内容放到缓存中
    private Collection<ApplicationListener<?>> retrieveApplicationListeners(
        ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {
    
    

        // 保存所有过滤好的listeners
        // 他包含编程式的实例和容器的bean
        List<ApplicationListener<?>> allListeners = new ArrayList<>();
        // 过滤后的监听器,将来做缓存用
        Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
        // 过滤后的监听器beanName,将来做缓存用
        Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);

        Set<ApplicationListener<?>> listeners;
        Set<String> listenerBeans;

        // 每个线程拷贝独立的listeners和listenerBeans
        synchronized (this.defaultRetriever) {
    
    
            // 优先做一个拷贝
            listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
            listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
        }

        // 将所有的listener实例遍历,过滤满足条件的
        for (ApplicationListener<?> listener : listeners) {
    
    
            if (supportsEvent(listener, eventType, sourceType)) {
    
    
                // 如果传递了缓存器,就将它存入filteredListeners
                if (retriever != null) {
    
    
                    filteredListeners.add(listener);
                }
                allListeners.add(listener);
            }
        }

        // bean要在这里获取初始化
        if (!listenerBeans.isEmpty()) {
    
    
            // 这里初始化
            ConfigurableBeanFactory beanFactory = getBeanFactory();
            for (String listenerBeanName : listenerBeans) {
    
    
                try {
    
    
                    // 判断beanFactory是不是支持该事件
                    if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
    
    
                        // 从bean工厂获取,实例化bean
                        ApplicationListener<?> listener =
                            beanFactory.getBean(listenerBeanName,ApplicationListener.class);
                        if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
    
    
                            if (retriever != null) {
    
    
                                // 如果是单例的就加入filteredListeners
                                if (beanFactory.isSingleton(listenerBeanName)) {
    
    
                                    filteredListeners.add(listener);
                                }
                                // 如果不是单例bean,则加入filteredListenerBeans
                                // 原因是非单例bean使用结束可能会被gc,下次使用需要重新实例化
                                // 所以,我们并不缓存非单例的listenerbean
                                else {
    
    
                                    filteredListenerBeans.add(listenerBeanName);
                                }
                            }
                            // 无论是不是单例,都将实例加入allListeners,
                            // 他将作为当前方法的返回值
                            allListeners.add(listener);
                        }
                    }
                    else {
    
    
                        // 删除不匹配的侦听器
                        // ApplicationListenerDetector,可能被额外的排除
                        // BeanDefinition元数据(例如工厂方法泛型)
                        Object listener = beanFactory.getSingleton(listenerBeanName);
                        if (retriever != null) {
    
    
                            filteredListeners.remove(listener);
                        }
                        allListeners.remove(listener);
                    }
                }
                catch (NoSuchBeanDefinitionException ex) {
    
    
                }
            }
        }

        // 给结果排序
        AnnotationAwareOrderComparator.sort(allListeners);

        // 实际进行缓存的地方
        // 在这里我们缓存了applicationListeners(编程式的和单例的bean)
        // 和applicationListenerBeans,主要是非单例bean的名字
        // 下次从缓存获取的时候还是会再次实例化非单例的bean
        if (retriever != null) {
    
    
            // 如果啥也没有过滤则添加全部的Listeners和ListenerBeans到retriever
            if (filteredListenerBeans.isEmpty()) {
    
    
                retriever.applicationListeners = new LinkedHashSet<>(allListeners);
                retriever.applicationListenerBeans = filteredListenerBeans;
            }
            else {
    
    
                retriever.applicationListeners = filteredListeners;
                retriever.applicationListenerBeans = filteredListenerBeans;
            }
        }
        return allListeners;
    }
}

value: Implementation of CachedListenerRetriever:

//listener缓存工具类,它封装了一组特定的目标侦听器,允许高效检索预先过滤的侦听器。每个事件类型和源类型缓存这个helper的实例。
private class CachedListenerRetriever {
    
    

    // 保存了满足条件的applicationListeners
    // 包含编程式的和容器内满足条件的单例bean
    @Nullable
    public volatile Set<ApplicationListener<?>> applicationListeners;
    // 保存了满足条件的非单例的applicationListenerBeans
    @Nullable
    public volatile Set<String> applicationListenerBeans;

    // 该方法是从特定缓存获取applicationListeners
    // 这个方法会再次实例化非单例的bean
    @Nullable
    public Collection<ApplicationListener<?>> getApplicationListeners() {
    
    
        Set<ApplicationListener<?>> applicationListeners = this.applicationListeners;
        Set<String> applicationListenerBeans = this.applicationListenerBeans;
        if (applicationListeners == null || applicationListenerBeans == null) {
    
    
            // Not fully populated yet
            return null;
        }
        // 创建一个临时的集合保存所有的监听器
        List<ApplicationListener<?>> allListeners = new ArrayList<>(
            applicationListeners.size() + applicationListenerBeans.size());
        allListeners.addAll(applicationListeners);
        // 这里实例化剩下的bean,容器内的非单例bean
        // 这里不一样的地方是非单单例的bean,每次清除缓存都要重新实例化
        if (!applicationListenerBeans.isEmpty()) {
    
    
            BeanFactory beanFactory = getBeanFactory();
            for (String listenerBeanName : applicationListenerBeans) {
    
    
                try {
    
    
                    allListeners.add(beanFactory.getBean(listenerBeanName, ApplicationListener.class));
                }
                catch (NoSuchBeanDefinitionException ex) {
    
    
                }
            }
        }
        // 对他进行重排序
        if (!applicationListenerBeans.isEmpty()) {
    
    
            AnnotationAwareOrderComparator.sort(allListeners);
        }
        return allListeners;
    }
}

Default listener memory:

private class DefaultListenerRetriever {
    
    

    public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();

    public final Set<String> applicationListenerBeans = new LinkedHashSet<>();

    public Collection<ApplicationListener<?>> getApplicationListeners() {
    
    
        List<ApplicationListener<?>> allListeners = new ArrayList<>(
            this.applicationListeners.size() + this.applicationListenerBeans.size());
        allListeners.addAll(this.applicationListeners);
        if (!this.applicationListenerBeans.isEmpty()) {
    
    
            BeanFactory beanFactory = getBeanFactory();
            for (String listenerBeanName : this.applicationListenerBeans) {
    
    
                try {
    
    
                    ApplicationListener<?> listener =
                        beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                    if (!allListeners.contains(listener)) {
    
    
                        allListeners.add(listener);
                    }
                }
                catch (NoSuchBeanDefinitionException ex) {
    
    
                    // 单例侦听器实例(没有支持bean定义)消失,可能在销毁阶段的中间
                }
            }
        }
        AnnotationAwareOrderComparator.sort(allListeners);
        return allListeners;
    }
}

You can try adding the following code to the listener:

System.out.println("thread --> " + Thread.currentThread().getName());

We can try to modify the previous code, of course we can also set the exception handler:

@Test
public void testEventMulticaster(){
    
    
    SimpleApplicationEventMulticaster caster = new SimpleApplicationEventMulticaster();
    // 这是任务执行器,线程池
    caster.setTaskExecutor(Executors.newFixedThreadPool(10));
    // 设置异常处理器
    caster.setErrorHandler(new ErrorHandler() {
    
    
        @Override
        public void handleError(Throwable t) {
    
    
            // 做补偿处理,发送通知等等操作
            System.out.println("有异常了,快来处理");
            logger.error("此处发生了异常-->", t);
        }
    });
    caster.addApplicationListener(new EmailListener());
    caster.addApplicationListener(new EmailListener());
    caster.addApplicationListener(new MessageListener());
    caster.multicastEvent(new OrderEvent(this));
}

We can see that the calling thread of the listener is indeed obtained from the thread pool.

7. Internationalization

Spring introduces the MessageSource mechanism, which can help us simply implement internationalization (i18n for short).

ResourceBundleMessageSource is a simple implementation. We can create the following files for testing:
Insert image description hereThe file contents are super simple, as follows:

  • File content of zh_CN: hello=Hello{0}
  • File content of en_us: hello=hello {0}

You can simply obtain the content of the corresponding Locale using the following test case:

@Test
public void testMessageSource() {
    
    
    ResourceBundleMessageSource rbs = new ResourceBundleMessageSource();
    rbs.setBasename("i18n/message");
    rbs.setDefaultEncoding("UTF-8");
    rbs.setDefaultLocale(Locale.CHINA);
    String hello = rbs.getMessage("hello", new Object[]{
    
    "tom"}, Locale.US);
    System.out.println(hello);
}

Description of several parameters in the method:

  • string code: key in the file
  • Object[] args: Parameters of the string to be parsed, variables with {} placeholders can exist in value
  • String defaultMessage: When the internationalization information cannot be read, it will be processed according to this default value.
  • Locale locale: international positioning, such as Locale.CHINA represents China

We can introduce more about internationalization later.

8. Expression


Guess you like

Origin blog.csdn.net/qq_45867699/article/details/129541192