肝spring源码(一)

spring源码

IOC容器

IOC:(inversion of control):控制反转,将组件间的依赖关系从程序内部提到外部来管理。

DI(dependency injection):依赖注入;将组件中的依赖通过外部以参数或其他方式注入。

(导演、剧本、角色、演员)

导演负责协调剧本、角色、演员的关系,将角色的办演者的控制权反转。

注入方式:

  • 构造方式注入

  • 属性注入:(需要用时进行注入)

  • 接口注入:实现接口,与属性没有差别,还会增加接口,不建议。

功能分析

1
2
3
4
5
6
7
8
9
10
11
12
public class BeanFactoryTest {

@Test
public void testSimpleLoad() {
ClassPathResource classPathResource = new ClassPathResource("test.xml");

BeanFactory bf = new XmlBeanFactory(classPathResource);
MyTestBean test = (MyTestBean)bf.getBean("myTestBean");
assertEquals("testStr", test.getTestStr());
}

}

上面代码主要进行一下几点:

  1. 读取配置文件
  2. 根据配置文件中的配置找到对应得类的配置,并实例化。
  3. 调用实例化后的实例

数据准备阶段逻辑:

首先对传入的resource参数做封装,目的考虑到Resource可能存在编码要求的情况,

其次通过SAX读取Xml文件的方式,来准备InputSource对象,

最后将准备的数据传入真正的核心处理部分XmlBeanDefinitionReader

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
/**
* Actually load bean definitions from the specified XML file.
* @param inputSource the SAX InputSource to read from
* @param resource the resource descriptor for the XML file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
*/
//从特定XML文件中实际载入Bean定义资源的方法
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//1: 获取对XML文件的验证模式
int validationMode = getValidationModeForResource(resource);
//2: 加载XML文件,并得到对应的Document
//将XML文件转换为DOM对象,解析过程由documentLoader实现
Document doc = this.documentLoader.loadDocument(
inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
//这里是启动对Bean定义解析的详细过程,该解析过程会用到Spring的Bean配置规则
//3:根据返回的Document注册Bean信息(支撑着整个spring容器部分实现的基础,尤其是对配置文件的解析,逻辑非常复杂)
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}

获取XML的验证模式

验证方式常见为两种:DTD和XSD

DTD:(Document Type Definition)即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种保证XML文档格式正确的有效方法。 DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则,下面看下声明方式的代码:

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
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
<!--
Spring XML Beans DTD, version 2.0
Authors: Rod Johnson, Juergen Hoeller, Alef Arendsen, Colin Sampaleanu, Rob Harrop

This defines a simple and consistent way of creating a namespace
of JavaBeans objects, managed by a Spring BeanFactory, read by
XmlBeanDefinitionReader (with DefaultBeanDefinitionDocumentReader).

This document type is used by most Spring functionality, including
web application contexts, which are based on bean factories.

Each "bean" element in this document defines a JavaBean.
Typically the bean class is specified, along with JavaBean properties
and/or constructor arguments.

A bean instance can be a "singleton" (shared instance) or a "prototype"
(independent instance). Further scopes can be provided by extended
bean factories, for example in a web environment.

References among beans are supported, that is, setting a JavaBean property
or a constructor argument to refer to another bean in the same factory
(or an ancestor factory).

As alternative to bean references, "inner bean definitions" can be used.
Singleton flags of such inner bean definitions are effectively ignored:
Inner beans are typically anonymous prototypes.

There is also support for lists, sets, maps, and java.util.Properties
as bean property types or constructor argument types.

For simple purposes, this DTD is sufficient. As of Spring 2.0,
XSD-based bean definitions are supported as more powerful alternative.

XML documents that conform to this DTD should declare the following doctype:

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
-->


<!--
The document root. A document can contain bean definitions only,
imports only, or a mixture of both (typically with imports first).
-->
<!ELEMENT beans (
description?,
(import | alias | bean)*
)>

<!--
Default values for all bean definitions. Can be overridden at
the "bean" level. See those attribute definitions for details.
-->
<!ATTLIST beans default-lazy-init (true | false) "false">
<!ATTLIST beans default-merge (true | false) "false">
<!ATTLIST beans default-autowire (no | byName | byType | constructor | autodetect) "no">
<!ATTLIST beans default-dependency-check (none | objects | simple | all) "none">
<!ATTLIST beans default-init-method CDATA #IMPLIED>
<!ATTLIST beans default-destroy-method CDATA #IMPLIED>

