【Java SpringIOC与ID随感录】 基于 XML 的 Bean 装配
前言
我们知道了 Spring 是⼀个开源框架,他让我们的开发更加简单。他⽀持⼴泛的应⽤场 景,有着活跃⽽庞⼤的社区,这也是 Spring 能够⻓久不衰的原因。
这里来举个例子:
开发业务逻辑层一般是:控制层、业务逻辑层、持久层。
SoldierServlet - 控制层 SoldierService soldierService = new SoldierServicelmpl(); SoldierService - 业务逻辑层 SoldierServicelmpl SoldierDao soldierDao = new SoldierDaolmpl(); SoldierDao - 持久层 SoldierDaolmpl
SoldierServlet - 控制层 SoldierService soldierService = new SoldierServicelmpl() -> (导致功能失效); SoldierService - 业务逻辑层 SoldierServicelmpl SoldierDao soldierDao = new SoldierDaolmpl() -> SoldierDaolmpl2; SoldierDao - 持久层 SoldierDaolmpl -> SoldierDaolmpl2
如果现在需要把 SoldierDaolmpl 改成 SoldierDaolmpl2 。那么 SoldierService 层也要接着改,而且这样会导致 SoldierServlet 层用不了。下层改动,导致上层不得不改动。我们将这个现象称之为层与层之间的耦合。在开发中,我们要尽量降低层之间的耦合。
那么Spring是怎么解决这个问题的呢?
SoldierServlet - 控制层 SoldierService soldierService = null; SoldierService - 业务逻辑层 SoldierServicelmpl SoldierDao soldierDao = null; SoldierDao - 持久层 SoldierDaolmpl -> SoldierDaolmpl2
将 soldierService 与 soldierDao 的值置为空,那么就算底层 SoldierDao 改动了,也不会影响上层。虽然这种方法解决了耦合,但是这样是肯定不行,会报 NullPointerException。因此,不能让它等于null。怎么解决呢?解决方法:在调用它的方法之前给它赋值。
那么此时就会存在两个问题:
① 对象实例谁去创建 ② 谁负责给这个变量赋值 之前,这个对象的创建以及变量的赋值都是程序员负责的。而现在Spring提供了一个工厂BeanFactory,这个工厂帮我们解决这两个问题。
- 对象的创建
- 依赖关系的维护
也就是说,对象的创建和依赖关系的维护从程序员的手中转移到 BeanFactory 我们将这个现象称之为控制反转(IOC),在 BeanFactory 工厂中负责注入每一个依赖关系的这种行为称之为(ID)。
控制:组件的生命周期的控制权 反转:控制权从程序员手中反转到 IOC 容器 本章知识就是基于 XML 配置文件去理解 SpringIOC 的使用。
前期回顾:【Java Maven框架】
目录
前言
快速搭建spring项目
1.添加依赖
2.创建实体类
3.配置 XML 文件
4.测试获取 UserDemo 实例对象
基于 XML 的 Bean 装配
SpringIOC与反射
属性赋值
setter 注入对象属性
ref 注入引用对象
内部 bean 的注入
引入外部 Properties 配置参数
构造器注入
p 名称空间注入
集合类型的注入
Bean 的作用域
bean 的初始化与销毁方法
快速搭建spring项目
1.添加依赖
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>6.1.14</spring.version>
<junit.version>5.11.3</junit.version>
<lombok.version>1.18.34</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
2.创建实体类
@Data
public class UserDemo {
private String Username;
public void User(){
System.out.println(Username+"欢迎用户登入");
}
}
注解使用效果: 使用 @Data 后,Lombok 会自动为 UserDemo 类的 name 字段生成 getter、setter、构造函数、toString 和 equals、hashCode 方法。
3.配置 XML 文件
在 resources 包下创建名字为 applicationContext.xml 的IOC配置文件,描述 UserDemo 这个类。
<?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 -->
<bean id="u01" class="com.thz.UserDemo">
<property name="Username" value="东方"/>
</bean>
</beans>
4.测试获取 UserDemo 实例对象
这样就不是程序员自己去 new 对象了,而是交给 IOC 容器,让它帮我们创建对象。
public class UserDemoTest {
@Test
public void UserTest() {
// 获取IOC容器
BeanFactory beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从IOC容器中取出userDemo
UserDemo userDemo = (UserDemo) beanFactory.getBean("u01");
userDemo.User();
}
}
基于 XML 的 Bean 装配
SpringIOC与反射
Spring的IOC容器实质上也是通过反射技术去创建bean实例,以上面代码为例,利用反射去创建对象实例并获取。
// 创建对象实例
Class clazz = Class.forName("com.thz.UserDemo");
clazz.getDeclaredConstructor().newInstance();
// 获取对象实例
UserDemo userDemo = (UserDemo) clazz.getDeclaredConstructor().newInstance();
userDemo.setUsername("东方");
userDemo.User();
运行结果:
东方欢迎用户登入
属性赋值
setter 注入对象属性
<bean id="u01" class="com.thz.UserDemo">
<property name="Username" value="东方"/>
</bean>
我们的实体类的属性赋值,是 IOC 通过 property 中的 value 标签直接赋值给对象属性吗?其实并不然,IOC 是通过 setter 来注入对象属性。setter 注入简单来说就是调用类的 set 方法对该类属性进行赋值。
public class UserDemo {
private String Username;
public void User(){
System.out.println(Username+"欢迎用户登入");
}
public void setUsername(String username) {
System.out.println("正在注入依赖~");
Username = username;
}
}
...
<bean id="u01" class="com.thz.UserDemo">
<property name="Username" value="东方"/>
</bean>
...
我们可以测试一下,取消掉注解并在 set 方法这里添加一行 print 语句观察打印结果如何?
运行结果:
正在注入依赖~
东方欢迎用户登入
发现 SpringIOC 在赋值类属性时自动调用了 set 方法。使用 setter 注入的好处就是写法上比较直观。
ref 注入引用对象
mxl 配置文件
<?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="u01" class="com.thz.UserDemo">
<property name="Username" value="东方"/>
<property name="books" ref="b01"/>
</bean>
<bean id="b01" class="com.thz.Book">
<property name="bookName" value="楚辞"/>
<property name="author" value="屈原"/>
<property name="bookPrice" value="9.9"/>
</bean>
</beans>
Book类
@Data
public class Book {
private String bookName;
private String author;
private double bookPrice;
}
UserDemo类
@Data
public class UserDemo {
private String Username;
private Book books;
public void User(){
System.out.println(Username+"购买了"+ books.getAuthor()+"作者的"+books.getBookName()+"作品,花了"+books.getBookPrice()+"元~");
}
}
运行结果:
东方购买了屈原作者的楚辞作品,花了9.9元~
标签 | 描述 |
---|---|
ref | 通过 bean 的 id 引用另一个 bean |
只要声明到 ioc 容器的 bean 对象,都可被其他 bean 引用。
内部 bean 的注入
内部 bean 与内部类差不多就是 bean 包 bean ,这样就避免了使用 ref 引用。
<bean id="u01" class="com.thz.UserDemo">
<property name="Username" value="东方"/>
<property name="books">
<bean class="com.thz.Book">
<property name="bookName" value="楚辞"/>
<property name="author" value="屈原"/>
<property name="bookPrice" value="9.9"/>
</bean>
</property>
</bean>
引入外部 Properties 配置参数
在 resources 资源包下新增一个 properties 文件 books.properties,写入 Book 对应属性:
bookName=MySQL
author=Monty
bookPrice=9.9
XML配置文件:
<!-- 先引入 properties 文件 -->
<context:property-placeholder location="classpath:books.properties"/>
<bean id="u01" class="com.thz.UserDemo">
<property name="Username" value="东方"/>
<property name="books">
<bean class="com.thz.Book">
<property name="bookName" value="${bookName}"/>
<property name="author" value="${author}"/>
<property name="bookPrice" value="${bookPrice}"/>
</bean>
</property>
</bean>
</beans>
运行结果:
东方购买了Monty作者的MySQL作品,花了9.9元~
这也算是 xml 的常见用法了,虽然现在用的大多是注解。我们在 xml 中导入 properties 文件;在利用 ${} 对类中对应属性进行提取,在通过 setter 注入。这样修改一些属性的时候便可以直接对文件进行操作,就避免了去看复杂的 xml 配置文件了。
构造器注入
依旧以上面的例子为例:
Book类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
private String bookName;
private String author;
private double bookPrice;
}
@NoArgsConstructor、@AllArgsConstructor 都是 Lombok 提供的注解,作用如下
注解 | 作用 |
---|---|
@NoArgsConstructor | 在类上使用,这个注解可以生成无参构造方法 |
@AllArgsConstructor | 在类上使用,这个注解可以自动生成一个包含所有实例变量的构造函数 |
xml 配置文件
<bean id="u01" class="com.thz.UserDemo">
<property name="Username" value="东方"/>
<property name="books">
<bean class="com.thz.Book">
<constructor-arg value="诗经"/>
<constructor-arg value="孔子"/>
<constructor-arg value="9.9"/>
</bean>
</property>
</bean>
使用构造器注入,类中一定要有一个无参构造方法。如果没有就会报错。构造注入使用的标签是 <constructor-arg> 必须按照类中定义的顺序进行注入,否则抛出 BeanCreationException 类型不匹配异常。
不过我们可以使用 index[索引] 来解决这个问题:
<bean id="u01" class="com.thz.UserDemo">
<property name="Username" value="东方"/>
<property name="books">
<bean class="com.thz.Book">
<constructor-arg value="9.9" index="2"/>
<constructor-arg value="诗经"/>
<constructor-arg value="孔子"/>
</bean>
</property>
</bean>
p 名称空间注入
p名称空间注入走的也是 setter 方法,简化 setter 注入需要 property 标签这个步骤,一步到位。
<bean id="u01" class="com.thz.UserDemo" p:username="东方" p:books-ref="b01"/>
<bean id="b01" class="com.thz.Book" p:bookName="诗经" p:author="孔子" p:bookPrice="9.9"/>
集合类型的注入
Spring框架支持注入集合类型的数据,下面将通过一个例子来说明如何使用Spring框架注入集合类型的数据:
假设我们的实体类包含数组类型、List类型、Set类型、Map类型等数据,具体如下:
Book类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
private String bookName;
private String author;
private double bookPrice;
}
Student类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private String StudentName;
private Book[] books;
private List<Book> bookList;
private Set<Book> bookSet;
private Map<String,Book> bookMap;
}
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"
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">
<bean id="book1" class="com.thz.Book" p:bookName="诗经" p:author="孔子" p:bookPrice="9.9"/>
<bean id="book2" class="com.thz.Book" p:bookName="离骚" p:author="屈原" p:bookPrice="19.9"/>
<bean id="book3" class="com.thz.Book" p:bookName="吕氏春秋" p:author="吕不韦" p:bookPrice="29.9"/>
<bean id="book4" class="com.thz.Book" p:bookName="平凡世界" p:author="路遥" p:bookPrice="59.9"/>
<bean id="student1" class="com.thz.Student">
<property name="studentName" value="东方"/>
<!-- 数组注入 -->
<property name="books">
<array>
<ref bean="book1"/>
<ref bean="book2"/>
<ref bean="book3"/>
</array>
</property>
<!-- list注入 -->
<property name="bookList">
<list>
<ref bean="book2"/>
<ref bean="book3"/>
</list>
</property>
<!-- set注入 -->
<property name="bookSet">
<set>
<ref bean="book3"/>
<ref bean="book4"/>
</set>
</property>
<!-- map注入 -->
<property name="bookMap">
<map>
<entry key="001" value-ref="book1"/>
<entry key="002" value-ref="book2"/>
<entry key="003" value-ref="book3"/>
<entry key="004" value-ref="book4"/>
</map>
</property>
</bean>
</beans>
测试类
public class StudentDemoTest {
@Test
public void StudentTest() throws Exception {
// 获取IOC容器
BeanFactory beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从IOC容器中取出Student
Student student = (Student) beanFactory.getBean("student1");
System.out.println(student);
}
}
Bean 的作用域
常用作用域:
取值 | 含义 | 创建对象的时机 | 默认值 |
---|---|---|---|
singleton | 在 IOC容器中,这个 bean 的对象始终为单实例 | I0C容器初始化时 | 是 |
prototype | 这个 bean 在 IOC 容器中有多个实例 | 获取 bean 时 | 否 |
案例:
Book类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
private String bookName;
private String author;
private double bookPrice;
}
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"
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">
<bean name="book1" class="com.thz.Book" p:bookName="诗经" p:author="孔子" p:bookPrice="9.9" scope="singleton"/>
</beans>
测试代码:
public class BookDemoTest {
private BeanFactory beanFactory;
@BeforeEach
public void setUp() {
beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml");
}
@Test
public void BookTest() {
Book book1 = (Book)beanFactory.getBean("book1");
Book book2 = (Book)beanFactory.getBean("book1");
System.out.println(book1 == book2);
}
}
运行结果为:
true
我们发现结果为 true ,说明创建的是同一个对象,所以这种默认标签 singleton 创建的是单例的。将标签改成 prototype 发现结果为 false。说明它不是单例的。
bean 的初始化与销毁方法
案例:
Book类
@Data
public class Book {
public Book(){
System.out.println("Book 正在被创建");
}
public void BookInit(){
System.out.println("Book 正在初始化");
}
public void BookDestroy(){
System.out.println("Book 正在被销毁");
}
}
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 name="Book1" class="com.thz.Book" init-method="BookInit" destroy-method="BookDestroy"/>
</beans>
测试代码:
public class BookTest {
private BeanFactory beanFactory;
@BeforeEach
public void setUp() {
beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml");
}
@Test
public void Test(){
System.out.println(beanFactory.getBean("Book1"));
((ClassPathXmlApplicationContext)beanFactory).close();
}
}
运行结果:
Book 正在被创建
Book 正在初始化
Book 正在被销毁
通过以上案例,我们可以知道:可以在 XML 文件中使用 init-method、destroy-method 来指定初始化与销毁时的内容。