接着上篇进行


p 命名空间

引入 p 命名空间

1
2
3
4
5
6
7
8
9
<?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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

引入p命名空间后,可以通过以下方式为bean的各个属性赋值

1
2
3
4
<!-- p命名空间方式 -->
<bean id="student1" class="com.ergoutree.di.Student"
p:stu_id="1411" p:name="李四" p:listLessons-ref="lesson1" p:mapTeachers-ref="mapTeachers">
</bean>

测试后依旧成立


引入外部属性文件

在Spring框架中,引入外部属性文件是一种管理应用程序配置的有效方法。当我们在Spring的 XML 配置文件中直接编写大量的固定配置信息(如数据库用户名、密码、URL,或是服务器IP地址、端口号等),这些信息如果频繁变动,将会使得配置文件变得难以维护,而且每当配置信息发生变化时,开发人员就需要打开源代码去修改XML配置文件,然后再重新打包部署项目,这是一个繁琐的过程。

为了解决这个问题,Spring 支持将这些容易变化的配置项提取出来,存放在单独的 properties 文件中,如database.properties 或 application.properties 。这些文件通常包含键值对的形式,例如

database.properties文件

1
2
3
db.username=admin
db.password=secretpassword
db.url=jdbc:mysql://localhost:3306/mydatabase

接下来,在Spring的XML配置文件中,我们可以使用PropertyPlaceholderConfigurer(在较旧版本中)或<context:property-placeholder>(在Spring 3.1及以上版本推荐使用)来引入和解析这些外部属性文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- Spring 3.1+ 使用context命名空间简化引入 -->
<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="...">

<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:database.properties"/>

<!-- 使用外部属性文件中的值注入Bean -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
<property name="url" value="${db.url}"/>
</bean>
</beans>

这样一来,当数据库的连接信息需要变更时,我们只需要修改database.properties文件,而不需要碰触到Spring的XML配置文件或Java代码。这样既实现了配置的模块化,又增强了配置的可维护性,同时降低了因配置变更导致的应用程序整体重新部署的频率。

此外,这种模式还可以方便地根据不同的环境(如开发、测试、生产)切换不同的配置文件,实现灵活部署。


bean作用域/生命周期

作用域

在Spring框架中,Bean(Java对象)的作用域指的是在应用程序的整个生命周期中,对于某个特定的Bean对象,Spring容器是如何管理和创建其实例的。Bean 的作用域决定了容器在何时以及如何创建Bean实例,以及这些实例之间的关系。

取值 含义 创建对象
singleton(默认)单实例 在IOC容器中,这个bean的对象始终为单实例 IOC容器初始化时
prototype 多实例 这个bean在IOC容器中有多个实例 获取bean时

如果实在 WebApplicationContext 环境下还有几个作用域

取值 含义
request 在一个请求范围内有效
session 在一个会话范围内有效

singleton 作用域:

  • 含义:当指定一个Bean为singleton时,Spring容器会在启动时创建该Bean的一个实例,并在整个容器的生命周期内,任何对该Bean的请求都将返回同一个共享的实例。这意味着不论有多少个地方请求该Bean,始终只会有一个Bean实例存在。
  • 创建对象的时机: Spring容器在初始化时(加载配置文件并创建Bean的过程中),就会创建singleton作用域的Bean实例。

prototype 作用域:

  • 含义: 当指定一个Bean为prototype时,每次客户端请求一个Bean时,Spring容器都会创建一个新的Bean实例,也就是说,每次getBean()方法被调用时都会产生一个新的对象实例。
  • 创建对象的时机: prototype作用域的Bean实例是在客户端请求时创建的,即每次调用ApplicationContext.getBean()方法请求一个prototype类型的Bean时,Spring容器才会创建新的实例。

示例:

新建 Order 类

1
public class Order {}

新建 bean-scope.xml

1
2
3
4
5
6
7
8
9
10
<?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">