<!--
Element containing informative text describing the purpose of the enclosing
element. Always optional.
Used primarily for user documentation of XML bean definition documents.
-->
<!ELEMENT description (#PCDATA)>


<!--
Specifies an XML bean definition resource to import.
-->
<!ELEMENT import EMPTY>

<!--
The relative resource location of the XML bean definition file to import,
for example "myImport.xml" or "includes/myImport.xml" or "../myImport.xml".
-->
<!ATTLIST import resource CDATA #REQUIRED>


<!--
Defines an alias for a bean, which can reside in a different definition file.
-->
<!ELEMENT alias EMPTY>

<!--
The name of the bean to define an alias for.
-->
<!ATTLIST alias name CDATA #REQUIRED>

<!--
The alias name to define for the bean.
-->
<!ATTLIST alias alias CDATA #REQUIRED>

<!--
Allows for arbitrary metadata to be attached to a bean definition.
-->
<!ELEMENT meta EMPTY>

<!--
Specifies the key name of the metadata parameter being defined.
-->
<!ATTLIST meta key CDATA #REQUIRED>

<!--
Specifies the value of the metadata parameter being defined as a String.
-->
<!ATTLIST meta value CDATA #REQUIRED>

<!--
Defines a single (usually named) bean.

A bean definition may contain nested tags for constructor arguments,
property values, lookup methods, and replaced methods. Mixing constructor
injection and setter injection on the same bean is explicitly supported.
-->
<!ELEMENT bean (
description?,
(meta | constructor-arg | property | lookup-method | replaced-method)*
)>

<!--
Beans can be identified by an id, to enable reference checking.

There are constraints on a valid XML id: if you want to reference your bean
in Java code using a name that's illegal as an XML id, use the optional
"name" attribute. If neither is given, the bean class name is used as id
(with an appended counter like "#2" if there is already a bean with that name).
-->
<!ATTLIST bean id ID #IMPLIED>

<!--
Optional. Can be used to create one or more aliases illegal in an id.
Multiple aliases can be separated by any number of spaces, commas, or
semi-colons (or indeed any mixture of the three).
-->
<!ATTLIST bean name CDATA #IMPLIED>

<!--
Each bean definition must specify the fully qualified name of the class,
except if it serves only as a parent definition for child bean definitions.
-->
<!ATTLIST bean class CDATA #IMPLIED>

<!--
Optionally specify a parent bean definition.

Will use the bean class of the parent if none specified, but can
also override it. In the latter case, the child bean class must be
compatible with the parent, i.e. accept the parent's property values
and constructor argument values, if any.

A child bean definition will inherit constructor argument values,
property values and method overrides from the parent, with the option
to add new values. If init method, destroy method, factory bean and/or factory
method are specified, they will override the corresponding parent settings.

The remaining settings will always be taken from the child definition:
depends on, autowire mode, dependency check, scope, lazy init.
-->
<!ATTLIST bean parent CDATA #IMPLIED>

<!--
The scope of this bean: typically "singleton" (one shared instance,
which will be returned by all calls to getBean() with the id),
or "prototype" (independent instance resulting from each call to
getBean(). Default is "singleton".

Singletons are most commonly used, and are ideal for multi-threaded
service objects. Further scopes, such as "request" or "session",
might be supported by extended bean factories (for example, in a
web environment).

Note: This attribute will not be inherited by child bean definitions.
Hence, it needs to be specified per concrete bean definition.

Inner bean definitions inherit the singleton status of their containing
bean definition, unless explicitly specified: The inner bean will be a
singleton if the containing bean is a singleton, and a prototype if
the containing bean has any other scope.
-->
<!ATTLIST bean scope CDATA #IMPLIED>

<!--
Is this bean "abstract", i.e. not meant to be instantiated itself but
rather just serving as parent for concrete child bean definitions.
Default is "false". Specify "true" to tell the bean factory to not try to
instantiate that particular bean in any case.

Note: This attribute will not be inherited by child bean definitions.
Hence, it needs to be specified per abstract bean definition.
-->
<!ATTLIST bean abstract (true | false) #IMPLIED>

<!--
If this bean should be lazily initialized.
If false, it will get instantiated on startup by bean factories
that perform eager initialization of singletons.

Note: This attribute will not be inherited by child bean definitions.
Hence, it needs to be specified per concrete bean definition.
-->
<!ATTLIST bean lazy-init (true | false | default) "default">

<!--
Optional attribute controlling whether to "autowire" bean properties.
This is an automagical process in which bean references don't need to be coded
explicitly in the XML bean definition file, but Spring works out dependencies.

There are 5 modes:

1. "no"
The traditional Spring default. No automagical wiring. Bean references
must be defined in the XML file via the <ref> element. We recommend this
in most cases as it makes documentation more explicit.

2. "byName"
Autowiring by property name. If a bean of class Cat exposes a dog property,
Spring will try to set this to the value of the bean "dog" in the current factory.
If there is no matching bean by name, nothing special happens;
use dependency-check="objects" to raise an error in that case.

3. "byType"
Autowiring if there is exactly one bean of the property type in the bean factory.
If there is more than one, a fatal error is raised, and you can't use byType
autowiring for that bean. If there is none, nothing special happens;
use dependency-check="objects" to raise an error in that case.

4. "constructor"
Analogous to "byType" for constructor arguments. If there isn't exactly one bean
of the constructor argument type in the bean factory, a fatal error is raised.

5. "autodetect"
Chooses "constructor" or "byType" through introspection of the bean class.
If a default constructor is found, "byType" gets applied.

The latter two are similar to PicoContainer and make bean factories simple to
configure for small namespaces, but doesn't work as well as standard Spring
behaviour for bigger applications.

Note that explicit dependencies, i.e. "property" and "constructor-arg" elements,
always override autowiring. Autowire behavior can be combined with dependency
checking, which will be performed after all autowiring has been completed.

Note: This attribute will not be inherited by child bean definitions.
Hence, it needs to be specified per concrete bean definition.
-->
<!ATTLIST bean autowire (no | byName | byType | constructor | autodetect | default) "default">

<!--
Optional attribute controlling whether to check whether all this
beans dependencies, expressed in its properties, are satisfied.
Default is no dependency checking.

"simple" type dependency checking includes primitives and String;
"objects" includes collaborators (other beans in the factory);
"all" includes both types of dependency checking.

Note: This attribute will not be inherited by child bean definitions.
Hence, it needs to be specified per concrete bean definition.
-->
<!ATTLIST bean dependency-check (none | objects | simple | all | default) "default">

<!--
The names of the beans that this bean depends on being initialized.
The bean factory will guarantee that these beans get initialized before.

Note that dependencies are normally expressed through bean properties or
constructor arguments. This property should just be necessary for other kinds
of dependencies like statics (*ugh*) or database preparation on startup.

Note: This attribute will not be inherited by child bean definitions.
Hence, it needs to be specified per concrete bean definition.
-->
<!ATTLIST bean depends-on CDATA #IMPLIED>

<!--
Indicates whether or not this bean should be considered when looking
for matching candidates to satisfy another bean's autowiring requirements.
Note that this does not affect explicit references by name, which will get
resolved even if the specified bean is not marked as an autowire candidate.
-->
<!ATTLIST bean autowire-candidate (true | false) #IMPLIED>

<!--
Optional attribute for the name of the custom initialization method
to invoke after setting bean properties. The method must have no arguments,
but may throw any exception.
-->
<!ATTLIST bean init-method CDATA #IMPLIED>

<!--
Optional attribute for the name of the custom destroy method to invoke
on bean factory shutdown. The method must have no arguments,
but may throw any exception.

Note: Only invoked on beans whose lifecycle is under full control
of the factory - which is always the case for singletons, but not
guaranteed for any other scope.
-->
<!ATTLIST bean destroy-method CDATA #IMPLIED>

<!--
Optional attribute specifying the name of a factory method to use to
create this object. Use constructor-arg elements to specify arguments
to the factory method, if it takes arguments. Autowiring does not apply
to factory methods.

If the "class" attribute is present, the factory method will be a static
method on the class specified by the "class" attribute on this bean
definition. Often this will be the same class as that of the constructed
object - for example, when the factory method is used as an alternative
to a constructor. However, it may be on a different class. In that case,
the created object will *not* be of the class specified in the "class"
attribute. This is analogous to FactoryBean behavior.

If the "factory-bean" attribute is present, the "class" attribute is not
used, and the factory method will be an instance method on the object
returned from a getBean call with the specified bean name. The factory
bean may be defined as a singleton or a prototype.

The factory method can have any number of arguments. Autowiring is not
supported. Use indexed constructor-arg elements in conjunction with the
factory-method attribute.

Setter Injection can be used in conjunction with a factory method.
Method Injection cannot, as the factory method returns an instance,
which will be used when the container creates the bean.
-->
<!ATTLIST bean factory-method CDATA #IMPLIED>

<!--
Alternative to class attribute for factory-method usage.
If this is specified, no class attribute should be used.
This should be set to the name of a bean in the current or
ancestor factories that contains the relevant factory method.
This allows the factory itself to be configured using Dependency
Injection, and an instance (rather than static) method to be used.
-->
<!ATTLIST bean factory-bean CDATA #IMPLIED>

<!--
Bean definitions can specify zero or more constructor arguments.
This is an alternative to "autowire constructor".
Arguments correspond to either a specific index of the constructor argument
list or are supposed to be matched generically by type.

Note: A single generic argument value will just be used once, rather than
potentially matched multiple times (as of Spring 1.1).

constructor-arg elements are also used in conjunction with the factory-method
element to construct beans using static or instance factory methods.
-->
<!ELEMENT constructor-arg (
description?,
(bean | ref | idref | value | null | list | set | map | props)?
)>

<!--
The constructor-arg tag can have an optional index attribute,
to specify the exact index in the constructor argument list. Only needed
to avoid ambiguities, e.g. in case of 2 arguments of the same type.
-->
<!ATTLIST constructor-arg index CDATA #IMPLIED>

<!--
The constructor-arg tag can have an optional type attribute,
to specify the exact type of the constructor argument. Only needed
to avoid ambiguities, e.g. in case of 2 single argument constructors
that can both be converted from a String.
-->
<!ATTLIST constructor-arg type CDATA #IMPLIED>

<!--
A short-cut alternative to a child element "ref bean=".
-->
<!ATTLIST constructor-arg ref CDATA #IMPLIED>

<!--
A short-cut alternative to a child element "value".
-->
<!ATTLIST constructor-arg value CDATA #IMPLIED>


<!--
Bean definitions can have zero or more properties.
Property elements correspond to JavaBean setter methods exposed
by the bean classes. Spring supports primitives, references to other
beans in the same or related factories, lists, maps and properties.
-->
<!ELEMENT property (
description?, meta*,
(bean | ref | idref | value | null | list | set | map | props)?
)>

<!--
The property name attribute is the name of the JavaBean property.
This follows JavaBean conventions: a name of "age" would correspond
to setAge()/optional getAge() methods.
-->
<!ATTLIST property name CDATA #REQUIRED>

<!--
A short-cut alternative to a child element "ref bean=".
-->
<!ATTLIST property ref CDATA #IMPLIED>

<!--
A short-cut alternative to a child element "value".
-->
<!ATTLIST property value CDATA #IMPLIED>


<!--
A lookup method causes the IoC container to override the given method and return
the bean with the name given in the bean attribute. This is a form of Method Injection.
It's particularly useful as an alternative to implementing the BeanFactoryAware
interface, in order to be able to make getBean() calls for non-singleton instances
at runtime. In this case, Method Injection is a less invasive alternative.
-->
<!ELEMENT lookup-method EMPTY>

<!--
Name of a lookup method. This method should take no arguments.
-->
<!ATTLIST lookup-method name CDATA #IMPLIED>

<!--
Name of the bean in the current or ancestor factories that the lookup method
should resolve to. Often this bean will be a prototype, in which case the
lookup method will return a distinct instance on every invocation. This
is useful for single-threaded objects.
-->
<!ATTLIST lookup-method bean CDATA #IMPLIED>


<!--
Similar to the lookup method mechanism, the replaced-method element is used to control
IoC container method overriding: Method Injection. This mechanism allows the overriding
of a method with arbitrary code.
-->
<!ELEMENT replaced-method (
(arg-type)*
)>

<!--
Name of the method whose implementation should be replaced by the IoC container.
If this method is not overloaded, there's no need to use arg-type subelements.
If this method is overloaded, arg-type subelements must be used for all
override definitions for the method.
-->
<!ATTLIST replaced-method name CDATA #IMPLIED>

<!--
Bean name of an implementation of the MethodReplacer interface in the current
or ancestor factories. This may be a singleton or prototype bean. If it's
a prototype, a new instance will be used for each method replacement.
Singleton usage is the norm.
-->
<!ATTLIST replaced-method replacer CDATA #IMPLIED>

<!--
Subelement of replaced-method identifying an argument for a replaced method
in the event of method overloading.
-->
<!ELEMENT arg-type (#PCDATA)>

<!--
Specification of the type of an overloaded method argument as a String.
For convenience, this may be a substring of the FQN. E.g. all the
following would match "java.lang.String":
- java.lang.String
- String
- Str

As the number of arguments will be checked also, this convenience can often
be used to save typing.
-->
<!ATTLIST arg-type match CDATA #IMPLIED>


<!--
Defines a reference to another bean in this factory or an external
factory (parent or included factory).
-->
<!ELEMENT ref EMPTY>

<!--
References must specify a name of the target bean.
The "bean" attribute can reference any name from any bean in the context,
to be checked at runtime.
Local references, using the "local" attribute, have to use bean ids;
they can be checked by this DTD, thus should be preferred for references
within the same bean factory XML file.
-->
<!ATTLIST ref bean CDATA #IMPLIED>
<!ATTLIST ref local IDREF #IMPLIED>
<!ATTLIST ref parent CDATA #IMPLIED>


<!--
Defines a string property value, which must also be the id of another
bean in this factory or an external factory (parent or included factory).
While a regular 'value' element could instead be used for the same effect,
using idref in this case allows validation of local bean ids by the XML
parser, and name completion by supporting tools.
-->
<!ELEMENT idref EMPTY>

<!--
ID refs must specify a name of the target bean.
The "bean" attribute can reference any name from any bean in the context,
potentially to be checked at runtime by bean factory implementations.
Local references, using the "local" attribute, have to use bean ids;
they can be checked by this DTD, thus should be preferred for references
within the same bean factory XML file.
-->
<!ATTLIST idref bean CDATA #IMPLIED>
<!ATTLIST idref local IDREF #IMPLIED>


<!--
Contains a string representation of a property value.
The property may be a string, or may be converted to the required
type using the JavaBeans PropertyEditor machinery. This makes it
possible for application developers to write custom PropertyEditor
implementations that can convert strings to arbitrary target objects.

Note that this is recommended for simple objects only.
Configure more complex objects by populating JavaBean
properties with references to other beans.
-->
<!ELEMENT value (#PCDATA)>

<!--
The value tag can have an optional type attribute, to specify the
exact type that the value should be converted to. Only needed
if the type of the target property or constructor argument is
too generic: for example, in case of a collection element.
-->
<!ATTLIST value type CDATA #IMPLIED>

<!--
Denotes a Java null value. Necessary because an empty "value" tag
will resolve to an empty String, which will not be resolved to a
null value unless a special PropertyEditor does so.
-->
<!ELEMENT null (#PCDATA)>


<!--
A list can contain multiple inner bean, ref, collection, or value elements.
Java lists are untyped, pending generics support in Java 1.5,
although references will be strongly typed.
A list can also map to an array type. The necessary conversion
is automatically performed by the BeanFactory.
-->
<!ELEMENT list (
(bean | ref | idref | value | null | list | set | map | props)*
)>

<!--
Enable/disable merging for collections when using parent/child beans.
-->
<!ATTLIST list merge (true | false | default) "default">

<!--
Specify the default Java type for nested values.
-->
<!ATTLIST list value-type CDATA #IMPLIED>


<!--
A set can contain multiple inner bean, ref, collection, or value elements.
Java sets are untyped, pending generics support in Java 1.5,
although references will be strongly typed.
-->
<!ELEMENT set (
(bean | ref | idref | value | null | list | set | map | props)*
)>

<!--
Enable/disable merging for collections when using parent/child beans.
-->
<!ATTLIST set merge (true | false | default) "default">

<!--
Specify the default Java type for nested values.
-->
<!ATTLIST set value-type CDATA #IMPLIED>


<!--
A Spring map is a mapping from a string key to object.
Maps may be empty.
-->
<!ELEMENT map (
(entry)*
)>

<!--
Enable/disable merging for collections when using parent/child beans.
-->
<!ATTLIST map merge (true | false | default) "default">

<!--
Specify the default Java type for nested entry keys.
-->
<!ATTLIST map key-type CDATA #IMPLIED>

<!--
Specify the default Java type for nested entry values.
-->
<!ATTLIST map value-type CDATA #IMPLIED>

<!--
A map entry can be an inner bean, ref, value, or collection.
The key of the entry is given by the "key" attribute or child element.
-->
<!ELEMENT entry (
key?,
(bean | ref | idref | value | null | list | set | map | props)?
)>

<!--
Each map element must specify its key as attribute or as child element.
A key attribute is always a String value.
-->
<!ATTLIST entry key CDATA #IMPLIED>

<!--
A short-cut alternative to a "key" element with a "ref bean=" child element.
-->
<!ATTLIST entry key-ref CDATA #IMPLIED>

<!--
A short-cut alternative to a child element "value".
-->
<!ATTLIST entry value CDATA #IMPLIED>

<!--
A short-cut alternative to a child element "ref bean=".
-->
<!ATTLIST entry value-ref CDATA #IMPLIED>

<!--
A key element can contain an inner bean, ref, value, or collection.
-->
<!ELEMENT key (
(bean | ref | idref | value | null | list | set | map | props)
)>


<!--
Props elements differ from map elements in that values must be strings.
Props may be empty.
-->
<!ELEMENT props (
(prop)*
)>

<!--
Enable/disable merging for collections when using parent/child beans.
-->
<!ATTLIST props merge (true | false | default) "default">

<!--
Element content is the string value of the property.
Note that whitespace is trimmed off to avoid unwanted whitespace
caused by typical XML formatting.
-->
<!ELEMENT prop (#PCDATA)>

<!--
Each property element must specify its key.
-->
<!ATTLIST prop key CDATA #REQUIRED>

XML Schema 语言就是XSD(XMLSchemas Definition)。描述了XMl文档的结构,可以指定一个指定的XML Schema来验证某个XML文档,检查XML文档是否符合要求。

要声明名称空间,

1
xmlns="http://www.springframework.org/schema/beans"

还需指定XML Schemas文档的存储位置

1
2
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"

验证模式的读取

从上文代码可以看到验证模式的读取:

1
int validationMode = getValidationModeForResource(resource);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Gets the validation mode for the specified {@link Resource}. If no explicit
* validation mode has been configured then the validation mode is
* {@link #detectValidationMode detected}.
* <p>Override this method if you would like full control over the validation
* mode, even when something other than {@link #VALIDATION_AUTO} was set.
*/
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
//如果手动指定了验证模式则使用指定的验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
//如果未指定就自动检测
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// Hmm, we didn't get a clear indication... Let's assume XSD,
// since apparently no DTD declaration has been found up until
// detection stopped (before finding the document's root tag).
return VALIDATION_XSD;
}

如何设定指定的验证模式:通过调用XmlBeanDefinitionReader中的setValidationMode方法进行设定

自动检测验证模式的功能是在函数detectValidationMode函数中将自动检测验证模式的工作委托给专门处理类XmlValidationModeDetector,调用了XmlValidationModeDetector中的detectValidationMode(InputStream inputStream)方法。

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
/**
* Detects which kind of validation to perform on the XML file identified
* by the supplied {@link Resource}. If the file has a {@code DOCTYPE}
* definition then DTD validation is used otherwise XSD validation is assumed.
* <p>Override this method if you would like to customize resolution
* of the {@link #VALIDATION_AUTO} mode.
*/
protected int detectValidationMode(Resource resource) {
if (resource.isOpen()) {
throw new BeanDefinitionStoreException(
"Passed-in Resource [" + resource + "] contains an open stream: " +
"cannot determine validation mode automatically. Either pass in a Resource " +
"that is able to create fresh streams, or explicitly specify the validationMode " +
"on your XmlBeanDefinitionReader instance.");
}

InputStream inputStream;
try {
inputStream = resource.getInputStream();
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
"Did you attempt to load directly from a SAX InputSource without specifying the " +
"validationMode on your XmlBeanDefinitionReader instance?", ex);
}

try {
//进行委托
return this.validationModeDetector.detectValidationMode(inputStream);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
resource + "]: an error occurred whilst reading from the InputStream.", ex);
}
}

XmlValidationModeDetector.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
/**
* Detect the validation mode for the XML document in the supplied {@link InputStream}.
* Note that the supplied {@link InputStream} is closed by this method before returning.
* @param inputStream the InputStream to parse
* @throws IOException in case of I/O failure
* @see #VALIDATION_DTD
* @see #VALIDATION_XSD
*/
public int detectValidationMode(InputStream inputStream) throws IOException {
// Peek into the file to look for DOCTYPE.
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
//如果读取的行是空或者是注释则略过
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
//读取到<开始符号,验证模式一定会在开始符号之前
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}


/**
* Does the content contain the the DTD DOCTYPE declaration?
*/
private boolean hasDoctype(String content) {
return (content.indexOf(DOCTYPE) > -1);
}

spring用来检测验证模式的办法就是判断是否包含DOCTYPE,包含就是DTD,否则就是XSD.

取Document

XmlBeanFactoryReader类将读取文档委托给DocumentLoader去执行,真正调用的为DefaultDocumentLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
* XML parser.
*/
//使用标准的JAXP将载入的Bean定义资源转换成document对象
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

//创建文件解析器工厂
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
//创建文档解析器
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
//解析Spring的Bean定义资源
return builder.parse(inputSource);
}

解析以及注册BeanDefinitions

XmlBeanDefinitionReader.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
/**
* Register the bean definitions contained in the given DOM document.
* Called by {@code loadBeanDefinitions}.
* <p>Creates a new instance of the parser class and invokes
* {@code registerBeanDefinitions} on it.
* @param doc the DOM document
* @param resource the resource descriptor (for context information)
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of parsing errors
* @see #loadBeanDefinitions
* @see #setDocumentReaderClass
* @see BeanDefinitionDocumentReader#registerBeanDefinitions
*/
//按照Spring的Bean语义要求将Bean定义资源解析并转换为容器内部数据结构
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//得到BeanDefinitionDocumentReader来对xml格式的BeanDefinition解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
documentReader.setEnvironment(this.getEnvironment());
//获得容器中注册的Bean数量
int countBefore = getRegistry().getBeanDefinitionCount();
//解析过程入口,这里使用了委派模式,BeanDefinitionDocumentReader只是个接口,
//具体的解析实现过程有实现类DefaultBeanDefinitionDocumentReader完成
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//统计解析的Bean数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}

其中参数doc是通过上一节loadDocument加载转换过来的。这个方法很好的应用了面向对象的单一职责的原则,将逻辑处理委托给单一的类处理,处理类就是BeanDefinitionDocumentReader(接口)。

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
/**
* {@inheritDoc}
* <p>This implementation parses bean definitions according to the "spring-beans" XSD
* (or DTD, historically).
* <p>Opens a DOM Document; then initializes the default settings
* specified at the {@code <beans/>} level; then parses the contained bean definitions.
*/
//根据Spring DTD对Bean的定义规则解析Bean定义Document对象
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
//获得XML描述符
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//获得Document的根元素
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
/**
* Register each bean definition within the given root {@code <beans/>} element.
* @throws IllegalStateException if {@code <beans profile="..."} attribute is present
* and Environment property has not been set
* @see #setEnvironment
*/
protected void doRegisterBeanDefinitions(Element root) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!this.environment.acceptsProfiles(specifiedProfiles)) {
return;
}
}

// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
//具体的解析过程由BeanDefinitionParserDelegate实现,
//BeanDefinitionParserDelegate中定义了Spring Bean定义XML文件的各种元素
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(this.readerContext, root, parent);


//在解析Bean定义之前,进行自定义的解析,增强解析过程的可扩展性
preProcessXml(root);
//从Document的根元素开始进行Bean定义的Document对象
parseBeanDefinitions(root, this.delegate);
//在解析Bean定义之后,进行自定义的解析,增加解析过程的可扩展性
postProcessXml(root);

this.delegate = parent;
}

默认标签的解析

spring的标签包括默认标签和自定义标签,两种标签的用法以及解析方式有很大的不同

DefaultBeanDefinitionDocumentReader.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//使用Spring的Bean规则解析Document元素节点
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//如果元素节点是<Import>导入元素,进行导入解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//如果元素节点是<Alias>别名元素,进行别名解析
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//元素节点既不是导入元素,也不是别名元素,即普通的<Bean>元素,
//按照Spring的Bean规则解析元素
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
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

/**
* Process the given bean element, parsing the bean definition
* and registering it with the registry.
*/
//解析Bean定义资源Document对象的普通元素
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
// BeanDefinitionHolder是对BeanDefinition的封装,即Bean定义的封装类
// 对Document对象中<Bean>元素的解析由BeanDefinitionParserDelegate实现
// BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
//向Spring IOC容器注册解析得到的Bean定义,这是Bean定义向IOC容器注册的入口
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
//在完成向Spring IOC容器注册解析得到的Bean定义之后,发送注册事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

此时我们先分析对bean标签的读取

  • 首先先委托BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder类型的实例bdHolder,经过这个方法后,bdHolder实例已经包含我们配置文件中配置的各种属性了。
  • 返回的bdHolder不为空的话,若存在默认标签的子节点下再有自定义属性的话,还需要再次对自定义标签进行解析
  • 解析完成后,对bdHolder进行注册,注册委托给了BeanDefinitionReaderUtils的registerBeanDefinition方法。
  • 最后发出响应事件,通知想关的监听器,这个bean已经加在完成了。

时序图如下:

解析BeanDefinition

1
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement();

我们从此处这里进入解析的方法了。

BeanDefinitionParserDelegate.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
//解析<Bean>元素的入口
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}

