这篇博客是我在阅读 Spring 英文文档过程中记录的对 Spring 的新认知,或者说是之前比较模糊的概念。总而言之,算是我个人的随笔记录,方便以后查看,如果感觉对你帮助不大,可以跳过不看本篇博客。
1. Container Overview
The org.springframework.context.ApplicationContext
interface represents the Spring IoC container and is responsible for instantiating, configuring, and assembling the beans. The container gets its instructions on what objects to instantiate, configure, and assemble by reading configuration metadata. The configuration metadata is represented in XML, Java annotations, or Java code. It lets you express the objects that compose your application and the rich interdependencies between those objects.
applicationcontext接口表示Spring IoC容器,并负责实例化、配置和组装bean。容器通过读取配置元数据来获取关于实例化、配置和组装哪些对象的指令。配置元数据以XML、Java注释或Java代码表示。它允许您表达组成应用程序的对象以及这些对象之间的丰富的相互依赖关系。
2. Configuration Metadata
For information about using other forms of metadata with the Spring container, see:
- Annotation-based configuration: Spring 2.5 introduced support for annotation-based configuration metadata.
- Java-based configuration: Starting with Spring 3.0, many features provided by the Spring JavaConfig project became part of the core Spring Framework. Thus, you can define beans external to your application classes by using Java rather than XML files. To use these new features, see the
@Configuration
,@Bean
,@Import
, and@DependsOn
annotations.
Spring configuration consists of at least one and typically more than one bean definition that the container must manage. XML-based configuration metadata configures these beans as <bean/>
elements inside a top-level <beans/>
element. Java configuration typically uses @Bean
-annotated methods within a @Configuration
class.
这段我认为需要理解好最后一句话:Java 配置通常在@Configuration 类中使用@Bean 注释的方法。
Spring3.0开始,@Configuration用于定义配置类,定义的配置类可以替换xml文件,一般和@Bean注解联合使用。@Configuration注解主要标注在某个类上,相当于xml配置文件中的
@Bean注解主要标注在某个方法上,相当于xml配置文件中的
2.1 the basic structure of XML-based configuration metadata
1 | <?xml version="1.0" encoding="UTF-8"?> |
- The
id
attribute is a string that identifies the individual bean definition. - The
class
attribute defines the type of the bean and uses the fully qualified classname.
3. Composing XML-based Configuration Metadata
It can be useful to have bean definitions span multiple XML files. Often, each individual XML configuration file represents a logical layer or module in your architecture.
You can use the application context constructor to load bean definitions from all these XML fragments. This constructor takes multiple Resource
locations, as was shown in the previous section. Alternatively, use one or more occurrences of the <import/>
element to load bean definitions from another file or files. The following example shows how to do so:
1 | <beans> |
In the preceding example, external bean definitions are loaded from three files: services.xml
, messageSource.xml
, and themeSource.xml
. All location paths are relative to the definition file doing the importing, so services.xml
must be in the same directory or classpath location as the file doing the importing, while messageSource.xml
and themeSource.xml
must be in a resources
location below the location of the importing file. As you can see, a leading slash is ignored. However, given that these paths are relative, it is better form not to use the slash at all. The contents of the files being imported, including the top level <beans/>
element, must be valid XML bean definitions, according to the Spring Schema.
上述内容主要就是描述了XML如何定义,其中黑体字标出的相对重要些:所有的位置路径都是相对于执行导入的定义文件的,因此services.xml必须与执行导入的文件位于相同的目录或类路径位置,而messageSource.xml和themeSource.xml必须位于导入文件位置下方的资源位置。
It is possible, but not recommended, to reference files in parent directories using a relative “../“ path. Doing so creates a dependency on a file that is outside the current application. In particular, this reference is not recommended for
classpath:
URLs (for example,classpath:../services.xml
), where the runtime resolution process chooses the “nearest” classpath root and then looks into its parent directory. Classpath configuration changes may lead to the choice of a different, incorrect directory.You can always use fully qualified resource locations instead of relative paths: for example,
file:C:/config/services.xml
orclasspath:/config/services.xml
. However, be aware that you are coupling your application’s configuration to specific absolute locations. It is generally preferable to keep an indirection for such absolute locations — for example, through “${…}” placeholders that are resolved against JVM system properties at runtime.
上述大概意思就是不推荐使用 “../” 这种形式的路径配置,这样做会在当前应用程序之外的文件上创建一个依赖项。特别是不建议
classpath:
URLs (比如classpath:../services.xml
) 这种形式的引用,运行时解析进程选择“最近的”类路径根,然后查看其父目录。可以使用完全限定的资源位置而不是绝对位置: 比如
file:C:/config/services.xml
或者classpath:/config/services.xml
。但是,请注意,您正在将应用程序的配置耦合到特定的绝对位置。对于这种绝对位置,通常更可取的做法是保持间接性ーー例如,通过在运行时根据 JVM 系统属性解析的“ ${ … }”占位符。
4. Naming Beans
Every bean has one or more identifiers. These identifiers must be unique within the container that hosts the bean. A bean usually has only one identifier. However, if it requires more than one, the extra ones can be considered aliases.
In XML-based configuration metadata, you use the id
attribute, the name
attribute, or both to specify the bean identifiers. The id
attribute lets you specify exactly one id.
The convention is to use the standard Java convention for instance field names when naming beans. That is, bean names start with a lowercase letter and are camel-cased from there. Examples of such names include accountManager
, accountService
, userDao
, loginController
, and so forth.
Naming beans consistently makes your configuration easier to read and understand. Also, if you use Spring AOP, it helps a lot when applying advice to a set of beans related by name.
With component scanning in the classpath, Spring generates bean names for unnamed components, following the rules described earlier: essentially, taking the simple class name and turning its initial character to lower-case. However, in the (unusual) special case when there is more than one character and both the first and second characters are upper case, the original casing gets preserved. These are the same rules as defined by java.beans.Introspector.decapitalize
(which Spring uses here).
上述前两段主要就是描述每个 bean 都有一个或多个标识符。这些标识符在承载 bean 的容器中必须是唯一的。一个 bean 通常只有一个标识符。但是,如果它需要多于一个,那么额外的那些可以被认为是别名。Bean的命名方式通常有 id
和 name
命名,
中间两段主要介绍了并推荐使用驼峰命名。但是这部分最后一句加粗的文字目前我并不理解,暂且搁置。
最后一段描述通过在类路径中进行组件扫描,Spring 为未命名的组件生成 bean 名称,遵循前面描述的规则: 本质上,使用简单的类名并将其初始字符转换为小写。但是,在(不寻常的)特殊情况下,当有多个字符且第一个和第二个字符都是大写字母时,原始大小写将得到保留。这些规则与 java.beans 定义的规则相同。这部分加粗文字和Java内省机制相关类 Introspector
有关,其中该类相关源码如下:
1 | public static String decapitalize(String name) { |
一般情况下,把字符串第一个字母变为小写,如把“FooBah”变为“fooBah”。但在特殊情况下,即字符串前两个字母都是大写的时候,什么也不做,如,遇到“URL”,原样返回。
decapitalize()的bug是:如果一个字符串,前两个字母大写,但后面还有小写字母,它仍然返回原字符串!
Hibernate的开发者注意到decapitalize()的特点,所以才在判断语句中使用一个或运算(不然只需要判断方法名截掉“get”,再改第一个字母为小写后的字符串与属性名是否相等即可,这也是按照JavaBean Specification定义的标准做法)。但是,Hibernate没有解决这个bug,可能是他们没有碰到我遇到的情况。
类似sAddress(一般性地说,第一个字母小写,第二个字母大写)属性命名就是bug的诱因。
那么,解决方法有三种:
把属性名改成SAddress,这样就满足上面匹配判断的第二个条件(方法名截掉“get”后,与属性名匹配)。但是,这样做不符合Java命名规范;
把getSAddress()改成getsAddress(),这样也满足上面匹配判断的第二个条件(方法名截掉“get”后,与属性名匹配)。但是,这样做不符合JavaBean命名规范;
把属性名改成strAddress,并形成一种约定:命名属性时,第二个字符只能是小写字母。这个方法不需要做更多地修改,符合所有规范,最为稳妥。
4.1 Aliasing a Bean outside the Bean Definition
子系统 a 的配置元数据可以通过 subsystemA-DataSource 的名称引用 DataSource。子系统 b 的配置元数据可以通过 subsystemB-DataSource 的名称来引用 DataSource。当组合使用这两个子系统的主应用程序时,主应用程序通过 myapp-DataSource 的名称引用 DataSource。要让所有三个名称都指向同一个对象,可以向配置元数据添加以下别名定义:
1 | <alias name="myApp-dataSource" alias="subsystemA-dataSource"/> |
现在,每个组件和主应用程序都可以通过一个惟一的名称引用 dataSource,并保证不会与任何其他定义发生冲突(有效地创建名称空间) ,但它们引用的是同一个 bean。
If you use Javaconfiguration, the
@Bean
annotation can be used to provide aliases. See Using the@Bean
Annotation for details.
4.2 Instantiation with a Static Factory Method(用静态工厂方法实例化)
调用这个方法(带有可选参数,如后面所述)并返回一个活动对象,随后该对象将被视为通过构造函数创建的。这种 bean 定义的一个用途是在遗留代码中调用静态工厂。
下面的 bean 定义指定通过调用 factory 方法创建 bean。该定义不指定返回对象的类型(类) ,只指定包含工厂方法的类。在本例中,createInstance ()方法必须是静态方法。下面的示例演示如何指定工厂方法:
1 | <bean id="clientService" |
下面的示例展示了一个与前面的 bean 定义一起工作的类:
1 | public class ClientService { |
遗留代码: Legacy Code
遗留代码是指不再受支持的应用程序系统源代码类型。遗留代码也可以指不支持的操作系统、硬件和格式。在大多数情况下,遗留代码被转换为现代软件语言和平台。然而,为了保留熟悉的用户功能,遗留代码有时会被带入新的环境中。
这里可以参考我的另一篇博客来理解静态工厂方法创建对象:Effective Java–创建和销毁对象
4.3 Instantiation by Using an Instance Factory Method (使用实例工厂方法实例化)
与通过静态工厂方法进行的实例化类似,使用实例工厂方法进行的实例化,从容器中调用现有 bean 的非静态方法来创建新 bean。要使用这种机制,保留 class 属性为空,并在 factory-bean 属性中,在当前(或父或祖先)容器中指定 bean 的名称,该容器包含要调用来创建对象的实例方法。使用 factory-method 属性设置 factory 方法本身的名称。下面的例子展示了如何配置这样一个 bean:
1 | <!-- the factory bean, which contains a method called createInstance() --> |
下面的示例展示了相应的类:
1 | public class DefaultServiceLocator { |
一个工厂类也可以容纳多个工厂方法,如下面的示例所示:
1 | <bean id="serviceLocator" class="examples.DefaultServiceLocator"> |
下面的示例展示了相应的类:
1 | public class DefaultServiceLocator { |
这种方法表明,工厂 bean 本身可以通过依赖注入管理器(DI)来管理和配置。详见依赖关系和配置。
在 Spring 文档中,”factory bean” 指的是在 Spring 容器中配置并通过实例或静态工厂方法创建对象的 bean。相比之下,FactoryBean (注意大写)引用了一个 spring 特定的 FactoryBean 实现类。
4.4 Determining a Bean’s Runtime Type
确定特定 bean 的运行时类型并不简单。Bean 元数据定义中的指定类仅仅是一个初始类引用,它可能与已声明的工厂方法或 FactoryBean 类结合在一起,后者可能导致 bean 的不同运行时类型,或者在实例级工厂方法(可以通过指定的工厂 bean 名称解析)的情况下根本不设置类。此外,AOP 代理可以使用基于接口的代理来包装 bean 实例,对目标 bean 的实际类型(仅仅是实现的接口)进行有限的公开。
查找特定 bean 的实际运行时类型的推荐方法是 BeanFactory.getType 调用指定的 bean 名称。这将考虑上述所有情况,并返回 BeanFactory.getBean 调用将返回的对象类型。
5. Dependencies
5.1 Constructor Argument Resolution
使用参数的类型进行构造函数参数解析匹配。如果 bean 定义的构造函数参数中没有潜在的歧义,那么在 bean 定义中定义构造函数参数的顺序就是在 bean 被实例化时这些参数被提供给相应的构造函数的顺序。参考下面的类:
1 | public class ThingOne { |
假设 ThingTwo 和 thingThree类不通过继承关联,则不存在潜在的歧义。因此,下面的配置可以很好地工作,不需要在 < constructor-arg/> 元素中显式地指定构造函数参数索引或类型。
1 | <beans> |
当引用另一个 bean 时,该类型是已知的,并且可以进行匹配(与前面的示例一样)。当使用简单类型时,如 < value > true </value>
,Spring 无法确定值的类型,因此在没有帮助的情况下无法按类型进行匹配。参考下面的类:
1 | public class ExampleBean { |
类型匹配
在前面的场景中,如果使用 type 属性显式指定构造函数参数的类型,那么容器可以对简单类型使用类型匹配,如下面的示例所示:
1 | <bean id="exampleBean" class="examples.ExampleBean"> |
构造函数参数的顺序
可以使用 index 属性显式指定构造函数参数的索引,如下面的示例所示:
1 | <bean id="exampleBean" class="examples.ExampleBean"> |
除了解决多个简单值的不确定性,指定索引还可以解决构造函数具有两个相同类型的参数时的不确定性。
5.2 Constructor-based or setter-based DI?
Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Required annotation on a setter method can be used to make the property be a required dependency; however, constructor injection with programmatic validation of arguments is preferable.
The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null
. Furthermore, constructor-injected components are always returned to the client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.
Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is therefore a compelling use case for setter injection.
Use the DI style that makes the most sense for a particular class. Sometimes, when dealing with third-party classes for which you do not have the source, the choice is made for you. For example, if a third-party class does not expose any setter methods, then constructor injection may be the only available form of DI.
译:由于可以混合使用基于构造函数和基于 setter 的 DI,因此对于强制依赖项使用构造函数和对于可选依赖项使用 setter 方法或配置方法是一个很好的经验法则。注意,在 setter 方法上使用@Required 注释可以使该属性成为必需的依赖项; 但是,带有参数编程验证的构造函数注入更可取。
Spring 团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。此外,构造函数注入的组件总是以完全初始化的状态返回给客户机(调用)代码。作为一个旁注,大量的构造函数参数是糟糕的代码,(本博客作者注:在《代码整洁之道》书中,作者建议一般不超过三个参数,超过的话意味着要对函数进行拆分),这意味着类可能有太多的责任,应该重构以更好地解决适当的关注点分离/代码。
Setter 注入应该主要用于可选的依赖项,这些依赖项可以在类中分配合理的默认值。否则,必须在代码使用依赖项的所有地方执行非空检查。Setter 注入的一个好处是 setter 方法使该类的对象容易在以后重新配置或重新注入。因此,通过 JMX MBeans进行管理是 setter 注入的一个引人注目的用例。
使用对特定类最有意义的DI样式。有时,在处理没有源代码的第三方类时,可以自行选择。例如,如果一个第三方类不公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式。
构造注入是必须把一个对象的所有依赖的对象都进行实例化,才能实例化这个对象
Setter注入是先实例化这个对象,然后找到依赖的对象,对依赖的对象进行实例化。
setter注入: 一般情况下所有的java bean, 我们都会使用setter方法和getter方法去设置和获取属性的值,示例如下:
1
2
3
4
5
6
7
8
9 public class namebean {
String name;
public void setName(String a) {
name = a;
}
public String getName() {
return name;
}
}我们会创建一个bean的实例然后设置属性的值,spring的配置文件如下:
我们会创建一个bean的实例然后设置属性的值,spring的配置文件如下:
1
2
3
4
5 <bean id="bean1″ >
<property name="name" >
<value>tom</value>
</property>
</bean>Spring会调用setName方法来只是name属性为tom
构造方法注入:构造方法注入中,我们使用带参数的构造方法如下:Spring会调用setName方法来只是name属性为tom
构造方法注入:构造方法注入中,我们使用带参数的构造方法如下:
1
2
3
4
5
6 public class namebean {
String name;
public namebean(String a) {
name = a;
}
}我们会在创建bean实例的时候以new namebean(”tom”)的方式来设置name属性, Spring配置文件如下:
我们会在创建bean实例的时候以new namebean(”tom”)的方式来设置name属性, Spring配置文件如下:
1
2
3
4
5 <bean id="bean1″ >
<constructor-arg>
<value>My Bean Value</value>
</constructor-arg>
</bean>使用constructor-arg标签来设置构造方法的参数。
使用constructor-arg标签来设置构造方法的参数。
5.3 Dependency Resolution Process(依赖解析的过程)
The container performs bean dependency resolution as follows:
- The
ApplicationContext
is created and initialized with configuration metadata that describes all the beans. Configuration metadata can be specified by XML, Java code, or annotations. - For each bean, its dependencies are expressed in the form of properties, constructor arguments, or arguments to the static-factory method (if you use that instead of a normal constructor). These dependencies are provided to the bean, when the bean is actually created.
- Each property or constructor argument is an actual definition of the value to set, or a reference to another bean in the container.
- Each property or constructor argument that is a value is converted from its specified format to the actual type of that property or constructor argument. By default, Spring can convert a value supplied in string format to all built-in types, such as
int
,long
,String
,boolean
, and so forth.
The Spring container validates the configuration of each bean as the container is created. However, the bean properties themselves are not set until the bean is actually created. Beans that are singleton-scoped and set to be pre-instantiated (the default) are created when the container is created. Scopes are defined in Bean Scopes. Otherwise, the bean is created only when it is requested. Creation of a bean potentially causes a graph of beans to be created, as the bean’s dependencies and its dependencies’ dependencies (and so on) are created and assigned. Note that resolution mismatches among those dependencies may show up late — that is, on first creation of the affected bean.
容器执行 bean 依赖项解析如下:
创建 ApplicationContext 并使用描述所有 bean 的配置元数据进行初始化。可以通过 XML、 Java 代码或注释指定配置元数据。
对于每个 bean,它的依赖关系以属性、构造函数参数或静态工厂方法的参数的形式表示(如果您使用静态工厂方法而不是普通的构造函数)。在实际创建 bean 时,这些依赖项被提供给 bean。
每个属性或构造函数参数都是要设置的值的实际定义,或对容器中另一个 bean 的引用。
作为值的每个属性或构造函数参数都将从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将以字符串格式提供的值转换为所有内置类型,如 int、 long、 String、 boolean 等。
Spring 容器在创建容器时验证每个 bean 的配置。但是,在 bean 实际创建之前,不会设置 bean 属性本身。在创建容器时,将创建单一作用域并设置为预实例化(默认情况)的 bean。作用域在 Bean 作用域中定义。否则,只有在请求 bean 时才会创建它。创建 bean 可能会导致创建 bean 图,因为创建和分配 bean 的依赖项及其依赖项的依赖项(等等)。在首次创建受影响的 bean 时,请注意,这些依赖项之间的解析不匹配可能会延迟出现。
You can generally trust Spring to do the right thing. It detects configuration problems, such as references to non-existent beans and circular dependencies, at container load-time. Spring sets properties and resolves dependencies as late as possible, when the bean is actually created. This means that a Spring container that has loaded correctly can later generate an exception when you request an object if there is a problem creating that object or one of its dependencies — for example, the bean throws an exception as a result of a missing or invalid property. This potentially delayed visibility of some configuration issues is why ApplicationContext
implementations by default pre-instantiate singleton beans. At the cost of some upfront time and memory to create these beans before they are actually needed, you discover configuration issues when the ApplicationContext
is created, not later. You can still override this default behavior so that singleton beans initialize lazily, rather than being eagerly pre-instantiated.
If no circular dependencies exist, when one or more collaborating beans are being injected into a dependent bean, each collaborating bean is totally configured prior to being injected into the dependent bean. This means that, if bean A has a dependency on bean B, the Spring IoC container completely configures bean B prior to invoking the setter method on bean A. In other words, the bean is instantiated (if it is not a pre-instantiated singleton), its dependencies are set, and the relevant lifecycle methods (such as a configured init method or the InitializingBean callback method) are invoked.
通常可以相信 Spring 会做正确的事情。它在容器加载时检测配置问题,例如对不存在的 bean 和循环依赖项的引用。Spring 在实际创建 bean 时设置属性并尽可能晚地解析依赖项。这意味着,如果创建对象或其某个依赖项时出现问题,那么正确加载的 Spring 容器随后可以在请求对象时生成异常ーー例如,由于缺少或无效属性,bean 抛出异常。一些配置问题的可见性可能会延迟,这就是为什么默认情况下 ApplicationContext 实现会预先实例化单例 bean。在实际需要这些 bean 之前,需要花费一些前期时间和内存来创建它们,因此在创建 ApplicationContext 时(而不是以后)会发现配置问题。您仍然可以覆盖这个默认行为,以便单例 bean 以惰性方式初始化,而不是急切地预先实例化。
如果不存在循环依赖关系,当一个或多个合作 bean 被注入到依赖 bean 中时,每个合作 bean 在被注入到依赖 bean 之前都会被完全配置。这意味着,如果 bean a 对 bean b 有依赖关系,那么在调用 bean a 上的 setter 方法之前,Spring IoC 容器将完全配置 bean b。换句话说,bean 被实例化(如果它不是预实例化的单例) ,它的依赖关系被设置,相关的生命周期方法(如配置的 init 方法或 InitializingBean 回调方法)被调用。
6. Dependencies and Configuration in Detail
6.1 Straight Values (Primitives, Strings, and so on)
元素的 value 属性将属性或构造函数参数指定为人类可读的字符串表示形式。Spring 的转换服务用于将这些值从 String 转换为属性或参数的实际类型。下面的示例显示了正在设置的各种值:
1 | <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> |
下面的示例使用 p 名称空间进行更简洁的 XML 配置:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
还可以配置 java.util.Properties 实例,如下所示:
1 | <bean id="mappings" |
Spring 容器将 < value/> 元素内的文本转换为 java.util。属性实例,使用 javabean PropertyEditor 机制。这是一个很好的快捷方式,也是 Spring 团队确实喜欢使用嵌套的 < value/> 元素而不是 value 属性样式的少数几个地方之一。
6.2 The idref
element
Idref
元素只是一种防错的方法,用于将容器中另一个 bean 的 id (字符串值——而不是引用)传递给一个 < constructor-arg/> 或 < property/> 元素。下面的例子展示了如何使用它:
1 | <bean id="theTargetBean" class="..."/> |
前面的 bean 定义片段与下面的片段完全等效(在运行时) :
1 | <bean id="theTargetBean" class="..." /> |
第一种形式比第二种更可取,因为使用 idref 标记可以让容器在部署时验证所引用的命名 bean 是否确实存在。在第二个变体中,不对传递给客户端 bean 的 targetName 属性的值执行验证。只有在实际实例化客户端 bean 时才会发现输入错误(很可能会导致致命的结果)。如果客户端 bean 是一个原型 bean,那么只有在部署容器之后很长时间才能发现这个排版错误和由此产生的异常。
Idref 元素的本地属性在4.0 beans XSD 中不再受支持,因为它不再在常规 bean 引用上提供值。升级到4.0模式时,将现有的 idref 本地引用更改为 idref bean。
6.3 Collections
<list/>
、<set/>
、<map/>
和<props/>
元素分别设置Java集合类型list、set、map和properties的属性和参数。下面的例子展示了如何使用它们:
1 | <bean id="moreComplexObject" class="example.ComplexObject"> |
6.4 Strongly-typed collection
随着Java 5中泛型类型的引入,您可以使用强类型集合。也就是说,可以声明一个Collection类型,使其只能包含(例如)String元素。如果您使用Spring将强类型集合的依赖项注入到bean中,那么您可以利用Spring的类型转换支持,以便在将强类型集合实例的元素添加到集合之前将其转换为适当的类型。下面的Java类和bean定义展示了如何做到这一点:
1 | public class SomeClass { |
1 | <beans> |
当 something
bean的 accounts
属性准备注入时,关于强类型 Map<String, Float>
的元素类型的泛型信息通过反射可用。因此,Spring的类型转换基础设施将各种值元素识别为Float
类型,并将字符串值(9.99
、2.75
和3.99
)转换为实际的Float
类型。
6.5 XML Shortcut with the p-namespace
p名称空间允许使用bean元素的属性(而不是嵌套的
Spring支持具有名称空间的可扩展配置格式,名称空间基于XML Schema定义。本章中讨论的bean配置格式是在XML Schema文档中定义的。但是,p名称空间并没有在XSD文件中定义,它只存在于Spring的核心中。
下面的例子展示了两个XML片段(第一个使用标准XML格式,第二个使用p-命名空间),它们解析到相同的结果:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
该示例显示了bean定义中p-namespace
中的一个名为email
的属性。这告诉Spring包含一个属性声明。如前所述,p-namespace
没有模式定义,因此可以将属性的名称设置为属性名称。
下一个例子包含另外两个bean定义,它们都有对另一个bean的引用:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
这个示例不仅包含使用p-namespace
的属性值,而且还使用一种特殊的格式来声明属性引用。第一个bean定义使用<property name="spouse" ref="jane"/>
来创建从bean john
到bean jane
的引用,而第二个bean定义使用p:spouse-ref="jane"
作为属性来做完全相同的事情。在本例中,spouse
是属性名,而-ref
部分表明这不是一个直接的值,而是对另一个bean的引用。
6.6 XML Shortcut with the c-namespace
与带有p-namespace
的XML Shortcut类似,在Spring 3.1中引入的c-namespace
允许内联属性来配置构造函数参数,而不是嵌套constructor-arg
元素。
下面的例子使用了c: namespace
来做和from基于构造函数的依赖注入一样的事情:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
c:
命名空间使用与p:
one相同的约定(后面的-ref表示bean引用),通过它们的名称设置构造函数参数。类似地,它需要在XML文件中声明,即使它没有在XSD模式中定义(它存在于Spring核心中)。
对于构造函数参数名不可用的罕见情况(通常是在没有调试信息的情况下编译字节码),你可以使用回退到参数索引,如下所示:
1 | <!-- c-namespace index declaration --> |
由于XML语法的原因,索引表示法要求前面有_,因为XML属性名称不能以数字开头(尽管有些ide允许)。对于
元素也可以使用相应的索引表记法,但并不常用,因为这里的声明顺序通常就足够了。
7. Method Injection
在大多数应用程序场景中,容器中的大多数bean都是单例的。当一个单例bean需要与另一个单例bean协作,或者一个非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖关系。当bean生命周期不同时,问题就出现了。假设单例bean A需要使用非单例(原型)bean B,也许是在对A的每个方法调用中。容器只创建一次单例bean A,因此只得到一次设置属性的机会。容器不能在每次需要bean B的新实例时为bean A提供新实例。
一种解决方案是放弃某些控制倒置。你可以通过实现ApplicationContextAware接口让bean A知道容器,并且每次bean A需要它时,通过getBean(“B”)调用容器来请求(典型的新)bean B实例。下面的例子展示了这种方法:
1 | // a class that uses a stateful Command-style class to perform some processing |
8. Request, Session, Application, and WebSocket Scopes
8.1 Request scope
Spring容器通过为每个HTTP请求使用LoginAction bean定义来创建LoginAction bean的新实例。也就是说,loginAction bean的作用域为HTTP请求级别。您可以随心所欲地更改创建的实例的内部状态,因为从相同的loginAction bean定义创建的其他实例不会在状态中看到这些更改。它们是针对个人要求的。当请求完成处理时,将范围限定在请求的bean丢弃。
在使用注释驱动的组件或Java配置时,可以使用@RequestScope注释将组件分配给请求范围。下面的例子展示了如何做到这一点:
1 | @RequestScope |
8.2 Session Scope
Spring容器通过在单个HTTP会话的生命周期中使用UserPreferences
bean定义来创建UserPreferences
bean的新实例。换句话说,userPreferences
bean有效地限定在HTTP Session
级别。与请求范围内bean一样,你可以改变内部状态的实例创建尽可能多的你想要的,知道其他HTTP会话实例也使用相同的实例创建userPreferences
bean定义看不到这些变化状态,因为他们是特定于一个单独的HTTP会话。当HTTP会话最终被丢弃时,限定在该特定HTTP会话范围内的bean也被丢弃。
在使用注释驱动的组件或Java配置时,可以使用@SessionScope
注释将组件分配给会话作用域。
1 | @SessionScope |
8.3 Application Scope
Spring容器通过对整个web应用程序使用一次AppPreferences bean
定义来创建一个AppPreferences bean
的新实例。也就是说,appPreferences bean
的作用域在ServletContext
级别,并存储为一个常规ServletContext
属性。这有点类似于弹簧单例bean,但在两个重要方面不同:它是一个单例每ServletContext
不是每Spring ApplicationContext
(可能有几个在任何给定的web应用程序),它实际上是暴露,因此可见ServletContext
属性。
在使用注释驱动的组件或Java配置时,可以使用@ApplicationScope
注释将组件分配给应用程序范围。下面的例子展示了如何做到这一点:
1 | @ApplicationScope |
8.4 WebSocket Scope
WebSocket作用域与WebSocket会话的生命周期相关,适用于WebSocket上的STOMP应用程序,更多细节请参阅WebSocket作用域。
9. Customizing the Nature of a Bean(定制Bean的性质)
Spring框架提供了许多接口,您可以使用这些接口来定制bean的性质。本节将它们分组如下:
9.1 Lifecycle Callbacks
要与容器对bean生命周期的管理交互,你可以实现Spring InitializingBean和DisposableBean接口。容器对前者调用afterPropertiesSet(),对后者调用destroy(),让bean在初始化和销毁bean时执行某些操作。
JSR-250
@PostConstruct
和@PreDestroy
注释通常被认为是现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean没有耦合到特定于spring的接口。详细信息请参见使用Using@PostConstruct
and@PreDestroy
。如果你不想使用JSR-250注释,但是你仍然想要移除耦合,考虑
init-method
和destroy-method
bean定义元数据。
在内部,Spring框架使用BeanPostProcessor
实现来处理它可以找到并调用适当方法的任何回调接口。如果您需要定制特性或Spring默认不提供的其他生命周期行为,您可以自己实现BeanPostProcessor
。有关更多信息,请参见Container Extension Points。
除了初始化和销毁回调,spring管理的对象还可以实现Lifecycle
接口,这样这些对象就可以参与启动和关闭过程,这是由容器自己的生命周期驱动的。
本节介绍生命周期回调接口。
9.1.1 Initialization Callbacks
initializingbean
接口允许bean在容器设置完bean上所有必要的属性后执行初始化工作。InitializingBean
接口指定了一个单一的方法:
1 | void afterPropertiesSet() throws Exception; |
我们建议您不要使用InitializingBean
接口,因为它不必要地将代码耦合到Spring。另外,我们建议使用@PostConstruct
注释或指定POJO初始化方法。在基于xml的配置元数据的情况下,可以使用init-method
属性指定具有空无参数签名的方法的名称。在Java配置中,您可以使用@Bean
的initMethod
属性。请参见接收生命周期回调。考虑以下例子:
1 | <bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/> |
1 | public class ExampleBean { |
上面的例子和下面的例子(包含两个清单)效果几乎完全相同:
1 | <bean id="exampleInitBean" class="examples.AnotherExampleBean"/> |
1 | public class AnotherExampleBean implements InitializingBean { |
然而,前面两个示例中的第一个并没有将代码与Spring耦合起来。(后一个继承了InitializingBean
)
9.1.2 Destruction Callbacks
实现这个接口( org.springframework.beans.factory.DisposableBean
)可以让bean在容器销毁时获得回调函数。DisposableBean接口指定了一个单独的方法:
1 | void destroy() throws Exception; |
我们建议您不要使用DisposableBean回调接口,因为它不必要地将代码与Spring耦合起来。另外,我们建议使用@PreDestroy注释或指定bean定义支持的泛型方法。使用基于xml的配置元数据,可以在
1 | <bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/> |
1 | public class ExampleBean { |
前面的定义与下面的定义具有几乎完全相同的效果:
1 | <bean id="exampleInitBean" class="examples.AnotherExampleBean"/> |
1 | public class AnotherExampleBean implements DisposableBean { |
然而,前面两个定义中的第一个并没有将代码与Spring耦合起来。
……此外本节还有一些没有摘录
9.1.3 Combining Lifecycle Mechanisms(结合生命周期机制)
在Spring 2.5中,你有三个控制bean生命周期行为的选项:
InitializingBean
和DisposableBean
回调接口- 自定义
init()
和destroy()
方法 @PostConstruct
和@PreDestroy
注释。您可以组合这些机制来控制给定的bean。
9.1.4 Startup and Shutdown Callbacks
Lifecycle
接口为任何有自己生命周期需求的对象定义了基本方法(比如启动和停止一些后台进程):
1 | public interface Lifecycle { |
任何spring管理的对象都可以实现Lifecycle
接口。然后,当ApplicationContext
本身接收到启动和停止信号时(例如,对于运行时的停止/重启场景),它将这些调用级联到在该上下文中定义的所有Lifecycle
实现。它通过委托给LifecycleProcessor
来实现,如下所示:
1 | public interface LifecycleProcessor extends Lifecycle { |
9.1.5 Shutting Down the Spring IoC Container Gracefully in Non-Web Applications
本节讲述了在非 web 应用程序中优雅地关闭 Spring IoC 容器,我个人认为这里暂时用的不多,以后有需要再看。
10 . Container Extension Points(扩展容器)
10.1 Customizing Beans by Using a BeanPostProcessor
BeanPostProcessor
接口定义了回调方法,您可以实现这些方法来提供您自己的(或覆盖容器的默认值)实例化逻辑、依赖关系解析逻辑,等等。如果您想在Spring容器完成实例化、配置和初始化bean之后实现一些自定义逻辑,您可以插入一个或多个自定义BeanPostProcessor
实现。
您可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor
实例运行的顺序。只有当BeanPostProcessor
实现了Ordered
接口时,才能设置此属性。如果编写自己的BeanPostProcessor
,还应该考虑实现Ordered
接口。要了解更多细节,请参阅BeanPostProcessor
和Ordered
接口的javadoc。请参见有关BeanPostProcessor
实例的编程式注册的说明。
10.2 Example: Hello World, BeanPostProcessor
-style
下面的例子展示了如何在ApplicationContext
中编写、注册和使用BeanPostProcessor
实例。
第一个示例演示了基本用法。这个例子展示了一个自定义的BeanPostProcessor实现,它在容器创建每个bean时调用它的toString()方法,并将结果字符串打印到系统控制台。
下面的清单显示了自定义的BeanPostProcessor实现类定义:
1 | package scripting; |
下面的bean元素使用了InstantiationTracingBeanPostProcessor
:
1 | <?xml version="1.0" encoding="UTF-8"?> |
注意InstantiationTracingBeanPostProcesso
r是如何定义的。它甚至没有名称,而且,因为它是一个bean,所以可以像其他任何bean一样进行依赖注入。(前面的配置还定义了一个由Groovy脚本支持的bean。Spring的动态语言支持将在“动态语言支持”一章中详细介绍。)
以下Java应用程序运行上述代码和配置:
1 | import org.springframework.context.ApplicationContext; |
上述应用程序的输出如下所示:
1 | Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961 |
10.3 Example: The Class Name Substitution PropertySourcesPlaceholderConfigurer
您可以使用propertysourcesconfigururer
来通过使用标准Java Properties格式将bean定义中的属性值外部化到单独的文件中。这样一来,部署应用程序的人员就可以定制特定于环境的属性,如数据库url和密码,而无需修改主XML定义文件或容器文件,从而避免了复杂性或风险。
参考以下基于xml的配置元数据片段,其中定义了一个具有占位符值的数据源:
1 | <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"> |
该示例显示了从外部properties文件配置的属性。在运行时,propertysourcesconfigururer
被应用于元数据,用来替换数据源的一些属性。要替换的值被指定为${property-name}
形式的占位符,它遵循Ant、log4j和JSP EL风格。
实际值来自另一个标准Java Properties
格式的文件:
1 | jdbc.driverClassName=org.hsqldb.jdbcDriver |
11. Annotation-based Container Configuration
注解是否比XML更适合配置Spring?
基于注释的配置的引入提出了这样一个问题:这种方法是否比XML“更好”。简短的回答是“视情况而定”。长一点的答案是,每种方法都有其优点和缺点,通常,这取决于开发人员决定哪种策略更适合他们。由于注解的定义方式,注解在其声明中提供了大量的上下文,从而使配置更短、更简洁。但是,XML擅长在不修改源代码或重新编译它们的情况下连接组件。一些开发人员更喜欢将连接连接到源代码附近,而另一些开发人员则认为,带注释的类不再是pojo,而且,配置变得分散,更难控制。
无论选择什么,Spring都可以容纳这两种风格,甚至可以将它们混合在一起。值得指出的是,通过它的JavaConfig选项,Spring允许以一种非侵入性的方式使用注释,而不涉及目标组件源代码。
一如既往,您可以将后处理器注册为单独的 bean 定义,但也可以通过在基于 xml 的 Spring 配置中包含以下标记来隐式注册它们(注意包含上下文名称空间) :
1 | <?xml version="1.0" encoding="UTF-8"?> |
<context:annotation-config/>
元素隐式地注册了以下的后处理器:
ConfigurationClassPostProcessor
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
EventListenerMethodProcessor
<仅在定义注解的应用程序上下文中查找bean上的注解。这意味着,如果你把
<context:annotation-config/>
放在一个DispatcherServlet
的WebApplicationContext
中,它只检查你的控制器中的@Autowired
bean,而不是你的服务。更多信息请参见DispatcherServlet
。
注释的相关讲解参考Spring官方文档
11.1 @Resource
和@Autowired
的区别
参考:https://blog.csdn.net/weixin_40906484/article/details/113937179
11.2 基于自定义注解的依赖注入
您可以创建自己的自定义限定符注释。要做到这一点,请定义一个注释,并在定义中提供@Qualifier
注释,如下所示:
1 | @Target({ElementType.FIELD, ElementType.PARAMETER}) |
然后你可以在自动连接的字段和参数上提供自定义限定符,如下所示:
1 | public class MovieRecommender { |
接下来,您可以为候选bean定义提供信息。您可以将<qualifier/>
标记添加为<bean/>
标记的子元素,然后指定类型和值,以匹配您的自定义限定符注释。该类型与注释的完全限定类名相匹配。另外,如果不存在名称冲突的风险,为了方便起见,可以使用简短的类名。下面的例子演示了这两种方法:
1 | <?xml version="1.0" encoding="UTF-8"?> |
还可以定义自定义限定符注释,在简单的value属性之外或之外接受命名属性。如果在一个要自动连接的字段或参数上指定了多个属性值,那么bean定义必须匹配所有这些属性值,才能被视为自动连接候选属性。例如,参考以下注释定义:
1 | @Target({ElementType.FIELD, ElementType.PARAMETER}) |
在这种情况下,Format
是一个enum,定义如下:
1 | public enum Format { |
要自动连接的字段用自定义限定符标注,并包含两个属性的值:genre
和format
,如下所示:
1 | public class MovieRecommender { |
最后,bean定义应该包含匹配的限定符值。这个例子还演示了您可以使用bean的元属性而不是<qualifier/>
元素。如果可用,<qualifier/>
元素及其属性优先,但如果没有这样的限定符,自动组合机制将返回到<meta/>
标记中提供的值,就像下面示例中的最后两个bean定义一样:
1 | <?xml version="1.0" encoding="UTF-8"?> |
12. AOP Concepts
相关术语:
1.通知(Advice)
就是你想要的功能,也就是上面说的 安全,事物,日志等。你给先定义好把,然后在想用的地方用一下。
2.连接点(JoinPoint)
这个更好解释了,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。
3.切入点(Pointcut)
上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
4.切面(Aspect)
切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
5.引入(introduction)
允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗
6.目标(target)
引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
7.代理(proxy)
怎么实现整套aop机制的,都是通过代理,这个一会给细说。
8.织入(weaving)
把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时,为什么是运行时,后面解释。
关键就是:切点定义了哪些连接点会得到通知