学习笔记:Spring框架源码Part.1——基础

学习视频链接:https://www.bilibili.com/video/BV1zd4y1L7YD


前言

如果你是一个初学者。刚开始阅读Spring源码,一定会感觉特别困难。因为其中涉及太多新东西。但这正说明,Spring源码是一个宝库。


第一章 bean的元数据

一、回顾bean的注入方式

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、注解

@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
public class UserConfiguration {
    
    
    @Bean("user")
    public User user(){
    
    
        User user = new User();
        user.setName("lily");
        user.setAge(20);
        return user;
    }
}

4、@Import注解

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

二、BeanDefiniiton详解

Spring在构造bean时,不可能像我们主动构造那样,随心所欲的new,赋值、以及完成方法调用。他需要将千差万别的class概括成为一种【统一的描述性】语言,Spring提供了一个接口BeanDefintion为我们统一了这种描述bean的元数据。
bean的元数据通常是我们使用xml或者注解进行配置的数据,我们的Spring容器启动之间第一步就是加载配置数据,这些元数据会被加载到内存以一个个beanDefinition的形式保存在一个map中。

一个BeanDefiniiton大概保存了以下信息:

  1. 定义了id、别名与Bean的对应关系(BeanDefinitionHolder)
  2. 具体的工厂方法(Class类型),包括工厂方法的返回类型,工厂方法的Method对象
  3. 构造函数、构造函数形参类型
  4. Bean的class对象
  5. 作用范围、是否懒加载等等

以下是BeanDefiniiton的类的结构图,这里没有用类图,我觉得这里用这样的图好看一些:
在这里插入图片描述

beanDefinition来源不同可能会有不同实现,目前我们最常用的实现就是GenericBeanDefinition这个实现类。

小知识:Generic(一般的,通用的),几乎所有以这个单词打头的实现,都是Spring的通用实现。

接口:接口是用来做顶层设计、依赖管理的。接口间的依赖决定了业务之间的耦合,接口是能力的抽象
抽象类:实现接口层的核心、共享的方法。模板方法设计模式涉及抽象类的运用,而Spring中即有所体现

1、认识BeanDefinition

BeanDefinition接口定义了大量的常量和方法:

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

该类对通用核心方法完成了实现(这也是抽象类的作用),同时对一些成员变量提供了默认值:

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

该类实现比较简单,提供了设置父子关系和构建实例的方法,该类及其子类是目前版本使用最多的BeanDefinition:

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

此时,我们可以编写如下的测试用例:

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

注:这个测试用例及其简单,但是他却可以使用字符串的形式描述一切的类。

我们再测试一种具有继承关系的bean:


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

public class TeddyDog extends Dog{
    
    
    private String name;
}

xml可以如下定义:

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

如果是手动定义如下:

@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在很多场景可以替换以上的内容,但是由于历史等原因,RootBeanDefinition依旧存在而且很重要,后期的归一处理还是要将不同的BeanDefinition转换或合并成一个RootBeanDefinition:

  • RootBeanDefinition与AbstractBeanDefinition是互补关系,RootBeanDefinition在AbstractBeanDefinition的基础上定义了更多属性。
  • RootBeanDefinition不能有父BeanDefinition,可以和ChildBeanDefinition配合使用构建父子关系(bean是可以继承的)。
  • 目前最常用的BeanDefinition是GenericBeanDefinition及其子类的实现,GenericBeanDefinition很强大,也可以很轻松的独立的构建父子关系。
  • 有时为了统一调用,不同的BeanDefinition可以合并、拷贝等。
    // 转换的
    GenericBeanDefinition definition = new GenericBeanDefinition(teddy);
    // 合并的
    definition.overrideFrom(dog);
    

三、BeanDefinition注册器

有了统一标准的元数据之后,我们就可以统一管理,这就需要一个容器去存储,当然我们可以使用map这样的集合类,当然spring差不多也是这样做的,他为我们提供了一个接口BeanDefinitionRegistry。只要实现了这个接口,就会拥有注册beanDefinition的能力。

