0x00 环境配置
环境:
- IDEA >= 2022.1.4
- JDK 17
- Maven 3.8.6
- Spring 6.0.0
- JUnit 4.13.2
- Log4j2
-
新建模块 spring001 -> 高级设置 -> 组 ID
-
在 /spring001/src/main/java 分别新建软件包
- com.spring.dao:持久层
- com.spring.service:业务层,调用持久层
- com.spring.web:表示层,调用业务层
- com.spring.client:测试
-
层和层之间使用接口交互
-
dao
-
新建接口 UserDao.java
public interface UserDao { void deleteById(); }
-
新建实现包 impl
-
新建实现类 UserDaoImplForMySQL.java
public class UserDaoImplForMySQL implements UserDao { @Override public void deleteById() { System.out.println("Deleting..."); } }
-
-
-
service
-
新建接口 UserService.java
public interface UserService { void deleteUser(); }
-
新建实现包 impl
-
新建实现类 UserServiceImpl.java
public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImplForMySQL(); @Override public void deleteUser() { userDao.deleteById(); } }
-
-
-
web
-
新建类 UserAction.java
public class UserAction { private UserService userService = new UserServiceImpl(); public void deleteRequest() { userService.deleteUser(); } }
-
-
client
-
新建类 Test.java
public class Test { public static void main(String[] args) { UserAction userAction = new UserAction(); userAction.deleteRequest(); } }
-
-
-
上述项目在需求升级后需要大面积替换原代码,很不方便且容易出错,故使用 Spring 进行调整
0x01 框架主要思想
-
OCP
- OCP 是软件七大开发原则之开闭原则
- 对扩展开发,对修改关闭
- OCP 是七大开发原则中最核心、最基本的原则,核心为:扩展功能时未修改原代码
- OCP 是软件七大开发原则之开闭原则
-
DIP
- DIP 是软件七大开发原则之依赖倒置原则
- 下层依赖上层时符合该原则
- DIP 核心为面向接口编程
- DIP 是软件七大开发原则之依赖倒置原则
-
IoC
- IoC 是控制反转,是一种编程思想,是新型设计模式
- 在程序中不使用硬编码的方式来新建对象和维护对象关系
- IoC 用于解决当前程序设计既违反了 OCP 又违反 DIP 的问题
- IoC 是控制反转,是一种编程思想,是新型设计模式
-
Spring 框架
- Spring 是实现 IoC 思想的容器
- IoC 的实现方法很多,其中比较重要的是依赖注入(DI)
- DI 常见的方式
- set 注入
- 构造方法注入
0x02 框架概述
(1)八大模块
- Spring AOP:面向切面编程
- Spring Web:支持集成常见的 Web 框架
- Spring Web MVC:Spring 自有的 MVC 框架
- Spring Webflux:Spring 自有的 响应式 Web 框架
- Spring DAO:单独的支持 JDBC 操作的 API
- Spring ORM:集成常见的 ORM 框架,如 MyBatis 等
- Spring Context:国际化消息、事件传播、验证支持、企业服务、Velocity 和 FreeMarket2 集成
- Spring Core:控制反转
(2)特点
- 轻量、非侵入式
- 控制反转
- 面向切面
- 容器
- 框架
0x03 入门程序
(1)Spring 下载与引用
-
graph LR
A(Projects<br />Spring Framework)-->Github
-->B(Access to Binaries<br />Spring Framework Artifacts)
-->C(Spring Repositories<br />https://repo.spring.io)
-->Artifacts
-->D(plugins-release/<br />org/springframework/<br />spring/6.0.0-RC2)
-->E(General/URL to file)
-->spring-x.x.x-dist.zip
字符 说明 第一个数字 主版本,有可能进行大的架构调整,各大版本之间并不一定兼容 第二个数字 次版本,在主版本架构不变的前提下,增加了一些新的特性或变化 第三个数字 增量版本,bug修复,细节的完善 M 里程碑版本,测试版本,发布版本的前兆 RC 候选发布版本,稳定版本,并不一定会发布 RELEASE/NULL 发布版本,稳定版本,在项目中真正可用的版本 -
Maven 引用 Spring
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.2.2RC2</version> </dependency> </dependencies>
(2)创建项目
-
修改 pom.xml 文件内容
<!-- 修改打包方式--> <packaging>jar</packaging> <!-- 配置多个仓库--> <repositories> <repository> <id>repository.spring.milestone</id> <name>Spring Milestone Repository</name> <url>https://repo.spring.io/milestone</url> </repository> </repositories> <!-- 依赖项--> <dependencies> <!-- Spring Context 依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.4</version> </dependency> <!-- junit 单元测试依赖--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies>
-
添加 Spring 配置
-
在 src/main/resources 目录下新建 spring.xml 文件(名称可变)来配置 Spring
-
在该文件中配置 bean,从而使 Spring 可以帮助我们管理对象(实现 IoC 的作用)
-
bean 有两个重要属性:
id
和class
id
:bean 的唯一标识class
:必须填写类的全路径,如com.xxx.xxx.ClassName
- 带包名的类名,双击需要引用的类名,右键选择“复制引用”
-
如:
<bean id="userBean" class="com.spring6.bean.User" />
-
也可以引用 Java 自带的无参类,如:
<bean id="date" class="java.util.Date" />
-
(3)编写程序
-
获取对象
-
在 src/text/java 目录下新建软件包
-
在该软件包中新建 Java 类,并写入以下代码:
public class FirstSpringTest { @Test public void testFirstSpringCode() { ApplicationContext appContext = new ClassPathXmlApplicationContext("spring.xml"); Object userBean = appContext.getBean("userBean"); } }
-
第一步,获取 Spring 容器对象——
ApplicationContext
ApplicationContext
本质是接口,其中有一个实现类为ClassPathXmlApplicationContext
,专门从类路径当中加载 Spring 配置文件的一个 Spring 上下文对象BeanFactory
是ApplicationContext
接口的超级父接口,是 IoC 容器的顶级接口,反映了 Spring 的 IoC 容器底层实际上使用了工厂模式- XML 解析、工厂模式、反射机制
-
第二步,根据 bean 的 id 从 Spring 容器中获取这个对象,通过
getBean()
方法实现
-
-
-
默认情况下,Spring 会通过反射机制,调用类的无参构造方法来实例化对象,其构造方法如下:
Class example = Class.forName("com.example"); Object obj = example.newInstance();
-
获取多个 Spring 容器对象
ApplicationContext appContext = new ClassPathXmlApplicationContext("spring_1.xml", "spring_2.xml", "spring_3.xml");
-
如果需要访问子类特有属性和方法时
-
方法一:强转
import java.util.Date; public class FirstSpringTest { @Test public void testFirstSpringCode() { // <bean id="date" class="java.util.Date" /> ApplicationContext appContext = new ClassPathXmlApplicationContext("spring.xml"); Date date = (Date)appContext.getBean("date"); } }
-
方法二:
import java.util.Date; public class FirstSpringTest { @Test public void testFirstSpringCode() { // <bean id="date" class="java.util.Date" /> ApplicationContext appContext = new ClassPathXmlApplicationContext("spring.xml"); Date date = appContext.getBean("date", Date.class); } }
-
(4)启用 Log4j2 日志框架
-
环境配置
-
引入依赖
// pom.xml <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.19.0</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j2-impl</artifactId> <version>2.19.0</version> </dependency>
-
在 src/main/resources 目录下新建文件 log4j2.xml,并写入以下内容
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <loggers> <!-- level 指定日志级别,以下是从低到高的排序--> <!-- ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF--> <root level="DEBUG"> <appender-ref ref="spring6log" /> </root> </loggers> <appenders> <!-- 输出日志信息到控制台--> <console name="spring6log" target="SYSTEM_OUT"> <!-- 控制日志输出的格式--> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n" /> </console> </appenders> </configuration>
-
-
使用 log4j2 记录日志信息
-
在 test 中的类 FirstSpringTest.java 里写入以下代码:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class FirstSpringTest { @Test public void testFirstSpringCode() { Logger logger = LoggerFactory.getLogger(FirstSpringTest.class); logger.info("Info Message"); logger.debug("Debug Message"); logger.trace("Trace Message"); } }
- 第一步,创建日志记录器对象
- 第二步,记录日志并根据不同的级别来输出日志
-
0x04 IoC 的实现
(1)IoC 控制反转
- 控制反转是一种思想
- 控制反转的目的
- 降低程序耦合度
- 达到 OCP 原则
- 达到 DIP 原则
- 反转内容
- 将对象的创建权力交出去,由第三方容器负责
- 将对象和对象之间的关系维护权交出去,由第三方容器负责
- 实现方法
- 依赖注入 DI
(2)依赖注入
-
Spring 通过依赖注入完成对 Bean 对象的创建和其中属性赋值的管理
-
依赖注入解释
- 依赖:对象和对象之间的关联关系
- 注入:一种数据传递行为,使对象之间产生关系
-
依赖注入常见形式
-
set 注入
- 基于 set 方法实现的,底层会通过反射机制调用属性对应的 set 方法而后给属性赋值,此方法要求属性必须对外提供 set 方法
- 构造 set 方法时,方法名必须以 set 开始
public void setUserDao(UserDao userDao) { this.userDao = userDao; }
-
Spring 调用对应 set 方法前,需要在 spring.xml 中配置相应 Bean 的属性 property
- property 标签中包含的 name 属性以首字母小写的方式填写 set 方法名中 set 后的全部内容
- property 标签中包含的 type 属性填写形参的 Bean 的 id
<bean id="userDaoBean" class="com.spring6.dao.UserDao" /> <bean id="userServiceBean" class="com.spring6.service.UserService"> <property name="userDao" ref="userDaoBean" /> </bean>
-
构造注入
- 通过构造方法为属性赋值
public UserService(UserDao userDao) { this.userDao = userDao; }
-
在 spring.xml 中配置相应 Bean 的属性 constructor-arg
- constructor-arg 标签中包含的 index 属性指定构造方法的第 index+1 个参数
- constructor-arg 标签中包含的 ref 属性指定对应的 Bean 的 id
<bean id="userDaoBean" class="com.spring6.dao.UserDao" /> <bean id="userServiceBean" class="com.spring6.service.UserService"> <constructor-arg index="0" ref="userDaoBean" /> </bean>
-
或类似于 set 注入方法配置 spring.xml
<bean id="userDaoBean" class="com.spring6.dao.UserDao" /> <bean id="userServiceBean" class="com.spring6.service.UserService"> <constructor-arg name="userDao" ref="userDaoBean" /> </bean>
-
或根据类型注入
<bean id="userDaoBean" class="com.spring6.dao.UserDao" /> <bean id="userServiceBean" class="com.spring6.service.UserService"> <constructor-arg ref="userDaoBean" /> </bean>
-
(3)set 注入详解
public void setUserDao(UserDao userDao) { this.userDao = userDao; }
a. 注入外部 Bean
- Bean 定义到外面,在 property 标签中使用 ref 属性进行注入
<bean id="userDaoBean" class="com.spring6.dao.UserDao" />
<bean id="userServiceBean" class="com.spring6.service.UserService">
<property name="userDao" ref="userDaoBean" />
</bean>
b. 注入内部 Bean
- 在 Bean 标签中嵌套着 Bean 标签
<bean id="userServiceBean" class="com.spring6.service.UserService">
<property name="userDao">
<bean class="com.spring6.dao.UserDao" />
</property>
</bean>
c. 注入简单类型
-
可以通过 set 注入的方式给属性赋值
-
在 src/main/java 新建 com.spring6.bean.User 类
package com.spring6.bean; public class User { private String username; private String password; private int age; public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "username='" + username + '\'' + ", password='" + password + '\'' + ", age=" + age + '}'; } }
-
在 src/main/resources/spring.xml 中配置如下,其中重点在于 property 标签的 value 属性用于传值
<bean id="userBean" class="com.spring6.bean.User"> <property name="username" value="张三" /> <property name="password" value="123" /> <property name="age" value="20" /> </bean>
-
测试该注入代码如下
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); User user = applicationContext.getBean("userBean", User.class); System.out.println(user);
-
-
查看简单类型
- 在 IDEA 中双击 Shift 键,搜索 BeanUtils
- 在 BeanUtils.class 中按 Ctrl + F12 搜索 isSimpleValueType 即可查看 Spring 中的全部简单类型
d. 级联属性赋值
-
User.java
private String name; // 级联注入添加 private Group group; public void setName(String name) { this.name = name; } // 级联注入添加 // 对应 spring.xml 中的 group.name public Group getGroup() { return group; } // 级联注入添加 public void setGroup(Group group) { this.group = group; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", group=" + group + '}'; }
-
Group.java
private String name; public void setName(String name) { this.name = name; } @Override public String toString() { return "Group{" + "name='" + name + '\'' + '}'; }
-
Spring.xml
<bean id="userBean" class="com.spring6.bean.User"> <property name="name" value="张三" /> <property name="group" ref="groupBean" /> <property name="group.name" value="第一组" /> </bean> <bean id="groupBean" class="com.spring6.bean.Group" />
-
Test.java
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); User user = applicationContext.getBean("userBean", User.class); System.out.println(user);
-
级联注入重点
- 需要 get 方法
- 配置中先指定 ref 后再赋值 value
e. 注入数组
-
User.java
private String[] hobbies; public void setHobbies(String[] hobbies) { this.hobbies = hobbies; } @Override public String toString() { return "User{" + "hobbies=" + Arrays.toString(hobbies) + '}'; }
-
spring.xml
<bean id="userBean" class="com.spring6.bean.User"> <property name="hobbies"> <array> <value>吃饭</value> <value>睡觉</value> <value>打豆豆</value> </array> </property> </bean>
f. 注入 List 集合
有序可重复
-
User.java
private List<String> hobbies; public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } @Override public String toString() { return "User{" + "hobbies=" + hobbies + '}'; }
-
spring.xml
<bean id="userBean" class="com.spring6.bean.User"> <property name="hobbies"> <list> <value>吃饭</value> <value>睡觉</value> <value>打豆豆</value> </list> </property> </bean>
g. 注入 Set 集合
无序不可重复
-
User.java
private Set<String> hobbies; public void setHobbies(Set<String> hobbies) { this.hobbies = hobbies; } @Override public String toString() { return "User{" + "hobbies=" + hobbies + '}'; }
-
spring.xml
<bean id="userBean" class="com.spring6.bean.User"> <property name="hobbies"> <set> <value>吃饭</value> <value>吃饭</value> <value>睡觉</value> <value>睡觉</value> <value>打豆豆</value> </set> </property> </bean>
- 有重复元素不会报错,但是仅输出一次
h. 注入 Map 集合
-
User.java
private Map<Integer, String> phones; public void setPhones(Map<Integer, String> phones) { this.phones = phones; } @Override public String toString() { return "User{" + "phones=" + phones + '}'; }
-
spring.xml
<bean id="userBean" class="com.spring6.bean.User"> <property name="phones"> <map> <entry key="1" value="110" /> <entry key="2" value="119" /> <entry key="3" value="120" /> </map> </property> </bean>
- 对应非简单类型可以使用
key-ref
和value-ref
- 对应非简单类型可以使用
i. 注入 Properties
-
User.java
private Properties properties; public void setProperties(Properties properties) { this.properties = properties; } @Override public String toString() { return "User{" + "properties=" + properties + '}'; }
-
spring.xml
<bean id="userBean" class="com.spring6.bean.User"> <property name="properties"> <props> <prop key="name">张三</prop> <prop key="sex">男</prop> </props> </property> </bean>
- prop 的 key 和 value 只能是 String 型
j. 注入 null 和空字符串
未设置时默认为 null
-
User.java
private String string1; private String string2; public void setString1(String string1) { this.string1 = string1; } public void setString2(String string2) { this.string2 = string2; } @Override public String toString() { return "User{" + "string1='" + string1 + '\'' + ", string2='" + string2 + '\'' + '}'; }
-
spring.xml
<bean id="userBean" class="com.spring6.bean.User"> <property name="string1"> <null /> </property> <property name="string2" value=""/> </bean>
k. 注入的值含有特殊字符
-
XML 中有 5 个特殊字符:
<
、>
、'
、"
、&
,这些字符会被当做 XML 语法的一部分进行解析 -
解决方法:
-
使用转义字符代替
特殊字符 转义字符 >
>
<
<
'
'
"
"
&
&
-
在 spring.xml 中配置值
<bean id="userBean" class="com.spring6.bean.User"> <property name="string1" value="2 < 3" /> </bean>
-
-
将含有特殊字符的字符串放到
<![CDATA[]]>
中-
在 spring.xml 中配置值
<bean id="userBean" class="com.spring6.bean.User"> <property name="string2"> <value><![CDATA[2 < 3]]></value> </property> </bean>
-
-
(4)p 命名空间注入
-
目的:简化 set 注入配置
-
前提条件
- 在 XML 头部信息中添加 p 命名空间的配置信息:
xmlns:p="http://www.springframework.org/schema/p"
- 需要对应的属性提供 setter 方法
- 在 XML 头部信息中添加 p 命名空间的配置信息:
-
User.java
private String string; private Date date; public void setString(String string) { this.string = string; } public void setDate(Date date) { this.date = date; } @Override public String toString() { return "User{" + "string='" + string + '\'' + ", date=" + date + '}'; }
-
spring.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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="userBean" class="com.spring6.bean.User" p:string="字符串" p:date-ref="dateBean" /> <bean id="dateBean" class="java.util.Date" /> </beans>
(5)c 命名空间注入
-
目的:简化构造方法注入配置
-
前提条件
- 在 XML 头部信息中添加 p 命名空间的配置信息:
xmlns:c="http://www.springframework.org/schema/c"
- 需要提供构造方法
- 在 XML 头部信息中添加 p 命名空间的配置信息:
-
User.java
private String string; private int number; public User(String string, int number) { this.string = string; this.number = number; } @Override public String toString() { return "User{" + "string='" + string + '\'' + ", number=" + number + '}'; }
-
spring.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c" 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="userBean" class="com.spring6.bean.User" c:_0="字符串" c:number="123" /> </beans>
(6)util 命名空间
-
目的:配置复用
-
前提条件:在 XML 头部信息中添加配置信息
xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
-
User.java(User2.java)
private Properties properties; public void setProperties(Properties properties) { this.properties = properties; } @Override public String toString() { return "User{" + "properties=" + properties + '}'; }
-
spring.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" 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 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <util:properties id="prop"> <prop key="key1">value1</prop> <prop key="key2">value2</prop> <prop key="key3">value3</prop> </util:properties> <bean id="userBean" class="com.spring6.bean.User"> <property name="properties" ref="prop" /> </bean> <bean id="userBean2" class="com.spring6.bean.User2"> <property name="properties" ref="prop" /> </bean> </beans>
-
Test.java
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); User user = applicationContext.getBean("userBean", User.class); User2 user2 = applicationContext.getBean("userBean2", User2.class); System.out.println(user); System.out.println(user2);
(7)基于 XML 的自动装配
-
Spring 可以完成自动化的注入,又称自动装配,基于 set 方法
-
方式:根据名称或类型装配
-
根据名称
-
spring.xml
<bean id="userService" class="com.spring6.service.UserService" autowire="byName" /> <bean id="userDao" class="com.spring6.dao.UserDao" />
- 其中,在第二行的 bean 里,属性 id 必须为 set 方法名中除
set
外其他内容首字母小写
- 其中,在第二行的 bean 里,属性 id 必须为 set 方法名中除
-
-
根据类型
-
spring.xml
<bean id="userDao" class="com.spring6.dao.UserDao"></bean> <bean id="userDao2" class="com.spring6.dao.UserDao2"></bean> <bean id="userService" class="com.spring6.service.UserService" autowire="byType" />
-
(8)Spring 引入外部属性配置
-
设置外部属性配置。在 src/main/resources 下新建 setting.properties
username=root password=1234
-
引入外部 properties 文件。在 spring.xml 中进行下述操作
-
引入 context 命名空间
-
指定属性配置文件的路径
-
使用
${key}
方法从外部属性配置文件中取值-
key 值首先加载当前操作系统默认的值,导致
username
对应的 value 为当前系统管理员名称,为避免上述问题并使 key 名称易于理解,可使用添加前缀的操作user.username=root user.password=1234
-
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="setting.properties" /> <bean id="userBean" class="com.spring6.bean.User"> <property name="username" value="${user.username}" /> <property name="password" value="${user.password}" /> </bean> </beans>
-
0x05 Bean 的作用域
(1)singleton
Singleton:单例
- 默认情况下,Bean 是单例的,其在 Spring 上下文初始化的时候实例化,每一次调用
getBean()
方法时,都返回那个单例对象 - 在配置 Bean 时可以通过添加 scope 属性值来设置 Bean 是单例或多例
(2)prototype
Prototype:原型 / 多例
- 当 Bean 的 scope 属性设置为
Prototype
时,Spring 上下文初始化时不会初始化这些 Bean,每一次调用getBean()
方法时,才实例化该对象
(3)其他 Scope
在 pom.xml 中添加依赖 SpringMVC,此时该 Spring 项目为 Web 项目
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>6.0.4</version> </dependency>
- request
- 仅限于在 Web 应用中使用
- 一次请求中只有一个 Bean
- session
- 仅限于在 Web 应用中使用
- 一次会话中只有一个 Bean
- global session
- portlet 应用中专用的
- 如果在 Servlet 的 Web 应用中使用时,其效果与 session 相同
- application
- 仅限于在 Web 应用中使用
- 一个应用对应一个 Bean
- websocket
- 仅限于在 Web 应用中使用
- 一个 websocket 生命周期对应一个 Bean
- 自定义 Scope
(4)自定义 Scope
以自定义线程级 scope 为例:一个线程对应一个 Bean
-
自定义 Scope,实现 Scope 接口
- Spring 内置了线程范围的类:
org.springframework.context.support.SimpleThreadScope
- Spring 内置了线程范围的类:
-
将自定义的 Scope 注册到 Spring 容器中
<!-- filename: spring-scope.xml --> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="myThread"> <bean class="org.springframework.context.support.SimpleThreadScope" /> </entry> </map> </property> </bean>
-
使用 Scope
<!-- filename: spring-scope.xml --> <bean id="userBean" class="com.spring6.bean.User" scope="myThread" />
-
测试
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); User user = applicationContext.getBean("userBean", User.class); System.out.println(user); new Thread(() -> { User user2 = applicationContext.getBean("userBean", User.class); System.out.println(user2); }).start();
0x06 GoF 之工厂模式
-
设计模式定义:一种可以被重复利用的解决方案
-
GoF:Gang of Four,四人组,出自《设计模式》
-
《设计模式》描述了 23 种设计模式,可分成三大类
-
创建型:解决对象创建问题
单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式
-
结构型:一些类或对象组合在一起的经典结构
代理模式、装饰模式、适配器模式、组合模式、享元模式、外观模式、桥接模式
-
行为型:解决类或对象之间交互问题
策略模式、模板方法模式、责任链模式、观察者模式、迭代子模式、命令模式、备忘录模式、状态模式、访问者模式、中介模式、解释器模式
-
-
(1)工厂模式的三种形态
- 简单工厂模式(Simple Factory)
- 又称静态工厂方法模式,不属于 23 种设计模式之一,是工厂方法模式的一种特殊实现
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
(2)简单工厂模式
-
抽象产品角色
// filename: Weapon.java public abstract class Weapon { public abstract void attack(); }
-
具体产品角色
// filename: Tank.java public class Tank extends Weapon { @Override public void attack() { System.out.println("Tank attack"); } }
-
工厂类角色
// filename: WeaponFactory.java public class WeaponFactory { public static Weapon get(String weaponType) { if ("Tank".equals(weaponType)) { return new Tank(); } else { throw new RuntimeException("Weapon type not supported"); } } }
-
测试
// filename: Test.java public class Test { public static void main(String[] args) { Weapon tank = WeaponFactory.get("Tank"); tank.attack(); } }
-
简单工厂模式优点
- 客户端程序不需要关心对象的创建细节,需要时索要即可,初步实现了生产和消费的责任分离
-
简单工厂模式缺点
- 工厂类如果出现问题会导致整个系统瘫痪
- 不符合 OCP 开闭原则,在系统扩展时需要修改工厂类
-
Spring 中的 BeanFactory 就使用了简单工厂模式
(3)工厂方法模式
-
抽象产品角色
// filename: Weapon.java public abstract class Weapon { public abstract void attack(); }
-
具体产品角色
// filename: Tank.java public class Tank extends Weapon { @Override public void attack() { System.out.println("Tank attack"); } }
-
抽象工厂角色
// filename: WeaponFactory.java public abstract class WeaponFactory { public abstract Weapon get(); }
-
具体工厂角色
// filename: TankFactory.java public class TankFactory extends WeaponFactory { @Override public Weapon get() { return new Tank(); } }
-
测试
// filename: Test.java public class Test { public static void main(String[] args) { WeaponFactory weaponFactory = new TankFactory(); Weapon tank = weaponFactory.get(); tank.attack(); } }
-
工厂方法模式优点
- 创建对象时仅需要知到名称即可
- 扩展性高
- 屏蔽产品的具体实现
-
工厂方法模式缺点
- 每增加一个产品就需要对应增加一个工厂,使得系统中类的个数成倍上升,增加了系统复杂度
(4)抽象工厂模式
-
对比
工厂方法模式 抽象工厂模式 针对目标 一个产品系列 多个产品系列 实现效果 一个产品系列一个工厂类 多个产品系列一个工厂类 -
特点:抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式,抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体情况下,创建多个产品族中的产品对象。多个抽象产品类中,每个抽象产品类可以派生出多个具体产品类;一个抽象工厂类可以创建出多个具体产品类的实例
-
抽象工厂模式优点:
- 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象
-
抽象工厂模式缺点
- 产品族扩展十分困难
0x07 Bean 的实例化方式
(1)通过构造方法实例化
- 默认情况下会调用 Bean 的无参数构造方法
(2)通过简单工厂模式实例化
-
创建产品类 Star.java
public class Star { public Star() { System.out.println("Star 无参构造方法"); } }
-
创建工厂类 StarFactory.java
- 静态方法
public class StarFactory { public static Star get() { return new Star(); } }
-
在配置文件中实例化 Bean
<bean id="starFactory" class="com.spring.bean.StarFactory" factory-method="get"/>
-
在测试文件中测试
@Test public void test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); Star star = applicationContext.getBean("starFactory", Star.class); System.out.println(star); }
(3)通过 factory-bean 实例化
-
具体产品类:Tank.java
public class Tank { public Tank() { System.out.println("Tank 无参构造"); } }
-
具体工厂类:TankFactory.java
- 实例方法
public class TankFactory { public Tank get() { return new Tank(); } }
-
在配置文件中实例化 Bean
<bean id="tankFactory" class="com.spring.bean.TankFactory" /> <bean id="tank" factory-bean="tankFactory" factory-method="get" />
-
在测试文件中测试
@Test public void test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); Tank tank = applicationContext.getBean("tank", Tank.class); System.out.println(tank); }
(4)通过 FactoryBean 接口实例化
-
在 Spring 中,当编写了类直接实现 FactoryBean 接口后,factory-bean 会自动指向实现 FactoryBean 接口的类,factory-method 会自动指向
getObject()
方法 -
创建产品类:Person.java
public class Person { public Person() { System.out.println("person"); } }
-
创建工厂类:PersonFactory.java
import org.springframework.beans.factory.FactoryBean; public class PersonFactory implements FactoryBean<Person> { @Override public Person getObject() throws Exception { return new Person(); } @Override public Class<?> getObjectType() { return null; } @Override public boolean isSingleton() { return true; } }
-
在 spring.xml 配置文件中配置
<bean id="personFactory" class="com.spring.bean.PersonFactory" />
-
测试
@Test public void test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); Person person = applicationContext.getBean("personFactory", Person.class); System.out.println(person); }
(5)BeanFactory 与 FactoryBean 的区别
- BeanFactory 是 Spring IoC 容器的顶级对象,负责创建 Bean 对象
- FactoryBean 是一个 Bean,能够辅助 Spring 实例化其他 Bean 对象的一个 Bean
- 在 Spring 中,Bean 可分为普通 Bean 和 工厂 Bean
(6)注入自定义 Date
-
Student.java
private Date birth; public void setBirth(Date birth) { this.birth = birth; } @Override public String toString() { return "Student{" + "birth=" + birth + '}'; }
-
StudentFactory.java
public class StudentFactory implements FactoryBean<Date> { private String strDate; public StudentFactory(String strDate) { this.strDate = strDate; } @Override public Date getObject() throws Exception { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date date = sdf.parse(strDate); return date; } @Override public Class<?> getObjectType() { return null; } }
-
spring.xml
<bean id="studentFactory" class="com.spring.bean.StudentFactory"> <constructor-arg index="0" value="2000-01-01" /> </bean> <bean id="student" class="com.spring.bean.Student"> <property name="birth" ref="studentFactory" /> </bean>
-
Test.java
@Test public void test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); Student student = applicationContext.getBean("student", Student.class); System.out.println(student); }
0x08 Bean 的生命周期
生命周期的本质在于程序在某个时间点调用了某个类的某个方法
Bean 生命周期的管理可以参考 Spring 源码中 AbstractAutowireCapableBeanFactory 类的
doCreateBean()
方法
(1)五步
- 实例化 Bean
- Bean 属性赋值
- 初始化 Bean
- 使用 Bean
- 销毁 Bean
-
User.java
public class User { private String name; public void destroyBean() { System.out.println("Step 5"); } public void initBean() { System.out.println("Step 3"); } public void setName(String name) { System.out.println("Step 2"); this.name = name; } public User() { System.out.println("Step 1"); } }
-
spring.xml
<bean id="user" class="com.spring.bean.User" init-method="initBean" destroy-method="destroyBean"> <property name="name" value="字符串" /> </bean>
-
Test.java
@Test public void test() { ApplicationContext applicationContext = new sPathXmlApplicationContext("spring.xml"); User user = applicationContext.getBean("user", User.class); System.out.println("Step 4 " + user); // 手动关闭 Spring 容器后 Spring 才会销毁 Bean ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext; context.close(); }
-
测试结果
Step 1 Step 2 Step 3 Step 4 com.spring.bean.User@1165b38 Step 5