主要是应用了Spring 中的 XML schema 扩展机制,可以参考这篇文章https://www.cnkirito.moe/spring-xsd/

0x00 自定义 XML 扩展

为了搞懂 Spring 的 XML 扩展机制,最直接的方式便是实现一个自定义的扩展。实现的步骤也非常简单,分为四步:

  1. 编写一个 XML schema 文件描述的你节点元素。
  2. 编写一个 NamespaceHandler 的实现类
  3. 编写一个或者多个 BeanDefinitionParser 的实现 (关键步骤).
  4. 注册上述的 schema 和 handler。

项目中一个shutter.xml的如下,xmlns:shutter 定义了schema的名字,xsi:schemaLocation定义了schema的位置。shutter(百叶窗)是一个动态配置中心,从早期的百度开源的Disconf演变而来。

shutter.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:shutter="http://www.xxx.cn/schema/shutter"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.xxx.cn/schema/shutter
http://www.xxx.cn/schema/shutter/shutter.xsd"
default-autowire="byName">

<shutter:security accessKey="***" secretKey="***"/>

<shutter:application name="demo" environment="${shutter.environment}" group="${shutter.group}"/>
<shutter:config-center protocol="http" address="${shutter.hostList}" download-dir="${shutter.output}"/>
<shutter:annotation-scan base-package="cn.xxxx.***"/>

<!-- 应用配置 -->
<shutter:property-placeholder location="***.properties" ignore-unresolvable="true"/>
</beans>

参照shutter的源码做一个简单的分析:

XML schema XSD

XML schema 文件也就是 xsd 文件,由于没有 shutter 的源码,从下载的带源码的 jar 中可以看到conf/shutter.xsd。

xml 中的的 property-placeholder, application, security, config-center, annotation-scan 等都对应之后用到的 shutter.xml 的配置项。targetNamespace 即 schema 中定义的 namespace。

shutter.xsd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.xxx.cn/schema/shutter"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://www.xxx.cn/schema/shutter"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/tool
http://www.springframework.org/schema/tool/spring-tool.xsd">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>