BeanDefinitionRegistry接口如下:

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

}

一个bean可以有一个id,和多个名字或别名,AliasRegistry为我们提供了注册别名的能力:

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

spring给我们提供了一个超级简单的实现SimpleBeanDefinitionRegistry,如下:

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

}

这里我们编写测试用例:

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

四、加载BeanDefinition

当然我们不可能为每一个类手动编写与之对应的BeanDefinition,元数据还是要从xml或注解或配置类中获取,spring也为我们提供了对应的工具。

1、读取xml配置文件

该类通过解析xml完成BeanDefinition的读取,并且将它解析的BeanDefinition注册到一个注册器中:

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

原理后边介绍。

2、加载带注解的bean

@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、读取配置类

ConfigurationClassBeanDefinitionReader可以读取配置类,只是这个类不让我们使用,该类提供了如下方法:

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

他会将读取的元数据封装成为:ConfigurationClassBeanDefinition。

4、类路径扫描

@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、包扫描的过程

无论是扫包还是其他方式,我们我们解析一个类无非有几种方式:

  1. 加载一个类到内存,获取Class对象,通过反射获取元数据
  2. 直接操纵字节码文件(.class),读取字节码内的元数据

毫无疑问spring选择了第二种,

原因:第二种性能要优于第一种。第一种会将扫描的类全部加载到堆内存,无疑会浪费空间,增加gc次数,第二种可以根据元数据按需加载

我们以包扫描的doScan方法为例(ClassPathBeanDefinitionScanner类):

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

我们可以紧接着看看其中很重要的一个方法:

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

我们可以添加如下的依赖,自动生成部分索引:

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

编译后的文件如下:

在这里插入图片描述

当然我们更加关注的是scanCandidateComponents方法:

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

上边的源码中我们看到读取类文件的真实的实例是simpleMetadataReader,spring选用了【read+visitor】的方式来读取字节码,read负责暴露接口,visitor负责真正的读取工作:

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

SimpleAnnotationMetadataReadingVisitor类使用了大量asm的内容,由此可见spring在读取元数据的时候,是直接读取class文件的内容,而非加载后通过反射获取,我们列举其中的个别属性和方法,大致能窥探一二:

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(
		// ...省略
	}

     // ...省略
}

这其中还是用了一些类如下:
在这里插入图片描述
这些类都是对注解和类的元数据进行了封装,提供更简单的访问方式,很简单。

我们写一个简单的小例子,来看看:

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

}

不同的register方式形成了不同的BeanDefinition子类:
在这里插入图片描述


第二章 基础工具

一、内省 api

1、什么是内省?

内省(IntroSpector)是Java 语言针对Bean类属性、事件的一种缺省处理方法,Spring的源码中也会经常出现相关的api,所以我们有必要了解一下。

JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。说起来与反射有些相似,事实上,内省机制也是通过反射来实现的。

相对于内省,反射则更加强大,他能在运行状态把Java类中的各种成分映射成相应的Java类,可以动态的获取所有的属性以及动态调用任意一个方法,强调的是运行状态。

内省和反射的常用api如下:
在这里插入图片描述在Java内省中,用到的基本上就是上述几个类。

内省api的一般的做法是通过类 Introspector 的 getBeanInfo方法来获取某个对象的 BeanInfo 信息,然后通过 BeanInfo 来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的 取值/赋值 方法,然后我们就可以通过反射机制来调用这些方法,这就是内省机制。

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

大名鼎鼎的BeanUtils可以更加简单优雅的操作bean:

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

二、更强的反射工具

在spring中,我们除了能看到内省相关的api,看到的更多的可能是反射api了,当然针对原生api的复杂性,spring同样进行了封装,让其使用起来更简单。

spring给我们提供了强大的反射工具BeanWrapper,下边的例子展示了该类如何配合BeanDefinition对其进行了实例化:

1、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);
}

我们可以看到BeanWrapperImpl仅仅需要一个Class就能十分友好的结合beanDefinition进行构建和赋值,而不需要通过复杂的反射获取构造器进行实例化,获取字段对象进行赋值,当然这仅仅是api封装的功劳,原理还是那些东西。

