Spring详解

Spring基础快速入门回顾 一、简介 1.1 简介 Spring使创建Java企业应用程序变得容易。它提供了你在企业环境中拥抱Java语言所需的一切,支持Groovy和Kotlin作为JVM上的替代语言,并根据应用的需要灵活地创建多种架构。从Spring Framework 6.0开始,Sprin

Spring基础快速入门回顾

一、简介

1.1 简介

Spring使创建Java企业应用程序变得容易。它提供了你在企业环境中拥抱Java语言所需的一切,支持Groovy和Kotlin作为JVM上的替代语言,并根据应用的需要灵活地创建多种架构。从Spring Framework 6.0开始,Spring需要Java 17+。

1.2 优点

  1. Spring是一个开源免费的框架 , 容器 .
  2. Spring是一个轻量级的框架 , 非侵入式的 .
  3. 控制反转 IoC , 面向切面 Aop
  4. 对事物的支持 , 对框架的支持
    Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。

1.3 组成

image-qwhr.png
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式
image-wveg.png
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  • 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

1.4 经典问题

1.4.1 什么是Spring框架,Spring框架有哪些主要模块

Spring框架是一个为Java应用程序开发提供综合、广泛的基础性支持的Java平台。Spring帮助开发者解决了开发中基础性的问题,使得开发人员可以专注于应用程序的开发。Spring框架本身也是按照设计模式精心打造的,这使得我们可以在开发环境中安心地集成Spring框架,不必担心Spring是如何在后台工作的。主要模块内容介绍可以参考之前章节的介绍。

1.4.2 使用Spring框架能带来哪些好处

(1)Dependency Injection(DI)使得构造器和JavaBean properties文件中的依赖关系一目了然。
(2)与EJB容器相比较,IoC容器更加趋向于轻量级。这样一来使用IoC容器在有限的内存和CPU资源的情况下进行应用程序的开发和发布就变得十分有利。
(3)Spring并没有闭门造车,Spring利用了已有的技术,比如ORM框架、logging框架、J2EE、Quartz和JDK Timer,以及其他视图技术。
(4)Spring框架是按照模块的形式来组织的。由包和类的编号就可以看出其所属的模块,开发者只需选用需要的模块即可。
(5)要测试一个用Spring开发的应用程序十分简单,因为测试相关的环境代码都已经囊括在框架中了。更加简单的是,利用JavaBean形式的POJO类,可以很方便地利用依赖注入来写入测试数据。
(6)Spring的Web框架也是一个精心设计的Web MVC框架,为开发者在Web框架的选择上提供了一个除主流框架(比如Struts)和过度设计的、不流行Web框架以外的选择。
(7)Spring提供了一个便捷的事务管理接口,适用于小型的本地事务处理(比如在单DB的环境下)和复杂的共同事务处理(比如利用JTA的复杂DB环境)。

1.4.3 Spring Bean作用域的区别是什么

Spring容器中的Bean可以分为5个作用域。所有作用域的名称都是自说明的,但是为了避免混淆,还是让我们来解释一下。
(1)singleton:这种Bean作用域是默认的,这种作用域确保不管接收到多少个请求,每个容器中只有一个Bean实例,单例模式由Bean Factory自身来维护。
(2)prototype:prototype作用域与singleton作用域相反,为每一个Bean请求提供一个实例。
(3)request:在请求Bean作用域内为每一个来自客户端的网络请求创建一个实例,在请求完成以后,Bean会失效并被垃圾回收器回收。
(4)Session:与request作用域类似,确保每个Session中有一个Bean实例,在Session过期后,Bean会随之失效。
(5)global-session:global-session和Portlet应用相关。当应用部署在Portlet容器中时,它包含很多Portlet。如果想让所有的Portlet共用全局存储变量,那么这个全局存储变量需要存储在global-session中。全局作用域与Servlet中的Session作用域效果相同。

1.4.4 什么是控制反转(IoC),什么是依赖注入

