Spring框架之Spring简介(二)

122 阅读11分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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的全限定名称(该类的全路径名称)

image.png

2. Bean标签范围配置

  • scope对象的作用范围,取值如下:

image.png

  • ① singleton 与 prototype主要的区别是:前者只能存储一个对象,而后者可以存储多个对象
  • 当将Bean标签范围配置为singleton 或 prototype时,对象Bean的创建时机:
  1. 当scope设置为 singleton 时,对象Bean的创建时机为:

image.png 从这里可以很明显的看出:Bean对象是在加载配置文件过来时就开始初始化了

image.png

  1. 当scope设置为 prototype 时,对象Bean的创建时机为:

image.png

从这里可以看出:Bean对象在加载配置文件的时候并没有初始化

image.png

从这里也可以很明显的看出:Bean对象是在调用getBean()方法的时候才创建/实例化对象的

小结:

  1. 当scope的取值为 singleton
    Bean的实例化次数:1次
    Bean的实例化时机:当Spring核心(配置)文件被加载时,实例化配置的Bean(对象)实例
    Bean的生命周期:
    对象创建:当应用加载、创建容器时,对象就被创建了
    对象运行:只要容器在,对象一直活着
    对象销毁:当应用卸载、销毁容器时,对象就被销毁了

  2. 当scope的取值为 prototype
    Bean的实例化次数:多次
    Bean的实例化时机:当调用getBean()方法时,实例化Bean
    对象创建:当使用对象时,创建新的对象实例
    对象运行:只要对象在使用中,就一直活着
    对象销毁:当对象长时间不用时,将会被Java的垃圾回收器(GC:garbage collector 垃圾回收机制)回收


3. Bean生命周期配置

该生命周期主要显示 Bean对象什么时候创建,什么时候销毁init-method:指定类中的初始化方法名称
destroy-method:指定类中销毁方法名称

在配置文件中设置好要初始化的方法与要销毁的方法

image.png PS:它(初始化方法、销毁方法)要写在该Bean准备实例化的Class对象中

运行代码如下:

image.png

为什么没有看到销毁代码的执行?难道它没被销毁嘛? 🤔
事实上它已经被销毁了。😀只是它还没被打印出来就被销毁了,所以就看不到destroy()方法所执行的代码了🙄

详细解说:
该代码(上述截图的代码)在一个单元测试中,并没有在一个tomcat服务器里。所以只要这个单元测试执行完毕,那么该销毁的东西都得被销毁。

那么销毁的话为什么没有打印这个方法咧? ==> 因为他还没有来得及打印,就被销毁掉了啊。😔

如果想要看结果,那么可以手动关闭容器,以此来达成销毁得目的。(因为在手动关闭容器时,容器能够意识到自己要挂了,所以他会在它彻底挂之前把销毁方法执行一下)

让我们来手动关闭一下吧😘

image.png

从这里我们看到😔 ApplicationContext么有对应的关闭方法🤡

所以只好用其实现类(ClassPathXmlApplicationContext)来关闭了🙄

image.png

PS:这个生命周期配置了解一下就好了~

4. Bean实例化三种方式

  • 无参构造方法实例化 (重点, 其他了解即可)
  • 工厂静态方法实例化
  • 工厂实例方法实例化

image.png

工厂静态方法实例化 与 工厂实例方法实例化的区别就在于静态方法直接在类中调用,而非静态(实例)方法需要用对象调用

PS:应用场景:工厂模式 ==> JDBC获取连接对象的时候采取工厂模式进行获取。 不要滥用哦~😘

5. 引入依赖注入的原因

  • 引入问题:

image.png

在上图中我们了解到了:在service层里我们直接调用UserService的实例,但此时我们也调用了UserService内部的UserDao实例。但由于 我们只关心我们所关心的实例(UserService),所以我们需要将Spring容器里的UserService实例与UserDao实例相关联(即勾连起来) ==> 为了解耦

  • 解决问题:

image.png

在Spring容器里就将其(相互依赖的对象/实例)相关联起来,而编译时就可以把重心放到我们想关心的地方上了。

6. Bean的依赖注入方式

既然有了解耦的思想了,那么该怎么将UserDao注入到UserService内部呢?

  • set方法 ==> 为service层中的dao对象设置对象的引用变量(从容器中获取该变量值)
  • 构造方法 ==> 通过使用有参构造方法将某个对象注入到某个对象里

1. 在服务层配置依赖注入方式:

image.png

2. 在配置文件中的依赖信息:

  1. 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>
  1. 构造方法的依赖信息:
    <!-- 将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>
  1. 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"/>

image.png

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()方法时,将会报错!

image.png java.lang.NullPointerException(空指针异常)


② 配置文件的描述信息:

image.png

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>

image.png

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>

输出结果为:

image.png

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的分文件

PSset方法注入 与 构造方法的注入只是引用依赖注入的标签不一样罢了,其他都一样。
(构造器注入需要写到<constructor-arg>标签里面)

2. Spring相关文件

① ApplicationContext的继承体系
ApplicationContext:接口类型,代表应用上下文,可以通过其实例获得Spring容器中的Bean对象

ApplicationContext继承关系图.jpg

② 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()方法的使用

image.png

  1. 当参数的数据类型是字符串时,表示根据Bean的id从容器中获得Bean实例,返回是Object,需要强转。
  2. 当参数的数据类型是Class类型时,表示根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时,则此方法会报错。

image.png

//---------- 获取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简介就结束了~😘
希望各位大大能给个反馈。