一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
书接上回:# Spring框架之Spring简介
这篇有点长。可以选择性的观看
必看项:
- 2 中的标签范围singleton、prototype的区别
- 4 Bean对象的三种实例化方式
- 6 Bean对象的依赖注入方式(重点看set)
- 9 Bean对象依赖注入的三种数据类型 了解项:
- 依赖注入的意义:解耦合
- 1 Bean标签里的id、class属性的用处
- 3 Bean的生命周期 ==> 告诉我们Bean对象什么时候创建,什么时候销毁
- 6、8一起看
- 10 的分模块开发思想(方便分工) 制作不易求点赞不过分吧😭
你们的点赞、评论、转发决定了我的更新速度
各位大哥大爷大姐大妈,鳅鳅惹🙇♂️
5. Spring入门
1. Spring配置文件
1. Bean标签的基本配置
用于配置对象,将该对象交由Spring来创建(以达解耦合的标准)
默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。
基本属性:
- id: Bean实例在Spring容器中的唯一标识
- class: Bean的全限定名称(该类的全路径名称)
2. Bean标签范围配置
- scope:指
对象的作用范围,取值如下:
- ① singleton 与 prototype主要的区别是:
前者只能存储一个对象,而后者可以存储多个对象 - ②
当将Bean标签范围配置为singleton 或 prototype时,对象Bean的创建时机:
- 当scope设置为
singleton时,对象Bean的创建时机为:
从这里可以很明显的看出:
Bean对象是在加载配置文件过来时就开始初始化了
- 当scope设置为
prototype时,对象Bean的创建时机为:
从这里可以看出:Bean对象在加载配置文件的时候并没有初始化
从这里也可以很明显的看出:Bean对象是在调用getBean()方法的时候才创建/实例化对象的
小结:
-
当scope的取值为
singleton时
Bean的实例化次数:1次
Bean的实例化时机:当Spring核心(配置)文件被加载时,实例化配置的Bean(对象)实例
Bean的生命周期:
对象创建:当应用加载、创建容器时,对象就被创建了
对象运行:只要容器在,对象一直活着
对象销毁:当应用卸载、销毁容器时,对象就被销毁了 -
当scope的取值为
prototype时
Bean的实例化次数:多次
Bean的实例化时机:当调用getBean()方法时,实例化Bean
对象创建:当使用对象时,创建新的对象实例
对象运行:只要对象在使用中,就一直活着
对象销毁:当对象长时间不用时,将会被Java的垃圾回收器(GC:garbage collector 垃圾回收机制)回收
3. Bean生命周期配置
该生命周期主要显示 Bean对象什么时候创建,什么时候销毁。 init-method:指定类中的初始化方法名称
destroy-method:指定类中销毁方法名称
在配置文件中设置好要初始化的方法与要销毁的方法
PS:它(初始化方法、销毁方法)要写在该Bean准备实例化的Class对象中
运行代码如下:
为什么没有看到销毁代码的执行?难道它没被销毁嘛? 🤔
事实上它已经被销毁了。😀只是它还没被打印出来就被销毁了,所以就看不到destroy()方法所执行的代码了🙄
详细解说:
该代码(上述截图的代码)在一个单元测试中,并没有在一个tomcat服务器里。所以只要这个单元测试执行完毕,那么该销毁的东西都得被销毁。
那么销毁的话为什么没有打印这个方法咧? ==> 因为他还没有来得及打印,就被销毁掉了啊。😔
如果想要看结果,那么可以手动关闭容器,以此来达成销毁得目的。(因为在手动关闭容器时,容器能够意识到自己要挂了,所以他会在它彻底挂之前把销毁方法执行一下)
让我们来手动关闭一下吧😘
从这里我们看到😔 ApplicationContext么有对应的关闭方法🤡
所以只好用其实现类(ClassPathXmlApplicationContext)来关闭了🙄
PS:这个生命周期配置了解一下就好了~
4. Bean实例化三种方式
- 无参构造方法实例化 (重点, 其他了解即可)
- 工厂静态方法实例化
- 工厂实例方法实例化
工厂静态方法实例化 与 工厂实例方法实例化的区别就在于静态方法直接在类中调用,而非静态(实例)方法需要用对象调用
PS:应用场景:工厂模式 ==> JDBC获取连接对象的时候采取工厂模式进行获取。 不要滥用哦~😘
5. 引入依赖注入的原因
- 引入问题:
在上图中我们了解到了:在service层里我们直接调用UserService的实例,但此时我们也调用了UserService内部的UserDao实例。但由于 我们只关心我们所关心的实例(UserService),所以我们需要将Spring容器里的UserService实例与UserDao实例相关联(即勾连起来) ==> 为了解耦。
- 解决问题:
在Spring容器里就将其(相互依赖的对象/实例)相关联起来,而编译时就可以把重心放到我们想关心的地方上了。
6. Bean的依赖注入方式
既然有了解耦的思想了,那么该怎么将UserDao注入到UserService内部呢?
set方法==> 为service层中的dao对象设置对象的引用变量(从容器中获取该变量值)构造方法==> 通过使用有参构造方法将某个对象注入到某个对象里
1. 在服务层配置依赖注入方式:
2. 在配置文件中的依赖信息:
- set方法的依赖信息:
<!-- 将UserDao存放到Spring容器中 -->
<bean id="userDao" class="com.kaf.dao.impl.UserDaoImpl"></bean>
<!-- TODO ①使用set方法来进行Bean依赖注入 -->
<!-- TODO 告诉容器以什么形式把dao注入到service中 -->
<bean id="userService" class="com.kaf.service.impl.UserServiceImpl">
<!-- 以set的形式注入 (另一种形式的注入方式是构造方法)
TODO name `> 指属性的名字(属性! 和JavaBean的取值方式一样!如:setXxxYyy `> xxxYyy 的形式) -->
<property name="userDao" ref="userDao"></property>
<!--以上步骤的解析为:
TODO 把容器中的 userDao 通过 userService 内部的一个叫 setUserDao()方法 注入进去-->
</bean>
- 构造方法的依赖信息:
<!-- 将UserDao存放到Spring容器中 -->
<bean id="userDao" class="com.kaf.dao.impl.UserDaoImpl"></bean>
<!-- TODO ②使用构造器来进行Bean依赖注入 -->
<bean id="userService" class="com.kaf.service.impl.UserServiceImpl">
<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
- Bean依赖注入的其他方式
-
- P命名空间注入
其本质也是set方法注入,但比起set方法注入更加方便,主要体现在配置文件中。如下:
首先,需要在beans标签中引入P命名空间:
<beans
xmlns:p="http://www.springframework.org/schema/p">
</beans>
其次,需要修改注入方式
<!-- 利用p命名空间来进行Bean依赖注入 该方式用的也比较少-->
<bean id="userService" class="com.kaf.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>
7. Bean的依赖注入概念
依赖注入(Dependency Injection) :它是Spring框架核心IOC的具体实现。目的为了解耦合
在编写程序时,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况。IOC解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。
那这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取
8. Bean的依赖注入步骤
① 在service层采取set方法实现Bean的依赖注入:
public class UserServiceImpl implements UserService{
// todo 如果容器有注入给我们的话,该值就不是null(不为空)了
private UserDao userDao;
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
@Override
public void save() {
/*
// todo 已经可以不用从Spring容器内部类获取dao了 `> 因为在Spring容器内部已经将dao注给我们了
// 加载配置文件,创建Spring容器
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) app.getBean("userDao");
userDao.save();
*/
userDao.save();
}
}
PS:当没有从容器中注入时,userDao的值将为null。 此时如果再web层调用service层的save()方法时,将会报错!
java.lang.NullPointerException(空指针异常)
② 配置文件的描述信息:
PS:是service层依赖于dao层。所以将dao层的对象,添加到service层中
③ web层的实现代码:
public class UserController {
public static void main(String[] args) {
// !要解耦! 耦合度太高了!
// TODO 如果配置好了Bean的依赖注入的话,再执行以下代码将会报空指针异常
UserService userService = new UserServiceImpl();
userService.save();
/*
// 如果使用了依赖注入,再使用以下代码,将实现解耦的效果
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) app.getBean("userService");
userService.save();
*/
}
}
9. Bean的依赖注入的三种数据类型
除了对象的引用可以注入,普通数据类型,集合等都可以在容器中进行注入。
注入数据的三种数据类型
- 普通数据类型
- 集合数据类型
- 引用数据类型
1. 普通数据类型的注入方式:
① 首先,先设置依赖的注入方式(这里采用set注入)
public class UserDaoImpl implements UserDao {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
...
}
② 然后,在配置文件中设置依赖信息:
<!-- TODO 为什么是在userDao里编写类型的依赖注入呢? -->
<!--TODO 因为我们是在userDao里设置set方法来获取类型依赖的 -->
<bean id="userDao" class="com.kaf.dao.impl.UserDaoImpl">
<property name="name" value="Alice"/> <!-- TODO 这里为什么把ref改成了value了呢
TODO 因为ref是用于引用依赖的;而value是用于类型依赖的 -->
<property name="age" value="17"/>
</bean>
2. 引用数据类型的注入方式:
① 首先,先设置依赖的注入方式(这里采用set注入)
public class UserDaoImpl implements UserDao {
// ------ 集合数据类型的依赖注入 测试 ------
private List<String> strList;
private Map<String, User> userMap;
private Properties properties;
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;
}
...
}
② 然后,在配置文件中设置依赖信息:
<!-- TODO 为什么是在userDao里编写类型的依赖注入呢? -->
<!--TODO 因为我们是在userDao里设置set方法来获取类型依赖的 -->
<bean id="userDao" class="com.kaf.dao.impl.UserDaoImpl">
<!-- 为普通数据类型的依赖 注入信息 -->
<property name="name" value="Alice"/> <!-- TODO 这里为什么把ref改成了value了呢
TODO 因为ref是用于引用依赖的;而value是用于类型依赖的 -->
<property name="age" value="17"/>
<!-- 为集合数据类型的依赖 注入信息 -->
<property name="strList">
<list>
<value>Alice</value>
<value>Peter</value>
<value>Tom</value>
</list>
</property>
<property name="userMap">
<map> <!-- PS: 因为我在Map集合中将值设置为:引用类型的了(注意如果该数据是引用类型的话,其接受参数肯定有ref) -->
<entry key="user1" value-ref="user1"></entry>
<entry key="u2" value-ref="user2"></entry>
<entry key="u3" value-ref="user3"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="p1">x</prop>
<prop key="p2">y</prop>
<prop key="p3">z</prop>
</props>
</property>
</bean>
<bean name="user1" class="com.kaf.dao.domain.User">
<property name="name" value="Alice"></property> <!-- 这里使用自闭和标签 -->
<property name="add" value="上海"/>
</bean>
<bean name="user2" class="com.kaf.dao.domain.User">
<property name="name" value="Peter"></property>
<property name="add" value="湖南"/>
</bean>
<bean name="user3" class="com.kaf.dao.domain.User">
<property name="name" value="Tom"/>
<property name="add" value="深圳"/>
</bean>
输出结果为:
10. 引入其他配置文件(分模块开发)
实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大。所以,可以将部分配置拆解到其他配置文件中,而在Spring主配置文件通过import标签进行加载。
如:将配置文件分成几个模块。然后主模块引用其他分模块以此来达成模块分发的效果。
Spring的重点配置
<bean>标签
id属性:在容器中Bean实例的唯一标识,不允许重复
class属性:要实例化的Bean的全限定名 `> 要被创建的Bean对象的全包名(通过反射,默认找无参构造)
scope属性:Bean的作用范围,常用是singleton(默认)和prototype
<property>标签:属性注入(① set方法)(依赖注入 要使用的标签)
name属性:属性名称
value属性:注入的普通属性值
ref属性:注入的对象引用值
<list>标签
<map>标签
<properties>标签
<constructor-arg>标签:属性注入(② 构造方法)
name属性:属性名称
value属性:注入的普通属性值
ref属性:注入的对象引用值
<list>标签
<map>标签
<properties>标签
<import>标签:导入其他的spring的分文件
PS:set方法注入 与 构造方法的注入只是引用依赖注入的标签不一样罢了,其他都一样。
(构造器注入需要写到<constructor-arg>标签里面)
2. Spring相关文件
① ApplicationContext的继承体系
ApplicationContext:接口类型,代表应用上下文,可以通过其实例获得Spring容器中的Bean对象
② ApplicationContext的实现类
1. ClassPathXmlApplicationContext
它是 从类的根路径下加载配置文件,推荐使用这种
2. FileSystemXmlApplicationContext
它是 从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
3. AnnotationConfigApplicationContext
当使用注解配置容器对象时,需要使用此类来创建spring容器。 它用来读取注解。
// TODO 1. 从类的根路径下加载配置文件.配置文件放在resources文件夹下
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
// TODO 2. 从磁盘路径上加载配置文件.配置文件可以放在磁盘的任意位置上
// ApplicationContext app = new FileSystemXmlApplicationContext("D:/IDEAWorkSpace/Demo/15_spring/src/main/resources/applicationContext.xml");
// TODO 3. AnnotationConfigApplicationContext 当使用注解配置容器对象时,需要使用此类来创建spring容器。
③getBean()方法的使用
- 当参数的数据类型是字符串时,
表示根据Bean的id从容器中获得Bean实例,返回是Object,需要强转。 - 当参数的数据类型是Class类型时,
表示根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时,则此方法会报错。
//---------- 获取Bean对象的两种不同方式 ----------
// TODO 1. 根据id获取 `> (根据Bean的id从容器中获取Bean实例) 泛用性更高
UserService userService = (UserService) app.getBean("userService");
UserService userService2 = (UserService) app.getBean("userService2");
userService.save();
userService2.save();
/*
// TODO 2. 根据类型获取 `> (根据类型从容器中匹配Bean实例)
// todo 当在容器中有相同的Class类型的Bean对象的时候,将会报错
UserService userService1 = app.getBean(UserService.class);
userService1.save();
*/
PS:到这里Spring简介就结束了~😘
希望各位大大能给个反馈。