Merge pull request #16 from huifer/master

source code
pull/17/head
AmyliaY 5 years ago committed by GitHub
commit 19a762e93a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,8 @@
# mybatis 缓存
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis Cache 源码
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
- `org.apache.ibatis.cache.Cache`
```java
public interface Cache {

@ -1,6 +1,8 @@
# mybatis 反射
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis 反射相关类的源码
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
## addDefaultConstructor
- mybatis 的反射相关内容在`org.apache.ibatis.reflection` 下存放. 本片主要讲解`org.apache.ibatis.reflection.Reflector`类, 先看一下该类的属性

@ -1,6 +1,8 @@
# mybatis 日志源码
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis 日志相关源码
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
## 核心类
- `org.apache.ibatis.logging.Log`
- `org.apache.ibatis.logging.LogFactory`

@ -1,6 +1,7 @@
# Mybatis Alias
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis Alias 源码
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
- 源码位置 :`org.apache.ibatis.type.Alias`
- 与 Alias 相关的一个方法`org.apache.ibatis.type.TypeAliasRegistry.registerAlias(java.lang.String, java.lang.Class<?>)`(别名注册)

@ -1,6 +1,7 @@
# Mybatis Cursor
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis Cursor 源码
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
## Cursor
- 源码位置:`org.apache.ibatis.cursor.Cursor`
- 继承`Iterable`说明是一个迭代器,继承`Closeable`说明有一个东西需要关闭

@ -1,6 +1,8 @@
# Mybatis DataSource
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis DataSource 源码
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
- `org.apache.ibatis.datasource.DataSourceFactory`
```java
/**

@ -1,5 +1,6 @@
# Mybatis DyanmicSqlSourcce
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
- `org.apache.ibatis.scripting.xmltags.DynamicSqlSource`
- `org.apache.ibatis.scripting.xmltags.DynamicContext.DynamicContext`

@ -2,6 +2,7 @@
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis MapperMethod 源码
- 源码地址: `org.apache.ibatis.binding.MapperMethod`,核心方法是`execute`
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
```java
/**

@ -0,0 +1,112 @@
# Mybatis MetaObject
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
- 源码位于:`org.apache.ibatis.reflection.MetaObject`
```java
/**
* @author Clinton Begin
*/
public class MetaObject {
/**
* 原始的数据对象,初始化时的对象
*/
private final Object originalObject;
/**
* 对象包装
*/
private final ObjectWrapper objectWrapper;
/**
* object 工厂
*/
private final ObjectFactory objectFactory;
/**
* object
*/
private final ObjectWrapperFactory objectWrapperFactory;
/**
* 反射工程
*/
private final ReflectorFactory reflectorFactory;
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
// 根据object不同实例进行不同的实例化方式
if (object instanceof ObjectWrapper) {
this.objectWrapper = (ObjectWrapper) object;
} else if (objectWrapperFactory.hasWrapperFor(object)) {
this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
} else if (object instanceof Map) {
this.objectWrapper = new MapWrapper(this, (Map) object);
} else if (object instanceof Collection) {
this.objectWrapper = new CollectionWrapper(this, (Collection) object);
} else {
this.objectWrapper = new BeanWrapper(this, object);
}
}
public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
if (object == null) {
return SystemMetaObject.NULL_META_OBJECT;
} else {
return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}
}
/**
* 获取value
* @param name 属性值名称
* @return
*/
public Object getValue(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
// 判断是否是空的metaObject
return null;
} else {
return metaValue.getValue(prop.getChildren());
}
} else {
return objectWrapper.get(prop);
}
}
/**
* metaObject 设置属性值方法
* {name:value}
*
* @param name 属性值名称
* @param value 属性值
*/
public void setValue(String name, Object value) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
// 获取属性实例
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
if (value == null) {
// value 空则返回
// don't instantiate child path if value is null
return;
} else {
// 创建属性值
metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
}
}
metaValue.setValue(prop.getChildren(), value);
} else {
objectWrapper.set(prop, value);
}
}
}
```

@ -1,6 +1,7 @@
# MethodSignature
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis MethodSignature 类
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
- `org.apache.ibatis.binding.MapperMethod.MethodSignature`
```java
/**

@ -1,5 +1,6 @@
# Mybatis ObjectWrapper
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
- 源码位于: `org.apache.ibatis.reflection.wrapper.ObjectWrapper`
类图:

@ -1,6 +1,8 @@
# ParamNameResolver 源码解析
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis `@Param` 注解和`ParamNameResolver`
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
## 源码
- `org.apache.ibatis.reflection.ParamNameResolver`
```java

@ -1,6 +1,7 @@
# sqlCommand
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis sqlCommand类的源码
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
- `org.apache.ibatis.binding.MapperMethod.SqlCommand`
```java

@ -1,6 +1,7 @@
# GenericTokenParser
- Author: [HuiFer](https://github.com/huifer)
-
- 源码阅读工程: [huifer-mybatis](https://github.com/huifer/javaBook-src/tree/old/mybatis-3)
```java
/**
* Copyright 2009-2019 the original author or authors.

@ -0,0 +1,905 @@
# Spring-BeanDefinitionParserDelegate
- Author: [HuiFer](https://github.com/huifer)
- 源码路径: `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate`
- 注意: 贴出的代码为当前类(`BeanDefinitionParserDelegate`)没有将其他的调用代码贴出,详细请看[huifer-srping](https://github.com/huifer/spring-framework).
- 该类的作用是将标签解析
- 下面这段代码是这个类的入口
```java
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// 获取id 属性
String id = ele.getAttribute(ID_ATTRIBUTE);
// 获取 name 属性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// 别名列表
List<String> aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
// beanName = <bean id=""/> 中id的属性值
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isTraceEnabled()) {
logger.trace("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
// 创建对象
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name, if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
```
![image-20191231142829639](/image/spring/image-20191231142829639.png)
该类大量的`parseXXX`开头的代码,这些方法都是对标签进行解析转换成具体的实体
### 测试用例
```xml
<bean id="personBean" class="com.huifer.source.spring.bean.Person" scope="prototype">
<property name="name" value="huifer"/>
<meta key="key" value="value"/>
</bean>
```
## parseBeanDefinitionAttributes
- 解析属性`scope`,`abstract`,`lazy-init`
```java
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
}
else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
}
else if (containingBean != null) {
// Take default from containing bean in case of an inner bean definition.
bd.setScope(containingBean.getScope());
}
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
if (isDefaultValue(lazyInit)) {
lazyInit = this.defaults.getLazyInit();
}
bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
bd.setAutowireMode(getAutowireMode(autowire));
if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
}
String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
if (isDefaultValue(autowireCandidate)) {
String candidatePattern = this.defaults.getAutowireCandidates();
if (candidatePattern != null) {
String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
}
}
else {
bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
}
if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
}
if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
bd.setInitMethodName(initMethodName);
}
else if (this.defaults.getInitMethod() != null) {
bd.setInitMethodName(this.defaults.getInitMethod());
bd.setEnforceInitMethod(false);
}
if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
bd.setDestroyMethodName(destroyMethodName);
}
else if (this.defaults.getDestroyMethod() != null) {
bd.setDestroyMethodName(this.defaults.getDestroyMethod());
bd.setEnforceDestroyMethod(false);
}
if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
}
if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
}
return bd;
}
```
![image-20191231162505748](/image/spring/image-20191231162505748.png)
其他的标签也同样的方式**`getAttribute`**获取
思路:
1. 判断是否有这个标签
1. 有直接获取,设置值
2. 没有跳过
## parseMetaElements
```java
/**
* 解析Meta 元素
* {@code <meta key="h" value="c"/>} => {@link BeanMetadataAttribute}
* Parse the meta elements underneath the given element, if any.
*/
public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
NodeList nl = ele.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {
Element metaElement = (Element) node;
// 获取 key 属性值
String key = metaElement.getAttribute(KEY_ATTRIBUTE);
// 获取 value 属性值
String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
attribute.setSource(extractSource(metaElement));
attributeAccessor.addMetadataAttribute(attribute);
}
}
}
```
![image-20191231164622063](/image/spring/image-20191231164622063.png)
## parseLookupOverrideSubElements
```java
/**
* 解析{@code <lookup-method bean="personBean2"></lookup-method>}
* Parse lookup-override sub-elements of the given bean element.
*/
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
Element ele = (Element) node;
String methodName = ele.getAttribute(NAME_ATTRIBUTE);
String beanRef = ele.getAttribute(BEAN_ELEMENT);
// 转换成JAVA对象
LookupOverride override = new LookupOverride(methodName, beanRef);
override.setSource(extractSource(ele));
overrides.addOverride(override);
}
}
}
```
### 测试用例
```xml
<bean id="personBean" class="com.huifer.source.spring.bean.Person" >
<property name="name" value="huifer"/>
<meta key="key" value="value"/>
<lookup-method name="getApple" bean="appleBean"/>
</bean>
<bean name="appleBean" class="com.huifer.source.spring.bean.Apple">
<property name="name" value="this is an apple !"/>
</bean>
```
![image-20191231165638975](/image/spring/image-20191231165638975.png)
## parseReplacedMethodSubElements
```java
/**
* {@code <replaced-method name="" replacer=""/>}
* Parse replaced-method sub-elements of the given bean element.
*/
public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
Element replacedMethodEle = (Element) node;
String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
// 转换成JAVA对象
ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
// Look for arg-type match elements.
// 参数解析 <arg-type match=""> 解析
List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
for (Element argTypeEle : argTypeEles) {
String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
if (StringUtils.hasText(match)) {
replaceOverride.addTypeIdentifier(match);
}
}
replaceOverride.setSource(extractSource(replacedMethodEle));
overrides.addOverride(replaceOverride);
}
}
}
```
### 测试用例
编写方法`dis`
```java
public class Person {
private String name;
private Apple apple;
private Integer age;
public void dis() {
System.out.println("dis");
}
}
```
编写一个替换`dis`方法的类
```java
public class Rc implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("替换原来的方法");
return null;
}
}
```
xml 配置
```java
<replaced-method name="dis" replacer="rcBean"/>
<bean name="rcBean" class="com.huifer.source.spring.bean.Rc"/>
```
![image-20200101093742238](/image/spring/image-20200101093742238.png)
## parseConstructorArgElements
```java
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
parseConstructorArgElement((Element) node, bd);
}
}
}
```
```java
/**
* 解析 constructor-arg 元素
* {@code <constructor-arg name="" value="" index="" ref="" type="" />}
* Parse a constructor-arg element.
*/
public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// 判断是否存在 index 属性
if (StringUtils.hasLength(indexAttr)) {
try {
int index = Integer.parseInt(indexAttr);
if (index < 0) {
error("'index' cannot be lower than 0", ele);
}
else {
try {
// ConstructorArgumentEntry 插入parseState
this.parseState.push(new ConstructorArgumentEntry(index));
Object value = parsePropertyValue(ele, bd, null);
// 转换成JAVA对象
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
// 不允许重复指定相同参数
if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
error("Ambiguous constructor-arg entries for index " + index, ele);
}
else {
bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
}
}
finally {
this.parseState.pop();
}
}
}
catch (NumberFormatException ex) {
error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
}
}
else {
try {
this.parseState.push(new ConstructorArgumentEntry());
Object value = parsePropertyValue(ele, bd, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
}
finally {
this.parseState.pop();
}
}
}
```
```JAVA
/**
* Get the value of a property element. May be a list etc.
* Also used for constructor arguments, "propertyName" being null in this case.
*/
@Nullable
public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
String elementName = (propertyName != null ?
"<property> element for property '" + propertyName + "'" :
"<constructor-arg> element");
// Should only have one child element: ref, value, list, etc.
NodeList nl = ele.getChildNodes();
Element subElement = null;
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 不处理 element 节点名称 = description 和 meta
if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
!nodeNameEquals(node, META_ELEMENT)) {
// Child element is what we're looking for.
if (subElement != null) {
error(elementName + " must not contain more than one sub-element", ele);
}
else {
subElement = (Element) node;
}
}
}
// 判断是否存在 ref 属性
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
// 判断是否 value 属性
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
// 判断1: ref属性存在 value 属性 或者 ref 和 value 有一个存在并且有 下级节点 抛出异常
if ((hasRefAttribute && hasValueAttribute) ||
((hasRefAttribute || hasValueAttribute) && subElement != null)) {
error(elementName +
" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
}
// 判断2: 存在 ref 返回 ref 的值
if (hasRefAttribute) {
String refName = ele.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
error(elementName + " contains empty 'ref' attribute", ele);
}
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
ref.setSource(extractSource(ele));
return ref;
}
// 判断3: 存在 value 返回 value 的值
else if (hasValueAttribute) {
TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
valueHolder.setSource(extractSource(ele));
return valueHolder;
}
else if (subElement != null) {
// 下级标签解析
return parsePropertySubElement(subElement, bd);
}
else {
// 没有抛出异常
// Neither child element nor "ref" or "value" attribute found.
error(elementName + " must specify a ref or value", ele);
return null;
}
}
```
### 测试用例
- 编辑构造函数
```java
public class Person {
private String name;
private Apple apple;
private Integer age;
public Person() {
}
public Person(Integer age) {
this.age = age;
}
```
- 添加配置
```xml
<constructor-arg name="age" value="10"/>
```
![image-20200101100906778](/image/spring/image-20200101100906778.png)
## parsePropertyElements
```java
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
parsePropertyElement((Element) node, bd);
}
}
}
```
```java
/**
* 解析 {@code <property name="" value=""/>}
* Parse a property element.
*/
public void parsePropertyElement(Element ele, BeanDefinition bd) {
String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
if (!StringUtils.hasLength(propertyName)) {
error("Tag 'property' must have a 'name' attribute", ele);
return;
}
this.parseState.push(new PropertyEntry(propertyName));
try {
if (bd.getPropertyValues().contains(propertyName)) {
error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
return;
}
Object val = parsePropertyValue(ele, bd, propertyName);
// 转换JAVA 对象
PropertyValue pv = new PropertyValue(propertyName, val);
parseMetaElements(ele, pv);
pv.setSource(extractSource(ele));
bd.getPropertyValues().addPropertyValue(pv);
}
finally {
this.parseState.pop();
}
}
```
### 测试用例
xml
```xml
<bean id="personBean" class="com.huifer.source.spring.bean.Person">
<property name="name" value="huifer"/>
</bean>
```
![image-20200101111755022](/image/spring/image-20200101111755022.png)
## parseQualifierElements
```java
public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ELEMENT)) {
parseQualifierElement((Element) node, bd);
}
}
}
```
```java
/**
* Parse a qualifier element.
*/
public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) {
String typeName = ele.getAttribute(TYPE_ATTRIBUTE);
// 判断是否有类型
if (!StringUtils.hasLength(typeName)) {
error("Tag 'qualifier' must have a 'type' attribute", ele);
return;
}
// 创建并且放入 parseState
this.parseState.push(new QualifierEntry(typeName));
try {
AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName);
qualifier.setSource(extractSource(ele));
String value = ele.getAttribute(VALUE_ATTRIBUTE);
// 判断是否有值
if (StringUtils.hasLength(value)) {
qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, value);
}
// 下级节点
NodeList nl = ele.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ATTRIBUTE_ELEMENT)) {
Element attributeEle = (Element) node;
// 获取 下级biao'qia
String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE);
String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE);
// 判断是否有 key 和 value
if (StringUtils.hasLength(attributeName) && StringUtils.hasLength(attributeValue)) {
// 转换成JAVA对象
BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue);
attribute.setSource(extractSource(attributeEle));
qualifier.addMetadataAttribute(attribute);
}
else {
// 没有抛出异常
error("Qualifier 'attribute' tag must have a 'name' and 'value'", attributeEle);
return;
}
}
}
bd.addQualifier(qualifier);
}
finally {
this.parseState.pop();
}
}
```
### 测试用例
- 编写一个注解
```java
/**
* 在spring-config.xml中配置 ,使用时通过 status + quality 来引入具体的bean
*/
@Target({ElementType.FIELD, ElementType.METHOD,
ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface PersonQualifier {
String status();
String quality();
}
```
```JAVA
public class PersonS {
private String personName;
public String getPersonName() {
return personName;
}
public void setPersonName(String personName) {
this.personName = personName;
}
}
```
```JAVA
import org.springframework.beans.factory.annotation.Autowired;
public class PersonService {
@Autowired
// @PersonQualifier(status = "status_teacher", quality = "quality_teacher")
@PersonQualifier(status = "status_student", quality = "quality_student")
private PersonS personS;
public PersonS getPerson() {
return personS;
}
public void setPerson(PersonS person) {
this.personS = person;
}
}
```
```JAVA
public class Student extends PersonS {
private String stdLocation;
public String getStdLocation() {
return stdLocation;
}
public void setStdLocation(String stdLocation) {
this.stdLocation = stdLocation;
}
}
```
```JAVA
public class Teacher extends PersonS {
private String subject;
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
}
```
```JAVA
public class QualifierSourceCode {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("QualifierSourceCode-beans.xml");
PersonService service = context.getBean(PersonService.class);
System.out.println(service.getPerson().getPersonName());
context.close();
}
}
```
```XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:annotation-config/>
<bean class="com.huifer.source.spring.qualifier.PersonService"/>
<bean class="com.huifer.source.spring.qualifier.Student">
<qualifier type="com.huifer.source.spring.qualifier.PersonQualifier">
<attribute key="status" value="status_student"/>
<attribute key="quality" value="quality_student"/>
</qualifier>
<property name="personName" value="Student sName"/>
</bean>
<bean class="com.huifer.source.spring.qualifier.Teacher">
<qualifier type="com.huifer.source.spring.qualifier.PersonQualifier">
<attribute key="status" value="status_teacher"/>
<attribute key="quality" value="quality_teacher"/>
</qualifier>
<property name="personName" value="Teacher tName"/>
</bean>
</beans>
```
![image-20200101155539501](/image/spring/image-20200101155539501.png)
### Bean解析结果
![image-20200102083512005](/image/spring/image-20200102083512005.png)
## importBeanDefinitionResource
```JAVA
/**
* 解析{@code <import>} 标签
* Parse an "import" element and load the bean definitions
* from the given resource into the bean factory.
*/
protected void importBeanDefinitionResource(Element ele) {
// 获取 resource 属性
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
// 是否有 resource 属性
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele);
return;
}
// Resolve system properties: e.g. "${user.dir}"
// 获取系统环境,解析路径
/**
* 1. getReaderContext() 获取{@link XmlReaderContext}
* 2. getEnvironment() 获取环境
* 3. resolveRequiredPlaceholders(location) 解析占位符${...}
*/
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
// 资源存放集合
Set<Resource> actualResources = new LinkedHashSet<>(4);
// Discover whether the location is an absolute or relative URI
// 相对路径 绝对路径的判断
boolean absoluteLocation = false;
try {
// 判断是相对路径还是绝对路径
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
}
catch (URISyntaxException ex) {
// cannot convert to an URI, considering the location relative
// unless it is the well-known Spring prefix "classpath*:"
}
// Absolute or relative?
if (absoluteLocation) {
try {
// 获取import中的数量
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isTraceEnabled()) {
logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
}
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error(
"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
}
}
else {
// No URL -> considering resource location as relative to the current file.
try {
int importCount;
// 本地地址
Resource relativeResource = getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
// 此处调用方式和加载一个xml文件相同
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
}
else {
String baseLocation = getReaderContext().getResource().getURL().toString();
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isTraceEnabled()) {
logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
}
}
catch (IOException ex) {
getReaderContext().error("Failed to resolve current resource location", ele, ex);
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error(
"Failed to import bean definitions from relative location [" + location + "]", ele, ex);
}
}
// 转换数组
Resource[] actResArray = actualResources.toArray(new Resource[0]);
/***
* fireImportProcessed()触发import事件,
* 并且通过{@link org.springframework.beans.factory.parsing.ReaderEventListener#importProcessed(ImportDefinition)} 宣布处理结果
*/
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
```
![image-20200102085031641](/image/spring/image-20200102085031641.png)
![image-20200102091421516](/image/spring/image-20200102091421516.png)

@ -0,0 +1,158 @@
# EntityResolver
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [huifer-spring](https://github.com/huifer/spring-framework)
- 源码路径: `org.xml.sax.EntityResolver`,非Spring类
## DelegatingEntityResolver#resolveEntity
- org.springframework.beans.factory.xml.DelegatingEntityResolver.resolveEntity
```java
@Override
@Nullable
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
throws SAXException, IOException {
if (systemId != null) {
if (systemId.endsWith(DTD_SUFFIX)) {
return this.dtdResolver.resolveEntity(publicId, systemId);
}
else if (systemId.endsWith(XSD_SUFFIX)) {
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
// Fall back to the parser's default behavior.
return null;
}
```
- 上述这段代码是针对xml进行校验
```xml
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
```
- 如上所示以`.xsd`结尾,应该执行` return this.schemaResolver.resolveEntity(publicId, systemId);`方法
`http://www.springframework.org/schema/beans/spring-beans.xsd`
- `org.springframework.beans.factory.xml.PluggableSchemaResolver.resolveEntity`
## PluggableSchemaResolver#resolveEntity
```java
@Override
@Nullable
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace("Trying to resolve XML entity with public id [" + publicId +
"] and system id [" + systemId + "]");
}
if (systemId != null) {
// 获取当前 systemId 对应的资源
// spring-framework\spring-beans\src\main\resources\org\springframework\beans\factory\xml\spring-beans.xsd
String resourceLocation = getSchemaMappings().get(systemId);
if (resourceLocation == null && systemId.startsWith("https:")) {
// Retrieve canonical http schema mapping even for https declaration
resourceLocation = getSchemaMappings().get("http:" + systemId.substring(6));
}
if (resourceLocation != null) {
// 加载 resourceLocation 转换成 Resource
Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
try {
// 读取
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isTraceEnabled()) {
logger.trace("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
}
return source;
}
catch (FileNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find XML schema [" + systemId + "]: " + resource, ex);
}
}
}
}
// Fall back to the parser's default behavior.
return null;
}
```
![image-20200108081404857](/images/spring//image-20200108081404857.png)
![image-20200108081623427](/images/spring//image-20200108081623427.png)
得到本地路径,后续直接返回读取资源
## BeansDtdResolver#resolveEntity
创建一个Dtd的约束文件
```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/dtd/spring-beans-2.0.dtd">
</beans>
```
```java
@Override
@Nullable
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace("Trying to resolve XML entity with public ID [" + publicId +
"] and system ID [" + systemId + "]");
}
if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
int lastPathSeparator = systemId.lastIndexOf('/');
int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
if (dtdNameStart != -1) {
// 获取静态变量组装成文件名称(spring-beans.dtd)
String dtdFile = DTD_NAME + DTD_EXTENSION;
if (logger.isTraceEnabled()) {
logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
}
try {
// 加载资源
Resource resource = new ClassPathResource(dtdFile, getClass());
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isTraceEnabled()) {
logger.trace("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
}
return source;
}
catch (FileNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
}
}
}
}
// Fall back to the parser's default behavior.
return null;
}
```
- systemId `https://www.springframework.org/dtd/spring-beans-2.0.dtd`
![image-20200108082335031](/images/spring//image-20200108082335031.png)
## 总结
- DelegatingEntityResolver#resolveEntity,是对xml文档的校验前置步骤,根据`dtd`和`xsd`加载本地资源文件
`spring-framework\spring-beans\src\main\resources\org\springframework\beans\factory\xml\spring-beans.dtd`
`spring-framework\spring-beans\src\main\resources\org\springframework\beans\factory\xml\spring-beans.xsd`
- `PluggableSchemaResolver`负责加载`xsd`文件
- `BeansDtdResolver`负责加载`dtd`文件

@ -0,0 +1,307 @@
# Spring-SimpleAliasRegistry
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [huifer-spring](https://github.com/huifer/spring-framework)
## AliasRegistry
- `SimpleAliasRegistry`继承`org.springframework.core.AliasRegistry`
```java
public interface AliasRegistry {
/**
* Given a name, register an alias for it.
* 别名注册
*
* @param name the canonical name
* @param alias the alias to be registered
* @throws IllegalStateException if the alias is already in use
* and may not be overridden
* @see SimpleAliasRegistry
* @see org.springframework.context.support.GenericApplicationContext
*/
void registerAlias(String name, String alias);
/**
* Remove the specified alias from this registry.
* 别名移除
*
* @param alias the alias to remove
* @throws IllegalStateException if no such alias was found
*/
void removeAlias(String alias);
/**
* Determine whether this given name is defines as an alias
* (as opposed to the name of an actually registered component).
* 是不是别名
*
* @param name the name to check
* @return whether the given name is an alias
*/
boolean isAlias(String name);
/**
* Return the aliases for the given name, if defined.
* 从别名注册map中获取别名信息
*
* @param name the name to check for aliases
* @return the aliases, or an empty array if none
*/
String[] getAliases(String name);
}
```
## SimpleAliasRegistry
```java
/**
* Simple implementation of the {@link AliasRegistry} interface.
* Serves as base class for
* {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}
* implementations.
*
* @author Juergen Hoeller
* @since 2.5.2
*/
public class SimpleAliasRegistry implements AliasRegistry {
/**
* Logger available to subclasses.
*/
protected final Log logger = LogFactory.getLog(getClass());
/**
* Map from alias to canonical name.
* 存放别名的map(线程安全的),
* 结构: alias-> name
*/
private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
/**
* {@code <alias name="appleBean" alias="hhh"/>}
*
* @param name the canonical name
* alias 标签的name属性
* @param alias the alias to be registered
* alias 标签的alias属性
*/
@Override
public void registerAlias(String name, String alias) {
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
synchronized (this.aliasMap) {
// 判断: 别名和名字是否相同
if (alias.equals(name)) {
//相同在别名map中移除
this.aliasMap.remove(alias);
if (logger.isDebugEnabled()) {
logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
}
}
else {
// 不相同
// 从map对象中获取别名为alias的value
String registeredName = this.aliasMap.get(alias);
if (registeredName != null) {
// 判断map中是否有有一个别名和传入的name相同的内容
if (registeredName.equals(name)) {
// An existing alias - no need to re-register
return;
}
if (!allowAliasOverriding()) {
throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
name + "': It is already registered for name '" + registeredName + "'.");
}
if (logger.isDebugEnabled()) {
logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
registeredName + "' with new target name '" + name + "'");
}
}
// 别名环检查
checkForAliasCircle(name, alias);
// 放入 map 对象中 alias-> name
this.aliasMap.put(alias, name);
if (logger.isTraceEnabled()) {
logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
}
}
}
}
/**
* Return whether alias overriding is allowed.
* Default is {@code true}.
* 是否允许重写别名
*/
protected boolean allowAliasOverriding() {
return true;
}
/**
* Determine whether the given name has the given alias registered.
* <p>
* 递归判断是否已经存在别名
*
* @param name the name to check
* @param alias the alias to look for
* @since 4.2.1
*/
public boolean hasAlias(String name, String alias) {
for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
// 获取key值
String registeredName = entry.getValue();
if (registeredName.equals(name)) {
String registeredAlias = entry.getKey();
// 循环引用判断
if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) {
return true;
}
}
}
return false;
}
/**
* 别名移除
*
* @param alias the alias to remove
*/
@Override
public void removeAlias(String alias) {
synchronized (this.aliasMap) {
// 判断是否移除成功
String name = this.aliasMap.remove(alias);
if (name == null) {
throw new IllegalStateException("No alias '" + alias + "' registered");
}
}
}
/**
* 判断是否是一个别名,校验方式{@link org.springframework.core.SimpleAliasRegistry#aliasMap} 的key是否包含
*
* @param name the name to check
* @return
*/
@Override
public boolean isAlias(String name) {
return this.aliasMap.containsKey(name);
}
/**
* 获取别名列表
*
* @param name the name to check for aliases
* @return
*/
@Override
public String[] getAliases(String name) {
List<String> result = new ArrayList<>();
synchronized (this.aliasMap) {
retrieveAliases(name, result);
}
return StringUtils.toStringArray(result);
}
/**
* Transitively retrieve all aliases for the given name.
* 根据 name 获取别名
*
* @param name the target name to find aliases for
* @param result the resulting aliases list
*/
private void retrieveAliases(String name, List<String> result) {
// 循环获取
this.aliasMap.forEach((alias, registeredName) -> {
if (registeredName.equals(name)) {
result.add(alias);
// 递归查询循环引用的别名
retrieveAliases(alias, result);
}
});
}
/**
* Resolve all alias target names and aliases registered in this
* factory, applying the given StringValueResolver to them.
* <p>The value resolver may for example resolve placeholders
* in target bean names and even in alias names.
*
* @param valueResolver the StringValueResolver to apply
*/
public void resolveAliases(StringValueResolver valueResolver) {
Assert.notNull(valueResolver, "StringValueResolver must not be null");
synchronized (this.aliasMap) {
Map<String, String> aliasCopy = new HashMap<>(this.aliasMap);
aliasCopy.forEach((alias, registeredName) -> {
String resolvedAlias = valueResolver.resolveStringValue(alias);
String resolvedName = valueResolver.resolveStringValue(registeredName);
if (resolvedAlias == null || resolvedName == null || resolvedAlias.equals(resolvedName)) {
this.aliasMap.remove(alias);
}
else if (!resolvedAlias.equals(alias)) {
String existingName = this.aliasMap.get(resolvedAlias);
if (existingName != null) {
if (existingName.equals(resolvedName)) {
// Pointing to existing alias - just remove placeholder
this.aliasMap.remove(alias);
return;
}
throw new IllegalStateException(
"Cannot register resolved alias '" + resolvedAlias + "' (original: '" + alias +
"') for name '" + resolvedName + "': It is already registered for name '" +
registeredName + "'.");
}
checkForAliasCircle(resolvedName, resolvedAlias);
this.aliasMap.remove(alias);
this.aliasMap.put(resolvedAlias, resolvedName);
}
else if (!registeredName.equals(resolvedName)) {
this.aliasMap.put(alias, resolvedName);
}
});
}
}
/**
* Check whether the given name points back to the given alias as an alias
* in the other direction already, catching a circular reference upfront
* and throwing a corresponding IllegalStateException.
* <p>
* 判断是否循环别名
*
* @param name the candidate name
* @param alias the candidate alias
* @see #registerAlias
* @see #hasAlias
*/
protected void checkForAliasCircle(String name, String alias) {
if (hasAlias(alias, name)) {
throw new IllegalStateException("Cannot register alias '" + alias +
"' for name '" + name + "': Circular reference - '" +
name + "' is a direct or indirect alias for '" + alias + "' already");
}
}
/**
* Determine the raw name, resolving aliases to canonical names.
*
* @param name the user-specified name
* @return the transformed name
*/
public String canonicalName(String name) {
String canonicalName = name;
// Handle aliasing...
String resolvedName;
do {
resolvedName = this.aliasMap.get(canonicalName);
if (resolvedName != null) {
canonicalName = resolvedName;
}
}
while (resolvedName != null);
return canonicalName;
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Loading…
Cancel
Save