<!--单实例对象演示 通过 scope 属性配置单实例还是多实例-->
<bean id="order" class="com.ergoutree.scope.Order" scope="singleton">

</bean>
</beans>

新建 TestOrder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestOrder {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-scope.xml");
// 单实例对象,输出地址相同
Order order = context.getBean("order", Order.class);
System.out.println(order);

Order order2 = context.getBean("order", Order.class);
System.out.println(order2);
}
}

可以发现输出地址是一样的,单实例对象

然而带上多实例对象,发现并非是一个实例化的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestOrder {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-scope.xml");
// 单实例对象,输出地址相同
Order order = context.getBean("order", Order.class);
System.out.println(order);

Order order2 = context.getBean("order", Order.class);
System.out.println(order2);

// 多实例对象
Order order3 = context.getBean("order2", Order.class);
System.out.println(order3);
Order order4 = context.getBean("order2", Order.class);
System.out.println(order4);
}
}

输出结果

1
2
3
4
com.ergoutree.scope.Order@7b8b43c7
com.ergoutree.scope.Order@7b8b43c7
com.ergoutree.scope.Order@44c73c26
com.ergoutree.scope.Order@41005828

生命周期

关于Bean的生命周期: 除了Bean的作用域之外,Bean还有一个完整的生命周期,它包括以下几个阶段:

  • 实例化(Instantiation):

    • 这是Bean生命周期的起点,类似于开始备料,即调用Bean的无参构造函数创建Bean实例。Spring容器通过反射机制找到合适的构造函数并创建Bean对象。
  • 依赖注入(Dependency Injection,DI):

    • 类似于加入必要的调料,Spring容器根据配置信息,通过setter方法、构造函数注入或其他注解方式(如@Autowired)将Bean依赖的其他对象注入到当前Bean中。
  • Bean的后置处理器(Post Processor Before Initialization):

    • 在Bean初始化之前,Spring容器会对Bean进行一系列额外的处理。例如,Spring的BeanPostProcessor接口提供了两个方法postProcessBeforeInitialization()和postProcessAfterInitialization(),在这一步骤中,Spring会调用postProcessBeforeInitialization()方法对Bean进行自定义的预处理。
  • 初始化(Initialization):

    • 此阶段相当于烹饪前的准备工作,如预热烤箱。Spring容器在完成所有依赖注入后,会检查Bean是否实现了InitializingBean接口,若实现了则调用afterPropertiesSet()方法;或者在配置文件中通过init-method属性指定了初始化方法,此时Spring容器会调用该方法对Bean进行初始化。
  • Bean的后置处理器(Post Processor After Initialization):

    • 初始化完成后,Spring容器会调用postProcessAfterInitialization()方法进一步处理Bean。这一阶段可用于在Bean正式投入使用前进行一些补充性的定制化工作。
  • 使用(Use):

    • 到了这个阶段,Bean就如同完成烹饪的菜肴一样,已经准备好供客户(应用程序)使用。通过Spring容器的getBean()方法,可以在应用程序中任意位置获取并使用这个Bean实例。
  • 销毁(Destruction):

    • 当Bean不再需要时,比如应用程序关闭或容器被销毁时,Spring容器会触发Bean的销毁过程。如果Bean实现了DisposableBean接口,则调用destroy()方法;或者在配置文件中通过destroy-method属性指定了销毁方法,Spring容器会调用该方法释放资源,如关闭数据库连接、清除缓存等,这就好比客人用餐结束后清理餐具、打扫卫生。
  • IOC容器关闭:

    • 当整个IoC容器需要关闭时,所有处于singleton作用域的Bean都会经历销毁阶段,以确保相关资源得到妥善释放,整个容器生命周期结束。

总的来说,在Spring框架中,Bean的作用域是指Bean实例的创建策略,而生命周期则是指Bean实例从创建到销毁的过程。通过理解并合理配置Bean的作用域和生命周期,可以更好地管理和控制Bean实例的行为,提高应用效率和资源利用率。

