Spring
约 5467 字大约 18 分钟
2025-04-10
本文转载自Spring中文文档
一、概述
- 低侵入/低耦合(降低组件之间的耦合度,实现软件各层之间的解耦。)
- 声明式 事务管理
- Spring框架中包括JavaEE 三层的每一层的解决方案 (一站式)
- 面向切面编程(AOP)
- Spring Boot:为基于Spring的应用开发提供了基础的项目构建;
- Spring Data:用于简化数据库访问,并支持云服务的开源框架。旨在统一和化对各类型持久化存储, 而不拘泥是关系型数据库还是NoSQL数据存储。
- Spring Batch:专门针对企业级系统中的日常批处理任务的轻量级框架,够帮助开发者方便地开发出强壮、高效的批处理应用程序。
- Spring Integration:为Spring编程模型提供了一个支持企业集成模(Enterprise Integration Patterns)的扩展,在应用程序中提供轻量级的消息制,可以通过声明式的适配器与外部系统进行集成。
- Spring Security:早期称为Acegi,基于Spring的企业应用系统提供声明式安全访问控制解决方案的安全框架;。
- Spring Roo:快速应用程序开发工具,可以在短时间内方便地构建应用程序。
- Spring Mobile:对Spring MVC的扩展,旨在简化移动Web应用的开发。
- Spring for Android:用于简化Android原生应用程序开发的Spring扩展。
- 等等。
二、Spring 架构图
核心容器
Core Container由
spring-core
,spring-beans
,spring-context
,spring-context-support
和spring-expression
(Spring 表达语言)模块组成。spring-core
和spring-beans
模块提供框架的基本部分,包括 IoC 和 依赖注入DI 功能。BeanFactory
是工厂模式的复杂实现。它消除了对编程单例的需求,并允许将依赖项的配置和规范与实际程序逻辑脱钩。spring-context
(Context) 模块构建在核心和 bean 类模块提供的坚实基础上。 Context 模块从 Beans 模块继承其功能,并增加了对国际化*(例如,使用资源束)*、事件传播、资源加载以及通过 Servlet 容器透明创建上下文的支持。上下文模块还支持 Java EE 功能,例如 EJB,JMX 和基本远程处理。ApplicationContext
接口是上下文模块的焦点。spring-context-support
支持将常见的第三方库集成到 Spring 应用程序上下文中以进行缓存(EhCache,Guava,JCache),邮件(JavaMail),调度(CommonJ,Quartz)和模板引擎(FreeMarker,JasperReports,Velocity)。spring-expression
模块提供了功能强大的Expression Language,用于在运行时查询和操作对象图。它是对 JSP 2.1 规范中指定的统一表达语言(统一 EL)的扩展。该语言支持设置和获取属性值,属性分配,方法调用,访问数组,集合和索引器,逻辑和算术运算符,命名变量以及按名称从 Spring 的 IoC 容器中检索对象的内容。它还支持列表投影和选择以及常见的列表聚合。AOP 和检测
spring-aop
模块提供了AOP Alliance 兼容的面向方面的编程实现,例如,您可以定义方法拦截器和切入点,以干净地解耦实现应分离功能的代码。spring-aspects
模块提供与 AspectJ 的集成。spring-instrument
模块提供了在某些应用程序服务器中使用的类检测支持和类加载器实现。spring-instrument-tomcat
模块包含 Spring 的 Tomcat 的检测代理。Spring Framework 4 包含一个
spring-messaging
模块,该模块具有来自Spring Integration项目的关键抽象,例如Message
,MessageChannel
,MessageHandler
等,这些模块可作为基于消息的应用程序的基础。该模块还包括一组 Comments,用于将消息 Map 到方法,类似于基于 Spring MVCComments 的编程模型。数据访问/集成
“数据访问/集成”层由 JDBC,ORM,OXM,JMS 和事务模块组成。
spring-jdbc
模块提供了JDBC-抽象层,从而无需进行繁琐的 JDBC 编码和解析数据库供应商特定的错误代码。spring-tx
模块支持程序性和声明性 TransactionManagement,以实现实现特殊接口的类以及所有 POJO(普通的 Java 老式对象)。spring-orm
模块为流行的object-relational mapping API 提供集成层,包括JPA,JDO和Hibernate。使用spring-orm
模块,您可以将所有这些 O/RMap 框架与 Spring 提供的所有其他功能结合使用,例如前面提到的简单的声明式事务管理功能。spring-oxm
模块提供了一个支持Object/XML mapping实现的抽象层,例如 JAXB,Castor,XMLBeans,JiBX 和 XStream。spring-jms
模块Java 消息服务包含用于生成和使用消息的功能。从 Spring Framework 4.1 开始,它提供了与spring-messaging
模块的集成。Web
Web 层由
spring-web
,spring-webmvc
,spring-websocket
和spring-webmvc-portlet
模块组成。spring-web
模块提供了面向 Web 的基本集成功能,例如 Multipart 文件上传功能以及使用 Servlet 侦听器和面向 Web 的应用程序上下文对 IoC 容器进行初始化。它还包含 HTTPClient 端和 Spring 远程支持的 Web 相关部分。spring-webmvc
模块 (也称为 Web-Servlet模块) 包含 Spring 的 model-view-controller(MVC) 和针对 Web 应用程序的 REST Web Services 实现。 Spring 的 MVC 框架在域模型代码和 Web 表单之间提供了清晰的分隔,并与 Spring 框架的所有其他功能集成在一起。spring-webmvc-portlet
模块 (也称为 Web-Portlet 模块) 提供了可在 Portlet 环境中使用的 MVC 实现,并镜像了基于 Servlet 的spring-webmvc
模块的功能。Test
spring-test
模块通过 JUnit 或 TestNG 支持 Spring 组件的 unit testing 和 integration testing。它提供了 SpringApplicationContext
的loading和那些上下文的caching。它还提供了mock objects,可用于隔离测试代码。
三、其他概述
什么是IOC?
**IoC 也称为“依赖注入”(DI)。**在此过程中,对象仅通过构造函数参数,工厂方法的参数或在对象实例从工厂方法构造或返回后设置的属性来定义其依赖关系,即与它们一起使用的其他对象。然后,容器在创建 Bean 时 “注入” 那些依赖项。此过程从根本上来说是相反的,因此名称为 控制反转(IoC) ,它是通过使用类的直接构造或 Service Locator模式之类的机制来控制其依赖项的实例化或位置的。
什么是BeanFactory ?和Bean有什么关系?
BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象。
ApplicationContext 是
BeanFactory
的子接口。它使与 Spring 的 AOP 功能的集成更加容易。消息资源处理(用于国际化),事件发布;以及特定于应用程序层的上下文,例如用于 Web 应用程序的 WebApplicationContext。简而言之,BeanFactory 提供了配置框架和基本功能,而ApplicationContext添加了更多企业特定的功能, ApplicationContext 是
BeanFactory
的完整超集。在 Spring 中,Bean 是指构成应用程序主干并由 Spring IoC 容器实例化、组装和以其他方式管理的对象。否则,bean 仅仅是应用程序中许多对象之一。 Bean 及其之间的“依赖关系”反映在容器使用的“配置元数据”中。
容器!容器!到底什么是容器?
接口
org.springframework.context.ApplicationContext
就代表 Spring IoC 容器,并负责实例化,配置和组装上述 bean。容器通过读取配置元数据来获取有关要实例化,配置和组装哪些对象的指令。配置元数据以 XML,Java 注解或 Java 代码表示。能表达组成应用程序的对象以及这些对象之间的丰富相互依赖关系。尽管 XML 是定义配置元数据的传统格式,但是您可以通过提供少量 XML 配置来声明性地支持这些附加元数据格式,从而指示容器使用 JavaComments 或代码作为元数据格式。
四、配置元数据
如上图所示,Spring IoC 容器使用一种形式的配置元数据;此配置元数据表示告诉 Spring 容器如何实例化、配置和组装应用程序中的对象。传统上,配置元数据以简单直观的 XML 格式提供。基于 XML 的元数据不是唯一允许的配置元数据形式。 Spring IoC 容器本身“完全”与实际写入此配置元数据的格式脱钩。如今,许多开发人员为他们的 Spring 应用程序选择 。
重要
:从 Spring 3.0 开始,可以使用 Java 而不是 XML 文件来定义应用程序类外部的 bean, 如@Configuration
,@Bean
,@Import
和@DependsOn
等注解。使用xml配置c3p0连接池元数据示例:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///spring01"></property>
<property name="user" value="root"></property>
<property name="password" value="admin"></property>
</bean>
</beans>
id
是这个bean唯一的名字,目的是能够从spring容器中得到这个bean。id 属性要求在同一个配置文件中必须是唯一的 。
class
:spring管理的bean的类型并使用完全限定的类名。
<property/>
为bean对象属性赋值,底层调用的set方法。
来给bean定义一个名字和别名
如果我们的bean需要其他的名字,可以通过name属性来为一个bean配置更多的名字,多个名字用逗号、分号或空格来分隔。在实际定义 bean 的地方指定所有别名并不总是足够的,也可以使用 <alias/>
在别处定义的 bean 引入别名。在代码中,使用beanFactory.getAlias 来得到一个bean的所有名字。
<bean id = "somebean" name="somebean1,somebean2" class="com.name.Somebean"/>
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
scope
可以用来指定该bean的作用域。
singleton (单例,默认值):只要配置文件一加载,就创建实例,放入map< id,创建的对象> 中 ,只创建一次对象。当每个人来getBean()时,使用的是同一个对象。
prototype 只要配置文件一加载,不创建实例。当第一个人来getBean()时,就创建一次放入容器中map<id , 创建的对象> 之后的getBean是clone新的对象。
此外作用域还有request、session、gloalSession、application和WebSocket。
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
ref
:可以引用另一个 bean 定义的名称。 id
和ref
元素之间的这种联系表达了协作对象之间的依赖性。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
</bean>
<bean id="accountDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao"></bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao"></bean>
</beans>
在Spring中,可以把配置文件分散到各个模块中,然后在总的配置文件中,使用 <import/>
元素引入。Spring提供两种前缀标记来辅助查找配置文件。示例如下:
<!-- classpath:代表从classpath开始寻找后面的资源文件 -->
<!-- file:代表使用文件系统的方式寻找后面的文件(文件完整路径)-->
<import resource="classpath:cn/itcast/spring/name/name.xml"/>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
所有位置路径都相对于执行导入的定义文件,因此services.xml
必须与执行导入的文件位于同一目录或 Classpath 位置,而messageSource.xml
和themeSource.xml
必须位于导入文件位置子目录的resources
位置。如您所见,前斜杠会被忽略,但是鉴于这些路径是相对的,最好不要使用任何斜杠。
提示
可以但不建议使用相对的../
路径引用父目录中的文件。这样做会创建对当前应用程序外部文件的依赖关系。特别是,不建议在 Classpath: URL
(例如,“Classpath:../ services.xml”)中使用此引用,在这些 URL 中,运行时解析过程会选择“最近”Classpath 根,然后查看其父目录。Classpath 配置的更改可能导致选择其他错误的目录。
您始终可以使用标准资源位置而不是相对路径:例如,"file:C:/config/services.xml"或 "classpath:/config/services.xml"。
五、实例化 bean
注
通常,在容器本身通过反射调用其构造函数直接创建 Bean 的情况下,指定要构造的 Bean 类,这在某种程度上等效于使用new
运算符的 Java 代码。
要指定包含将被调用以创建对象的静态工厂方法的实际类,在不太常见的情况下,容器将在类上调用静态工厂方法来创建 Bean。从静态工厂方法的调用返回的对象类型可以是同一类,也可以是完全不同的另一类。
使用构造函数实例化
当通过构造方法创建一个 bean 时,所有普通类都可以被 Spring 使用并与之兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定 bean 类就足够了,但是需要一个默认(空)构造函数,并且需要有工厂类(Spring已内置)。
使用静态工厂方法实例化
定义使用静态工厂方法创建的 bean 时,可以使用
class
属性指定包含静态工厂方法的类,并使用名为factory-method
的属性来指定工厂方法本身的名称。以下 bean 定义指定将通过调用工厂方法来创建 bean。该定义不指定返回对象的类型(类),而仅指定包含工厂方法的类。<bean id="user" class="com.mySpring.BeanFactory" factory-method="createUser"/>
工厂方法类如下所示:
public class BeanFactory{ //静态工厂 public static User createUser(){ return new User(); } }
使用实例工厂方法实例化
与通过静态工厂方法实例化类似,使用实例工厂方法实例化从容器中调用现有 bean 的非静态方法以创建新 bean。因为方法不是静态的,需要创建出对象才能进行调用,所以先要有工厂对象。
注
官方文档中是这么解释的:要使用此机制,请将
class
属性留空,并在factory-bean
属性中,在当前(或父级/祖先)容器中指定包含要创建该对象的实例方法的 Bean 的名称。使用factory-method
属性设置工厂方法本身的名称。以下为使用实例:
<bean id="beanFactoty" class="com.mySpring.BeanFactory"/> <bean id="user" factory-bean="beanFactory" factory-method="CreateUser"></bean>
对应的工厂方法如下:
public class BeanFactory{ //实例工厂 public User createUser(){ return new User(); } }
六、Dependencies
基于构造函数的依赖项注入
基于容器的 DI 是通过容器调用具有多个参数(每个参数代表一个依赖项)的构造函数来完成的。调用带有特定参数的
static
工厂方法来构造 Bean 几乎是等效的。这就规定了类要有参构造器(还得有无参的要不newInstence()不能执行)。<bean id="aaaa" class="xxx.yyyy.Aaaa"> <!-- 简单类型:--> <construtor-arg name="属性名" value="值" ></constructor-arg> <!-- 简单类型:使用type属性显式指定了构造函数参数的类型 --> <constructor-arg type="int" value="7500000"/> <!-- 简单类型:使用index属性可以显式指定构造函数参数的索引 索引从0开始--> <constructor-arg index="0" value="7500000"/> <!-- 自定义类型 --> <constructor-arg ref="cccc"/> </bean> <bean id="cccc" class="xxx.yyyy.Cccc"/>
当构造器有多个自定义类型时,配置的顺序就是为参数赋值的顺序。
public class Foo { public Foo(Bar bar, Baz baz) { // ... } }
假设
Bar
和Baz
类没有通过继承关联,则不存在潜在的歧义。<beans> <bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> </bean> <bean id="bar" class="x.y.Bar"/> <bean id="baz" class="x.y.Baz"/> </beans>
使用setter方法
基于 Setter 的DI是通过在调用无参数构造函数或无参数静态工厂方法实例化 bean 之后,在 bean 上调用 setter 方法来完成的。
当类中字段为String 、包装类型和基本类型:
<bean id="xxx" class="a.b.Xxx"> <property name="属性名" value="值"></property> </bean>
当类中字段为对象类型:
<bean id="xxx" class="a.b.Xxx"> <property name="属性名" ref="对应对象的bean的id "> </bean>
复杂类型(数组,集合,map):
<bean id="" class=""> <!-- String 数组 --> <property name="属性名" > <list> <value></value> </list> </property> <!-- list集合 --> <property name="属性名" > <list> <value></value> <ref bean="id名"/> </list> </property> <!-- map --> <property name="属性名" > <map> <entry key="" value="" ></entry> <entry key="" value-ref=""></entry> </map> </property> <!-- properties --> <property name="属性名" > <props> <prop key=" ">值</prop> <prop key=" ">值</prop> </props> </property> </bean>
使用p命名空间
需要在当前的配置中导入p名称空间。
xmlns:p="http://www.springframework.org/schema/p"
。示例如下:<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--底层还是调用set方法--> <bean id="" class="" p:属性名="属性值" p:属性名-ref="属性值"/> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql://localhost:3306/mydb" p:username="root" p:password="masterkaoli"/> </beans>
Constructor-based or setter-based DI?
可以混合使用基于构造函数的 DI 和基于 setter 的 DI,因此,将构造函数用于强制依赖项 和将 setter 方法或配置方法用于可选依赖项 是一个很好的经验法则。请注意,在 setter 方法上使用注解可用于使属性成为必需的依赖项。
Spring 团队通常提倡构造函数注入,因为它使人们能够将应用程序组件实现为不可变对象 并确保所需的依赖项不是
null
。此外,构造函数注入的组件始终以完全初始化的状态返回到 Client 端(调用)代码。附带说明一下,大量的构造函数参数是“不好的代码味道”,这意味着该类可能承担了太多的职责,应该对其进行重构以更好地解决关注点分离问题。Setter 注入主要应仅用于可以在类中分配合理的默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。 setter 注入的一个好处是,setter 方法使该类的对象在以后可以重新配置或重新注入。
使用最适合特定类的 DI 风格。有时,在处理没有源代码的第三方类时,将为您做出选择。例如,如果第三方类未公开任何 setter 方法,则构造函数注入可能是 DI 的唯一可用形式。
延迟初始化Bean
默认情况下,作为初始化过程的一部分,
ApplicationContext
实现会急于创建和配置所有singleton bean。通常,这种预初始化是可取的,因为与数小时甚至数天后相比,会立即发现配置或周围环境中的错误。如果此行为是“不希望的”,则可以通过将 bean 定义标记为延迟初始化来防止对 singleton bean 的预先实例化。延迟初始化的 bean 告诉 IoC 容器在首次请求时而不是在启动时创建一个 bean 实例。在 XML 中,此行为由
lazy-init
属性控制;例如:<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/> <bean name="not.lazy" class="com.foo.AnotherBean"/>
但是,当延迟初始化的 bean 是非延迟初始化的单例 bean 的依赖项时,
ApplicationContext
在启动时会创建延迟初始化的 bean,因为它必须满足单例的依赖关系。延迟初始化的 bean 被注入到其他未延迟初始化的单例 bean 中。自动装配对象
在每个 bean 的基础上,您可以从自动装配中排除一个 bean。使用 Spring 的 XML 格式,将元素的
autowire-candidate
属性设置为false
。但是autowire-candidate
属性旨在仅影响基于类型的自动装配。它不会影响名称的显式引用,即使指定的 bean 未标记为自动装配候选,名称也将得到解析。因此,如果名称匹配,按名称自动装配仍将注入 Bean。方法注入
在大多数应用场景中,容器中的大多数 bean 是singletons。当单例 Bean 需要与另一个单例 Bean 协作时,或者非单例 Bean 需要与另一个非单例 Bean 协作时,通常可以通过将一个 Bean 定义为另一个 Bean 的属性来处理依赖性。当 bean 的生命周期不同时会出现问题。假设单例 bean A 需要使用非单例(原型)bean B,也许在 A 的每个方法调用上都使用。容器只创建一次单例 bean A,因此只有一次机会来设置属性。每次需要一个容器时,容器都无法为 bean A 提供一个新的 bean B 实例。
一个解决方案是放弃某些控制反转。您可以通过实现
ApplicationContextAware
接口来使 bean A 知道容器,并在每次 bean A 需要它时对容器进行 getBean(“ B”)调用询问(通常是新的)bean B 实例。以下是此方法的示例:public class CommandManager implements ApplicationContextAware { private ApplicationContext applicationContext; public Object process(Map commandState) { Command command = createCommand(); command.setState(commandState); return command.execute(); } protected Command createCommand() { return this.applicationContext.getBean("command", Command.class); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }