本文主要是介绍学习笔记:Spring框架源码Part.1——基础,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
学习视频链接:https://www.bilibili.com/video/BV1zd4y1L7YD
Spring源码学习笔记—基础
- 前言
- 第一章 bean的元数据
- 一、回顾bean的注入方式
- 二、BeanDefiniiton详解
- 1、认识BeanDefinition
- 2、AbstractBeanDefinition
- 3、GenericBeanDefinition
- 三、BeanDefinition注册器
- 四、加载BeanDefinition
- 1、读取xml配置文件
- 2、加载带注解的bean
- 3、读取配置类
- 4、类路径扫描
- 5、包扫描的过程
- 第二章 基础工具
- 一、内省 api
- 1、什么是内省?
- 二、更强的反射工具
- 1、bean的创建
- 2、批量构造
- 3、ResolvableType
- 三、类型转化
- 1、转换服务
- 2、独立编写转化器
- 3、转化器源码
- 4、PropertyEditor
- 四、资源获取
- 1、内置的 Resource的实现
- 2、xml解析
- 五、环境抽象
- 1、properties
- 2、Profiles
- 3、激活一个配置
- 六、发布订阅
- 1、简介
- 2、源码阅读
- 七、国际化
- 八、表达式
前言
如果你是一个初学者。刚开始阅读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 {@Overridepublic 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大概保存了以下信息:
- 定义了id、别名与Bean的对应关系(BeanDefinitionHolder)
- 具体的工厂方法(Class类型),包括工厂方法的返回类型,工厂方法的Method对象
- 构造函数、构造函数形参类型
- Bean的class对象
- 作用范围、是否懒加载等等
以下是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,可以只对有父子关系的beanvoid 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();// 设置是否主要beanvoid 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 BeanMetadataAttributeAccessorimplements 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_DEFAULTprivate 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 {@Nullableprivate String parentName;public GenericBeanDefinition() {super();}// 通过深拷贝创建一个beanpublic GenericBeanDefinition(BeanDefinition original) {super(original);}@Overridepublic void setParentName(@Nullable String parentName) {this.parentName = parentName;}@Override@Nullablepublic String getParentName() {return this.parentName;}@Overridepublic 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的创建需要依赖父DefinitionChildBeanDefinition 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 {// 注册一个BeanDefinitionvoid 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的增删查改@Overridepublic 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);}@Overridepublic void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {if (this.beanDefinitionMap.remove(beanName) == null) {throw new NoSuchBeanDefinitionException(beanName);}}@Overridepublic BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {BeanDefinition bd = this.beanDefinitionMap.get(beanName);if (bd == null) {throw new NoSuchBeanDefinitionException(beanName);}return bd;}@Overridepublic boolean containsBeanDefinition(String beanName) {return this.beanDefinitionMap.containsKey(beanName);}@Overridepublic String[] getBeanDefinitionNames() {return StringUtils.toStringArray(this.beanDefinitionMap.keySet());}@Overridepublic int getBeanDefinitionCount() {return this.beanDefinitionMap.size();}@Overridepublic boolean isBeanNameInUse(String beanName) {return isAlias(beanName) || containsBeanDefinition(beanName);}}
这里我们编写测试用例:
@Test
public void testRegistryByJava(){// 定义一个注册器,用来注册和管理BeanDefinitionBeanDefinitionRegistry 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(){// 定义一个注册器,用来注册和管理BeanDefinitionBeanDefinitionRegistry 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() {// 定义一个注册器,用来注册和管理BeanDefinitionBeanDefinitionRegistry 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() {// 定义一个注册器,用来注册和管理BeanDefinitionBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();// 通过扫描包的方式ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);scanner.scan("com.ydlclass");logger.info(Arrays.toString(registry.getBeanDefinitionNames()));
}
5、包扫描的过程
无论是扫包还是其他方式,我们我们解析一个类无非有几种方式:
- 加载一个类到内存,获取Class对象,通过反射获取元数据
- 直接操纵字节码文件(.class),读取字节码内的元数据
毫无疑问spring选择了第二种,
原因:第二种性能要优于第一种。第一种会将扫描的类全部加载到堆内存,无疑会浪费空间,增加gc次数,第二种可以根据元数据按需加载
我们以包扫描的doScan方法为例(ClassPathBeanDefinitionScanner类):
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {// BeanDefinitionHolder持有 BeanDefinition实例和名字以及别名Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();for (String basePackage : basePackages) {// 这里是具体的扫描过程,找出全部符合过滤器要求的BeanDefinition// 返回的BeanDefinition的实际类型为ScannedGenericBeanDefinitionSet<BeanDefinition> candidates = findCandidateComponents(basePackage);// 根据不同的bean类型做统一处理,如附默认值等// 因为有些数据我们并没有配置,需要这里做默认处理for (BeanDefinition candidate : candidates) {// 如果存在,则解析@Scope注解,为候选bean设置代理的方式ScopedProxyMode,XML属性也能配置:scope-resolver、scoped-proxy,可以指定代理方式jdk或者cglibScopeMetadata 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支持成为自动注入候选beanif (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}// 如果bean定义是AnnotatedBeanDefinition类型,ScannedGenericBeanDefinition同样属于AnnotatedBeanDefinition类型if (candidate instanceof AnnotatedBeanDefinition) {// 4 处理类上的其他通用注解:@Lazy, @Primary, @DependsOn, @Role, @DescriptionAnnotationConfigUtils.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方法会生成一个元数据读取器// 我们的例子中是SimpleMetadataReaderMetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);// 检查读取到的类是否可以作为候选组件,即是否符合TypeFilter类型过滤器的要求// 使用IncludeFilter。就算目标类上没有@Component注解,它也会被扫描成为一个Bean// 使用ExcludeFilter,就算目标类上面有@Component注解也不会成为Beanif (isCandidateComponent(metadataReader)) {// 构建一个ScannedGenericBeanDefinitionScannedGenericBeanDefinition 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需要结合一个visitorgetClassReader(resource).accept(visitor, PARSING_OPTIONS);this.resource = resource;// 元数据都是visitor的能力,典型的访问者设计模式this.annotationMetadata = visitor.getMetadata();}// 通过资源获取一个ClassReaderprivate static ClassReader getClassReader(Resource resource) throws IOException {try (InputStream is = resource.getInputStream()) {try {return new ClassReader(is);}}}// 提供了通用能力@Overridepublic ClassMetadata getClassMetadata() {return this.annotationMetadata;}@Overridepublic AnnotationMetadata getAnnotationMetadata() {return this.annotationMetadata;}
}
SimpleAnnotationMetadataReadingVisitor类使用了大量asm的内容,由此可见spring在读取元数据的时候,是直接读取class文件的内容,而非加载后通过反射获取,我们列举其中的个别属性和方法,大致能窥探一二:
final class SimpleAnnotationMetadataReadingVisitor extends ClassVisitor {// 访问一个内部类的方法@Overridepublic void visitInnerClass(String name, @Nullable String outerName, String innerName, int access) {// ...省略}// 访问注解的方法@Override@Nullablepublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {// ...省略}// 访问方法的方法@Override@Nullablepublic 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、通过任意形式捕获beanDefinitionGenericBeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClassName("com.ydlclass.User");MutablePropertyValues propertyValues = new MutablePropertyValues();propertyValues.addPropertyValue("name","lily");propertyValues.addPropertyValue("age",12);beanDefinition.setPropertyValues(propertyValues);// 2、通过权限定名称获得ClassClass<?> 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、通过任意形式捕获beanDefinitionBeanDefinitionRegistry 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(); // Listt.getGeneric(1); // List<String>//第二个泛型,里面的泛型,即List<String>里面的Stringt.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 {@Nullableprivate 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.通过任意形式捕获beanDefinitionBeanDefinitionRegistry 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>() {@Overridepublic Integer convert(@NonNull TypedStringValue source) {return Integer.valueOf(Objects.requireNonNull(source.getValue()));}});conversionService.addConverter(new Converter<TypedStringValue, String>() {@Overridepublic 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) {// 如果没有限定转换类型,添加到globalConvertersthis.globalConverters.add(converter);}else {// 如果已经存在转换类型,我们写的都在这里for (ConvertiblePair convertiblePair : convertibleTypes) {// 找到与之匹配的加进去,这里是个链表getMatchableConverters(convertiblePair).add(converter);}}}@Nullablepublic 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;}@Nullableprivate GenericConverter getRegisteredConverter(TypeDescriptor sourceType,
TypeDescriptor targetType, ConvertiblePair convertiblePair) {// 根据convertiblePair获取ConvertersForPairConvertersForPair 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);}@Nullablepublic 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-ioIOUtils.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的过程:
- 加载.class文件资源,这里和类加载不是一个概念。
- 加载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.xmllogger.info(classPathResource.getURI().toString()); Resource fileResource = resourcePatternResolver.getResource("file:/Users/ziang.zhang/Downloads/chrome/dcf57c3fb1a448bba392e5b1f0cd302f.png");// file:/Users/ziang.zhang/Downloads/chrome/dcf57c3fb1a448bba392e5b1f0cd302f.pnglogger.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.factorieslogger.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)) {// recursedoRegisterBeanDefinitions(ele);}
}
我们跟进到一个解析bean标签的方法,一直跟进可以找到如下代码:
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {this.parseState.push(new BeanEntry(beanName));// 获取classNameString 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);// 解析qualifierparseQualifierElements(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";@Overrideprotected 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);}@Overridepublic 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 {@Beanpublic 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 {// 定义一个线程池,事件被触发时可由他来实现事件,默认为nullprivate 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;}@Nullableprotected ErrorHandler getErrorHandler() {return this.errorHandler;}//设置一个自定义执行器(线程池)来调用每个侦听器。public void setTaskExecutor(@Nullable Executor taskExecutor) {this.taskExecutor = taskExecutor;}@Nullableprotected Executor getTaskExecutor() {return this.taskExecutor;}// 广播一个事件@Overridepublic void multicastEvent(ApplicationEvent event) {multicastEvent(event, resolveDefaultEventType(event));}// 广播一个事件的具体实现@Overridepublic void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));Executor executor = getTaskExecutor();// 核心一:getApplicationListeners(event, type)稍后看// 他的重点是如何设计的缓存// 获取所有与event事件匹配的listener并调用核心方法onApplicationEventfor (ApplicationListener<?> listener : getApplicationListeners(event, type)) {// 如果你设置了线程池他会将任务丢给线程池if (executor != null) {// 核心二:调用Listener的方法invokeListenerexecutor.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时产生放入errorHandlererrorHandler.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 AbstractApplicationEventMulticasterimplements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {// 真正保存listener的地方,他保存了传入的listener实例和容器里的监听器bean的名字// 其结构如图1private final DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever();// 这个集合是对已调用的event和listener的缓存// ListenerCacheKey保存了一组event和source// CachedListenerRetriever保存了已注册的、容器的单例bean、容器的非单例beanName// 非单例的bean只能缓存name,实例会消亡final Map<ListenerCacheKey, Cached Retriever> retrieverCache = new ConcurrentHashMap<>(64);@Nullableprivate ClassLoader beanClassLoader;@Nullableprivate ConfigurableBeanFactory beanFactory;// 以编程的方式添加监听器@Overridepublic void addApplicationListener(ApplicationListener<?> listener) {// 这个过程可能存在线程安全的问题,比如一线线程synchronized (this.defaultRetriever) {// 因为你的Aop可能影响到该listener// 我们需要将代理对象从defaultRetriever中删除,因为我们并不需要Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);if (singletonTarget instanceof ApplicationListener) {this.defaultRetriever.applicationListeners.remove(singletonTarget);}// 添加原生的监听器到defaultRetrieverthis.defaultRetriever.applicationListeners.add(listener);// 清理缓存this.retrieverCache.clear();}}// 直接以bean的方式将listenerBeanName添加到defaultRetriever@Overridepublic 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(增删查改)代码// 我们看一个比较有难度的方法,该方法根据事件获取满足条件的listenersprotected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {// 获得事件源(那个类中发布的时间(如OrderService类发布是orderEvent))// source就是OrderService,event就是orderEventObject source = event.getSource();Class<?> sourceType = (source != null ? source.getClass() : null);// 通过事件类型和源类型创建缓存key// 第二次请求就避免了再次检索ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);// 定义新的Listener缓存器,他是作为retrieverCache的valueCachedListenerRetriever 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// 他包含编程式的实例和容器的beanList<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和listenerBeanssynchronized (this.defaultRetriever) {// 优先做一个拷贝listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);}// 将所有的listener实例遍历,过滤满足条件的for (ApplicationListener<?> listener : listeners) {if (supportsEvent(listener, eventType, sourceType)) {// 如果传递了缓存器,就将它存入filteredListenersif (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工厂获取,实例化beanApplicationListener<?> listener =beanFactory.getBean(listenerBeanName,ApplicationListener.class);if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {if (retriever != null) {// 如果是单例的就加入filteredListenersif (beanFactory.isSingleton(listenerBeanName)) {filteredListeners.add(listener);}// 如果不是单例bean,则加入filteredListenerBeans// 原因是非单例bean使用结束可能会被gc,下次使用需要重新实例化// 所以,我们并不缓存非单例的listenerbeanelse {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的名字// 下次从缓存获取的时候还是会再次实例化非单例的beanif (retriever != null) {// 如果啥也没有过滤则添加全部的Listeners和ListenerBeans到retrieverif (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@Nullablepublic volatile Set<ApplicationListener<?>> applicationListeners;// 保存了满足条件的非单例的applicationListenerBeans@Nullablepublic volatile Set<String> applicationListenerBeans;// 该方法是从特定缓存获取applicationListeners// 这个方法会再次实例化非单例的bean@Nullablepublic Collection<ApplicationListener<?>> getApplicationListeners() {Set<ApplicationListener<?>> applicationListeners = this.applicationListeners;Set<String> applicationListenerBeans = this.applicationListenerBeans;if (applicationListeners == null || applicationListenerBeans == null) {// Not fully populated yetreturn 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() {@Overridepublic 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 代表中国
更多关于国际化的内容我们可以在后边再做介绍。
八、表达式
这篇关于学习笔记:Spring框架源码Part.1——基础的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!