2、批量构造

我们可以使用如下的方法进行批量构造:

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

貌似事与愿违,此时抛出一个异常,是说无法将一个TypedStringValue类型的数据转化为一个Integer,没有合适的转化器:

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

这个问题,我们先按下不表,后边再解释,接着我们看几个常用的类:

3、ResolvableType

该类可以封装Java类型,提供对超类类型、接口和泛型参数的访问,以及最终解析为类的能力,这是非常常见的一个类,他能及其方便的简化对反射api的调用,该类在spring中的使用率非常高。

ResolvableType可以从字段、方法参数、方法返回类型或类中获得。这个类上的大多数方法本身都会返回一个ResolvableType,以便于链式调用。

官方的案例如下:

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
}

我们也可以写测试用例测试一下,光看不练是记不牢的:

@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:可解析的,可分解的。spring中经常出现这个单词。如ResolvableAttribute,ResolvableType,registerResolvableDependency,后期遇到我们再学习。

三、类型转化

我们从xml中搜集到的所有数据都是【字符串】,但是实际的类中的成员变量可能是数字,数组,集合,或者是复杂的引用数据类型,所以spring给我们提供了强大的转换服务(conversionService接口)。

1、转换服务

ConversionService接口很简单,可以根据源类型和目标类型进行判断是否可以转换,并执行转换:

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

我们不妨看看DefaultConversionService的源码,更多核心的功能是在器父类中实现的,在构造实例时,他会默认传入大量可用转化器:

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、独立编写转化器

在上一个例子中,我们的异常说是TypedStringValue到Integer无法转化,TypedStringValue是Spring对String的一个包装,具体的值是存在TypedStringValue中的,我们的DefaultConversionService没有默认的转换器,当然实际Spring在转换的时候会做出处理,我们也可以自己写TypedStringValueToInteger和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、转化器源码

所有注册的convertor都会存储在Converters中,该类结构相对比较复杂:

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:表示一对,包含sourceType和targetType

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

ConvertersForPair:这一对对应的转换器们(因为能处理一对类型转换可能存在多个转换器),内部使用一个双端队列Deque来存储,保证顺序。它的结构如下:
在这里插入图片描述

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

你还有可能看到有些地方的文章将类型转换器使用的是PropertyEditor,这个类是定义在java.beans中的接口,功能和本章内容相似,是spring3.0之前使用的转换器,我们就不用看了:
在这里插入图片描述

BeanWapper中使用如下方法添加:beanWrapper.registerCustomEditor(xxxxPropertyEditor);
有兴趣的可以自行学习。

四、资源获取

Spring的【Resource】接口位于【org.springframework.core.io】 包,他抽象了对资源的访问的能力。 下面提供了【Resource】接口的概述, Spring本身广泛地使用了Resource接口,这个我们之前就接触过了。

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、内置的 Resource的实现

Spring包含了几个内置的 Resource 实现,如下所示:

  • UrlResource:UrlResource包装了java.net.URL,可以用来访问任何需要通过URL访问的对象,例如文件、HTTPS目标、FTP目标等。 所有URL都用一个标准化的字符串表示,这样就可以使用适当的标准化前缀来表示不同类型的URL。 这包括用于访问文件系统路径的’ file: ‘,用于通过https协议访问资源的’ https: ‘,用于通过ftp访问资源的’ ftp: '等。
  • ClassPathResource:该类表示应该从【类路径】中获取的资源。 它使用线程上下文类装入器、给定的类装入器或给定的类装入资源。
  • FileSystemResource:这是面向java.io的Resource实现,可以简单的实现对系统文件的操作。
  • InputStreamResource:给定的InputStream的Resource实现。 只有当没有特定的资源实现适用时,才应该使用它。
  • ByteArrayResource:这是一个给定字节数组的资源实现。

我们编写以下三个测试有用例来简单学习感知一下:

@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解析