示例:

创建 Applyskd 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Applyskd {

private String name;

//初始化方法
public void init() {
System.out.println("生命周期:3、初始化~");
}

//销毁方法
public void destroy() {
System.out.println("生命周期:5、销毁~");
}

public Applyskd() {
System.out.println("生命周期:1、创建对象~");
}

public Applyskd(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
System.out.println("生命周期:2、依赖注入~");
this.name = name;
}

public String toString() {
return "User{name = " + name + "}";
}
}

创建 xml 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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">

<!-- 定义一个名为"user"的Bean,对应于com.ergoutree.liferoutine.Applyskd类 -->
<!-- 该Bean的生命周期由Spring容器管理 -->
<bean id="user" class="com.ergoutree.liferoutine.Applyskd"
scope="singleton" init-method="init" destroy-method="destroy">

<property name="name" value="sakura" />

</bean>

<!-- 注解解释:
id属性:为Bean定义一个唯一标识符;
class属性:指定Bean的实现类;
scope属性:定义Bean的作用域,此处为"singleton",表示该Bean在容器中只存在一个实例;
init-method属性:指定Bean初始化时要执行的方法,此处为"init";
destroy-method属性:指定Bean销毁时要执行的方法,此处为"destroy"。 -->

</beans>

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test {
@org.junit.jupiter.api.Test
public void test() {
//想要实现bean生命周期的销毁,需要使用ClassPathXmlApplicationContext
//而不是用ApplicationContext,它里面没有destroy方法
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean-lifeRoutine.xml");

Applyskd bean = context.getBean(Applyskd.class);
System.out.println("生命周期:4、通过IOC容器获取bean并使用~");
bean.destroy();
}
}

/*
生命周期:1、创建对象~
生命周期:2、依赖注入~
生命周期:3、初始化~
生命周期:4、通过IOC容器获取bean并使用~
生命周期:5、销毁~
*/

基于 xml 自动装配

自动装配:

  • 根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值

Spring 自动装配:

  • 它能在背后悄悄帮你处理Bean之间的关联关系。当你定义了一些Bean,并告诉Spring使用自动装配时,Spring会在创建这些Bean时,自动找出并填充它们所需要的依赖对象。
  • 打个比方,假设你在装修房子,有电工、木工和油漆工三个工种。如果没有自动装配,你需要亲自安排电工去做电线安装,木工去做家具组装,油漆工去做刷漆工作。而有了自动装配功能,你只需要告诉Spring哪个房间需要装修,Spring会根据每个工人的技能(电工擅长电,木工擅长木工活,油漆工擅长刷漆)自动分配任务,不需要你一一指定谁去做什么。
  • 在Spring中,不同的自动装配策略相当于不同的分工规则,比如按名字找合适的工人(byName)、按工种找合适的工人(byType)等。这样,你就可以更加专注于整体的设计和规划,而无需过于关注每个细小环节的具体实施。

主要有如下几种自动装配的策略:

  1. byName
    • Spring容器会根据Bean的属性名查找IoC容器中ID(或name)与该属性名相同的Bean进行注入。
    • Spring会看你的Bean缺少什么“工具”,然后去查找名字一样的工具。
  2. byType:
    • Spring容器会查找IoC容器中类型与待注入属性类型匹配的Bean进行注入。如果有多个类型匹配的Bean,则会抛出异常,除非其中一个被标记为主Bean。
    • Spring会根据你的Bean需要的工具类型,去找仓库里拥有同样类型的工具。
  3. constructor
    • 类似于byType,但专门针对构造函数参数,根据构造函数参数类型查找并注入Bean。
    • 当你创建Bean时,Spring会检查构造函数需要哪些工具,然后自动提供正确的工具。
  4. default / no
    • 这是默认策略,表示不进行自动装配,所有依赖都需要显式配置。
    • 如果关闭自动装配,那就得你自己一个个地把工具交给Bean。