/**
* Parses the supplied {@code &lt;bean&gt;} element. May return {@code null}
* if there were errors during parse. Errors are reported to the
* {@link org.springframework.beans.factory.parsing.ProblemReporter}.
*/
//解析Bean定义资源文件中的<Bean>元素,这个方法中主要处理<Bean>元素的id,name
//和别名属性
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
//获取<Bean>元素中的id属性值
String id = ele.getAttribute(ID_ATTRIBUTE);
//获取<Bean>元素中的name属性值
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

//获取<Bean>元素中的alias属性值
List<String> aliases = new ArrayList<String>();
//将<Bean>元素中的所有name属性值存放到别名中
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}

String beanName = id;
//如果<Bean>元素中没有配置id属性时,将别名中的第一个值赋值给beanName
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}

//检查<Bean>元素所配置的id或者name的唯一性,containingBean标识<Bean>
//元素中是否包含子<Bean>元素
if (containingBean == null) {
//检查<Bean>元素所配置的id、name或者别名是否重复
checkNameUniqueness(beanName, aliases, ele);
}

//详细对<Bean>元素中配置的Bean定义进行解析的地方
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
//如果<Bean>元素中没有配置id、别名或者name,且没有包含子元素
//<Bean>元素,为解析的Bean生成一个唯一beanName并注册
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
//如果<Bean>元素中没有配置id、别名或者name,且包含了子元素
//<Bean>元素,为解析的Bean使用别名向IOC容器注册
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.
//为解析的Bean使用别名注册时,为了向后兼容
//Spring1.2/2.0,给别名添加类名后缀
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("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);
}
//当解析出错时,返回null
return null;
}