这里我们接着之前的源码继续深入,回顾我们之前的加载beanDefinition的过程:

  1. 加载.class文件资源,这里和类加载不是一个概念。
  2. 加载xml文件

首先我们看一个小知识点:

  • 前缀[classpath:*] :只会到target下面的class路径中查找文件,通常匹配一个资源。
  • 前缀[classpath*:] :不仅包含target下面的class路径,还包括jar文件中(target下面的class路径)进行查找,可以匹配多个资源,这种场景非常多,比如在实现了springboot自动装配相关的jar包中绝大多数都会有spring.factories文件。在这里插入图片描述

spring给我们提供了springPathMatchingResourcePatternResolver类,该工具可以很灵活的帮助我们获取对应的Resource实例,他提供了getResource和getResources方法为我们使用:

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

我们写一个测试用例如下:

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

当然我们也可以调用getResources方法获取类路径下的所有的同名文件:

我们可以写如下的测试用例:

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

结果如下:
在这里插入图片描述
源码的部分,我们从以下方法看起:

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

回到我们的doLoadBeanDefinitions方法中:

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

继续进入核心方法loadDocument:

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

此时,xml文件变成了Document对象,以下的内容无需再看,解析使用的不是dom4j而是jdk自带的org.w3c.dom相关的api。

以下是解析dom的部分:

我们回到doLoadBeanDefinitions中,再看一眼registerBeanDefinitions方法,该方法是将DOM注册为beanDefinition的地方

int count = registerBeanDefinitions(doc, resource);

一直进入核心方法,如下正是在解决各个一级标签,如import、bean、alias等:

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

我们跟进到一个解析bean标签的方法,一直跟进可以找到如下代码:

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

看到此处,如何加载xml文件我们已经了然于胸。

五、环境抽象

spring提供了Environment接口,是一个对环境的抽象,集成在容器中,它模拟了应用程序环境的两个关键方面,分别是profiles和properties。

一个profile是一个给定名字的,在【逻辑上分了组】的beanDifination配置,只有在给定的profile是激活的情况下才向容器注册。

properties 在几乎所有的应用程序中都扮演着重要的角色,他就是一大堆的key-value的集合,他可能源自各种来源:属性文件、JVM系统属性、系统环境变量、JNDI、servlet上下文参数、特定的【Properties】对象、“Map”对象等等。 与属性相关的Environment对象的作用是为用户提供一个方便的服务接口,用于配置属性源并从那里解析属性。

1、properties

Spring中的环境对象提供了对【属性】的搜索操作,我们看一下下边的例子:

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

当然我们也可以debug,观察其中的env变量:
在这里插入图片描述在前面的代码片段中,我们看到了查询在spring的env中是否存在【JAVA_HOME】属性。 为了回答这个问题,Environment对象对propertySources中的【PropertySource】执行搜索。

这里边又出现了PropertySource的概念,他是对任何【键值对源】的一个简单抽象, spring的StandardEnvironment配置了两个基础PropertySource对象:

(1)一个代表JVM系统属性的集合(“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);
    }
}

在这里插入图片描述(2)一个代表系统环境变量的设置(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);
    }
}

其值如下,部分截取:
在这里插入图片描述StandardEnvironment的源码如下,默认情况便确实会将这两个propertySource加入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()));
    }
}

当然,整个机制都是可配置的。 我们可以将自定义的属性源集成到此搜索中。 为此,我们可以实例化自己的【PropertySource】,并将它添加到当前’ Environment ‘的’ propertyssources '集合中,如下:

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

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

测试用例如下:

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

【@PropertySource 】注解提供了一种方便的声明性机制,用于向Spring的【Environment】中添加【 PropertySource】。

给定一个名为app的propertis文件,输入内容,如【teacherName=itnanls】,编写如下配置类:

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

测试用例如下:

@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在核心容器中提供了一种机制,允许在不同环境中注册不同的Bean。 “环境”这个词对不同的用户有不同的含义,

  • 在开发中使用内存中的数据源,还是在生产中从JNDI中查找的数据源。
  • 为客户A和客户B部署注册定制的bean实现。