通过启用自动装配功能,Spring可以帮助开发者减少大量手动配置依赖注入的工作,提高开发效率,同时保持代码结构清晰。不过,过度依赖自动装配可能会导致问题不易排查,因此在实际项目中应合理权衡是否使用自动装配以及选择哪种装配策略。

总之,自动装配就是Spring帮你自动完成对象间的依赖关系建立,让你编写代码时更轻松,不过要注意避免因自动装配带来的不确定性,适当地结合显式配置更为稳健。

代码示例:

做一个UserController、UserService、UserDao三层之间的实际场景模拟

创建UserController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UserController {
//生成service层对象
private UserService userService;

//设置service属性set方法
public void setUserService(UserService userService)
{
this.userService = userService;
}

public void conMethod(){
//调用service层方法
userService.setMethod();
System.out.println("UserController方法执行了~");
}
}

创建 UserDao 接口 和 UserDaoImpl 实现类

1
2
3
public interface UserDao {
public void method();
}
1
2
3
4
5
6
public class UserDaoImpl implements UserDao {
@Override
public void method() {
System.out.println("UserDao方法执行了~");
}
}

创建UserService 接口 和 UserServiceImpl 实现类

1
2
3
public interface UserService {
public void setMethod();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserServiceImpl implements UserService {
//生成dao层对象
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void setMethod() {
//调用dao层方法
userDao.method();
System.out.println("UserService方法执行了~");
}
}

上面的都还是准备工作,现在就实现真正的自动装配

使用bean标签的autowire属性设置自动装配效果

根据类型进行自动装配–byType

若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null

若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常 NoUniqueBeanDefinitionException

配置spring bean xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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="controller" class="com.ergoutree.user.UserController" autowire="byType">

</bean>

<bean id="service" class="com.ergoutree.user.UserServiceImpl" autowire="byType">

</bean>

<bean id="dao" class="com.ergoutree.user.UserDaoImpl" autowire="byType">

</bean>
</beans>

根据属性名进行自动装配–byName

配置spring bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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="controller" class="com.ergoutree.user.UserController" autowire="byName">

</bean>

<bean id="service" class="com.ergoutree.user.UserServiceImpl" autowire="byName">

</bean>

<bean id="dao" class="com.ergoutree.user.UserDaoImpl" autowire="byName">

</bean>
</beans>

测试结果

1
2
3
4
5
6
7
8
class autoTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-user.xml");
UserController controller = (UserController) context.getBean("controller");
controller.conMethod();
}
}
image-20250422160019960
image-20250422160019960

ApplicationContext

我们从创建Spring容器的代码:

1
ApplicationContext context = new ClassPathXmlApplicationContext("bean-user.xml");

可以看到,Spring容器就是ApplicationContext,它是一个接口,有很多实现类,这里我们选择ClassPathXmlApplicationContext,表示它会自动从classpath(文件管理系统)中查找指定的XML配置文件。

获得了ApplicationContext的实例,就获得了IoC容器的引用。从ApplicationContext中我们可以根据Bean的ID获取Bean,但更多的时候我们根据Bean的类型获取Bean的引用:

1
UserService userService = context.getBean(UserService.class);

Spring还提供另一种IoC容器叫BeanFactory,使用方式和ApplicationContext类似

1
2
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
MailService mailService = factory.getBean(MailService.class);

BeanFactoryApplicationContext的区别在于,BeanFactory的实现是按需创建,即第一次获取Bean时才创建这个Bean,而ApplicationContext会一次性创建所有的Bean。实际上,ApplicationContext接口是从BeanFactory接口继承而来的,并且,ApplicationContext提供了一些额外的功能,包括国际化支持、事件和通知机制等。通常情况下,我们总是使用ApplicationContext,很少会考虑使用BeanFactory