(1)控制反转是应用于软件工程领域的,在运行时被装配器对象用来绑定耦合对象的一种编程技巧,对象之间的耦合关系在编译时通常是未知的。在传统的编程方式中,业务逻辑的流程是由应用程序中早已被设定好关联关系的对象来决定的。在使用控制反转的情况下,业务逻辑的流程是由对象关系图来决定的,该对象关系图由装配器负责实例化,这种实现方式还可以将对象之间的关联关系的定义抽象化。绑定的过程是通过“依赖注入”实现的。
(2)控制反转是一种以给予应用程序中目标组件更多控制为目的设计范式,并在实际工作中起到了有效的作用。
(3)依赖注入是在编译阶段尚未知所需的功能是来自哪个的类的情况下,将其他对象所依赖的功能对象实例化的模式。这就需要一种机制来激活相应的组件以提供特定的功能,所以依赖注入是控制反转的基础。否则如果在组件不受框架控制的情况下,框架又怎么知道要创建哪个组件呢?

二、核心技术

2.1 IoC 容器

控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法。简单来说,就是原本 Bean 与 Bean 之间的这种互相调用,变成了由 IoC 容器去统一调配。如果没使用 IoC 容器统一管理业务 Bean,你的应用在部署、修改、迭代的时候,业务 Bean 是会侵入代码实现并互相调用的。
image-nouk.png
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

2.2 Bean概览