考虑一个实际应用程序中的第一个用例,它需要一个“数据源”。 在测试环境中,配置可能类似如下:

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

现在考虑如何将该应用程序部署到生产环境中,假设应用程序的数据源已注册到生产应用程序服务器的JNDI目录中。 我们的 ‘dataSource’ bean现在看起来如下所示:

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

重点:问题是如何根据当前环境在使用这两种数据源之间进行切换?

当然,我们可以使用 @Profile。

【@Profile】注解允许您指出,当一个或多个bean在哪一种Profile被激活时被注入。 使用前面的例子,我们可以将dataSource配置重写如下:

@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也可以在方法级别声明,只包含一个配置类的一个特定bean(例如,对于一个特定bean的替代变体),如下面的示例所示:

@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、激活一个配置

现在我们已经更新了配置,我们仍然需要指示Spring哪个配置文件是活动的。 如果我们现在启动我们的样例应用程序,我们会看到抛出一个NoSuchBeanDefinitionException,因为容器无法找到名为dataSource的Spring bean。

激活配置文件有几种方式,但最直接的方式是通过【ApplicationContext】可用的【Environment】API以编程方式执行。 下面的例子展示了如何做到这一点:

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

此外,你还可以通过spring.profiles来声明性地激活环境【active】属性,它可以通过系统环境变量、JVM系统属性、servlet上下文参数在’ web.xml '中指定。

请注意,配置文件不是一个“非此即彼”的命题。 你可以一次激活多个配置文件。 通过编程方式,你可以向 ‘setActiveProfiles()’ 方法提供多个配置文件名,该方法接受 'String…'可变参数。 下面的示例激活多个配置文件:

加入启动参数:

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

编程的方式:

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

spring-boot中会更加简单,我们这里就不赘述了:

spring.profiles.active=dev

六、发布订阅

1、简介

spring为我们提供了event multicaster,可以十分简单的实现发布订阅模式:

multicast(组播): 也叫多播, 多点广播或群播。 指把信息同时传递给一组目的地址。它使用策略是最高效的,因为消息在每条网络链路上只需传递一次,而且只有在链路分叉的时候,消息才会被复制。

在这里插入图片描述
spring启动时也会默认注入一个如下的bean:simpleApplicationEventMulticaster,我们可以使用这个类轻松实现事件机制,这是典型的【观察者设计模式】:

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

结果如下:

在这里插入图片描述

2、源码阅读

这其中有很多复杂的情况,比如listener是我们直接手动注册的实例呢,还是spring工厂中的bean呢,如果是bean是singleton还是prototype呢?

因为源码比较复杂,我们需要结合下图一起看:

图一:
在这里插入图片描述
图二显示了当listener被调用执行后,如何进行了缓存:
在这里插入图片描述

SimpleApplicationEventMulticaster源码如下:

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

其父类中有更多实现,包括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: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;
    }
}

默认的listener的存储器:

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

你可以尝试在listener中添加如下代码:

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

我们可以尝试修改之前的代码,当然我们也可以设定异常处理器:

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

我们可以看到,现在listener的调用线程确实是从线程池中获取。

七、国际化

spring引入了MessageSource机制,可以帮我们简单的实现国际化( internationalization),简称i18n。

ResourceBundleMessageSource是一个简单的实现,我们可以建立如下的文件用以测试:
在这里插入图片描述文件内容超级简单,分别是:

  • zh_CN的文件内容:hello=你好 {0}
  • en_us的文件内容:hello=hello {0}

使用以下的测试用例就可以简单的获取对应Locale的内容:

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

关于方法中的几个参数的描述:

  • string code:文件中的key
  • Object[] args:待解析字符串的参数,value中可以存在带{}占位符的变量
  • String defaultMessage:读取不到国际化信息时依据此默认值进行处理
  • Locale locale:国际化定位,如 Locale.CHINA 代表中国

更多关于国际化的内容我们可以在后边再做介绍。

八、表达式


猜你喜欢

转载自blog.csdn.net/qq_45867699/article/details/129541192
今日推荐