提示:搭配课程效果更佳
目录
三、Bean生命周期配置—init-method与destroy-method
参考课程
黑马程序员SSM框架教程:07-Spring配置文件-详解1_哔哩哔哩_bilibili
一、Bean标签的基本属性—id与class
- id:Bean实例在Spring容器中的唯一标识,通过id去获得对象,不允许重复
- class:Bean的全限定名
- 用于配置对象交由Spring创建
-
默认情况下调用类中无参构造函数
二、Bean标签范围配置—scope
1.scope取值范围
取值范围 | 说明 |
singleton | 默认值(即bean没有配置scope的取值范围),意思为单个的,即Bean对象仅有单个 |
prototype | 意思为多个的,即Bean对象可有多个 |
2.验证scope取值范围
- 取值为singleton的情况
首先设置scope值:
<bean id ="test" class="dao.BeanTest" scope="singleton"/>
测试的思路为打印对象的地址,如果打印出的对象地址相同,则说明Bean对象仅有单个。
编写BeanTest测试类,打印出对象的地址,代码如下:
public class BeanTest{
public void test() {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); // 加载文件,创建Spring容器
BeanTest bean1 = (BeanTest) app.getBean("test");
BeanTest bean2 = (BeanTest) app.getBean("test");
System.out.println("bean1的地址为:" + bean1);
System.out.println("bean2的地址为:" + bean2);
}
public static void main(String[] args) {
BeanTest beanTest = new BeanTest();
beanTest.test();
}
}
输出结果如下,对象地址相同,说明容器内仅存在单个Bean对象:
bean1的地址为:dao.BeanTest@3c22fc4c
bean2的地址为:dao.BeanTest@3c22fc4c
- 取值为prototype的情况:
同上,设置scope值:
<bean id ="test" class="dao.BeanTest" scope="prototype"/>
测试类代码同上,观察输出结果:
bean1的地址为:dao.BeanTest@3c22fc4c
bean2的地址为:dao.BeanTest@460d0a57
对象地址不同,说明容器内存在多个Bean对象。
3.Bean对象的实例化时机与生命周期
取值范围 | 实例化时机 |
singleton | Spring核心文件被加载时 |
prototype | 调用getBean()方法时实例化 |
4.验证Bean对象的实例化时机与生命周期
在UseDaoImpl中重写无参构造,首先将scpoe值设置为singleton:
<!-- applicationContext.xml -->
<bean id ="test" class="dao.UserDaoImpl" scope="singleton"/>
// 编写UserDao接口
public interface UserDao {
public void sayHello();
}
// 编写UserDao接口实现类
public class UserDaoImpl implements UserDao{
public UserDaoImpl() {
System.out.println("UserDaoImpl方法被调用...");
// 打印一次,说明无参构造方法就调用一次,对象就创建一次
}
public void sayHello() {
System.out.println("Hello World!");
}
}
public class BeanTest{
public void test() {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); // 加载文件,创建Spring容器
UserDaoImpl bean1 = (UserDaoImpl) app.getBean("test");
UserDaoImpl bean2 = (UserDaoImpl) app.getBean("test");
System.out.println("bean1的地址为:" + bean1);
System.out.println("bean2的地址为:" + bean2);
}
public static void main(String[] args) {
BeanTest beanTest = new BeanTest();
beanTest.test();
}
}
在BeanTest类中进行断点测试,可以发现无参构造只被调用一次,在加载配置文件时就创建bean
同样,将scpoe值设置为prototype:
<bean id ="test" class="dao.UserDaoImpl" scope="prototype"/>
同样进行断点测试,可以发现与之前不同,变为在getBean时创建一个bean
三、Bean生命周期配置—init-method与destroy-method
首先配置好两个方法:
<bean id ="test" class="dao.UserDaoImpl" init-method="init" destroy-method="destroy"/>
然后分别编写两个方法:
// 编写UserDao接口实现类
public class UserDaoImpl implements UserDao{
public UserDaoImpl() {
System.out.println("UserDaoImpl方法被调用...");
// 打印一次,说明无参构造方法就调用一次,对象就创建一次
}
public void init() {
System.out.println("初始化方法...");
// 实现初始化方法
}
public void destroy() {
System.out.println("销毁方法...");
// 实现销毁方法
}
public void sayHello() {
System.out.println("Hello World!");
}
}
如果想要实现销毁方法,还需要手动关闭:
public class BeanTest{
public void test() {
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); // 加载文件,创建Spring容器
UserDaoImpl bean1 = (UserDaoImpl) app.getBean("test");
System.out.println("bean1的地址为:" + bean1);
app.close(); // 手动关闭容器,让销毁的方法执行
}
public static void main(String[] args) {
BeanTest beanTest = new BeanTest();
beanTest.test();
}
}
最后输出如下:
UserDaoImpl方法被调用...
初始化方法...
bean1的地址为:dao.UserDaoImpl@6107227e
销毁方法...
四、Bean实例化的三种方式
1.无参构造方法实例化
见上,不再赘述。
2.工厂静态方法实例化
首先创建一个静态工厂:
package factory;
import dao.UserDao;
import dao.UserDaoImpl;
// 创建静态工厂
public class StaticFactory {
public static UserDao getUserDao() {
return new UserDaoImpl(); // 创建对象
}
}
其次bean需要重新配置:
<!-- 有factory-method,就找包名内对应的无参构造方法,返回对应的对象 -->
<bean id="test" class="factory.StaticFactory" factory-method="getUserDao"></bean>
其余代码不变,可以正常输出。
3.工厂实例方法实例化
首先创建一个静态工厂:
// 创建实例工厂
public class DynamicFactory {
public UserDaoImpl getUserDao() {
return new UserDaoImpl();
}
}
其次bean需要重新配置,与之前不同,需要用Spring容器生成factory对象,然后再获得工厂内部的某个对象:
<!-- 工厂实例方法实例化 -->
<!-- 先需要用Spring容器生成factory对象 -->
<bean id="factory" class="factory.DynamicFactory"></bean>
<!-- 然后再获得工厂内部的某个对象 -->
<bean id="test" factory-bean="factory" factory-method="getUserDao"></bean>
其余代码不变,可以正常输出。
五、Bean的依赖注入
1.代码引入
首先创建业务层代码:
package service;
public interface UserService {
public void save();
}
// 编写业务层代码
public class UserServiceImpl implements UserService {
public void save() {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) app.getBean("test");
userDao.save();
}
}
其次创建外部层代码:
// 编写外部层代码
public class UserController {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) app.getBean("userService");
userService.save();
}
}
在UserDaoImpl中创建save()方法:
public void save() {
System.out.println("save running...");
}
最后配置bean,让Spring生成userService:
<!-- Spring产生userService -->
<bean id ="test" class="dao.UserDaoImpl" />
<bean id="userService" class="service.impl.UserServiceImpl"></bean>
最后输出:
UserDaoImpl方法被调用...
UserDaoImpl方法被调用...
save running...
2.分析代码
观察输出,为什么UserDaoImpl的调用语句会输出两次呢?
第一次调用:Spring 容器启动创建id为test的bean。
第二次调用:在实现UserServiceImpl的save方法时,再次创建了一个Spring容器实例,并从该容器中获取UserDaoImpl的bean。
思考:两次调用的bean既然都在容器里面,而且程序最终使用的是UserService,那可不可以将UserDao设置到UserService内部呢?
换一句说法就是,坐等框架把持久层对象传入业务层,而不用我们自己去获取,也就是依赖注入的作用
3.Bean的依赖注入方式
(1)set方法与p命名空间
首先,修改业务层代码,实现setter方法:
// 编写业务层代码
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save() {
// 生成userDao的setter方法后,就不用再向容器获得bean实例
// ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
// UserDao userDao = (UserDao) app.getBean("test");
// userDao.save();
userDao.save();
}
}
其次,在配置文件中配置setter方法:
<!-- setter方法配置 -->
<bean id="test" class="dao.UserDaoImpl" />
<bean id="userService" class="service.impl.UserServiceImpl">
<property name="userDao" ref="test"></property>
<!-- 在property中,name后面为set方法中的内容,ref表示bean对象的引用 -->
</bean>
输出结果如下,可以发现UserDaoImpl的调用语句只输出了一次,即实现了依赖注入:
UserDaoImpl方法被调用...
save running...
思考:property的写法是否可以更简单呢?于是就有了p命名空间注入。
实现p命名空间注入的步骤:
-
首先,引入p命名空间
xmlns:p="http://www.springframework.org/schema/p"
-
其次修改注入方式
<!-- setter方法配置-p命名空间注入 -->
<bean id="test" class="dao.UserDaoImpl" />
<bean id="userService" class="service.impl.UserServiceImpl" p:userDao-ref="test"/>
(2)构造方法
首先,业务层代码中添加方法:
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
public UserServiceImpl() {
}
其次,在配置文件中配置构造方法:
<!-- 构造方法配置 -->
<bean id="test" class="dao.UserDaoImpl" />
<bean id="userService" class="service.impl.UserServiceImpl">
<!-- name后面为构造内部的参数名 -->
<constructor-arg name="userDao" ref="test"></constructor-arg>
</bean>
(3)依赖注入数据类型
-
普通数据类型的注入
之前的都为普通数据类型注入,不再赘述。
-
引用数据类型的注入
首先,在UserDaoImpl类中用getter和setter方法注入:
// 编写UserDao接口实现类
public class UserDaoImpl implements UserDao{
// 引用数据类型的注入的演示
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void sayHello() {
System.out.println("Hello World!");
}
@Override
public void save() {
System.out.println("name:" + name + ",age:" + age);
System.out.println("save running...");
}
}
其次,还需要修改配置文件,注意普通属性值用value:
<!-- 引用数据类型的注入配置 -->
<bean id="test" class="dao.UserDaoImpl">
<!-- 普通属性值用value -->
<property name="name" value="Jack"></property>
<property name="age" value="20"></property>
</bean>
<bean id="userService" class="service.impl.UserServiceImpl">
<property name="userDao" ref="test"></property>
</bean>
-
集合数据类型注入
首先定义List,Map与Properties:
// 集合数据类型的注入的演示
private List<String> strlist;
private Map<String, User> userMap;
private Properties properties;
private String name2;
private String addr;
// 提供集合数据类型的注入的set方法
public void setStrlist(List<String> strlist) {
this.strlist = strlist;
}
public void setUserMap(Map<String, User> userMap) {
this.userMap = userMap;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
然后再在domain包下创建User类,并生成toString方法:
package dao.domain;
public class User {
private String name;
private String addr;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", addr='" + addr + '\'' +
'}';
}
}
最后再配置好配置文件,需要注意:
- 列表既不属于value也不属于ref
- String为普通数据类型,用value
- entry标签中的key为map中的key,叫什么都可以,而value-ref中的内容为bean的引用
<!-- 集合数据类型的注入配置 -->
<bean id="userService" class="service.impl.UserServiceImpl">
<property name="userDao" ref="test"></property>
</bean>
<bean id="test" class="dao.UserDaoImpl">
<property name="strlist">
<list>
<value>Jack</value>
<value>Tom</value>
</list>
</property>
<property name="userMap">
<map>
<!-- map是键值对,key后面为键值对的键,叫什么都可以,value-ref为下面bean的id引用 -->
<entry key="user1" value-ref="user1"></entry>
<entry key="user2" value-ref="user2"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="p1">value1</prop>
<prop key="p2">value2</prop>
</props>
</property>
</bean>
<bean id="user1" class="domain.User">
<property name="name" value="Jack"></property>
<property name="addr" value="China"></property>
</bean>
<bean id="user2" class="domain.User">
<property name="name" value="Tom"></property>
<property name="addr" value="America"></property>
</bean>
最后输出如下,本次学习笔记就结束啦:
strlist:[Jack, Tom]
userMap:{user1=User{name='Jack', addr='China'}, user2=User{name='Tom', addr='America'}}
properties:{p1=value1, p2=value2}
save running...