2.2.1 Bean 命名

  • 每个Bean都有一个或多个标识符(identifier)。这些标识符在承载Bean的容器中必须是唯一的。一个Bean通常只有一个标识符。然而,如果它需要一个以上的标识符,多余的标识符可以被视为别名。
  • 在基于XML的配置元数据中,你可以使用 id 属性、name 属性或两者来指定Bean标识符。id 属性允许你精确地指定一个 id。传统上,这些名字是字母数字('myBean'、'someService’等),但它们也可以包含特殊字符。如果你想为Bean引入其他别名,你也可以在 name 属性中指定它们,用逗号(,)、分号(;)或空格分隔
  • 如果你不明确地提供 name 或 id,容器将为该 Bean 生成一个唯一的名称。然而,如果你想通过使用 ref 元素或服务定位器风格的查找来引用该 bean 的名称,你必须提供一个名称。
  • 单独指定别名标签可以使用 <alias/> 元素来实现这一点,
//一个名为 fromName 的bean(在同一个容器中)在使用这个别名定义后,也可以被称为 toName
<alias name="fromName" alias="toName"/>

子系统A的配置元数据可以引用一个名为 subsystemA-dataSource 的数据源,子系统B的配置元数据可以引用一个名为 subsystemB-dataSource 的数据源,主应用程序以 myApp-dataSource 的名字来引用数据源。为了让这三个名字都指代同一个对象,你可以在配置元数据中添加以下别名定义

<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

2.2.2 实例化 Bean

bean 定义(definition)本质上是创建一个或多个对象的“配方”。容器在被要求时查看命名的Bean的“配方”,并使用该Bean定义所封装的配置元数据来创建(或获取)一个实际的对象。
如果你使用基于XML的配置元数据,你要在 <bean/> 元素的 class 属性中指定要实例化的对象的类型(或class)。

2.2.2.1 用构造函数进行实例化

当你用构造函数的方法创建一个Bean时,你可能需要一个默认(空)构造函数。
通过基于XML的配置元数据,你可以按以下方式指定你的bean类。

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
2.2.2.2 用静态工厂方法进行实例化

在定义一个用静态工厂方法创建的Bean时,使用 class 属性来指定包含 static 工厂方法的类,并使用名为 factory-method 的属性来指定工厂方法本身的名称。
下面的Bean定义规定,Bean将通过调用工厂方法来创建。该定义并没有指定返回对象的类型(class),而是指定了包含工厂方法的类。在这个例子中,createInstance() 方法必须是一个 static 方法。
Bean定义类:

public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

XML的配置:

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
2.2.2.3 用实例工厂方法进行实例化

与 通过静态工厂方法进行的实例化 类似,用实例工厂方法进行的实例化从容器中调用现有 bean 的非静态方法来创建一个新的 bean。要使用这种机制,请将 class 属性留空,并在 factory-bean 属性中指定当前(或父代或祖代)容器中的一个 Bean 的名称,该容器包含要被调用来创建对象的实例方法。用 factory-method 属性设置工厂方法本身的名称。
相应的类:

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

XML的配置:

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

2.3 依赖注入

2.3.1 基于构造器的依赖注入

假设 ThingTwo 和 ThingThree 类没有继承关系,就不存在潜在的歧义。因此,下面的配置可以正常工作,你不需要在 <constructor-arg/> 元素中明确指定构造函数参数的索引或类型
定义类:

package x.y;
public class ThingOne {
    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

XML:

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

当使用一个简单的类型时,比如 <value>true</value>,Spring不能确定值的类型,所以在没有帮助的情况下不能通过类型进行匹配。

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private final int years;

    // The Answer to Life, the Universe, and Everything
    private final String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

XML:

<!-- 根据参数类型设置 -->
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

<!-- 根据参数名字设置 -->
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
</bean>

<!-- 根据index参数下标设置 -->
<!-- index指构造方法 , 下标从0开始 -->
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

2.3.2 基于Setter的依赖注入

基于 Setter 的 DI 是通过容器在调用无参数的构造函数或无参数的 static 工厂方法来实例化你的 bean 之后调用 Setter 方法来实现的。
求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is .
Address类:

public class Address {
     private String address;
     get,set方法都有...
 }

Student类:

public class Student {
 
     private String name;
     private Address address;
     private String[] books;
     private List<String> hobbys;
     private Map<String,String> card;
     private Set<String> games;
     private String wife;
     private Properties info;

    get,set都有...
 }
  1. 常量注入
<bean id="student" class="com.kuang.pojo.Student">
     <property name="name" value="小明"/>
 </bean>
  1. Bean注入
 <bean id="addr" class="com.kuang.pojo.Address">
     <property name="address" value="重庆"/>
 </bean>
 
 <bean id="student" class="com.kuang.pojo.Student">
     <property name="name" value="小明"/>
     <property name="address" ref="addr"/>
 </bean>
  1. 数组注入
<bean id="student" class="com.kuang.pojo.Student">
     <property name="name" value="小明"/>
     <property name="address" ref="addr"/>
     <property name="books">
         <array>
             <value>西游记</value>
             <value>红楼梦</value>
             <value>水浒传</value>
         </array>
     </property>
 </bean>
  1. List注入
<property name="hobbys">
     <list>
         <value>听歌</value>
         <value>看电影</value>
         <value>爬山</value>
     </list>
 </property>
  1. Map注入
<property name="card">
     <map>
         <entry key="中国邮政" value="456456456465456"/>
         <entry key="建设" value="1456682255511"/>
     </map>
 </property>
  1. set注入
<property name="games">
     <set>
         <value>LOL</value>
         <value>BOB</value>
         <value>COC</value>
     </set>
 </property>
  1. Null注入
 <property name="wife"><null/></property>
  1. Properties注入
<property name="info">
     <props>
         <prop key="学号">20190604</prop>
         <prop key="性别">男</prop>
         <prop key="姓名">小明</prop>
     </props>
 </property>

2.3.3 依赖注入方式选择

  1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
  2. 可选依赖使用setter注入进行,灵活性强
  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入实际开发
  5. 过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
  6. 自己开发的模块推荐使用setter注入

2.4 依赖自动装配

Ioc容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称之为自动装配

2.4.1 自动装配方式

  1. 按类型(常用)
  2. 按名称
  3. 按构造方法
  4. 不启用构造方法

2.4.2 代码演示

  1. XML的配置元数据
    你可以用 <bean/> 元素的 autowire 属性来指定bean定义的自动注入模式。自动注入功能有四种模式。你可以为每个Bean指定自动注入,从而选择哪些要自动注入。下表描述了四种自动注入模式。
<bean id="" class="" autowire=""></bean>
模式解释
no(默认)没有自动注入。Bean引用必须由 ref 元素来定义。对于大型部署来说,不建议改变默认设置,因为明确指定协作者会带来更大的控制力和清晰度。在某种程度上,它记录了一个系统的结构。
byName通过属性名称进行自动注入。Spring寻找一个与需要自动注入的属性同名的Bean。例如,如果一个Bean定义被设置为按名称自动注入,并且它包含一个 master 属性(也就是说,它有一个 setMaster(..) 方法),Spring会寻找一个名为 master 的Bean定义并使用它来设置该属性。
byType如果容器中正好有一个 property 类型的 bean 存在,就可以自动注入该属性。如果存在一个以上的bean,就会抛出一个致命的 exception,这表明你不能对该bean使用 byType 自动注入。如果没有匹配的 bean,就不会发生任何事情(该属性没有被设置)。
constructor类似于 byType,但适用于构造函数参数。如果容器中没有一个构造函数参数类型的bean,就会产生一个致命的错误。
  1. 小结
    当一个bean节点带有 autowire byName的属性时。
  • 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
  • 去spring容器中寻找是否有此字符串名称id的对象。
  • 如果有,就取出注入;如果没有,就报空指针异常。

2.4.3 依赖自动装配特征

  1. 自动装配用于引用类型依赖注入,不能对简单类型进行操作
  2. 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
  3. 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
  4. 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效

2.5 Bean的作用域

Spring框架支持六个scope,在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .
几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

Scope说明
singleton(默认情况下)为每个Spring IoC容器将单个Bean定义的Scope扩大到单个对象实例。
prototype将单个Bean定义的Scope扩大到任何数量的对象实例。
request将单个Bean定义的Scope扩大到单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的Bean实例,该实例是在单个Bean定义的基础上创建的。只在Web感知的Spring ApplicationContext 的上下文中有效。
session将单个Bean定义的Scope扩大到一个HTTP Session 的生命周期。只在Web感知的Spring ApplicationContext 的上下文中有效。
application将单个Bean定义的 Scope 扩大到 ServletContext 的生命周期中。只在Web感知的Spring ApplicationContext 的上下文中有效。
websocket将单个Bean定义的 Scope 扩大到 WebSocket 的生命周期。仅在具有Web感知的 Spring ApplicationContext 的上下文中有效。

2.6 Bean的生命周期

生命周期:从创建到死亡的完整过程
bean生命周期:bean从创建到死亡的过程
bean生命周期的控制:在bean创建后得到销毁做一些事情

2.6.1 Spring Bean 的生命周期

我们知道对于普通的 Java 对象来说,它们的生命周期就是:

  • 实例化
  • 该对象不再被使用时通过垃圾回收机制进行回收

而对于 Spring Bean 的生命周期来说:

  • 实例化 Instantiation
  • 属性赋值 Populate
  • 初始化 Initialization
  • 销毁 Destruction
    实例化 -> 属性赋值 -> 初始化 -> 销毁

2.6.2 Bean 自身的方法

实例化 Instantiation --->属性赋值 Populate--->初始化 Initialization--->销毁 Destruction
init-method 和 destory-method 所指定的方法,需要类实现InitializingBean,DisposableBean接口

 <bean id="" class="" init-method="" destroy-method=""></bean>

2.6.3 容器级的方法(BeanPostProcessor 一系列接口)

主要是后处理器方法,比如下图的 InstantiationAwareBeanPostProcessor、BeanPostProcessor 接口方法。这些接口的实现类是独立于 Bean 的,并且会注册到 Spring 容器中。在 Spring 容器创建任何 Bean 的时候,这些后处理器都会发生作用。
c9b8551a65947d5b77500d3ad15a0c2.png

方法描述
BeanNameAware该接口只有一个方法 setBeanName(String name),用来获取 bean 的 id 或者 name。
BeanFactoryAware该接口只有一个方法 setBeanFactory(BeanFactory beanFactory),用来获取当前环境中的 BeanFactory。
ApplicationContextAware该接口只有一个方法 setApplicationContext(ApplicationContext applicationContext),用来获取当前环境中的 ApplicationContext。
InitializingBean该接口只有一个方法 afterPropertiesSet(),在属性注入完成后调用。
DisposableBean该接口只有一个方法 destroy(),在容器销毁的时候调用,在用户指定的 destroy-method 之前调用。
BeanPostProcessor 该接口有两个方法postProcessBeforeInitialization(Object bean, String beanName):在初始化之前调用此方法
postProcessAfterInitialization(Object bean, String beanName):在初始化之后调用此方法
通过方法签名我们可以知道,我们可以通过 beanName 来筛选出我们需要进行个性化定制的 bean。
InstantiationAwareBeanPostProcessor 该类是 BeanPostProcessor 的子接口 常用的有如下三个方法:postProcessBeforeInstantiation(Class beanClass, String beanName):在bean实例化之前调用
postProcessProperties(PropertyValues pvs, Object bean, String beanName):在bean实例化之后、设置属性前调用
postProcessAfterInstantiation(Class beanClass, String beanName):在bean实例化之后调用
LICENSED UNDER CC BY-NC-SA 4.0
Comment