Spring6
一 、Spring6的启示录
1.1Spring6的引入
根据三层架构写代码,需要司令官,Dao,Service
UserController类 司令官指定秘书做业务逻辑
public class UserController{
private UserService userService = new UserServiceImpl();
public void login(){
String username = "admin";
String password = "123456";
boolean success = userService.login(username, password);
if (success) {
// 登录成功
} else {
// 登录失败
}
}
}
UserService接口
UserServiceImpl类 秘书做业务逻辑的时候需要用到Dao,也就是在业务逻辑时调用需要的数据库操作
public class UserServiceImpl implements UserService{
private UserDao userDao = new UserDaoImplForMySql();
public boolean login(String username, String password) {
User user = userDao.selectByUsernameAndPassword(username, password);
if (user != null) {
return true;
}
return false;
}
}
UserDao接口
UserDaoImplForMySql类 持久化层,专门做数据库操作
public class UserDaoImplForMySql implements UserDao{
public User selectByUsernameAndPassword(String username, String password) {
// 连接MySQL数据库,根据用户名和密码查询用户信息
return null;
}
}
以上代码的UserDaoImpForMySql主要是连接MySql数据库进行的操作,如果说更换到Oracle数据库上,则需要再提供一个Dao实现类UserDaoImpForOracle
如下
public class UserDaoImplForOracle implements UserDao {
public User selectByUsernameAndPassword(String username, String password) {
// 连接Oracle数据库,根据用户名和密码查询用户信息
return null;
}
}
很明显,以上的操作是在进行功能的扩展,但是这个功能的扩展会导致项目的其他类发生改变嘛?
答案是会的
哪里改变了呢?
目光移到UserServiceImpl类中,可以发现,他的UserDao userdao = new ???后面发生了改变。
public class UserServiceImpl implements UserService {
//private UserDao userDao = new UserDaoImplForMySQL();
private UserDao userDao = new UserDaoImplForOracle();
public boolean login(String username, String password) {
User user = userDao.selectByUsernameAndPassword(username, password);
if (user != null) {
return true;
}
return false;
}
}
1.1 OCP开闭原则
OCP开闭原则就是在软件开发过程中对扩展开放,对修改关闭。
也就是说,在进行功能扩展的时候,添加额外的类时没有问题的,但因为功能扩展而修改之前运行正常的程序,这是不被允许的。
因为一旦修改之前运行正常的程序,就会导致项目整体要进行全方位的重新测试。这是相当麻烦的过程。导致以上问题的主要原因是:代码和代码之间的耦合度太高。
根据图,可以明显的看出,上层是依赖下层的。
UserController依赖UserServiceImpl,而UserServiceImpl依赖UserDaoImplForMySQL,这样就会导致下面只要改动,上面必然会受牵连(跟着也会改),所谓牵一发而动全身。这样也就同时违背了另一个开发原则:依赖倒置原则。
1.2依赖倒置原则DIP
DIP主要倡导面对接口变成面对抽象编程,不要面对具体编程。
使得上层不再依赖下层,下面改动了,上面的代码也不会受到牵连。降低程序的耦合度,耦合度低了,扩展力就强了,同时代码复用性也增强了。
注意,软件七大开发原则都是在为解耦合服务
1.2.1什么是面对抽象编程?
面对抽象编程是类中只有定义的抽象类类型的成员变量,该成员变量并没有new对象,没有使用具体的接口实现类。
以下是面对抽象编程。符合依赖倒置原则DIP。
但存在的问题是,这样的话userDao就是null,执行时会产生空指针异常。
既然如此,为了实现面对接口编程我们需要解决这个问题。解决空指针异常问题,其实就是解决以下两个核心问题
1、谁来创建对象。也就是说如何创建接口的具体实现类【也就是说谁来:new UserDaoImplForOracle()/newUserDaoImplForMySQL()】
2、谁来将已经创建好的对象赋值到这个属性上。【也就是说谁来把上面创建的对象赋给userDao属性】
如果解决上述问题,则我们就做到了符合OCP开闭原则,有符合依赖倒置原则。
很荣幸的通知你:Spring框架可以做到。
在Spring框架中,它可以帮助我们new对象,并且它还可以将new出来的对象赋到属性上。换句话说,Spring框架可以帮助我们创建对象,并且可以帮助我们维护对象和对象之间的关系。比如:
很显然,这种方式是将对象的创建权/管理权交出去了,不再使用硬编码的方式了。同时也把对象关系的管理权交出去了,也不再使用硬编码的方式了。像这种把对象的创建权交出去,把对象关系的管理权交出去,被称为控制反转。
1.3控制反转
控制反转(Inversion of Control,缩写为IoC),是面向对象编程的一种设计思想,可以用来降低代码之间的耦合度,符合依赖倒置原则。
控制反转的核心是:把对象的创建权交出去,把对象和对象之间关系的管理权交出去,由第三方容器来负责创建和维护。
控制反转常见的实现方式:依赖注入(Dependency Injection,简称DI)
通常,依赖注入的实现由包括两种方式:
● set方法注入
● 构造方法注入
而Spring框架就是一个实现了IoC思想的框架。
控制反转是设计思想,依赖注入是这种思想的实现方式、手段。
IoC可以认为是一种全新的设计模式,但是理论和时间成熟相对较晚,并没有包含在GoF中。(GoF指的是23种设计模式)
二、Spring概述
2.1Spring简介
Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。
从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。
Spring是一个轻量级的控制反转IoC和面向切面AOP的容量框架。。
Spring最初的出现是为了解决EJB臃肿的设计,以及难以测试等问题。
Spring为简化开发而生,让程序员只需关注核心业务的实现,尽可能的不再关注非业务逻辑代码(事务控制,安全日志等)。
2.2Spring的8大模块
注意:Spring5版本之后是8个模块。在Spring5中新增了WebFlux模块。
-
Spring Core模块
这是Spring框架最基础的部分,它提供了依赖注入(DependencyInjection)特征来实现容器对Bean的管理。核心容器的主要组件是 BeanFactory,BeanFactory是工厂模式的一个实现,是任何Spring应用的核心。它使用IoC将应用配置和依赖从实际的应用代码中分离出来。 -
Spring Context模块
如果说核心模块中的BeanFactory使Spring成为容器的话,那么上下文模块就是Spring成为框架的原因。
这个模块扩展了BeanFactory,增加了对国际化(I18N)消息、事件传播、验证的支持。另外提供了许多企业服务,例如电子邮件、JNDI访问、EJB集成、远程以及时序调度(scheduling)服务。也包括了对模版框架例如Velocity和FreeMarker集成的支持 -
Spring AOP模块
Spring在它的AOP模块中提供了对面向切面编程的丰富支持,Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中,可以自定义拦截器、切点、日志等操作。 -
Spring DAO模块
提供了一个JDBC的抽象层和异常层次结构,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析,用于简化JDBC。 -
Spring ORM模块
Spring提供了ORM模块。Spring并不试图实现它自己的ORM解决方案,而是为几种流行的ORM框架提供了集成方案,包括Hibernate、JDO和iBATIS SQL映射,这些都遵从 Spring 的通用事务和 DAO 异常层次结构。 -
Spring Web MVC模块
Spring为构建Web应用提供了一个功能全面的MVC框架。虽然Spring可以很容易地与其它MVC框架集成,例如Struts,但Spring的MVC框架使用IoC对控制逻辑和业务对象提供了完全的分离。 -
Spring WebFlux模块
Spring Framework 中包含的原始 Web 框架 Spring Web MVC 是专门为 Servlet API 和 Servlet 容器构建的。反应式堆栈 Web 框架 Spring WebFlux 是在 5.0 版的后期添加的。它是完全非阻塞的,支持反应式流(Reactive Stream)背压,并在Netty,Undertow和Servlet 3.1+容器等服务器上运行。 -
Spring Web模块
Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文,提供了Spring和其它Web框架的集成,比如Struts、WebWork。还提供了一些面向服务支持,例如:实现文件上传的multipart请求。
2.3 Spring的特点
- 轻量
a. 从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。
b. Spring是非侵入式的:Spring应用中的对象不依赖于Spring的特定类。 - 控制反转
a. Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。 - 面向切面
a. Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。 - 容器
a. Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。 - 框架
a. Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持。
2.4 本教程软件版本
● IDEA工具:2022.1.4
● JDK:Java17(Spring6要求JDK最低版本是Java17)
● Maven:3.8.6
● Spring:6.0.0-M2
● JUnit:4.13.2
三、Spring的第一个入门程序
3.1Spring框架的jar包
注意:
如果你只是想用Spring的IoC功能,仅需要引入:spring-context即可。将这个jar包添加到classpath当中。
当加入spring context的依赖之后,会关联引入其他依赖:
spring aop:面向切面编程
spring beans:IoC核心
spring core:spring的核心工具包
spring jcl:spring的日志包
spring expression:spring表达式
如果采用maven只需要引入context的依赖即可。
3.2第一个Spring程序
1、前期准备
● 打开IDEA创建Empty Project:spring6
● 设置JDK版本17,编译器版本17
● 设置IDEA的Maven:关联自己的maven
● 在空的工程spring6中创建第一个模块:spring6-001-first
2、配置文件pom.xml
spring-context依赖
要实现控制反转思想,需要在pom.xml配置文件中加入spring-context依赖
因为spring6尚未发布,因此我们需要用到spring6里程碑版本的仓库。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.powernode</groupId>
<artifactId>spring6-001-first</artifactId>
<version>1.0-SNAPSHOT</version>
<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.0-M2</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>
注意:打包方式jar。
Junit依赖
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
Log4j2日志框架
从Spring5之后,Spring框架支持集成的日志框架是Log4j2
<!--log4j2的依赖-->
<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>
注意加入了日志依赖之后,还需要配置日志.xml文件
在类的根路径下配置log4j2.xml配置文件(文件名固定为: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>
使用日志框架
// 第一步:创建日志记录器对象
// 获取FirstSpringTest类的日志记录器对象,也就是说只要是FirstSpringTest类中的代码执行记录日志的话,就输出相关的日志信息。
Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
// 第二步:记录日志,根据不同的级别来输出日志
logger.info("我是一条消息");
logger.debug("我是一条调试信息");
logger.error("我是一条错误信息");
需要输出哪个类的日志信息就在该类定义日志记录器对象。
private static final Logger logger = LoggerFactory.getLogger(该类的class)
在具体的成员方法内部输出日志信息
For Example
public class UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
public void insert(){
//System.out.println("数据库正在保存用户信息。");
// 使用一下log4j2日志框架
logger.info("数据库正在保存用户信息。");
}
}
3、 定义bean:User
package com.powernode.spring6.bean;
/**
* bean,封装用户信息。
* @author 动力节点
* @version 1.0
* @since 1.0
*/
public class User {
}
4、编写Spring配置文件:beans.xml
<?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="userBean" class="com.powernode.spring6.bean.User"/>
</beans>
bean的id和class属性:
● id属性:代表对象的唯一标识。可以看做一个人的身份证号。
● class属性:用来指定要创建的java对象的类名,这个类名必须是全限定类名(带包名)
5、编写测试程序
package com.powernode.spring6.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Spring6Test {
@Test
public void testFirst(){
// 第一步:获取Spring容器对象。
// ApplicationContext 翻译为:应用上下文。其实就是Spring容器。
// ApplicationContext 是一个接口。
// ApplicationContext 接口下有很多实现类。其中有一个实现类叫做:ClassPathXmlApplicationContext
// ClassPathXmlApplicationContext 专门从类路径当中加载spring配置文件的一个Spring上下文对象。
// 这行代码只要执行:就相当于启动了Spring容器,解析spring.xml文件,并且实例化所有的bean对象,放到spring容器当中。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// 第二步:根据bean的id从Spring容器中获取这个对象。
Object userBean = applicationContext.getBean("userBean");
System.out.println(userBean);
}
}
运行结果
3.2.1 第一个spring程序剖析
1、在spring的配置文件中id是不能重名
2、spring是通过反射机制调用类的无参数构造方法来创建对象的,所以要想让spring给你创建对象,必须保证无参数构造方法是存在的。
可以调用无参构造方法说明该类不是抽象类且该类有无参构造方法。
3、spring把创建好的对象存储到一个Map当中
4、spring配置文件名字是我们负责提供的,spring配置文件的名字是随意的。
spring的配置文件可以有多个,在ClassPathXmlApplicationContext构造方法的参数上传递文件路径即可。且配置文件的文件名、文件路径都是任意的
//ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml");
//ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml", "beans.xml");
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml", "beans.xml", "xml/beans.xml");
// 源码:
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, (ApplicationContext)null);
}
5、getBean()方法调用时,id不存在的时候,会出现异常
getBean()方法返回的类型是Object,如果访问子类的特有属性和方法时,还需要向下转型,有其它办法可以解决这个问题吗?
//Object nowTime = applicationContext.getBean("nowTime");
//Date nowTime = (Date) applicationContext.getBean("nowTime");
// 不想强制类型转换,可以使用以下代码(通过第二个参数来指定返回的bean的类型。)
Date nowTime = applicationContext.getBean("nowTime", Date.class);
6、ClassPathXmlApplicationContext是从类路径中加载配置文件,如果没有在类路径当中,又应该如何加载配置文件呢?
没有在类路径中的话,需要使用FileSystemXmlApplicationContext类进行加载配置文件。
这种方式较少用。一般都是将配置文件放到类路径当中,这样可移植性更强。
ApplicationContext applicationContext2 = new FileSystemXmlApplicationContext("d:/spring6.xml");
7、ApplicationContext是用来生产bean对象的,ApplicationContext的超级负借口是BeanFactory
//ApplicationContext接口的超级父接口是:BeanFactory(翻译为Bean工厂,就是能够生产Bean对象的一个工厂对象。)
//BeanFactory是IoC容器的顶级接口。
//Spring的IoC容器底层实际上使用了:工厂模式。
//Spring底层的IoC是怎么实现的?XML解析 + 工厂模式 + 反射机制
//ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml");
BeanFactory applicationContext = new ClassPathXmlApplicationContext("spring6.xml");
User user = applicationContext.getBean("userBean", User.class);
System.out.println(user);
Spring的IoC容器是怎么实现的?XML+工厂模式+反射机制
spring不是在调用getBean()方法的时候创建对象,执行new ClassPathXmlApplicationContext(“spring6.xml”);的时候,就会实例化对象。
也就是说Spring容器是通过ApplicationContext调用getBean(“id”,类类型),底部进行了XML+工厂模式+反射机制调用了无参构造方法来创建对象的。
四、Spring对IoC的实现
4.1IoC控制反转
控制反转是一种思想。
控制反转是为了降低程序耦合度,提高程序扩展力,达到OCP原则,达到DIP原则。
控制反转,反转的是什么?
将对象的创建权利交出去,交给第三方容器负责。
将对象和对象之间关系的维护权交出去,交给第三方容器负责。
控制反转这种思想如何实现呢?
DI(Dependency Injection):依赖注入
4.2依赖注入
1、依赖注入实现了控制反转的思想
Spring通过依赖注入的方式来完成Bean管理的
Bean管理说的是:Bean对象的创建以及Bean对象中属性的赋值(对象与对象之间关系的维护)。
2、依赖注入:
依赖指的是对象和对象之间的关联关系。
注入指的是一种数据传递行为,通过注入行为来让对象和对象产生关系。
3、依赖注入常见的实现方式包括两种:
第一种:set注入
第二种:构造注入
4.2.1 set注入
set注入,是基于set党发实现的,底层会通过反射机制调用属性对应的set方法然后给属性赋值,这就要求属性必须对外提供set方法。
public class UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
public void insert(){
//System.out.println("数据库正在保存用户信息。");
// 使用一下log4j2日志框架
logger.info("数据库正在保存用户信息。");
}
}
public class VipDao {
private static final Logger logger = LoggerFactory.getLogger(VipDao.class);
public void insert(){
logger.info("正在保存Vip信息!!!!");
}
}
public class UserService{
private UserDao userDao;
private VipDao vipDao;
//该set方法不符合javabean规范
public void setAbc(VipDao vipDao){
this.vipDao = vipDao;
}
//该set方法符合javabean规范:set+属性名(第一个字母大写)
public void setUserDao(UserDao userDao){
this.userDao=userDao;
}
public void saveUser(){
// 保存用户信息到数据库
userDao.insert();
vipDao.insert();
}
}
写好类,就要在spring.xml中给类配置其对象。
有属性的类要对属性赋值(除常量属性外)set注入中采用
<property name="属性名" value/ref="属性值">
spring.xml文件
<!--配置dao类-->
<bean id = "userDaoBean" class="com.powernode.spring6.dao.UserDao">
<bean id="vipDaoBean" class="com.powernode.spring6.dao.VipDao">
<!--配置service-->
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<!-- 想让Spring调用对应的set方法,需要配置property标签 -->
<!-- name属性怎么指定值:set方法的方法名,去掉set,然后把剩下的单词首字母变小写,写到这里。-->
<!-- ref翻译为引用。英语单词:references。ref后面指定的是要注入的bean的id。-->
<!--<property name="mySQLUserDao" ref="userDaoBean"/>-->
<!--set方法起名的时候,不要为难自己,按照规范来。所以一般情况下name位置写属性名就行了。-->
<property name="userDao" ref="userDaoBean"/>
<!--<property name="vipDao" ref="vipDaoBean"/>-->
<property name="abc" ref="vipDaoBean"/>
实现原理
通过property标签获取到属性名:userDao
通过属性名推断出set方法名:setUserDao
通过反射机制调用setUserDao()方法给属性赋值
property标签的name是属性名。
property标签的ref是要注入的bean对象的id。(通过ref属性来完成bean的装配,这是bean最简单的一种装配方式。装配指的是:创建系统组件之间关联的动作)
property标签的name是:setUserDao()方法名演变得到的。演变的规律是:
setUsername() 演变为 username
setPassword() 演变为 password
setUserDao() 演变为 userDao
setUserService() 演变为 userService
另外,对于property标签来说,ref属性也可以采用标签的方式,但使用ref属性是多余的:
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="userDao">
<ref bean="userDaoBean"/>
</property>
</bean>
set注入的核心实现原理:通过反射机制调用set方法来给属性赋值,让两个对象之间产生关系。
4.2.2构造注入
核心原理:通过调用构造方法来给属性赋值。
与set注入相比,构造注入是在创建对象的同时进行注入,进行属性的赋值,而set注入是在对象创建之后。
在CustomerService类中有两个属性,通过构造方法对属性赋值。
public class CustomerService {
private UserDao userDao;
private VipDao vipDao;
public CustomerService(UserDao userDao, VipDao vipDao) {
this.userDao = userDao;
this.vipDao = vipDao;
}
public void save(){
userDao.insert();
vipDao.insert();
}
}
写完类在spring.xml文件中管理bean对象
<!--管理Dao类bean对象-->
<bean id="xxxx" class="com.powernode.spring6.dao.UserDao"/>
<bean id="yyyy" class="com.powernode.spring6.dao.VipDao"/>
<!--管理Service类bean对象--根据构造方法参数名称注入-->
<bean id= "csBeans3" class="com.powernode.spring6.service.CustomerService">
<constructor-arg name="vipDao" ref="yyyy"/>
<constructor-arg name="userDao" ref = "xxxx"/>
</bean>
<!--管理Service类bean对象--根据构造方法参数下标注入-->
<bean id= "csBeans2" class="com.powernode.spring6.service.CustomerService">
<constructor-arg index="0"ref="yyyy"/>
<constructor-arg index="1" ref = "xxxx"/>
</bean>
<!--管理Service类bean对象--spring自动做类型匹配-->
<bean id= "csBeans2" class="com.powernode.spring6.service.CustomerService">
<constructor-arg ref="yyyy"/>
<constructor-arg ref = "xxxx"/>
</bean>
通过测试得知,通过构造方法注入的时候:
可以通过下标
可以通过参数名
也可以不指定下标和参数名,可以类型自动推断。
Spring在装配方面做的还是比较健壮的。
4.3set注入专题
4.3.1注入外部Bean
一个类中包含以另一个类为数据类型的对象。
如Service类中有成员属性VipDao vipDao;
外部Bean的特点:bean定义到外面,在property标签中使用ref属性进行注入。通常这种方式是常用。
<!--声明/定义Bean-->
<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"></bean>
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
<!--使用ref属性来引入。这就是注入外部Bean-->
<property name="orderDao" ref="orderDaoBean"/>
</bean>
4.3.2注入内部Bean
内部Bean的方式:在bean标签中嵌套bean标签,不使用property标签的ref属性。
<bean id="orderServiceBean2" class="com.powernode.spring6.service.OrderService">
<property name="orderDao">
<!--在property标签中使用嵌套的bean标签,这就是内部Bean-->
<bean class="com.powernode.spring6.dao.OrderDao"></bean>
</property>
</bean>
4.3.3 注入简单类型
如果给简单类型赋值,使用value属性或value标签。而不是ref。
通过源码分析得知,BeanUtils类,简单类型包括:
基本数据类型
基本数据类型对应的包装类
String或其他的CharSequence子类
Number子类
Date子类,java.util.Date是简单类型
Enum子类
URI
URL
Temporal子类,Temporal是Java8提供的时间和时区类型
Locale,Locale是语言类,也是简单类型。
Class
另外还包括以上简单值类型对应的数组类型。
4.3.3.1注入简单值类型的测试
public class BeanUtils{
//.......
/**
* Check if the given type represents a "simple" property: a simple value
* type or an array of simple value types.
* <p>See {@link #isSimpleValueType(Class)} for the definition of <em>simple
* value type</em>.
* <p>Used to determine properties to check for a "simple" dependency-check.
* @param type the type to check
* @return whether the given type represents a "simple" property
* @see org.springframework.beans.factory.support.RootBeanDefinition#DEPENDENCY_CHECK_SIMPLE
* @see org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#checkDependencies
* @see #isSimpleValueType(Class)
*/
public static boolean isSimpleProperty(Class<?> type) {
Assert.notNull(type, "'type' must not be null");
return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}
/**
* Check if the given type represents a "simple" value type: a primitive or
* primitive wrapper, an enum, a String or other CharSequence, a Number, a
* Date, a Temporal, a URI, a URL, a Locale, or a Class.
* <p>{@code Void} and {@code void} are not considered simple value types.
* @param type the type to check
* @return whether the given type represents a "simple" value type
* @see #isSimpleProperty(Class)
*/
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
//........
}
编写程序,测试简单类型
package com.powernode.spring6.beans;
import java.net.URI;
import java.net.URL;
import java.time.LocalDate;
import java.util.Date;
import java.util.Locale;
public class A {
private byte b;
private short s;
private int i;
private long l;
private float f;
private double d;
private boolean flag;
private char c;
private Byte b1;
private Short s1;
private Integer i1;
private Long l1;
private Float f1;
private Double d1;
private Boolean flag1;
private Character c1;
private String str;
private Date date;
private Season season;
private URI uri;
private URL url;
private LocalDate localDate;
private Locale locale;
private Class clazz;
// 生成setter方法
// 生成toString方法
}
enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
在spring.xml中配置,管理bean对象
<?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="a" class="com.powernode.spring6.beans.A">
<property name="b" value="1"/>
<property name="s" value="1"/>
<property name="i" value="1"/>
<property name="l" value="1"/>
<property name="f" value="1"/>
<property name="d" value="1"/>
<property name="flag" value="false"/>
<property name="c" value="a"/>
<property name="b1" value="2"/>
<property name="s1" value="2"/>
<property name="i1" value="2"/>
<property name="l1" value="2"/>
<property name="f1" value="2"/>
<property name="d1" value="2"/>
<property name="flag1" value="true"/>
<property name="c1" value="a"/>
<property name="str" value="zhangsan"/>
<!--注意:value后面的日期字符串格式不能随便写,必须是Date对象toString()方法执行的结果。-->
<!--报错了,说1970-10-11这个字符串无法转换成java.util.Date类型。-->
<!--<property name="birth" value="1970-10-11"/>-->
<!--如果你硬要把Date当做简单类型的话,使用value赋值的话,这个日期字符串格式有要求-->
<!--在实际开发中,我们一般不会把Date当做简单类型,虽然它是简单类型。一般会采用ref给Date类型的属性赋值。-->
<!--如果想使用其他格式的日期字符串,就需要进行特殊处理了。具体怎么处理,可以看后面的课程!!!!-->
<property name="date" value="Fri Sep 30 15:26:38 CST 2022"/>
<property name="season" value="WINTER"/>
<property name="uri" value="/save.do"/>
<!--spring6之后,会自动检查url是否有效,如果无效会报错。-->
<property name="url" value="http://www.baidu.com"/>
<property name="localDate" value="EPOCH"/>
<!--java.util.Locale 主要在软件的本地化时使用。它本身没有什么功能,更多的是作为一个参数辅助其他方法完成输出的本地化。-->
<property name="locale" value="CHINESE"/>
<property name="clazz" value="java.lang.String"/>
</bean>
</beans>
需要注意的是:
如果把Date当做简单类型的话,日期字符串格式不能随便写。格式必须符合Date的toString()方法格式。显然这就比较鸡肋了。如果我们提供一个这样的日期字符串:2010-10-11,在这里是无法赋值给Date类型的属性的。
spring6之后,当注入的是URL,那么这个url字符串是会进行有效性检测的。如果是一个存在的url,那就没问题。如果不存在则报错。
4.3.3.2 给数据源的属性注值
已知所有的数据源都要实现javax.sql.DataSource接口,数据源是能够给你提供Connection对象的,因此数据源中应该有连接数据库的所有信息,如driver,url,username,password等等。
/**
* 所有的数据源都要实现java规范:javax.sql.DataSource
* 什么是数据源:能够给你提供Connection对象的,都是数据源。
**/
public class MyDataSource implements DataSource { // 可以把数据源交给Spring容器来管理。
private String driver;
private String url;
private String username;
private String password;
public void setDriver(String driver) {
this.driver = driver;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "MyDataSource{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
@Override
public Connection getConnection() throws SQLException {
// 获取数据库连接对象的时候需要4个信息:driver url username password
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
配置spring.xml
<bean id = "dataSourceBean" class="com.powernode.spring6.jdbc.MyDataSource">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
测试程序
@Test
public void testDataSource(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-datasource.xml");
MyDataSource dataSource = applicationContext.getBean("dataSource", MyDataSource.class);
System.out.println(dataSource);
}
4.3.4 级联属性赋值
4.3.5 注入数组
4.3.5.1 当类中的成员变量是简单类型数组时
public class QianDaye{
String[] aiHaos;
public void setAiHaos(String[] aiHaos){
this.aiHaos=aiHaos;
}
@Override
public String toString() {
return "QianDaYe{" +
"aiHaos=" + Arrays.toString(aiHaos) +
'}';
}
}
在spring.xml中配置,让spring容器管理Bean对象
<bean id="yuQian" class="">
<property name="aiHaos">
<!-- 这个数组属性当中的元素类型是String简单类型 -->
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</array>
</property>
</bean>
4.3.5.2 当类中的成员变量是非简单类型数组时
public class Woman {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Woman{" +
"name='" + name + '\'' +
'}';
}
}
public class QianDaYe {
private String[] aiHaos;
// 多个女性朋友
private Woman[] womens;
public void setWomens(Woman[] womens) {
this.womens = womens;
}
public void setAiHaos(String[] aiHaos) {
this.aiHaos = aiHaos;
}
@Override
public String toString() {
return "QianDaYe{" +
"aiHaos=" + Arrays.toString(aiHaos) +
", womens=" + Arrays.toString(womens) +
'}';
}
}
一个对象对应一个bean对象,QianDaye有多个女性朋友,则多个女性朋友对应多个bean对象
<bean id="w1" class="com.powernode.spring6.bean.Woman">
<property name="name" value="小花"/>
</bean>
<bean id="w2" class="com.powernode.spring6.bean.Woman">
<property name="name" value="小亮"/>
</bean>
<bean id="w3" class="com.powernode.spring6.bean.Woman">
<property name="name" value="小明"/>
</bean>
<bean id="yuQian" class="com.powernode.spring6.bean.QianDaYe">
<!-- 这个数组属性当中的元素类型是String简单类型 -->
<property name="aiHaos">
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</array>
</property>
<!-- 这个数组当中的类型就不是简单类型了-->
<property name="womens">
<array>
<ref bean="w1"/>
<ref bean="w2"/>
<ref bean="w3"/>
</array>
</property>
</bean>
要点:
如果数组中是简单类型,使用value标签。属性值
如果数组中是非简单类型,使用ref标签。
4.3.6注入单列集合List与Set
List集合:有序可重复
注意:注入List集合的时候使用list标签,如果List集合中是简单类型使用value标签,反之使用ref标签。
Set集合:无序不可重复
要点:
使用标签
set集合中元素是简单类型的使用value标签,反之使用ref标签。
public class Person {
// 注入List集合
private List<String> names;
// 注入Set集合
private Set<String> addrs;
public void setNames(List<String> names) {
this.names = names;
}
public void setAddrs(Set<String> addrs) {
this.addrs = addrs;
}
@Override
public String toString() {
return "Person{" +
"names=" + names +
", addrs=" + addrs +
'}';
}
}
<bean id="personBean" class="com.powernode.spring6.bean.Person">
<property name="names">
<!--list集合有序可重复-->
<list>
<value>张三</value>
<value>李四</value>
<value>王五</value>
<value>张三</value>
<value>张三</value>
<value>张三</value>
<value>张三</value>
</list>
</property>
<property name="addrs">
<!--set集合无序不可重复-->
<set>
<value>北京大兴区</value>
<value>北京大兴区</value>
<value>北京海淀区</value>
<value>北京海淀区</value>
<value>北京大兴区</value>
</set>
</property>
</bean>
4.3.7注入双列集合Map和Properties
Map集合:
使用<map>标签
如果key是简单类型,使用 key 属性,反之使用 key-ref 属性。
如果value是简单类型,使用 value 属性,反之使用 value-ref 属性。
java.util.Properties继承java.util.Hashtable,所以Properties也是一个Map集合。
Properties使用<props>标签嵌套<prop>标签完成。
public class Person {
// 注入Map集合
// 多个电话
private Map<Integer, String> phones;
// 注入属性类对象
// Properties本质上也是一个Map集合。
// Properties的父类Hashtable,Hashtable实现了Map接口。
// 虽然这个也是一个Map集合,但是和Map的注入方式有点像,但是不同。
// Properties的key和value只能是String类型。
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
public void setPhones(Map<Integer, String> phones) {
this.phones = phones;
}
@Override
public String toString() {
return "Person{" +
"phones=" + phones +
", properties=" + properties +
'}';
}
}
spring.xml
<bean id="personBean class="com.powernode.spring6.bean.Person">
<property name="properties">
<props>
<prop key = "driver">com.mysql.cj.jdbc.Driver</prop>
<prop key="url">jdbc:mysql://localhost:3306/spring6</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
<property name="phones">
<map>
<!--如果key和value不是简单类型就用这个配置。-->
<!--<entry key-ref="" value-ref=""/>-->
<!--如果是简单类型就是key和value-->
<entry key="1" value="110">
<entry key="2" value="120"/>
<entry key="3" value="119"/>
</map>
</property>
</bean>
4.3.8 注入空字符串或null
注入空字符串使用: 或者 value=“”
注入null使用: 或者 不为该属性赋值
public class Cat {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
<bean id="catBean" class="com.powernode.spring6.bean.Cat">
<!--不给属性注入,属性的默认值就是null-->
<!--<property name="name" value="tom"></property>-->
<!-- 这不是注入null,这只是注入了一个"null"字符串-->
<!--<property name="name" value="null"/>-->
<!--这种方式是手动注入null-->
<!--<property name="name">
<null/>
</property>-->
<!--注入空字符串第一种方式-->
<!--<property name="name" value=""/>-->
<!--注入空字符串第二种方式-->
<property name="name">
<value/>
</property>
<property name="age" value="3"></property>
</bean>
4.3.9 注入的值中含有特殊符号
XML中有5个特殊字符,分别是:<、>、'、"、&
以上5个特殊符号在XML中会被特殊对待,会被当做XML语法的一部分进行解析,如果这些特殊符号直接出现在注入的字符串当中,会报错。
解决方案包括两种:
第一种:特殊符号使用转义字符代替。
第二种:将含有特殊符号的字符串放到:<![CDATA[]]> 当中。因为放在CDATA区中的数据不会被XML文件解析器解析。使用CDATA时,不能使用value属性,只能使用value标签。
5个特殊字符对应的转义字符分别是:
public class MathBean {
private String result;
public void setResult(String result) {
this.result = result;
}
@Override
public String toString() {
return "MathBean{" +
"result='" + result + '\'' +
'}';
}
}
<bean id="mathBean" class="com.powernode.spring6.bean.MathBean">
<!--第一种方案:使用实体符号代替特殊符号-->
<!--<property name="result" value="2 < 3" />-->
<!--第二种方案:使用<![CDATA[]]>-->
<property name="result">
<!--只能使用value标签-->
<value><![CDATA[2 < 3]]></value>
</property>
</bean>
4.4 p命名空间注入
目的:简化配置。
p命名空间实际上是对set注入的简化。
使用p命名空间注入的前提条件包括两个:
第一:在XML头部信息中添加p命名空间的配置信息:xmlns:p=“http://www.springframework.org/schema/p”
第二:p命名空间注入是基于setter方法的,所以需要对应的属性提供setter方法。
public class Dog {
// 简单类型
private String name;
private int age;
// 非简单类型
private Date birth;
// p命名空间注入底层还是set注入,只不过p命名空间注入可以让spring配置变的更加简单。
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", birth=" + birth +
'}';
}
}
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--
第一步:在spring的配置文件头部添加p命名空间。xmlns:p="http://www.springframework.org/schema/p"
第二步:使用 p:属性名 = "属性值"
-->
<bean id="dogBean" class="com.powernode.spring6.bean.Dog" p:name="小花" p:age="3" p:birth-ref="birthBean"/>
<!--这里获取的是当前系统时间。-->
<bean id="birthBean" class="java.util.Date"/>
</beans>
4.5 c命名空间注入
c命名空间是简化构造方法注入的。
使用c命名空间的两个前提条件:
第一:需要在xml配置文件头部添加信息:xmlns:c=“http://www.springframework.org/schema/c”
第二:需要提供构造方法。 c命名空间是依靠构造方法的。 注意:不管是p命名空间还是c命名空间,注入的时候都可以注入简单类型以及非简单类型。
public class People {
private String name;
private int age;
private boolean sex;
// c命名空间是简化构造注入的。
// c命名空间注入办法是基于构造方法的。
public People(String name, int age, boolean sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
第一步:在spring的配置文件头部添加: xmlns:c="http://www.springframework.org/schema/c"
第二步:使用
c:_0 下标方式
c:name 参数名方式
-->
<!--<bean id="peopleBean" class="com.powernode.spring6.bean.People" c:_0="zhangsan" c:_1="30" c:_2="true"></bean>-->
<bean id="peopleBean" class="com.powernode.spring6.bean.People" c:name="jack" c:age="30" c:sex="true"></bean>
</beans>
4.6 util命名空间
使用util命名空间可以让配置复用。
使用util命名空间的前提是:在spring配置文件头部添加配置信息。如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
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">
例如在多个数据源中连接数据库的配置信息都是一样的,没有必要写两份相同属性的不同bean对象,可以采用util将配置信息得到复用。
数据源1
package com.powernode.spring6.beans;
import java.util.Properties;
public class MyDataSource1 {
// Properties属性类对象,这是一个Map集合,key和value都是String类型。
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "MyDataSource1{" +
"properties=" + properties +
'}';
}
}
数据源2
package com.powernode.spring6.beans;
import java.util.Properties;
public class MyDataSource2 {
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "MyDataSource2{" +
"properties=" + properties +
'}';
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
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命名空间
在spring的配置文件头部添加:
xmlns:util="http://www.springframework.org/schema/util"
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
-->
<util:properties id="prop">
<prop key="driver">com.mysql.cj.jdbc.Driver</prop>
<prop key="url">jdbc:mysql://localhost:3306/spring6</prop>
<prop key="username">root</prop>
<prop key="password">123</prop>
</util:properties>
<!--数据源1-->
<bean id="ds1" class="com.powernode.spring6.jdbc.MyDataSource1">
<property name="properties" ref="prop"/>
</bean>
<!--数据源2-->
<bean id="ds2" class="com.powernode.spring6.jdbc.MyDataSource2">
<property name="properties" ref="prop"/>
</bean>
</beans>
格式:
<util:properties id="prop">
<prop key="driver">com.mysql.cj.jdbc.Driver</prop>
<prop key="url">jdbc:mysql://localhost:3306/spring6</prop>
<prop key="username">root</prop>
<prop key="password">123</prop>
</util:properties>
<!--数据源1-->
<bean id="ds1" class="com.powernode.spring6.jdbc.MyDataSource1">
<property name="properties" ref="prop"/>
</bean>
<!--数据源2-->
<bean id="ds2" class="com.powernode.spring6.jdbc.MyDataSource2">
<property name="properties" ref="prop"/>
</bean>
4.7基于XML的自动装配
4.7.1根据名字自动装配
如果根据名称装配(byName),底层会调用set方法进行注入。
<!--根据名字进行自动装配-->
<!--注意:自动装配也是基于set方式实现的。-->
<bean id="orderService" class="com.powernode.spring6.service.OrderService" autowire="byName"></bean>
<!--根据名字进行自动装配的时候,被注入的对象的bean的id不能随便写,怎么写?set方法的方法名去掉set,剩下单词首字母小写。-->
<bean id="orderDao" class="com.powernode.spring6.dao.OrderDao"/>
这个配置起到关键作用:
OrderService Bean中需要添加autowire=“byName”,表示通过名称进行装配。
OrderService 类中有一个OrderDao属性,而OrderDao属性的名字是orderDao,对应的set方法是setOrderDao(),正好和OrderDao Bean的id是一样的。这就是根据名称自动装配。
<!--根据类型进行自动装配-->
<!--自动装配是基于set方法的-->
<!--根据类型进行自动装配的时候,在有效的配置文件当中,某种类型的实例只能有一个。-->
<bean class="com.powernode.spring6.dao.VipDao"></bean>
<bean id="x" class="com.powernode.spring6.dao.UserDao"></bean>
<!--如果byType,根据类型装配时,如果配置文件中有两个类型一样的bean会报错-->
<!--<bean id="y" class="com.powernode.spring6.dao.UserDao"></bean>-->
<bean id="cs" class="com.powernode.spring6.service.CustomerService" autowire="byType"></bean>
无论是byName还是byType,在装配的时候都是基于set方法的。所以set方法是必须要提供的。提供构造方法是不行的.
如果byType,根据类型装配时,如果配置文件中有两个类型一样的bean会报错
当byType进行自动装配的时候,配置文件中某种类型的Bean必须是唯一的,不能出现多个。
public class CustomerService {
private UserDao userDao;
private VipDao vipDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setVipDao(VipDao vipDao) {
this.vipDao = vipDao;
}
public void save(){
userDao.insert();
vipDao.insert();
}
}
<!--根据类型进行自动装配-->
<!--自动装配是基于set方法的-->
<!--根据类型进行自动装配的时候,在有效的配置文件当中,某种类型的实例只能有一个。-->
<bean class="com.powernode.spring6.dao.VipDao"></bean>
<bean id="x" class="com.powernode.spring6.dao.UserDao"></bean>
<!--如果byType,根据类型装配时,如果配置文件中有两个类型一样的bean会报错-->
<!--<bean id="y" class="com.powernode.spring6.dao.UserDao"></bean>-->
<bean id="cs" class="com.powernode.spring6.service.CustomerService" autowire="byType"></bean>
4.8spring引入外部属性配置文件
我们之前学的Mybatis,javaweb,jdbc中我们在封装数据库连接的配置信息时,都将相关连接数据库的信息单独写到了一个属性配置文件 jdbc.properties中,这样用户的连接数据库的信息修改时,用户无需关心代码内部,我们开发者也无需修改内部代码,用户只需要在文件中修改即可。
则新建一个jdbc.properties文件
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring6
jdbc.username=root
jdbc.password=123
则在spring.xml文件中引入该外部配置文件
第一步:引入context命名空间。
xmlns:context=“http://www.springframework.org/schema/context”
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
第二步:使用标签context:property-placeholder的location属性来指定属性配置文件的路径。
location默认从类的根路径下开始加载资源。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/beans/spring-context.xsd ">
<!--
引入外部的properties文件
第一步:引入context命名空间。
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
第二步:使用标签context:property-placeholder的location属性来指定属性配置文件的路径。
location默认从类的根路径下开始加载资源。
-->
<context:property-placeholder location="jdbc.properties"/>
<!--配置数据源-->
<bean id="ds" class="com.powernode.spring6.jdbc.MyDataSource">
<!--怎么取值呢?第三步:${key}-->
<property name="driver" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<!--加前缀是由于spring加载变量是优先从系统变量中进行加载,username系统变量已经存在-->
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
</beans>
五、Bean的作用域
5.1singleton
默认情况下,Spring的IoC容器创建的Bean对象是单例的。来测试一下:
@Test
public void testScope(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
SpringBean sb1 = applicationContext.getBean("sb", SpringBean.class);
System.out.println(sb1);
SpringBean sb2 = applicationContext.getBean("sb", SpringBean.class);
System.out.println(sb2);
}
通过测试得知:Spring的IoC容器中,默认情况下,Bean对象是单例的。
将测试程序中getBean()所在行代码注释掉:
@Test
public void testScope(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
}
通过测试得知,默认情况下,Bean对象的创建是在初始化Spring上下文的时候就完成的。
5.2prototype
如果想让Spring的Bean对象以多例的形式存在,可以在bean标签中指定scope属性的值为:prototype,这样Spring会在每一次执行getBean()方法的时候创建Bean对象,调用几次则创建几次。
在spring.xml文件中标签中加入属性scope=“prototype”
<?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="sb" class="com.powernode.spring6.beans.SpringBean" scope="prototype" />
</beans>
@Test
public void testScope(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
SpringBean sb1 = applicationContext.getBean("sb", SpringBean.class);
System.out.println(sb1);
SpringBean sb2 = applicationContext.getBean("sb", SpringBean.class);
System.out.println(sb2);
}
我们可以把测试代码中的getBean()方法所在行代码注释掉:
@Test
public void testScope(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
}
可以看到这一次在初始化Spring上下文的时候,并没有创建Bean对象。
那你可能会问:scope如果没有配置,它的默认值是什么呢?默认值是singleton,单例的。
5.3其他scope
scope属性的值不止两个,它一共包括8个选项:
● singleton:默认的,单例。
● prototype:原型。每调用一次getBean()方法则获取一个新的Bean对象。或每次注入的时候都是新对象。
● request:一个请求对应一个Bean。仅限于在WEB应用中使用。
● session:一个会话对应一个Bean。仅限于在WEB应用中使用。
● global session:portlet应用中专用的。如果在Servlet的WEB应用中使用global session的话,和session一个效果。(portlet和servlet都是规范。servlet运行在servlet容器中,例如Tomcat。portlet运行在portlet容器中。)
● application:一个应用对应一个Bean。仅限于在WEB应用中使用。
● websocket:一个websocket生命周期对应一个Bean。仅限于在WEB应用中使用。
● 自定义scope:很少使用。
六、GoF之工厂模式
● 设计模式:一种可以被重复利用的解决方案。
● GoF(Gang of Four),中文名——四人组。
● 《Design Patterns: Elements of Reusable Object-Oriented Software》(即《设计模式》一书),1995年由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著。这几位作者常被称为"四人组(Gang of Four)"。
● 该书中描述了23种设计模式。我们平常所说的设计模式就是指这23种设计模式。
● 不过除了GoF23种设计模式之外,还有其它的设计模式,比如:JavaEE的设计模式(DAO模式、MVC模式等)。
● GoF23种设计模式可分为三大类:
○ 创建型(5个):解决对象创建问题。
■ 单例模式
■ 工厂方法模式
■ 抽象工厂模式
■ 建造者模式
■ 原型模式
○ 结构型(7个):一些类或对象组合在一起的经典结构。
■ 代理模式
■ 装饰模式
■ 适配器模式
■ 组合模式
■ 享元模式
■ 外观模式
■ 桥接模式
○ 行为型(11个):解决类或对象之间的交互问题。
■ 策略模式
■ 模板方法模式
■ 责任链模式
■ 观察者模式
■ 迭代子模式
■ 命令模式
■ 备忘录模式
■ 状态模式
■ 访问者模式
■ 中介者模式
■ 解释器模式
● 工厂模式是解决对象创建问题的,所以工厂模式属于创建型设计模式。这里为什么学习工厂模式呢?这是因为Spring框架底层使用了大量的工厂模式。
6.1工厂模式的三种形态
工厂模式通常有三种形态:
● 第一种:简单工厂模式(Simple Factory):不属于23种设计模式之一。简单工厂模式又叫做:静态 工厂方法模式。简单工厂模式是工厂方法模式的一种特殊实现。
● 第二种:工厂方法模式(Factory Method):是23种设计模式之一。
● 第三种:抽象工厂模式(Abstract Factory):是23种设计模式之一。
6.1.1简单工厂模式
简单工厂模式的角色包括三个:
● 抽象产品 角色
● 具体产品 角色
● 工厂类 角色
简单工厂模式的代码如下:
抽象产品角色:
package com.powernode.factory;
/**
* 武器(抽象产品角色)
* @author 动力节点
* @version 1.0
* @className Weapon
* @since 1.0
**/
public abstract class Weapon {
/**
* 所有的武器都有攻击行为
*/
public abstract void attack();
}
具体产品角色
package com.powernode.factory;
/**
* 坦克(具体产品角色)
* @author 动力节点
* @version 1.0
* @className Tank
* @since 1.0
**/
public class Tank extends Weapon{
@Override
public void attack() {
System.out.println("坦克开炮!");
}
}
package com.powernode.factory;
/**
* 战斗机(具体产品角色)
* @author 动力节点
* @version 1.0
* @className Fighter
* @since 1.0
**/
public class Fighter extends Weapon{
@Override
public void attack() {
System.out.println("战斗机投下原子弹!");
}
}
package com.powernode.factory;
/**
* 匕首(具体产品角色)
* @author 动力节点
* @version 1.0
* @className Dagger
* @since 1.0
**/
public class Dagger extends Weapon{
@Override
public void attack() {
System.out.println("砍他丫的!");
}
}
工厂类角色
package com.powernode.factory;
/**
* 工厂类角色
* @author 动力节点
* @version 1.0
* @className WeaponFactory
* @since 1.0
**/
public class WeaponFactory {
/**
* 根据不同的武器类型生产武器
* @param weaponType 武器类型
* @return 武器对象
*/
public static Weapon get(String weaponType){
if (weaponType == null || weaponType.trim().length() == 0) {
return null;
}
Weapon weapon = null;
if ("TANK".equals(weaponType)) {
weapon = new Tank();
} else if ("FIGHTER".equals(weaponType)) {
weapon = new Fighter();
} else if ("DAGGER".equals(weaponType)) {
weapon = new Dagger();
} else {
throw new RuntimeException("不支持该武器!");
}
return weapon;
}
}
测试
package com.powernode.factory;
/**
* @author 动力节点
* @version 1.0
* @className Client
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
Weapon weapon1 = WeaponFactory.get("TANK");
weapon1.attack();
Weapon weapon2 = WeaponFactory.get("FIGHTER");
weapon2.attack();
Weapon weapon3 = WeaponFactory.get("DAGGER");
weapon3.attack();
}
}
简单工厂模式的优点:
● 客户端程序不需要关心对象的创建细节,需要哪个对象时,只需要向工厂索要即可,初步实现了责任的分离。客户端只负责“消费”,工厂负责“生产”。生产和消费分离。
简单工厂模式的缺点:
● 缺点1:工厂类集中了所有产品的创造逻辑,形成一个无所不知的全能类,有人把它叫做上帝类。显然工厂类非常关键,不能出问题,一旦出问题,整个系统瘫痪。
● 缺点2:不符合OCP开闭原则,在进行系统扩展时,需要修改工厂类。
Spring中的BeanFactory就使用了简单工厂模式。
6.1.2工厂方法模式
工厂方法模式既保留了简单工厂模式的优点,同时又解决了简单工厂模式的缺点。
工厂方法模式的角色包括:
● 抽象工厂角色
● 具体工厂角色
● 抽象产品角色
● 具体产品角色
抽象产品角色
package com.powernode.factory;
/**
* 武器类(抽象产品角色)
* @author 动力节点
* @version 1.0
* @className Weapon
* @since 1.0
**/
public abstract class Weapon {
/**
* 所有武器都有攻击行为
*/
public abstract void attack();
}
package com.powernode.factory;
/**
* 具体产品角色
* @author 动力节点
* @version 1.0
* @className Gun
* @since 1.0
**/
public class Gun extends Weapon{
@Override
public void attack() {
System.out.println("开枪射击!");
}
}
package com.powernode.factory;
/**
* 具体产品角色
* @author 动力节点
* @version 1.0
* @className Fighter
* @since 1.0
**/
public class Fighter extends Weapon{
@Override
public void attack() {
System.out.println("战斗机发射核弹!");
}
}
package com.powernode.factory;
/**
* 武器工厂接口(抽象工厂角色)
* @author 动力节点
* @version 1.0
* @className WeaponFactory
* @since 1.0
**/
public interface WeaponFactory {
Weapon get();
}
package com.powernode.factory;
/**
* 具体工厂角色
* @author 动力节点
* @version 1.0
* @className GunFactory
* @since 1.0
**/
public class GunFactory implements WeaponFactory{
@Override
public Weapon get() {
return new Gun();
}
}
package com.powernode.factory;
/**
* 具体工厂角色
* @author 动力节点
* @version 1.0
* @className FighterFactory
* @since 1.0
**/
public class FighterFactory implements WeaponFactory{
@Override
public Weapon get() {
return new Fighter();
}
}
package com.powernode.factory;
/**
* @author 动力节点
* @version 1.0
* @className Client
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
WeaponFactory factory = new GunFactory();
Weapon weapon = factory.get();
weapon.attack();
WeaponFactory factory1 = new FighterFactory();
Weapon weapon1 = factory1.get();
weapon1.attack();
}
}
如果想扩展一个新的产品,只要新增一个产品类,再新增一个该产品对应的工厂即可,例如新增:匕首
我们可以看到在进行功能扩展的时候,不需要修改之前的源代码,显然工厂方法模式符合OCP原则。
工厂方法模式的优点:
● 一个调用者想创建一个对象,只要知道其名称就可以了。
● 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
● 屏蔽产品的具体实现,调用者只关心产品的接口。
工厂方法模式的缺点:(类爆炸)
● 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
6.1.3抽象工厂模式
抽象工厂模式相对于工厂方法模式来说,就是工厂方法模式是针对一个产品系列的,而抽象工厂模式是针对多个产品系列的,即工厂方法模式是一个产品系列一个工厂类,而抽象工厂模式是多个产品系列一个工厂类。
抽象工厂模式特点:抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。它有多个抽象产品类,每个抽象产品类可以派生出多个具体产品类,一个抽象工厂类,可以派生出多个具体工厂类,每个具体工厂类可以创建多个具体产品类的实例。每一个模式都是针对一定问题的解决方案,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式针对的是多个产品等级结果。
抽象工厂中包含4个角色:
● 抽象工厂角色
● 具体工厂角色
● 抽象产品角色
● 具体产品角色
抽象工厂模式相对于工厂方法模式来说,就是工厂方法模式是针对一个产品系列的,而抽象工厂模式是针对多个产品系列的,即工厂方法模式是一个产品系列一个工厂类,而抽象工厂模式是多个产品系列一个工厂类。
抽象工厂模式特点:抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。它有多个抽象产品类,每个抽象产品类可以派生出多个具体产品类,一个抽象工厂类,可以派生出多个具体工厂类,每个具体工厂类可以创建多个具体产品类的实例。每一个模式都是针对一定问题的解决方案,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式针对的是多个产品等级结果。
抽象工厂中包含4个角色:
● 抽象工厂角色
● 具体工厂角色
● 抽象产品角色
● 具体产品角色
package com.powernode.product;
/**
* 武器产品族
* @author 动力节点
* @version 1.0
* @className Weapon
* @since 1.0
**/
public abstract class Weapon {
public abstract void attack();
}
package com.powernode.product;
/**
* 武器产品族中的产品等级1
* @author 动力节点
* @version 1.0
* @className Gun
* @since 1.0
**/
public class Gun extends Weapon{
@Override
public void attack() {
System.out.println("开枪射击!");
}
}
package com.powernode.product;
/**
* 武器产品族中的产品等级2
* @author 动力节点
* @version 1.0
* @className Dagger
* @since 1.0
**/
public class Dagger extends Weapon{
@Override
public void attack() {
System.out.println("砍丫的!");
}
}
package com.powernode.product;
/**
* 水果产品族
* @author 动力节点
* @version 1.0
* @className Fruit
* @since 1.0
**/
public abstract class Fruit {
/**
* 所有果实都有一个成熟周期。
*/
public abstract void ripeCycle();
}
package com.powernode.product;
/**
* 水果产品族中的产品等级1
* @author 动力节点
* @version 1.0
* @className Orange
* @since 1.0
**/
public class Orange extends Fruit{
@Override
public void ripeCycle() {
System.out.println("橘子的成熟周期是10个月");
}
}
package com.powernode.product;
/**
* 水果产品族中的产品等级2
* @author 动力节点
* @version 1.0
* @className Apple
* @since 1.0
**/
public class Apple extends Fruit{
@Override
public void ripeCycle() {
System.out.println("苹果的成熟周期是8个月");
}
}
package com.powernode.factory;
import com.powernode.product.Fruit;
import com.powernode.product.Weapon;
/**
* 抽象工厂
* @author 动力节点
* @version 1.0
* @className AbstractFactory
* @since 1.0
**/
public abstract class AbstractFactory {
public abstract Weapon getWeapon(String type);
public abstract Fruit getFruit(String type);
}
package com.powernode.factory;
import com.powernode.product.Dagger;
import com.powernode.product.Fruit;
import com.powernode.product.Gun;
import com.powernode.product.Weapon;
/**
* 武器族工厂
* @author 动力节点
* @version 1.0
* @className WeaponFactory
* @since 1.0
**/
public class WeaponFactory extends AbstractFactory{
public Weapon getWeapon(String type){
if (type == null || type.trim().length() == 0) {
return null;
}
if ("Gun".equals(type)) {
return new Gun();
} else if ("Dagger".equals(type)) {
return new Dagger();
} else {
throw new RuntimeException("无法生产该武器");
}
}
@Override
public Fruit getFruit(String type) {
return null;
}
}
package com.powernode.factory;
import com.powernode.product.*;
/**
* 水果族工厂
* @author 动力节点
* @version 1.0
* @className FruitFactory
* @since 1.0
**/
public class FruitFactory extends AbstractFactory{
@Override
public Weapon getWeapon(String type) {
return null;
}
public Fruit getFruit(String type){
if (type == null || type.trim().length() == 0) {
return null;
}
if ("Orange".equals(type)) {
return new Orange();
} else if ("Apple".equals(type)) {
return new Apple();
} else {
throw new RuntimeException("我家果园不产这种水果");
}
}
}
package com.powernode.client;
import com.powernode.factory.AbstractFactory;
import com.powernode.factory.FruitFactory;
import com.powernode.factory.WeaponFactory;
import com.powernode.product.Fruit;
import com.powernode.product.Weapon;
/**
* @author 动力节点
* @version 1.0
* @className Client
* @since 1.0
**/
public class Client {
public static void main(String[] args) {
// 客户端调用方法时只面向AbstractFactory调用方法。
AbstractFactory factory = new WeaponFactory(); // 注意:这里的new WeaponFactory()可以采用 简单工厂模式 进行隐藏。
Weapon gun = factory.getWeapon("Gun");
Weapon dagger = factory.getWeapon("Dagger");
gun.attack();
dagger.attack();
AbstractFactory factory1 = new FruitFactory(); // 注意:这里的new FruitFactory()可以采用 简单工厂模式 进行隐藏。
Fruit orange = factory1.getFruit("Orange");
Fruit apple = factory1.getFruit("Apple");
orange.ripeCycle();
apple.ripeCycle();
}
}
抽象工厂模式的优缺点:
● 优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
● 缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在AbstractFactory里加代码,又要在具体的里面加代码。
七、Bean的实例化方式
Spring为Bean提供了多种实例化方式,通常包括4种方式。(也就是说在Spring中为Bean对象的创建准备了多种方案,目的是:更加灵活)
● 第一种:通过构造方法实例化
● 第二种:通过简单工厂模式实例化
● 第三种:通过factory-bean实例化
● 第四种:通过FactoryBean接口实例化
7.1通过构造方法实例化
package com.powernode.spring6.bean;
/**
* @author 动力节点
* @version 1.0
* @className User
* @since 1.0
**/
public class User {
public User() {
System.out.println("User类的无参数构造方法执行。");
}
}
<?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="userBean" class="com.powernode.spring6.bean.User"/>
</beans>
package com.powernode.spring6.test;
import com.powernode.spring6.bean.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 动力节点
* @version 1.0
* @className SpringInstantiationTest
* @since 1.0
**/
public class SpringInstantiationTest {
@Test
public void testConstructor(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("userBean", User.class);
System.out.println(user);
}
}
7.2通过简单工厂模式实例化
在工厂类中利用get方法返回实例化的对象,在spring.xml中利用工厂类的get方法管理bean对象,先实例化工厂类,再实例化管理的bean对象,get方法用factory-method属性指定。
public class VipFactory{
public static Vip get(){
return new Vip();
}
}
<bean id="vipBean" class="com.powernode.spring6.bean.VipFactor" factory-method="get">
@Test
public void testSimpleFactory(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Vip vip = applicationContext.getBean("vipBean", Vip.class);
System.out.println(vip);
}
7.3通过factory-bean实例化
这种方式本质上是:通过工厂方法模式进行实例化。
package com.powernode.spring6.bean;
/**
* @author 动力节点
* @version 1.0
* @className Order
* @since 1.0
**/
public class Order {
}
package com.powernode.spring6.bean;
/**
* @author 动力节点
* @version 1.0
* @className OrderFactory
* @since 1.0
**/
public class OrderFactory {
public Order get(){
return new Order();
}
}
<bean id="orderFactory" class="com.powernode.spring6.bean.OrderFactory"/>
<bean id="orderBean" factory-bean="orderFactory" factory-method="get"/>
7.4通过FactoryBean接口实例化
以上的第三种方式中,factory-bean是我们自定义的,factory-method也是我们自己定义的。
在Spring中,当你编写的类直接实现FactoryBean接口之后,factory-bean不需要指定了,factory-method也不需要指定了。
factory-bean会自动指向实现FactoryBean接口的类,factory-method会自动指向getObject()方法。
package com.powernode.spring6.bean;
/**
* @author 动力节点
* @version 1.0
* @className Person
* @since 1.0
**/
public class Person {
}
package com.powernode.spring6.bean;
import org.springframework.beans.factory.FactoryBean;
/**
* @author 动力节点
* @version 1.0
* @className PersonFactoryBean
* @since 1.0
**/
public class PersonFactoryBean implements FactoryBean<Person> {
@Override
public Person getObject() throws Exception {
return new Person();
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
// true表示单例
// false表示原型
return true;
}
}
<bean id="personBean" class="com.powernode.spring6.bean.PersonFactoryBean"/>
@Test
public void testFactoryBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Person personBean = applicationContext.getBean("personBean", Person.class);
System.out.println(personBean);
Person personBean2 = applicationContext.getBean("personBean", Person.class);
System.out.println(personBean2);
}
FactoryBean在Spring中是一个接口。被称为“工厂Bean”。“工厂Bean”是一种特殊的Bean。所有的“工厂Bean”都是用来协助Spring框架来创建其他Bean对象的。
FactoryBean是单例的;
7.5BeanFactory和FactoryBean的区别
7.5.1 BeanFactory
Spring IoC容器的顶级对象,BeanFactory被翻译为“Bean工厂”,在Spring的IoC容器中,“Bean工厂”负责创建Bean对象。
BeanFactory是工厂。
7.5.2 FactoryBean
FactoryBean:它是一个Bean,是一个能够辅助Spring实例化其它Bean对象的一个Bean。
在Spring中,Bean可以分为两类:
● 第一类:普通Bean
● 第二类:工厂Bean(记住:工厂Bean也是一种Bean,只不过这种Bean比较特殊,它可以辅助Spring实例化其它Bean对象。)
7.6注入自定义Date
在前面已知,Date是被当做简单类型,简单类型在注入的时候可以直接使用value或value标签来完成,但是对于Date类型来说,用value属性或value标签赋值的时候,对Date字符串的要求是非常严格的,必须是Mon Oct 14:30:26 CST 2022.
其他格式是不被识别的。
例如 利用set注入管理StudentBean对象
public class Student{
private Date birth;
public void setBirth(Date birth){
this.birth=birth;
}
@Override
public String toString() {
return "Student{" +
"birth=" + birth +
'}';
}
}
spring.xml
<bean id="studentBean" class="com.powernode.spring6.bean.Student">
<property name="birth" value="Mon Oct 10 14:30:26 CST 2002"/>
</bean>
@Test
public void testDate(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student studentBean = applicationContext.getBean("studentBean", Student.class);
System.out.println(studentBean);
}
但是换个角度想一下,这个Date字符串的格式根本不符合我们中国人的思维啊,我们能不能按任意格式写呢,按照我们的思维是指定这个格式呢?
很荣幸的讲:完全可以哒,通过工厂模式对Date进行加工,加工出我们想要的Date格式。
编写DateFactoryBean实现FactoryBean接口
public class DateFactoryBean implements FactoryBean<Date>{
private String date;
/ 通过构造方法给日期字符串属性赋值
public DateFactoryBean(String date) {
this.date = date;
}
@Override
public Date getObject() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.parse(this.date);
}
@Override
public Class<?> getObjectType() {
return null;
}
}
spring.xml
<bean id="dateBean" class="com.powernode.spring6.bean.DateFactoryBean">
<constructor-arg name="date" value="1990-10-11"/>
</bean>
<bean id="studentBean" class="com.powernode.spring6.bean.Student">
<property name="birth" ref="dateBean"/>
</bean>
八、Bean的生命周期
8.1 什么是Bean的生命周期
Spring其实就是一个管理Bean对象的工厂。它负责对象的创建,对象的销毁等。
所谓的生命周期就是:对象从创建开始到最终销毁的整个过程。
什么时候创建Bean对象?
创建Bean对象的前后会调用什么方法?
Bean对象什么时候销毁?
Bean对象的销毁前后调用什么方法?
8.2 为什么要知道Bean的生命周期
其实生命周期的本质是:在哪个时间节点上调用了哪个类的哪个方法。
我们需要充分的了解在这个生命线上,都有哪些特殊的时间节点。
只有我们知道了特殊的时间节点都在哪,到时我们才可以确定代码写到哪。
我们可能需要在某个特殊的时间点上执行一段特定的代码,这段代码就可以放到这个节点上。当生命线走到这里的时候,自然会被调用。
8.3 Bean的生命周期之5步
Bean生命周期的管理,可以参考Spring的源码:AbstractAutowireCapableBeanFactory类的doCreateBean()方法。
Bean生命周期可以粗略的划分为五大步:
● 第一步:实例化Bean(调用Bean对象的无参构造方法)
● 第二步:Bean属性赋值(调用set)
● 第三步:初始化Bean
● 第四步:使用Bean
● 第五步:销毁Bean
编写程序
package org.example;
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("第二步:给Bean对象属性赋值,利用set方法...");
}
public void init(){
System.out.println("第三步:初始化Bean对象...");
}
public void destory(){
System.out.println("第五步:销毁Bean对象...");
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
public User() {
System.out.println("第一步,实例化Bean对象,调用User的无参构造方法...");
}
public User(String name) {
this.name = name;
}
}
<?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 = "userBean" class="org.example.User" init-method="init" destroy-method="destory">
<property name="name" value="李东旭"/>
</bean>
</beans>
import org.example.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserTest {
@Test
public void testUserfive(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User userBean = applicationContext.getBean("userBean", User.class);
System.out.println(userBean);
System.out.println("第四步:使用userBean");
//必须手动关闭Spring,这样Spring容器才会销毁Bean对象
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
context.close();
}
}
注意
第一:只有正常关闭spring容器,bean的销毁方法才会被调用。
第二:ClassPathXmlApplicationContext类才有close()方法。
第三:在spring.xml配置文件中的标签中的属性init-method指定初始化方法,destroy-method指定销毁方法。
8.4Bean生命周期之七步
在以上的5步中,第3步是初始化Bean,如果你还想在初始化前和初始化后添加代码,可以加入“Bean后处理器”。
“Bean后处理器”编写一个类实现BeanPostProcessor类
,并且重写before和after方法
Bean生命周期七步:比五步添加的那两步在哪里?在初始化Bean的前和后。
第一步:实例化Bean
第二步:Bean属性赋值
第三步:执行“Bean后处理器”的before方法。
第四步:初始化Bean
第五步:执行“Bean后处理器”的after方法。
第六步:使用Bean
第七步:销毁Bean
注意还要在spring.xml文件中对LogBeanPostProcessor进行配置
要让这个类作用域所有的bean对象
/**
* 日志Bean后处理器。
**/
public class LogBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第三步:执行Bean后处理器的before方法。");
// return 代码不用修改,不用动
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
// 方法有两个参数:
// 第一个参数:刚创建的bean对象。
// 第二个参数:bean的名字。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第五步:执行Bean后处理器的after方法。");
// return 代码不用修改,不用动
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
<!--配置Bean后处理器。-->
<!--注意:这个Bean后处理器将作用于整个配置文件中所有的bean。-->
<!-- 这个Bean后处理器也只在当前配置文件起作用 -->
<bean class="com.powernode.spring6.bean.LogBeanPostProcessor"/>
<!--需要手动指定初始化方法,和销毁方法。-->
<!-- init-method="initBean" 配置对象的初始化方法 -->
<!-- destroy-method="destroyBean" 配置对象的销毁方法 -->
<bean id="user" class="com.powernode.spring6.bean.User"
init-method="initBean" destroy-method="destroyBean">
<property name="name" value="zhangsan"/>
</bean>
public class User{
private String name;
public User() {
System.out.println("第一步:无参数构造方法执行。");
}
public void setName(String name) {
System.out.println("第二步:给对象的属性赋值。");
this.name = name;
}
// 这个方法需要自己写,自己配。方法名随意。
public void initBean(){
System.out.println("第四步:初始化Bean。");
}
// 这个方法需要自己写,自己配。方法名随意。
public void destroyBean(){
System.out.println("第七步:销毁Bean。");
}
}
@Test
public void testBeanLifecycleFive(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println("第六步:使用Bean:" + user);
// 注意:必须手动关闭Spring容器,这样Spring容器才会销毁Bean.
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
context.close();
}
一定要注意:在spring.xml文件中配置的Bean后处理器将作用于当前配置文件中所有的Bean,这样才能完成七步曲。
8.5Bean生命周期之10步
Bean生命周期十步:比七步添加的那三步在哪里?
点位1:在“Bean后处理器”before方法之前
干了什么事儿?
检查Bean是否实现了Aware相关的接口,如果实现了接口则调用这些接口中的方法。
然后调用这些方法的目的是为了给你传递一些数据,让你更加方便使用。
点位2:在“Bean后处理器”before方法之后
干了什么事儿?
检查Bean是否实现了InitializingBean接口,如果实现了,则调用接口中的方法。
点位3:使用Bean之后,或者说销毁Bean之前
干了什么事儿?
检查Bean是否实现了DisposableBean接口,如果实现了,则调用接口中的方法。
添加的这三个点位的特点:都是在检查你这个Bean是否实现了某些特定的接口,如果实现了这些接口,则Spring容器会调用这个接口中的方法。
上图中检查Bean是否实现了Aware的相关接口是什么意思?
Aware相关的接口包括:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware
● 当Bean实现了BeanNameAware,Spring会将Bean的名字传递给Bean。
● 当Bean实现了BeanClassLoaderAware,Spring会将加载该Bean的类加载器传递给Bean。
● 当Bean实现了BeanFactoryAware,Spring会将Bean工厂对象传递给Bean。
测试以上10步,可以让User类实现5个接口,并实现所有方法:
● BeanNameAware
● BeanClassLoaderAware
● BeanFactoryAware
● InitializingBean
● DisposableBean
package com.powernode.spring6.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
/**
* @author 动力节点
* @version 1.0
* @className User
* @since 1.0
**/
public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
private String name;
public User() {
System.out.println("1.实例化Bean");
}
public void setName(String name) {
this.name = name;
System.out.println("2.Bean属性赋值");
}
public void initBean(){
System.out.println("6.初始化Bean");
}
public void destroyBean(){
System.out.println("10.销毁Bean");
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("3.类加载器:" + classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("3.Bean工厂:" + beanFactory);
}
@Override
public void setBeanName(String name) {
System.out.println("3.bean名字:" + name);
}
@Override
public void destroy() throws Exception {
System.out.println("9.DisposableBean destroy");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("5.afterPropertiesSet执行");
}
}
package com.powernode.spring6.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* @author 动力节点
* @version 1.0
* @className LogBeanPostProcessor
* @since 1.0
**/
public class LogBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("4.Bean后处理器的before方法执行,即将开始初始化");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("7.Bean后处理器的after方法执行,已完成初始化");
return bean;
}
}
通过测试可以看出来:
● InitializingBean的方法早于init-method的执行。
● DisposableBean的方法早于destroy-method的执行。
对于SpringBean的生命周期,掌握之前的7步即可。够用。
8.6 Bean的作用域不同,管理方式不同
Spring 根据Bean的作用域来选择管理方式。
● 对于singleton作用域的Bean,Spring 能够精确地知道该Bean何时被创建,何时初始化完成,以及何时被销毁;
● 而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。
8.6.1自己new的对象让Spring管理
@Test
public void testRegisterBean(){
// 自己new的对象
Student student = new Student();
System.out.println(student);
// 将以上自己new的这个对象纳入Spring容器来管理。半路上交给Spring来管理。
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 自己new的对象注册到spring容器中
factory.registerSingleton("studentBean", student);
// 从spring容器中获取
Object studentBean = factory.getBean("studentBean");
System.out.println(studentBean);
}
九、Bean的循环依赖问题
A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。
比如:丈夫类Husband,妻子类Wife。Husband中有Wife的引用。Wife中有Husband的引用。
package com.powernode.spring6.bean;
/**
* @author 动力节点
* @version 1.0
* @className Husband
* @since 1.0
**/
public class Husband {
private String name;
private Wife wife;
}
package com.powernode.spring6.bean;
/**
* @author 动力节点
* @version 1.0
* @className Wife
* @since 1.0
**/
public class Wife {
private String name;
private Husband husband;
}
9.1 singleton+setter模式解决循环依赖
package com.powernode.spring6.bean;
/**
* @author 动力节点
* @version 1.0
* @className Husband
* @since 1.0
**/
public class Husband {
private String name;
private Wife wife;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setWife(Wife wife) {
this.wife = wife;
}
// toString()方法重写时需要注意:不能直接输出wife,输出wife.getName()。要不然会出现递归导致的栈内存溢出错误。
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
package com.powernode.spring6.bean;
/**
* @author 动力节点
* @version 1.0
* @className Wife
* @since 1.0
**/
public class Wife {
private String name;
private Husband husband;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
// toString()方法重写时需要注意:不能直接输出husband,输出husband.getName()。要不然会出现递归导致的栈内存溢出错误。
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
<?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="husbandBean" class="com.powernode.spring6.bean.Husband" scope="singleton">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="singleton">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
</bean>
</beans>
package com.powernode.spring6.test;
import com.powernode.spring6.bean.Husband;
import com.powernode.spring6.bean.Wife;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 动力节点
* @version 1.0
* @className CircularDependencyTest
* @since 1.0
**/
public class CircularDependencyTest {
@Test
public void testSingletonAndSet(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
System.out.println(husbandBean);
System.out.println(wifeBean);
}
}
1、通过测试得知:在singleton + set注入的情况下,循环依赖是没有问题的。Spring可以解决这个问题。
2、通过测试得知,当循环依赖的**所有Bean的scope=“prototype”**的时候,产生的循环依赖,Spring是无法解决的,会出现BeanCurrentlyInCreationException异常。
3、大家可以测试一下,以上两个Bean,如果其中一个是singleton,另一个是prototype,是没有问题的。
4、构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的。
9.2 Spring解决循环依赖的机理
Spring为什么可以解决set + singleton模式下循环依赖?
根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。
实例化Bean的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。
给Bean属性赋值的时候:调用setter方法来完成。
两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。
也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。
那么在Spring框架底层源码级别上是如何实现的呢?请看:
在以上类中包含三个重要的属性:
Cache of singleton objects: bean name to bean instance.
单例对象的缓存:key存储bean名称,value存储Bean对象【一级缓存】
Cache of early singleton objects: bean name to bean instance.
早期单例对象的缓存:key存储bean名称,value存储早期的Bean对象【二级缓存】
Cache of singleton factories: bean name to ObjectFactory.
单例工厂缓存:key存储bean名称,value存储该Bean对应的ObjectFactory对象【三级缓存】
这三个缓存其实本质上是三个Map集合。
1/一级缓存存储的是:单例Bean对象。
完整的单例Bean对象,也就是说这个缓存中的Bean对象的属性都已经赋值了。是一个完整的Bean对象。
2/二级缓存存储的是:早期的单例Bean对象。
这个缓存中的单例Bean对象的属性没有赋值。只是一个早期的实例对象。
3/三级缓存存储的是:单例工厂对象。
这个里面存储了大量的“工厂对象”,每一个单例Bean对象都会对应一个单例工厂对象。这个集合中存储的是,创建该单例对象时对应的那个单例工厂对象。
我们再来看,在该类中有这样一个方法addSingletonFactory(),这个方法的作用是:将创建Bean对象的ObjectFactory对象提前曝光。
从源码中可以看到,spring会先从一级缓存中获取Bean,如果获取不到,则从二级缓存中获取Bean,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的ObjectFactory对象,通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题。
总结:
Spring只能解决setter方法注入的单例bean之间的循环依赖。ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。
Spring6老杜【下】欲知后事如何,且听下回分解