【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 文件](#3.配置 XML 文件)

[4.测试获取 UserDemo 实例对象](#4.测试获取 UserDemo 实例对象)

[基于 XML 的 Bean 装配](#基于 XML 的 Bean 装配)

SpringIOC与反射

属性赋值

[setter 注入对象属性](#setter 注入对象属性)

[ref 注入引用对象](#ref 注入引用对象)

[内部 bean 的注入](#内部 bean 的注入)

[引入外部 Properties 配置参数](#引入外部 Properties 配置参数)

构造器注入

[p 名称空间注入](#p 名称空间注入)

集合类型的注入

[Bean 的作用域](#Bean 的作用域)

[bean 的初始化与销毁方法](#bean 的初始化与销毁方法)

快速搭建spring项目


1.添加依赖

java 复制代码
    <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.创建实体类

java 复制代码
@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 这个类。

java 复制代码
<?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 容器,让它帮我们创建对象。

java 复制代码
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实例++,以上面代码为例,利用反射去创建对象实例并获取。

java 复制代码
        // 创建对象实例
        Class clazz = Class.forName("com.thz.UserDemo");
        clazz.getDeclaredConstructor().newInstance();
        // 获取对象实例
        UserDemo userDemo = (UserDemo) clazz.getDeclaredConstructor().newInstance();
        userDemo.setUsername("东方");
        userDemo.User();

运行结果:

java 复制代码
东方欢迎用户登入

属性赋值


setter 注入对象属性

java 复制代码
    <bean id="u01" class="com.thz.UserDemo">
        <property name="Username" value="东方"/>
    </bean>

我们的实体类的属性赋值,是 IOC 通过 property 中的 value 标签直接赋值给对象属性吗?其实并不然,IOC 是通过 setter 来注入对象属性。++setter 注入简单来说就是调用类的 set 方法对该类属性进行赋值。++

java 复制代码
public class UserDemo {
    private String Username;
    public void User(){
        System.out.println(Username+"欢迎用户登入");
    }

    public void setUsername(String username) {
        System.out.println("正在注入依赖~");
        Username = username;
    }
}
java 复制代码
...
    <bean id="u01" class="com.thz.UserDemo">
        <property name="Username" value="东方"/>
    </bean>
...

我们可以测试一下,取消掉注解并在 set 方法这里添加一行 print 语句观察打印结果如何?

运行结果:

java 复制代码
正在注入依赖~
东方欢迎用户登入

发现 SpringIOC 在赋值类属性时自动调用了 set 方法。使用 setter 注入的好处就是写法上比较直观。

ref 注入引用对象

mxl 配置文件

java 复制代码
<?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类

java 复制代码
@Data
public class Book {
    private String bookName;
    private String author;
    private double bookPrice;
}

UserDemo类

java 复制代码
@Data
public class UserDemo {
    private String Username;
    private Book books;
    public void User(){
        System.out.println(Username+"购买了"+ books.getAuthor()+"作者的"+books.getBookName()+"作品,花了"+books.getBookPrice()+"元~");
    }
}

运行结果:

java 复制代码
东方购买了屈原作者的楚辞作品,花了9.9元~
标签 描述
ref 通过 bean 的 id 引用另一个 bean

只要声明到 ioc 容器的 bean 对象,都可被其他 bean 引用。

内部 bean 的注入

内部 bean 与内部类差不多就是 bean 包 bean ,这样就避免了使用 ref 引用。

java 复制代码
 <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 对应属性:

java 复制代码
bookName=MySQL
author=Monty
bookPrice=9.9

XML配置文件:

java 复制代码
    <!-- 先引入 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>

运行结果:

java 复制代码
东方购买了Monty作者的MySQL作品,花了9.9元~

这也算是 xml 的常见用法了,虽然现在用的大多是注解。我们在 xml 中导入 properties 文件;在利用 ${} 对类中对应属性进行提取,在通过 setter 注入。这样修改一些属性的时候便可以直接对文件进行操作,就避免了去看复杂的 xml 配置文件了。

构造器注入

依旧以上面的例子为例:

Book类

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    private String bookName;
    private String author;
    private double bookPrice;
}

@NoArgsConstructor、@AllArgsConstructor 都是 Lombok提供的注解,作用如下

注解 作用
@NoArgsConstructor 在类上使用,这个注解可以生成无参构造方法
@AllArgsConstructor 在类上使用,这个注解可以自动生成一个包含所有实例变量的构造函数

xml 配置文件

java 复制代码
<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[索引] 来解决这个问题:

java 复制代码
<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 标签这个步骤,一步到位。

java 复制代码
<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类

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    private String bookName;
    private String author;
    private double bookPrice;
}

Student类

java 复制代码
@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 配置文件

java 复制代码
<?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>

测试类

java 复制代码
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类

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    private String bookName;
    private String author;
    private double bookPrice;
}

XML 配置文件

java 复制代码
<?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>

测试代码:

java 复制代码
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);
    }
}

运行结果为:

java 复制代码
true

我们发现结果为 true ,说明创建的是同一个对象,所以这种默认标签 singleton 创建的是单例的。将标签改成 prototype 发现结果为 false。说明它不是单例的。

bean 的初始化与销毁方法


案例:

Book类

java 复制代码
@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 配置文件

java 复制代码
<?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>

测试代码:

java 复制代码
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();
    }
}

运行结果:

java 复制代码
Book 正在被创建
Book 正在初始化
Book 正在被销毁

通过以上案例,我们可以知道:可以在 XML 文件中使用 init-method、destroy-method 来指定初始化与销毁时的内容。

相关推荐
爱上语文几秒前
苍穹外卖 添加购物车接口
java·数据库·spring boot·后端·mybatis
梅见十柒17 分钟前
数据结构与算法分析——你真的理解查找算法吗——基于散列的查找(代码详解+万字长文)
java·c语言·c++·笔记·算法·哈希算法·查找算法
JAVA在开发hui25 分钟前
基于 Java 的 Spring Boot 和 Vue 的宠物领养系统设计与实现
java·开发语言·宠物
idealzouhu28 分钟前
Spring Boot 实现文件上传下载功能
java·spring boot·1024程序员节
VaporGas36 分钟前
Java设计模式-单例模式和工厂模式的思路解析
java·单例模式·设计模式
carl.xu38 分钟前
QNX 7.0.0开发总结
java·大数据·开发语言
FIN技术铺1 小时前
问:缓存穿透、雪崩、预热、击穿、降级,怎么办?
数据库·spring·缓存
2401_858120261 小时前
探索自然之美:SpringBoot驱动的安康旅游网站开发
java·spring boot·旅游
SunsPlanter1 小时前
00 DSA-- 入门、实现动态数组、实现链表、栈和队列、环形数组、哈希表
java·开发语言
计算机学姐2 小时前
基于uniapp微信小程序的校园二手书交易系统
java·vue.js·spring boot·mysql·微信小程序·java-ee·uni-app