<xsd:complexType name="propertyLoading">
<xsd:attribute name="location" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The location of the properties file to resolve placeholders against, as a Spring
resource location: a URL, a "classpath:" pseudo URL, or a relative file path.
Multiple locations may be specified, separated by commas. If neither location nor
properties-ref is specified, placeholders will be resolved against system properties.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="properties-ref" type="xsd:string">
<xsd:annotation>
<xsd:documentation source="java:java.util.Properties"><![CDATA[
The bean name of a Properties object that will be used for property substitution.
If neither location nor properties-ref is specified, placeholders will be resolved
against system properties.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="file-encoding" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the encoding to use for parsing properties files. Default is none,
using the java.util.Properties default encoding. Only applies to classic
properties files, not to XML files.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:token">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the order for this placeholder configurer. If more than one is present
in a context, the order can be important since the first one to be match a
placeholder will win.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ignore-resource-not-found" type="xsd:boolean" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies if failure to find the property resource location should be ignored.
Default is "false", meaning that if there is no file in the location specified
an exception will be raised at runtime.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ignore-unresolvable" type="xsd:boolean" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies if failure to find the property value to replace a key should be ignored.
Default is "false", meaning that this placeholder configurer will raise an exception
if it cannot resolve a key. Set to "true" to allow the configurer to pass on the key
to any others in the context that have not yet visited the key in question.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="local-override" type="xsd:boolean" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies whether local properties override properties from files.
Default is "false": Properties from files override local defaults.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>

<xsd:element name="property-placeholder">
<xsd:annotation>
<xsd:documentation><![CDATA[
Activates replacement of ${...} placeholders by registering a
PropertySourcesPlaceholderConfigurer within the application context. Properties will
be resolved against the specified properties file or Properties object -- so called
"local properties", if any, and against the Spring Environment's current set of
PropertySources.

Note that as of Spring 3.1 the system-properties-mode attribute has been removed in
favor of the more flexible PropertySources mechanism. However, applications may
continue to use the 3.0 (and older) versions of the spring-context schema in order
to preserve system-properties-mode behavior. In this case, the traditional
PropertyPlaceholderConfigurer component will be registered instead of the newer
PropertySourcesPlaceholderConfigurer.

See ConfigurableEnvironment javadoc for more information on usage.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="propertyLoading">
<xsd:attribute name="application">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the application for this shutter client.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="group">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the cluster for this shutter client.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cluster">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the cluster for this shutter client.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="system-properties-mode" default="ENVIRONMENT">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls how to resolve placeholders against system properties. As of Spring 3.1, this
attribute value defaults to "ENVIRONMENT", indicating that resolution of placeholders
against system properties is handled via PropertySourcesPlaceholderConfigurer and its
delegation to the current Spring Environment object.

For maximum backward compatibility, this attribute is preserved going forward with the
3.1 version of the context schema, and any values other than the default "ENVIRONMENT"
will cause a traditional PropertyPlaceholderConfigurer to be registered instead of the
newer PropertySourcesPlaceholderConfigurer variant. In this case, the Spring Environment
and its property sources are not interrogated when resolving placeholders. Users are
encouraged to consider this attribute deprecated, and to take advantage of the
Environment and PropertySource mechanisms. See ConfigurableEnvironment javadoc for examples.

"ENVIRONMENT" indicates placeholders should be resolved against the current Environment and against any local properties;
"NEVER" indicates placeholders should be resolved only against local properties and never against system properties;
"FALLBACK" indicates placeholders should be resolved against any local properties and then against system properties;
"OVERRIDE" indicates placeholders should be resolved first against system properties and then against any local properties;
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="ENVIRONMENT"/>
<xsd:enumeration value="NEVER"/>
<xsd:enumeration value="FALLBACK"/>
<xsd:enumeration value="OVERRIDE"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="value-separator" default=":">
<xsd:annotation>
<xsd:documentation><![CDATA[
The separating character between the placeholder variable and the associated
default value: by default, a ':' symbol.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="trim-values">
<xsd:annotation>
<xsd:documentation><![CDATA[
Whether to trim resolved values before applying them, removing superfluous
whitespace (in particular tab characters) from the beginning and end.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="null-value">
<xsd:annotation>
<xsd:documentation><![CDATA[
A value that should be treated as 'null' when resolved as a placeholder value:
e.g. "" (empty String) or "null". By default, no such null value is defined.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>

<xsd:element name="application">
<xsd:complexType>
<xsd:attribute name="name" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ 应用名. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="environment" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ 运行环境. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="group" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ 分组. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cluster" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ 集群. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>

<xsd:element name="security">
<xsd:complexType>
<xsd:attribute name="accessKey" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[ Access Key. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="secretKey" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[ Secret Key. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>

<xsd:element name="config-center">
<xsd:complexType>
<xsd:attribute name="address" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ 配置中心地址 ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="download-dir" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ 配置下载的本地路径. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="download-retry-times" type="xsd:string" default="3">
<xsd:annotation>
<xsd:documentation><![CDATA[ 配置文件下载重试次数. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="download-retry-milli" type="xsd:string" default="2000">
<xsd:annotation>
<xsd:documentation><![CDATA[ 配置文件下载重试时间间隔. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="observer-mode" type="xsd:string" default="http">
<xsd:annotation>
<xsd:documentation><![CDATA[ 设置观察模式,目前支持zookeeper,http. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="protocol" type="xsd:string" default="grpc">
<xsd:annotation>
<xsd:documentation><![CDATA[ 协议类型,目前支持grpc,http. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="enable" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[ 是否启用shutter-server ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>

<xsd:element name="annotation-scan">
<xsd:complexType>
<xsd:attribute name="base-package" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[ 注解扫描路径 ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

ShutterNamespaceHandler

其中注册了5个BeanDefinitionParser对应xsd中定义的5个标签

ShutterNamespaceHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package cn.xxx.shutter.client.spring.namespace;

import cn.xxx.shutter.client.spring.parser.PropertyPlaceholderBeanDefinitionParser;
import cn.xxx.shutter.client.spring.parser.ShutterApplicationBeanDefinitionParser;
import cn.xxx.shutter.client.spring.parser.ShutterClientBeanDefinitionParser;
import cn.xxx.shutter.client.spring.parser.ShutterPackageBeanDefinitionParser;
import cn.xxx.shutter.client.spring.parser.ShutterSecurityBeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

/**
* ShutterNamespaceHandler
*
*/
public class ShutterNamespaceHandler extends NamespaceHandlerSupport {

@Override
public void init() {
registerBeanDefinitionParser("application", new ShutterApplicationBeanDefinitionParser());
registerBeanDefinitionParser("security", new ShutterSecurityBeanDefinitionParser());
registerBeanDefinitionParser("config-center", new ShutterClientBeanDefinitionParser());
registerBeanDefinitionParser("annotation-scan", new ShutterPackageBeanDefinitionParser());
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
}
}

schema解析器

1
2
3
4
5
ShutterApplicationBeanDefinitionParser.class
ShutterSecurityBeanDefinitionParser.class
ShutterClientBeanDefinitionParser.class
ShutterPackageBeanDefinitionParser.class
PropertyPlaceholderBeanDefinitionParser.class

这些parser解析shutter.xml,并转换成相应的bean加入spring容器。

Spring SPI

  • META-INF/spring.handlers
1
http\://www.xxx.cn/schema/shutter=cn.xxx.shutter.client.spring.namespace.ShutterNamespaceHandler
  • META-INF/spring.schemas
1
http\://www.xxx.cn/schema/shutter/shutter.xsd=conf/shutter.xsd

主要是用来向spring定义自定义 schema 的所在之处以及对应的处理器

0x01 解析器详解

前面提到了5个BeanDefinitionParser,我们需要对其进行详细说明。

ShutterApplicationBeanDefinitionParser

ShutterApplicationBeanDefinitionParser解析器比较简单,主要就是解析了xml中定义的name, environment, group并存放到了ShutterApplicationConfig中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package cn.xxx.shutter.client.spring.parser;

import cn.xxx.shutter.client.config.ShutterApplicationConfig;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class ShutterApplicationBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

@Override
protected Class<?> getBeanClass(Element element) {
return ShutterApplicationConfig.class;
}

@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException {
return getBeanClass(element).getSimpleName();
}

@Override
protected boolean shouldParseNameAsAliases() {
return false;
}

@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String id = resolveId(element, builder.getRawBeanDefinition(), parserContext);
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate <shutter:application ... />");
}

String application = System.getenv("APPNAME");
if (!StringUtils.hasLength(application)) {
application = element.getAttribute("name");
}
if (StringUtils.hasLength(application)) {
ShutterApplicationConfig.getInstance().setName(application);
}
// 删除 name,以免被解析为beanId
element.removeAttribute("name");

String environment = System.getenv("ENV");
if (!StringUtils.hasLength(environment)) {
environment = element.getAttribute("environment");
}
if (StringUtils.hasLength(environment)) {
ShutterApplicationConfig.getInstance().setEnv(environment);
}

String cluster = System.getenv("CLUSTER");
if (!StringUtils.hasLength(cluster)) {
cluster = element.getAttribute("cluster");
}
if (!StringUtils.hasLength(cluster)) {
cluster = element.getAttribute("group");
}
if (StringUtils.hasLength(cluster)) {
ShutterApplicationConfig.getInstance().setCluster(cluster);
}
}
}

ShutterSecurityBeanDefinitionParser

这个parser主要是解析了accessKeysecurityKey并存放到了ShutterSecurityConfig。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package cn.xxx.shutter.client.spring.parser;

import cn.xxx.shutter.client.config.ShutterSecurityConfig;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class ShutterSecurityBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

@Override
protected Class<?> getBeanClass(Element element) {
return ShutterSecurityConfig.class;
}

@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws
BeanDefinitionStoreException {
return getBeanClass(element).getSimpleName();
}

@Override
protected boolean shouldParseNameAsAliases() {
return false;
}

@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String id = resolveId(element, builder.getRawBeanDefinition(), parserContext);
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate <shutter:security ... />");
}

String accessKey = element.getAttribute("accessKey");
if (!StringUtils.hasLength(accessKey)) {
throw new IllegalArgumentException("Access Key must not be empty");
}
ShutterSecurityConfig.getInstance().setAccessKey(accessKey);

String secretKey = element.getAttribute("secretKey");
if (!StringUtils.hasLength(secretKey)) {
throw new IllegalArgumentException("Secret Key must not be empty");
}
ShutterSecurityConfig.getInstance().setSecretKey(secretKey);
}
}

ShutterClientBeanDefinitionParser

这个parser主要解析了protocol, addressdownload-dir等存到了ShutterClientConfig。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package cn.xxx.shutter.client.spring.parser;

import cn.xxx.shutter.client.ShutterClient;
import cn.xxx.shutter.client.common.constants.Constants;
import cn.xxx.shutter.client.config.ShutterApplicationConfig;
import cn.xxx.shutter.client.config.ShutterClientConfig;
import cn.xxx.shutter.client.config.ShutterSecurityConfig;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class ShutterClientBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

@Override
protected Class<?> getBeanClass(Element element) {
return ShutterClient.class;
}

@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws
BeanDefinitionStoreException {
return getBeanClass(element).getSimpleName();
}

@Override
protected boolean shouldParseNameAsAliases() {
return false;
}

@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String id = resolveId(element, builder.getRawBeanDefinition(), parserContext);
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate <shutter:config-center ... />");
}

builder.addDependsOn(ShutterApplicationConfig.class.getSimpleName());
builder.addDependsOn(ShutterSecurityConfig.class.getSimpleName());

String address = System.getenv("SHUTTER");
if (!StringUtils.hasLength(address)) {
address = element.getAttribute("address");
}
if (StringUtils.hasLength(address)) {
ShutterClientConfig.getInstance().setHostList(address);
}

if (StringUtils.hasLength(System.getenv("DC"))) {
ShutterClientConfig.getInstance().setDownloadDir(Constants.STANDARD_DOWNLOAD_DIR);
} else {
String downloadDir = element.getAttribute("download-dir");
if (StringUtils.hasLength(downloadDir)) {
ShutterClientConfig.getInstance().setDownloadDir(downloadDir);
}
}

String retryTimes = element.getAttribute("download-retry-times");
if (StringUtils.hasLength(retryTimes)) {
ShutterClientConfig.getInstance().setDownloadRetryTimes(Integer.parseInt(retryTimes));
}

String retryMilli = element.getAttribute("download-retry-milli");
if (StringUtils.hasLength(retryMilli)) {
ShutterClientConfig.getInstance().setDownloadRetryMilli(Integer.parseInt(retryMilli));
}

String observerMode = element.getAttribute("observer-mode");
if (StringUtils.hasLength(observerMode)) {
ShutterClientConfig.getInstance().setObserverMode(observerMode);
}

String protocol = element.getAttribute("protocol");
if (StringUtils.hasLength(protocol)) {
ShutterClientConfig.getInstance().setProtocol(protocol);
}

String enable = element.getAttribute("enable");
if (StringUtils.hasLength(enable)) {
ShutterClientConfig.getInstance().setEnable(Boolean.parseBoolean(enable));
}
}
}

ShutterPackageBeanDefinitionParser

这个parser主要解析了base-package也就是shutter要扫描的包名,并向Spring注册了ShutterStaticScanner和ShutterDynamicScanner两个bean。这两个Scanner放到后面再细看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package cn.xxx.shutter.client.spring.parser;

import cn.xxx.shutter.client.ShutterClient;
import cn.xxx.shutter.client.ShutterDynamicScanner;
import cn.xxx.shutter.client.ShutterStaticScanner;
import cn.xxx.shutter.client.config.ShutterPackageConfig;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

public class ShutterPackageBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

@Override
protected Class<?> getBeanClass(Element element) {
return ShutterPackageConfig.class;
}

@Override
protected boolean shouldGenerateId() {
return true;
}

@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String basePackage = element.getAttribute("base-package");
ShutterPackageConfig.addBasePackage(basePackage);

// 静态扫描bean
String staticScannerBeanName = ShutterStaticScanner.class.getSimpleName();
if (!parserContext.getRegistry().containsBeanDefinition(staticScannerBeanName)) {
BeanDefinitionBuilder staticScannerBuilder
= BeanDefinitionBuilder.genericBeanDefinition(ShutterStaticScanner.class);
staticScannerBuilder.addConstructorArgReference(ShutterClient.class.getSimpleName());
parserContext.getRegistry().registerBeanDefinition(staticScannerBeanName,
staticScannerBuilder.getRawBeanDefinition());
}

// 动态扫描bean
String dynamicScannerBeanName = ShutterDynamicScanner.class.getSimpleName();
if (!parserContext.getRegistry().containsBeanDefinition(dynamicScannerBeanName)) {
BeanDefinitionBuilder dynamicScannerBuilder
= BeanDefinitionBuilder.genericBeanDefinition(ShutterDynamicScanner.class);
dynamicScannerBuilder.addConstructorArgReference(ShutterClient.class.getSimpleName());
parserContext.getRegistry().registerBeanDefinition(dynamicScannerBeanName,
dynamicScannerBuilder.getRawBeanDefinition());
}
}
}

PropertyPlaceholderBeanDefinitionParser

这个parser继承了AbstractPropertyLoadingBeanDefinitionParser,parse时先调用了AbstractPropertyLoadingBeanDefinitionParser的parse,读取了locations等参数,然后再调用自己的parse方法读取了value-separator等参数。并最终返回了PropertyPlaceholderConfigurer或PropertySourcesPlaceholderConfigurer。这个类最终来实现的placeholder的值的写入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package cn.xxx.shutter.client.spring.parser;

import cn.xxx.shutter.client.ShutterClient;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class PropertyPlaceholderBeanDefinitionParser extends AbstractPropertyLoadingBeanDefinitionParser {

private static final String SYSTEM_PROPERTIES_MODE_ATTRIBUTE = "system-properties-mode";

private static final String SYSTEM_PROPERTIES_MODE_DEFAULT = "ENVIRONMENT";

@Override
protected Class<?> getBeanClass(Element element) {
// As of Spring 3.1, the default value of system-properties-mode has changed from
// 'FALLBACK' to 'ENVIRONMENT'. This latter value indicates that resolution of
// placeholders against system properties is a function of the Environment and
// its current set of PropertySources.
if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
return PropertySourcesPlaceholderConfigurer.class;
}

// The user has explicitly specified a value for system-properties-mode: revert to
// PropertyPlaceholderConfigurer to ensure backward compatibility with 3.0 and earlier.
return PropertyPlaceholderConfigurer.class;
}

@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
super.doParse(element, parserContext, builder);
builder.addDependsOn(ShutterClient.class.getSimpleName());

builder.addPropertyValue("ignoreUnresolvablePlaceholders",
Boolean.valueOf(element.getAttribute("ignore-unresolvable")));

String systemPropertiesModeName = element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE);
if (StringUtils.hasLength(systemPropertiesModeName) &&
!systemPropertiesModeName.equals(SYSTEM_PROPERTIES_MODE_DEFAULT)) {
builder.addPropertyValue("systemPropertiesModeName", "SYSTEM_PROPERTIES_MODE_" + systemPropertiesModeName);
}

if (element.hasAttribute("value-separator")) {
builder.addPropertyValue("valueSeparator", element.getAttribute("value-separator"));
}
if (element.hasAttribute("trim-values")) {
builder.addPropertyValue("trimValues", element.getAttribute("trim-values"));
}
if (element.hasAttribute("null-value")) {
builder.addPropertyValue("nullValue", element.getAttribute("null-value"));
}
}
}

AbstractPropertyLoadingBeanDefinitionParser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package cn.xxx.shutter.client.spring.parser;

import cn.xxx.shutter.client.ShutterClient;
import cn.xxx.shutter.client.config.ShutterClientConfig;
import cn.xxx.shutter.client.spring.ShutterPropertiesFactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class AbstractPropertyLoadingBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

@Override
protected boolean shouldGenerateId() {
return true;
}

@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String location = element.getAttribute("location");
if (StringUtils.hasLength(location)) {
try {
location = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(location);
} catch (Throwable ignored) {
}
String[] locations = StringUtils.commaDelimitedListToStringArray(location);

if (ShutterClientConfig.getInstance().isEnable()) {
BeanDefinitionBuilder factoryBuilder
= BeanDefinitionBuilder.genericBeanDefinition(ShutterPropertiesFactoryBean.class);
factoryBuilder.addConstructorArgReference(ShutterClient.class.getSimpleName());
factoryBuilder.addPropertyValue("locations", locations);

String application = element.getAttribute("application");
if (StringUtils.hasLength(application)) {
factoryBuilder.addPropertyValue("app", application);
}

String cluster = element.getAttribute("cluster");
if (!StringUtils.hasLength(cluster)) {
cluster = element.getAttribute("group");
}
if (StringUtils.hasLength(cluster)) {
factoryBuilder.addPropertyValue("cluster", cluster);
}

builder.addPropertyValue("properties", factoryBuilder.getRawBeanDefinition());
} else {
builder.addPropertyValue("locations", locations);
}
}

String propertiesRef = element.getAttribute("properties-ref");
if (StringUtils.hasLength(propertiesRef)) {
builder.addPropertyReference("properties", propertiesRef);
}

String fileEncoding = element.getAttribute("file-encoding");
if (StringUtils.hasLength(fileEncoding)) {
builder.addPropertyValue("fileEncoding", fileEncoding);
}

String order = element.getAttribute("order");
if (StringUtils.hasLength(order)) {
builder.addPropertyValue("order", Integer.valueOf(order));
}

builder.addPropertyValue("ignoreResourceNotFound",
Boolean.valueOf(element.getAttribute("ignore-resource-not-found")));

builder.addPropertyValue("localOverride",
Boolean.valueOf(element.getAttribute("local-override")));

builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
}
}

BeanDefinitionRegistryPostProcessor

回过头去看ShutterStaticScannerShutterDynamicScanner

ShutterStaticScanner实现了BeanDefinitionRegistryPostProcessor,BeanDefinitionRegistryPostProcessor又继承了BeanFactoryPostProcessor。

根据源码对此类的解释:对标准{@link BeanFactoryPostProcessor} SPI的扩展,允许在常规BeanFactoryPostProcessor检测开始之前注册更多的bean定义。开发者可以通过该类实现扩展,在类初始之前对beanDefinition进行修改以及新增注册。

先看看Spring的BeanDefinitionRegistryPostProcessor的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.beans.factory.support;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;

/**
* Extension to the standard {@link BeanFactoryPostProcessor} SPI, allowing for
* the registration of further bean definitions <i>before</i> regular
* BeanFactoryPostProcessor detection kicks in. In particular,
* BeanDefinitionRegistryPostProcessor may register further bean definitions
* which in turn define BeanFactoryPostProcessor instances.
*
* @author Juergen Hoeller
* @since 3.0.1
* @see org.springframework.context.annotation.ConfigurationClassPostProcessor
*/
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

/**
* Modify the application context's internal bean definition registry after its
* standard initialization. All regular bean definitions will have been loaded,
* but no beans will have been instantiated yet. This allows for adding further
* bean definitions before the next post-processing phase kicks in.
* @param registry the bean definition registry used by the application context
* @throws org.springframework.beans.BeansException in case of errors
*/
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

}

ShutterStaticScanner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package cn.xxx.shutter.client;

import cn.xxx.shutter.client.config.ShutterClientConfig;
import cn.xxx.shutter.client.config.ShutterPackageConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;

import java.util.List;

/**
* ShutterStaticScanner 总入口
*
* @version 2014-5-23
*/
public class ShutterStaticScanner implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {

private static final Logger LOGGER = LoggerFactory.getLogger(ShutterStaticScanner.class);

private ShutterClient shutterClient;

public ShutterStaticScanner(ShutterClient shutterClient) {
this.shutterClient = shutterClient;
}

@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}

/**
* 这个函数无法达到最高优先级,例如PropertyPlaceholderConfigurer
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

}

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

// 进行扫描
try {
staticScan(ShutterPackageConfig.getBasePackage());
} catch (Exception e) {
throw new BeanDefinitionStoreException("shutter static scan error", e);
}
}

/**
* 第一次扫描,静态扫描 for annotation config
*/
private synchronized void staticScan(List<String> scanPackageList) throws Exception {

// 该函数不能调用两次
if (shutterClient.getScanMgr().isStaticFinished()) {
LOGGER.info("shutter static scan has been done, ignored.");
return;
}

// 第一次扫描并入库
shutterClient.getScanMgr().staticScan(scanPackageList);
shutterClient.getShutterCoreMgr().process();
}
}

ShutterStaticScanner中主要是调用了ItemStaticScanner/FileStaticScanner/NonAnnotationFileStaticScanner这三个类扫描了shutter.xml中配置的配置文件定义。

FileStaticScanner.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package cn.xxx.shutter.client.scan.scanner.statically.impl;

import cn.xxx.shutter.client.common.annotations.ShutterFile;
import cn.xxx.shutter.client.common.annotations.ShutterFileContent;
import cn.xxx.shutter.client.common.annotations.ShutterFileItem;
import cn.xxx.shutter.client.common.constants.ConfigType;
import cn.xxx.shutter.client.common.constants.Constants;
import cn.xxx.shutter.client.common.constants.FileExtension;
import cn.xxx.shutter.client.common.exception.ShutterDuplicateKeyException;
import cn.xxx.shutter.client.common.http.ShutterApiPathMgr;
import cn.xxx.shutter.client.common.model.ShutterFileModel;
import cn.xxx.shutter.client.common.model.ShutterItemModel;
import cn.xxx.shutter.client.common.model.ShutterMetadata;
import cn.xxx.shutter.client.scan.scanner.statically.StaticScanner;
import cn.xxx.shutter.client.scan.scanner.statically.model.StaticScanModel;
import cn.xxx.shutter.client.store.ShutterStoreProcessorFactory;
import cn.xxx.shutter.client.support.utils.ClassUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* 配置文件的静态扫描
*
*/
public class FileStaticScanner implements StaticScanner {

protected static final Logger LOGGER = LoggerFactory.getLogger(FileStaticScanner.class);

@Override
public void scanData2Store(StaticScanModel scanModel) throws ShutterDuplicateKeyException {

// 转换配置文件
List<ShutterFileModel> fileModelList = getFileModelList(scanModel);
ShutterStoreProcessorFactory.getFileStoreProcessor().transformScanData(fileModelList);
}

@Override
public void exclude(Set<String> keySet) {
ShutterStoreProcessorFactory.getFileStoreProcessor().exclude(keySet);
}

/**
* 获取配置文件数据
*/
private static List<ShutterFileModel> getFileModelList(StaticScanModel scanModel) {

List<ShutterFileModel> modelList = new ArrayList<>();

Set<Class<?>> shutterFileClsSet = scanModel.getShutterFileClsSet();
for (Class<?> shutterFilesCls : shutterFileClsSet) {

Set<Field> fields = scanModel.getShutterFileItemMap().get(shutterFilesCls);
if (fields == null) {
continue;
}

ShutterFileModel model = transform(shutterFilesCls, fields);

modelList.add(model);
}

return modelList;
}

/**
* 转换配置文件
*/
private static ShutterFileModel transform(Class<?> cls, Set<Field> fields) {

ShutterFileModel model = new ShutterFileModel();

//
// class
model.setCls(cls);

ShutterFile annotation = cls.getAnnotation(ShutterFile.class);

//
// file name
model.setFileName(annotation.filename());

// file type
model.setExtension(FileExtension.getByFilename(annotation.filename()));

// 注解式
model.setAnnotation(Boolean.TRUE);

// is watch
model.setObservable(annotation.observable());

// is recursive
model.setRecursive(annotation.recursive());

//
// shutterMetadata
ShutterMetadata metadata = ShutterMetadata.createMetadata(annotation.app(), annotation.cluster());

//设置请求参数
Map<String, String> parameters = ShutterApiPathMgr.getRemoteParameter(metadata.getApp(),
metadata.getCluster(),
metadata.getEnv(),
model.getFileName());
parameters.put(Constants.FILE_RECURSIVE_KEY, String.valueOf(model.isRecursive()));
parameters.put(Constants.CONFIG_TYPE_KEY, String.valueOf(ConfigType.FILE.getType()));
// Remote URL
String remoteUrl = ShutterApiPathMgr.getRemoteFileUrl(parameters);

metadata.setParameters(parameters);
metadata.setRemoteServerUrl(remoteUrl);

model.setMetadata(metadata);
//
// KEY & VALUE
//
Map<String, ShutterItemModel> keyMaps = new HashMap<>();

for (Field field : fields) {

//
ShutterFileItem fileItem = field.getAnnotation(ShutterFileItem.class);
if (fileItem == null) {
fileItem = field.getAnnotation(ShutterFileContent.class)
.annotationType()
.getAnnotation(ShutterFileItem.class);
}
String keyName = fileItem.name();

// access
field.setAccessible(true);

// get setter method
Method setterMethod = ClassUtils.getSetterMethodFromField(cls, field);

ShutterItemModel itemModel = new ShutterItemModel();
itemModel.setKey(keyName);
itemModel.setField(field);
itemModel.setSetterMethod(setterMethod);

// static 则直接获取其值
if (itemModel.isStatic()) {
try {
itemModel.setValue(itemModel.getFieldDefaultValue());
} catch (Exception e) {
LOGGER.warn(e.toString(), e);
}
}

keyMaps.put(keyName, itemModel);
}

// 设置
model.setKeyMaps(keyMaps);

return model;
}

}

ShutterDynamicScanner

ShutterDynamicScanner实现了InitializingBean。InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候会执行该方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package cn.xxx.shutter.client;

import cn.xxx.shutter.client.config.ShutterClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;

/**
* ShutterDynamicScanner
*
*/
public class ShutterDynamicScanner implements InitializingBean {

protected static final Logger LOGGER = LoggerFactory.getLogger(ShutterDynamicScanner.class);

private ShutterClient shutterClient;

public ShutterDynamicScanner(ShutterClient shutterClient) {
this.shutterClient = shutterClient;
}

/**
* 第二次扫描, 动态扫描, for annotation config
*/
public void afterPropertiesSet() throws Exception {
// 该函数必须第一次运行后才能运行
if (!shutterClient.getScanMgr().isStaticFinished()) {
LOGGER.info("should run static scan before dynamic scan.");
return;
}

// 第二次扫描也只能做一次
if (shutterClient.getScanMgr().isDynamicFinished()) {
LOGGER.info("shutter dynamic scan has been done, ignored.");
return;
}

// 扫描回调函数
shutterClient.getScanMgr().dynamicScan();

// 注入数据至配置实体中
// 获取数据/注入/Watch
shutterClient.getShutterCoreMgr().inject2Instance();
}
}

afterPropertiesSet方法中调用了ScanMgr.dynamicScan和ShutterCoreMgr.inject2Instance。

ScanMgr.dynamicScan最终调用了DynamicScanner.scanUpdateCallbacks,但是这个方法似乎没有做什么操作,先不管吧。

DynamicScanner.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package cn.xxx.shutter.client.scan.scanner.dynamic;

import cn.xxx.shutter.client.common.annotations.ShutterObservable;
import cn.xxx.shutter.client.common.constants.ConfigType;
import cn.xxx.shutter.client.common.model.ShutterKey;
import cn.xxx.shutter.client.common.observer.ShutterObserver;
import cn.xxx.shutter.client.scan.scanner.common.ScanVerify;
import cn.xxx.shutter.client.scan.scanner.statically.model.StaticScanModel;
import cn.xxx.shutter.client.store.ShutterStoreProcessor;
import cn.xxx.shutter.client.store.ShutterStoreProcessorFactory;
import cn.xxx.shutter.client.support.registry.Registry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* 动态扫描 与 Store模块的转换器
*
* @version 2014-6-18
*/
public class DynamicScanner {

protected static final Logger LOGGER = LoggerFactory.getLogger(DynamicScanner.class);

/**
* 扫描更新回调函数
*/
public static void scanUpdateCallbacks(StaticScanModel scanModel, Registry registry) {

// 扫描出来
Map<ShutterKey, List<ShutterObserver>> observerMap = analysis4ShutterUpdate(scanModel, registry);

// 写到仓库中
transformItemObserver(observerMap);
}

/**
* 第二次扫描, 获取更新 回调的实例<br/>
* <p/>
* 分析出更新操作的相关配置文件内容
*/
private static Map<ShutterKey, List<ShutterObserver>> analysis4ShutterUpdate(StaticScanModel scanModel,
Registry registry) {

// 配置项或文件
Map<ShutterKey, List<ShutterObserver>> inverseMap = new HashMap<>();

Set<Class<?>> observableClsSet = scanModel.getShutterItemObservableClsSet();
for (Class<?> observableCls : observableClsSet) {

// 回调对应的参数
ShutterObservable observable = observableCls.getAnnotation(ShutterObservable.class);

//
// 校验是否有继承正确,是否继承shutterItemObserver
if (!ScanVerify.hasShutterItemObserver(observableCls)) {
continue;
}

//
// 获取回调接口实例
ShutterObserver observer = getShutterItemObserverInstance(observableCls, registry);
if (observer == null) {
continue;
}

//
// 配置项
processItems(inverseMap, observable, observer);
//
// 配置文件
processFiles(inverseMap, observable, observer);
}

return inverseMap;
}

/**
* 获取回调对应配置项列表
*/
private static void processItems(Map<ShutterKey, List<ShutterObserver>> inverseMap,
ShutterObservable observable,
ShutterObserver observer) {

List<String> keys = Arrays.asList(observable.keys());

// 反索引
for (String key : keys) {

ShutterKey shutterKey = new ShutterKey(ConfigType.ITEM, key);
addOne2InverseMap(shutterKey, inverseMap, observer);
}
}


/**
* 获取回调对应配置项列表
*/
private static void processFiles(Map<ShutterKey, List<ShutterObserver>> inverseMap,
ShutterObservable observable,
ShutterObserver observer) {

List<String> filenames = Arrays.asList(observable.filenames());
// 反索引
for (String filename : filenames) {

ShutterKey shutterKey = new ShutterKey(ConfigType.FILE, filename);
addOne2InverseMap(shutterKey, inverseMap, observer);
}
}

/**
* 获取回调接口的实现
* <p/>
* // 回调函数需要实例化出来,这里
* // 非Spring直接New
* // Spring要GetBean
* //
*/
private static ShutterObserver getShutterItemObserverInstance(Class<?> observableCls, Registry registry) {

Object observer = registry.getFirstByType(observableCls, true);
if (observer == null) {
return null;
}
return (ShutterObserver) observer;

}

/**
* 将一个配置回调item写到map里
*/
private static void addOne2InverseMap(ShutterKey shutterKey, Map<ShutterKey, List<ShutterObserver>> inverseMap,
ShutterObserver observer) {
if (inverseMap.containsKey(shutterKey)) {
inverseMap.get(shutterKey).add(observer);
} else {
List<ShutterObserver> observerList = new ArrayList<>();
observerList.add(observer);
inverseMap.put(shutterKey, observerList);
}
}

/**
* 第二次扫描<br/>
* 转换 更新 回调函数,将其写到 仓库中
*/
private static void transformItemObserver(Map<ShutterKey, List<ShutterObserver>> observers) {

ShutterStoreProcessor fileStoreProcessor = ShutterStoreProcessorFactory.getFileStoreProcessor();
ShutterStoreProcessor itemStoreProcessor = ShutterStoreProcessorFactory.getItemStoreProcessor();

for (Map.Entry<ShutterKey, List<ShutterObserver>> entry : observers.entrySet()) {
ShutterKey shutterKey = entry.getKey();
try {
if (shutterKey.getConfigType().equals(ConfigType.FILE)) {

if (!fileStoreProcessor.containsConfig(shutterKey.getKey())) {
throw new Exception();
}
fileStoreProcessor.addObservers(shutterKey.getKey(), observers.get(shutterKey));
} else if (shutterKey.getConfigType().equals(ConfigType.ITEM)) {

if (!itemStoreProcessor.containsConfig(shutterKey.getKey())) {
throw new Exception();
}
itemStoreProcessor.addObservers(shutterKey.getKey(), observers.get(shutterKey));
}
} catch (Exception e) {
// 找不到回调对应的配置,这是用户配置 错误了
StringBuilder builder = new StringBuilder();
builder.append("Cannot find ").append(shutterKey).append("for: ");
for (ShutterObserver serClass : observers.get(shutterKey)) {
builder.append(serClass.toString()).append("\t");
}
LOGGER.error(builder.toString());
}
}
}

}

接着看ShutterCoreMgr.inject2Instance,ShutterCoreMgr跟ShutterStaticScanner也有3个子类,ShutterConfigCoreProcessor, ShutterFileCoreProcessor, ShutterItemCoreProcessor,先看下ShutterFileCoreProcessor。

ShutterFileCoreProcessor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package cn.xxx.shutter.client.core.processor.impl;

import cn.xxx.shutter.client.common.constants.ConfigType;
import cn.xxx.shutter.client.common.exception.ShutterException;
import cn.xxx.shutter.client.common.http.ValueVo;
import cn.xxx.shutter.client.common.model.ShutterFileModel;
import cn.xxx.shutter.client.common.model.ShutterKey;
import cn.xxx.shutter.client.common.model.ShutterMetadata;
import cn.xxx.shutter.client.common.model.ShutterValue;
import cn.xxx.shutter.client.core.processor.ShutterCoreProcessor;
import cn.xxx.shutter.client.core.utils.ShutterCoreProcessorUtils;
import cn.xxx.shutter.client.core.utils.ShutterFileProcessorUtils;
import cn.xxx.shutter.client.fetcher.FetcherMgr;
import cn.xxx.shutter.client.store.ShutterStoreProcessor;
import cn.xxx.shutter.client.store.ShutterStoreProcessorFactory;
import cn.xxx.shutter.client.support.registry.Registry;
import cn.xxx.shutter.client.watch.WatchMgr;
import cn.xxx.shutter.http.client.HttpResponseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

/**
* 配置文件处理器实现
*
* @version 2014-8-4
*/
public class ShutterFileCoreProcessor implements ShutterCoreProcessor {

protected static final Logger LOGGER = LoggerFactory.getLogger(ShutterFileCoreProcessor.class);

// 仓库算子
private ShutterStoreProcessor<ShutterFileModel> processor = ShutterStoreProcessorFactory.getFileStoreProcessor();

// bean registry
private Registry registry = null;

// 抓取器
private FetcherMgr fetcherMgr = null;

// 监控器
private WatchMgr watchMgr = null;

public ShutterFileCoreProcessor(Registry registry, FetcherMgr fetcherMgr, WatchMgr watchMgr) {
this.registry = registry;
this.fetcherMgr = fetcherMgr;
this.watchMgr = watchMgr;
}

@Override
public void processAllItems() {
for (String fileName : processor.getConfigKeySet()) {
processOneItem(fileName);
}
}

@Override
public void processOneItem(String key) {
ShutterFileModel model = processor.getConfigModel(key);

if (model != null) {
updateOneConfFile(key, model);
}
}

/**
* 更新 一个配置
*/
private void updateOneConf(String keyName) {

ShutterFileModel model = processor.getConfigModel(keyName);

if (model != null) {

// 更新仓库
updateOneConfFile(keyName, model);

// 更新实例
inject2OneConf(keyName, model);
}
}

/**
* 更新 一個配置文件, 下载、注入到仓库、Watch 三步骤
*/
private void updateOneConfFile(String fileName, ShutterFileModel model) {
ValueVo response = null;
// 下载配置
try {
response = fetcherMgr.getFileFromServer(fileName, model.getMetadata());
} catch (Exception e) {
LOGGER.error(model.getMetadata().getRemoteServerUrl(), e);
if (e instanceof HttpResponseException) {
return;
}
}

Map<String, Object> dataMap = new HashMap<>();
if (response != null) {
try {
dataMap = ShutterFileProcessorUtils.getKvMap(model.getExtension(), response.getData());
} catch (Exception e) {
LOGGER.error("cannot parse config: " + fileName, e);
}
}

// 注入到仓库中
processor.inject2Store(fileName, new ShutterValue(null, dataMap));

// 必须是注解形式, 没有开启配置查找,开启了监听
if (model.isAnnotation() && !model.isRecursive() && model.isObservable()) {
if (watchMgr != null) {
ShutterMetadata metadata = model.getMetadata();
String hash = response == null ? null : response.getHash();
ShutterKey shutterKey = new ShutterKey(ConfigType.FILE, fileName);
watchMgr.watchPath(this, metadata, shutterKey, hash);
} else {
LOGGER.warn("cannot monitor {} because watch module is null", fileName);
}
}
}

/**
* 更新消息: 某个配置文件 + 回调
*/
@Override
public void updateOneConfAndCallback(ShutterKey shutterKey) throws ShutterException {

ShutterFileModel model = processor.getConfigModel(shutterKey.getKey());

if (model != null) {
// 保存旧值
Object value = model.getKV();
// 更新 配置
updateOneConf(shutterKey.getKey());
// 获取新值
Object newValue = model.getKV();

// 回调
ShutterCoreProcessorUtils.callObserver(processor, shutterKey.getKey(), value, newValue);
}
}

/**
* 为某个配置文件进行注入实例中
*/
private void inject2OneConf(String fileName, ShutterFileModel model) {

if (!model.isAnnotation()) {
return;
}

try {
Object object = model.getObject();
if (object == null) {
object = registry.getFirstByType(model.getCls(), false, true);
}

// 注入实体中
processor.inject2Instance(object, fileName);
} catch (Exception e) {
LOGGER.error("inject config " + fileName + " failed", e);
}
}

@Override
public void inject2Conf() throws Exception {
/**
* 配置文件列表处理
*/
for (String key : processor.getConfigKeySet()) {
ShutterFileModel model = processor.getConfigModel(key);
if (model != null) {
inject2OneConf(key, model);
}
}

ShutterCoreProcessorUtils.invokeInitializingBean(processor);
}
}

ShutterFileCoreProcessor中读取的之前ShutterStaticScanner生成的ShutterFileModel,并向其中注入properties。最终注入实体调用了ShutterFileStoreProcessor.inject2Instance。

ShutterFileStoreProcessor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package cn.xxx.shutter.client.store.processor;

import cn.xxx.shutter.client.common.exception.ShutterDuplicateKeyException;
import cn.xxx.shutter.client.common.model.ShutterFileModel;
import cn.xxx.shutter.client.common.model.ShutterItemModel;
import cn.xxx.shutter.client.common.model.ShutterMetadata;
import cn.xxx.shutter.client.common.model.ShutterValue;
import cn.xxx.shutter.client.common.observer.ShutterObserver;
import cn.xxx.shutter.client.store.ShutterStoreProcessor;
import cn.xxx.shutter.client.store.center.ShutterStoreCenter;
import cn.xxx.shutter.client.store.center.ShutterStoreFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* 配置文件仓库实现器
*
*/
public class ShutterFileStoreProcessor implements ShutterStoreProcessor<ShutterFileModel> {

protected static final Logger LOGGER = LoggerFactory.getLogger(ShutterFileStoreProcessor.class);

private static final ShutterStoreCenter<ShutterFileModel> storeCenter = ShutterStoreFactory.getFileStoreCenter();

@Override
public ShutterFileModel getConfigModel(String keyName) {
return storeCenter.getConfig(keyName);
}

@Override
public Set<String> getConfigKeySet() {
return storeCenter.keySet();
}

@Override
public boolean containsConfig(String keyName) {
return storeCenter.contains(keyName);
}

@Override
public void addObservers(String keyName, List<ShutterObserver> observers) {
if (containsConfig(keyName)) {
ShutterFileModel model = getConfigModel(keyName);
for (ShutterObserver observer : observers) {
model.addObserver(observer);
}
}
}

@Override
public List<ShutterObserver> getObservers(String keyName) {
if (containsConfig(keyName)) {
return getConfigModel(keyName).getObservers();
}
return Collections.emptyList();
}

@Override
public ShutterMetadata getMetadata(String keyName) {
if (containsConfig(keyName)) {
return storeCenter.getConfig(keyName).getMetadata();
}
return null;
}

@Override
public Object getConfig(String fileName, String keyName) {
if (!containsConfig(fileName)) {
return null;
}

ShutterFileModel shutterFileModel = getConfigModel(fileName);

ShutterItemModel shutterItemModel = shutterFileModel.getKeyMaps().get(keyName);
if (shutterItemModel == null) {
return null;
}

return shutterItemModel.getValue();
}

@Override
public void inject2Instance(Object object, String fileName) {
if (!containsConfig(fileName)) {
return;
}

ShutterFileModel shutterFileModel = getConfigModel(fileName);

if (object != null && shutterFileModel.getObject() == null) {
shutterFileModel.setObject(object);
}

// 注入实体
Map<String, ShutterItemModel> keMap = shutterFileModel.getKeyMaps();
for (Map.Entry<String, ShutterItemModel> fileItem : keMap.entrySet()) {
ShutterItemModel shutterItemModel = fileItem.getValue();

if (object != null && shutterItemModel.getObject() == null) {
shutterItemModel.setObject(object);
}

// 根据类型设置值
try {
if (shutterItemModel.getValue() == null) {
// 如果仓库值为空,则实例 直接使用默认值
Object defaultValue = shutterItemModel.getFieldDefaultValue();
shutterItemModel.setValue(defaultValue);
} else {
// 如果仓库里的值为非空,则实例使用仓库里的值
shutterItemModel.setValue4FileItem(shutterItemModel.getValue());
}
} catch (Exception e) {
LOGGER.error("inject config to object failed,filename:" + fileName, e);
}
}
}

@Override
public void inject2Store(String fileName, ShutterValue shutterValue) {
if (shutterValue == null || shutterValue.getFileData() == null) {
return;
}

if (!containsConfig(fileName)) {
return;
}

ShutterFileModel shutterFileModel = getConfigModel(fileName);

Map<String, ShutterItemModel> keyMaps = shutterFileModel.getKeyMaps();
//注入非注解配置
if (!shutterFileModel.isAnnotation()) {
for (Map.Entry<String, Object> entry : shutterValue.getFileData().entrySet()) {
ShutterItemModel shutterItemModel = new ShutterItemModel();
shutterItemModel.setValue(entry.getValue());
keyMaps.put(entry.getKey(), shutterItemModel);
}
return;
}

// 存储
for (Map.Entry<String, ShutterItemModel> fileItem : keyMaps.entrySet()) {

Object object = shutterValue.getFileData().get(fileItem.getKey());
if (object == null) {
continue;
}

// 根据类型设置值
try {
Object value = fileItem.getValue().getFieldValueByType(object);
fileItem.getValue().setValue(value);
} catch (Exception e) {
LOGGER.error("inject config to store failed,filename:" + fileName, e);
}
}
}

@Override
public void transformScanData(List<ShutterFileModel> modelList) throws ShutterDuplicateKeyException {
for (ShutterFileModel model : modelList) {
transformScanData(model);
}
}

@Override
public void transformScanData(ShutterFileModel model) throws ShutterDuplicateKeyException {
storeCenter.store(model);
}

@Override
public void exclude(Set<String> keySet) {
for (String key : keySet) {
storeCenter.remove(key);
}
}
}

到这里大概的分析已经结束了,还有很多细节等待发掘。

补充:配置文件是什么时候下载的

ShutterStaticScanner.staticScan->ShutterCoreMgrImpl.process->ShutterFileCoreProcessor.processAllItems->ShutterFileCoreProcessor.processOneItem->ShutterFileCoreProcessor.updateOneConfFile->FetchMgrImpl.getFileFromServer