解析步骤:

  • 提取元素中的id以及name属性
  • 进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中。
  • 如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName。
  • 将获取到的信息封装在BeanDefinitionHolder中。

解析其他属性

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
//详细对<Bean>元素中配置的Bean定义其他属性进行解析,由于上面的方法中已经对
//Bean的id、name和别名等属性进行了处理,该方法中主要处理除这三个以外的其他属性数据
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {

//记录解析的<Bean>
this.parseState.push(new BeanEntry(beanName));

//这里只读取<Bean>元素中配置的class名字,然后载入到BeanDefinition中去
//只是记录配置的class名字,不做实例化,对象的实例化在依赖注入时完成
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}

try {
String parent = null;
//如果<Bean>元素中配置了parent属性,则获取parent属性的值
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}

//根据<Bean>元素配置的class名称和parent属性值创建BeanDefinition
//为载入Bean定义信息做准备
AbstractBeanDefinition bd = createBeanDefinition(className, parent);

//对当前的<Bean>元素中配置的一些属性进行解析和设置,如配置的单态(singleton)属性等
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
//为<Bean>元素解析的Bean设置description信息
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

//对<Bean>元素的meta(元信息)属性解析
parseMetaElements(ele, bd);
//对<Bean>元素的lookup-method属性解析
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//对<Bean>元素的replaced-method属性解析
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

//解析<Bean>元素的构造方法设置
parseConstructorArgElements(ele, bd);
//解析<Bean>元素的<property>设置
parsePropertyElements(ele, bd);
//解析<Bean>元素的qualifier属性
parseQualifierElements(ele, bd);

//为当前解析的Bean设置所需的资源和依赖对象
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));

return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
}
//解析<Bean>元素出错时,返回null
return null;
}

创建用于属性承载的BeanDefinition

解析属性要先创建用于承载属性的实例,也就是创建GenericBeanDefinition类型的实例。下面的代码就是实现此功能。

1
2
3
4
5
6
protected AbstractBeanDefinition createBeanDefinition(String className, String parentName)
throws ClassNotFoundException {

return BeanDefinitionReaderUtils.createBeanDefinition(
parentName, className, this.readerContext.getBeanClassLoader());
}

本文标题:肝spring源码(一)

文章作者:wsylp

发布时间:2020年06月07日 - 15:06

最后更新:2020年06月07日 - 15:06

原始链接:http://wsylp.github.io/2020/06/07/肝spring源码-一/

许可协议: 本文为 wsylp 版权所有 转载请保留原文链接及作者。

-------------本文结束感谢阅读-------------