学前要求: 已学完本系列(1)、(2)
*系列(2)的结尾我们尝试了一个静态工厂的案列,现在我们进一步了解一下工厂的魅力(请留意与前面的静态工厂的不同)
1.制造工厂类
package com.javala.factory;
import com.javala.service.TestDemo3;
import com.javala.service.interfacela.Test3Demo;
/**
* @author ***
* 工厂实例化对象 不同点: 不再是静态(static)
* */
public class UserServiceFactory1 {
public Test3Demo getTest3Demo() {
return new TestDemo3();
}
}
2.applicationContext.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: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/context/spring-context.xsd">
<!-- 使用实例工厂实例化bean -->
<!-- 步骤:
* 1. 先找出工厂bean造出来
* 2. 指定工厂实例和工厂所使用方法
-->
<bean id="userFactory" class="com.javala.factory.UserServiceFactory1"/> <!-- 对应步骤一 -->
<bean id="test3Demo3" factory-method="getTest3Demo" factory-bean="userFactory"/>
</beans>
3.使用案列类
package com.javala.dosomething;
import com.javala.service.interfacela.Test3Demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author ***
* 实例工厂初始化bean
* */
public class App3 {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Test3Demo test3Demo = (Test3Demo) ctx.getBean("test3Demo3");
test3Demo.say();
}
}
相信只要认真看过代码后就能发现 我已经在代码中说到 不再是static了,即不再是静态工厂,而为实例化工厂
学习就是不断探索的过程,同时又夹带这一些质疑精神,想上面这个实现就有一个致命的缺点,即配置文件中的实例化工厂类,这另外写一行代码专门服务于这个让人感觉十分的不爽,有解吗?有!
look一下如下代码:
新建一个UserServiceFactoryBean类
package com.javala.factory;
import com.javala.service.TestDemo3;
import com.javala.service.interfacela.Test3Demo;
import org.springframework.beans.factory.FactoryBean;
/**
* @author ***
* */
public class UserServiceFactoryBean implements FactoryBean<Test3Demo> {
// 代替原始实例工厂中创建对象的方法
@Override
public Test3Demo getObject() throws Exception {
// return null; // 原语句
return new TestDemo3();
}
// 告诉类型
@Override
public Class<?> getObjectType() {
// return null; // 原语句
return Test3Demo.class;
}
}
然后applicationContext.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: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/context/spring-context.xsd">
<!-- 使用factoryBean实例化bean -->
<bean id="test3Demo4" class="com.javala.factory.UserServiceFactoryBean"/>
</beans>
很明显,这里就不再是两行代码了
案例使用类
package com.javala.dosomething;
import com.javala.service.interfacela.Test3Demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author ***
* 对app3的优化
* */
public class App4 {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Test3Demo test3Demo = (Test3Demo) ctx.getBean("test3Demo4");
test3Demo.say();
}
}
优化完成,但是问题也随之而至,单例问题
单例问题
在Spring框架中,单例是一种用于管理bean生命周期的模式。指在整个应用程序中,所有对某个特定bean的请求都将得到同一个示例(即单例)。与之相反,如果我们使用原型模式,每次请求都会创建新的实例。
在Spring中,默认情况下创建的bean是单例的,在启动Spring容器时就会被创建,并且在整个容器的生命周期内只有一个实例。如果我们需要创建其他生命周期或作用域的bean,则可以通过设定其scope属性来实现,例如"prototype"表示原型作用域,"request"表示单次HTTP请求作用域等。
单例优点:单例模式的优点在于它可以显著提升应用程序的性能,因为我们不需要为每个请求都创建一个新的bean实例。另外,由于单例是全局共享的,因此也可以方便地实现数据共享和部署集群等功能。
单例缺点:由于单例模式存在全局共享的特点,所以在多线程环境下可能会发生线程安全问题,需要注意相关的并发控制。同时,将大量对象变成单例也会导致内存占用问题,需要做好内存管理。
下面就讲解一下如何使用设置这个单例
在原本的UserServiceFactoryBean中加上如下代码:
// 设置是否单例 true: 单例 false: 非单例
@Override
public boolean isSingleton() {
// return FactoryBean.super.isSingleton(); // 原语句
return true;
}
Bean的生命周期
在Spring框架中,每个bean都有不同的生命周期。理解Spring bean的生命周期是理解Spring框架的关键之一,因为它涉及到了如何创建、初始化和销毁bean对象。
以下是一个标准的Spring bean生命周期:
1.实例化Bean:Spring容器负责创建bean实例。当我们配置一个bean时,Spring并不会立即创建该bean对象。相反,只有在第一次请求该bean时才会创建它。如果该bean被定义为单例,则Spring只会创建一个实例,而如果它被定义为原型,则每次请求都会创建一个新实例。
2.设置对象属性:在创建完bean实例以后,Spring容器将检查bean是否包含其它依赖或者关联的bean。如果找到了依赖项,则Spring容器将自动注入这些依赖项。这就是所谓的"自动装配"(autowiring)。
3.调用BeanNameAware.setBeanName()(可选):如果该bean类实现了BeanNameAware接口,则Spring容器将把该bean的ID传递给BeanNameAware.setBeanName()方法。这样,bean就可以知道自己在容器中的ID。
4.调用BeanFactoryAware.setBeanFactory()(可选):如果该bean类实现了BeanFactoryAware接口,则Spring容器将饮食传递给BeanFactoryAware.setBeanFactory()方法。通过这种方式,bean就可以访问容器本身,并可以使用容器提供的各种服务功能。
5.调用ApplicationContextAware.setApplicationContext()方法(可选):如果该bean类实现了ApplicationContextAware接口,则Spring容器将向它传递ApplicationContext对象。通过这种方式,bean就可以访问整个应用Web上下文信息,或者其他某些特定的Spring功能。
6.调用BeanPostProcessor.beforeInitialization()方法(可选):如果在Spring容器中定义了任何BeanPostProcessor bean,则在初始化bean前会回调BeanPostProcessor.beforeInitialization()方法。这里我们可以对bean进行一些自定义处理或操作。
7.调用InitializingBean.afterPropertiesSet()方法(可选):如果该bean类实现了InitializingBean接口,则Spring容器将调用afterPropertiesSet()方法。我们通常可以在该方法内执行一些初始化代码、数据校验等操作。
8.调用自定义init方法(可选):如果我们在配置文件中设置了自定义的初始化方法,则Spring容器将优先调用该方法。如果Bean实现了InitializingBean接口,则该init方法将被视为后备选择。
9.Bean可以使用了:现在bean已经完成了初始化并且可以使用了。它们将一直播放自己的职责,直到应用程序关闭或它们被标记为 garbage collection 时。
10.调用BeanPostProcessor.afterInitialization()方法(可选):和BeanPostProcessor.beforeInitialization相反,如果我们定义该回调,在bean初始化操作之后,Spring容器将自动回调BeanPostProcessor.afterInitialization()方法。我们可以在该方法内执行自定义处理或操作。
11.DisposableBean.destroy()(可选):如果该bean类实现了DisposableBean接口,则Spring容器将在销毁bean之前调用destroy()方法。我们可以在该方法内进行一些清理操作,以确保系统安全地关闭。通常,我们还可以使用"destroy-method"方式配置一个自定义的销毁方法。
12.调用自定义destroy方法(可选):如果我们在配置文件中设置了自定义的销毁方法,则Spring容器将优先调用该方法。如果bean实现了DisposableBean接口,则该destroy方法将被视为后备选择。
需要注意的是,部分这个生命周期并不一定全部都会进行,例如如果我们没有实现特定的接口或者未在配置文件中定义相应方法,那么它们就不会被调用。
简单说整个过程就是从bean的创建到bean的销毁,而我们做的事情就是在这个过程中发生
下面就简单看看该如何控制这bean
温馨提示:往后代码提示将进一步简化
1.新建文件
2.TestDemo5内容
package com.javala.dao;
/**
* @author ***
* */
public class TestDemo5 {
public void speak(){
System.out.println("nothing to speak~");
}
// 表示bean初始化对应的操作
public void init() {
System.out.println("init...");
}
// 表示bean销毁前对应的操作
public void destory() {
System.out.println("destory...");
}
}
3.applicationContext.xml配置
<!-- 探索bean的周期所用 方法作用就是直译 -->
<bean id="testdemo5" class="com.javala.dao.TestDemo5" init-method="init" destroy-method="destory"/>
4.实例使用类
// 从此开始后类的包引入不再书写(需时请自行import)
public class App5 {
public static void main(String[] args) {
// 下面这一行代码已更改 请甄别差异
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
TestDemo5 testdemo5 = (TestDemo5) ctx.getBean("testdemo5");
testdemo5.speak();
// ctx.close(); // 运行destory所需 在虚拟机退出之前关闭ICO容器 关闭的一种方式 另一种:设置关闭钩子
ctx.registerShutdownHook(); // 运行destory所需 注册关闭钩子 (唯一任意 比close更好)
}
}
5.运行结果
上面的代码自然不用说,也有优化
Spring框架的设计者并没有强制实现"init-method"和"destroy-method"这两个配置属性,而是提供了InitializingBean和DisposableBean两个接口,以及@PostConstruct和@PreDestroy注解来实现bean的初始化方法和销毁方法。
那我们先看看接口的法子:
InitializingBean是一个在bean属性设置完成之后进行自定义初始化操作的回调接口。如果我们需要在初始化bean时执行一些指定的操作,可以选择实现InitializingBean接口并重写afterPropertiesSet()方法来完成目标操作。
同样地,DisposableBean是在bean销毁前进行清理操作的回调接口。如果我们需要在该bean销毁时执行一些清理工作,可以选择实现它,并重写destroy()方法来处理任务。
同样地,DisposableBean是在bean销毁前进行清理操作的回调接口。如果我们需要在该bean销毁时执行一些清理工作,可以选择实现它,并重写destroy()方法来处理任务。
让TestDemo5是实现这两类后,就可以将配置中的init-method和destory-method删除,同时别忘记了去实现方法:
// 表示bean销毁前对应的操作
@Override
public void destroy() throws Exception {
System.out.println("destory....");
}
// 表示bean初始化对应的操作
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("init....");
}
再运行,结果无异
总结
那么学到这我们就已经掌握了bean的生命周期
我是哈利巴多先生,如果觉得不错,还望多多鼓励(文章不定时更新)
文章暂无大目录部分,后期将会补全以助成为学习spring的工具,文章初意并非像大多数文章一样将spring讲的精细完好,但是本系列是由我本人(up本人也为一名带动学习spring的初学者)的身份去讲解,相比其他文章的优点是我知道初始者学时的状态与理解范围,并会加以注解难懂点,文章作用也只是告诉初学者读者不用慌里慌张,大家都是初学者,一起进步,一起从菜鸟到较好的认识这个Spring家族(我相信国内和我一样做这种行为的一定很少,所以资源也更加珍贵~不吹不闹,祝大家早上好,中午好,晚上好)