Spring——bean标签

一、Bean 标签

在 spring 的配置文件中,bean 标签可以说是非常重要的了,bean 标签主要是用来对 spring 中的 bean 进行配置的

<bean id="userDao" class="com.fang.dao.impl.UserDaoImpl"/>

1.1 id 和 class

id 和 class 是我们在 bean 标签中最常见到的两个属性,其中 id 是这个 bean 在 xml 文件中的唯一标识,在一个 spring 的配置文件中是不能出现两个相同的 id 的,不然会报错,class 就不用说了,指定这个 bean 的全路径

在项目开始运行,bean 的配置信息被提取到 BeanDefinition 中时,bean 标签中配置的 id 最终会转换成 bean 的名称,也是 beanDafinitionMap 和 singletonObjects 集合中存储的元素的 key,最终我们就可以通过这个名称来获取 bean
在这里插入图片描述
在这里插入图片描述

而当我们不给 bean 标签设置 id 时,bean 的名称默认就是全路径

<bean class="com.fang.dao.impl.UserDaoImpl"/>

在这里插入图片描述
在这里插入图片描述

这时我们要获取 UserDao 直接在 getBean 方法中传递 xml 中配置的全路径即可

//获取bean
UserDao userDao = (UserDao) applicationContext.getBean("com.fang.dao.impl.UserDaoImpl");

1.2 name 属性

name 属性是用来设置 bean 的别名的

<bean id="userDao" name="aaa, bbb" class="com.fang.dao.impl.UserDaoImpl"/>

当设置了别名后,我们获取 bean 除了使用 userDao,也可以使用 aaa 和 bbb

UserDao userDao1 = (UserDao) applicationContext.getBean("userDao");
UserDao userDao2 = (UserDao) applicationContext.getBean("aaa");
UserDao userDao3 = (UserDao) applicationContext.getBean("bbb");

那这里的 bean 他是拥有了三个名字吗?
在这里插入图片描述
在这里插入图片描述

通过打断点发现并没有,bean 还是只有一个名字,就是 id 指向的值,我们可以通过别名获取到 bean 实际上是因为 beanFactory 中维护了一个 aliasMap 集合,集合中指定了别名和 bean 的映射关系

在这里插入图片描述

那当我们不设置 id 只设置别名的时候,是不是也可以通过全路径名和别名两种方式来获取 bean 呢?我们来试一试

<bean name="aaa, bbb" class="com.fang.dao.impl.UserDaoImpl"/>

在这里插入图片描述

控制台提示 No bean named 'com.fang.dao.impl.UserDaoImpl' 没有找到名字是 com.fang.dao.impl.UserDaoImpl 的 bean,那证明这里没有 id,但是 bean 的名字不是类的全路径

我们可以打断点检查一下问题出在哪里
在这里插入图片描述
在这里插入图片描述

通过打断点可以发现这时候的 bean 的名字变成了 aaa

实际上,在配置 bean 的时候,如果你配置了 id,那么 bean 的名字就是 id 指向的值,如果你没有配置 id 但是配置了 name,那么 bean 的名字就是你 name 属性中的第一个别名,如果你 id 和 name 都不配置,才会默认以类的全路径为 bean 的名字

1.3 scope 属性

在 spring 中,我们知道 bean 默认情况下是单例的,通过配置 scope 属性,我们可以改变 bean 的作用范围,使 bean 不是单例的

在默认的 spring 环境中,bean 的作用范围有两个:singleton 和 prototype

在这里插入图片描述

singleton 是单例的,也是 bean 默认的策略,我们不配置 scope 时,默认就是 singleton

prototype 原型的,当我们配置 scope 的值是 prototype 时,每次获取 bean 时 spring 都会去创建一个新的 bean 实例

<bean id="userDao" class="com.fang.dao.impl.UserDaoImpl" scope="prototype"/>
UserDao userDao1 = (UserDao) applicationContext.getBean("userDao");
UserDao userDao2 = (UserDao) applicationContext.getBean("userDao");
UserDao userDao3 = (UserDao) applicationContext.getBean("userDao");
System.out.println("userDao1 = " + userDao1);
System.out.println("userDao2 = " + userDao2);
System.out.println("userDao3 = " + userDao3);

在这里插入图片描述

1、在作用范围是 singleton 时,默认情况下spring 的配置文件一加载完成,bean 就会被实例化创建完成并且存放到 beanFactory 的单例池 (singletonObjects) 中,每次获取 bean 都是直接去单例池中获取同一个引用,即每次获取都是同一个对象

2、在作用范围是 prototype 时,配置文件加载完并不会对 bean 进行创建,而是在你获取 bean 时,bean 才会被创建,且创建的 bean 的引用不会存储到单例池中,下一次再获取 bean 时,spring 会再创建一个对象,每个对象使用完成,就会被垃圾回收

在默认的 spring 环境下,scope 是只有 singleton 和 prototype 这两个值的,而在我们引入 webmvc 的依赖时,scope 的取值还可以是 request 和 session

这两个的作用分别就是将创建的 bean 放到 request 或者 session 中

1.4 lazy-init 属性

这个属性是控制 bean 的延迟加载的

在这里插入图片描述

spring 的单例默认实际上是一种类似于饿汉式的单例,在配置文件加载完成时,bean 就已经被创建好存放在容器中了,无论你是否需要使用到这个 bean,当你需要使用 bean 时,再去单例池中获取

而开启了 lazy-init 的 bean,在 配置文件加载完成时,并不会将 bean 创建,而是在你第一次获取 bean 时,才会去将 bean 创建出来,然后存放到单例池中,后续再获取 bean 时,才从单例池中获取,类似于懒汉式单例

1.5 init-method 和 destroy-method 属性

这两个属性,前者是配置 bean 的初始化方法,后者配置 bean 的销毁方法

初始化方法在 bean 创建完成且属性设置之后执行,销毁方法在 bean 销毁之前执行

首先我们在实体类中提供一个初始化方法和一个销毁方法

public class UserDaoImpl implements UserDao {

    private String name;

    public UserDaoImpl() {
        System.out.println("构造器执行");
    }

    public void setName(String name) {
        System.out.println("name属性设置了");
        this.name = name;
    }

    /**
     * 初始化方法:在 bean 创建完成,设置完属性之后执行
     */
    public void init() {
        System.out.println("初始化方法执行");
    }

    /**
     * 销毁方法:在容器关闭,bean 销毁之前执行
     */
    public void destroy() {
        System.out.println("销毁方法执行");
    }
}

然后在 xml 中指定初始化方法和销毁方法,并且设置 setter 的属性注入

<bean id="userDao" class="com.fang.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy">
    <property name="name" value="张三"/>
</bean>

在测试类中获取 bean

public class BeanTest {
    public static void main(String[] args) {
        //加载配置文件信息
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        //获取bean
        UserDao userDao1 = (UserDao) applicationContext.getBean("userDao");
        System.out.println("userDao1 = " + userDao1);
    }
}

当配置文件加载完成时,将会执行构造器,如果配置了 setter 属性注入,就会执行 setter 方法,之后才执行初始化方法

在这里插入图片描述

销毁的方法需要容器显示的进行关闭时才会被调用

当我们代码执行完毕,或者打了断点在代码还没执行完时将项目停止,这样容器都会关闭,但是这样的关闭不是显示的,容器并不知道自己会关闭,所以也就没有将关闭的操作告知 bean,bean 也不知道容器即将关闭,自己即将被销毁,所以 bean 中的销毁方法并不会被执行

需要 bean 中销毁的方法被执行,需要容器主动进行关闭

在 ClassPathXmlApplicationContext 中存在一个 close() 方法,这个方法执行后容器将会关闭,并且会执行 bean 中配置的销毁方法

补充:实现 InitializingBean 接口的初始化方法

初始化方法我们除了通过在 xml 指定之外,还可以通过实现 InitializingBean 接口来设置

这个接口中提供了一个方法 afterPropertiesSet,在 bean 创建完成,并设置完属性后,将会执行这个方法

public class UserDaoImpl implements UserDao, InitializingBean {

    private String name;

    //省略上面代码写的构造器、setter方法、初始化方法和销毁方法......

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("执行 afterPropertiesSet 方法");
    }
}

在这里插入图片描述

再一次执行代码我们可以发现执行顺序为:

构造器 -> setter方法 -> InitializingBean 的 afterPropertiesSet 方法 -> 自己定义的初始化方法

1.6 bean 的实例化

spring 实例化 bean 的方式主要有两种:构造器、工厂方法

1.6.1 构造器方式

我们目前所写的代码实例化 bean 使用的都是构造器,而且是无参的构造器

<bean id="userDao" class="com.fang.dao.impl.UserDaoImpl"/>
或者
<bean id="userDao" class="com.fang.dao.impl.UserDaoImpl">
	<property name="name" value="张三"/>
</bean>
都是属于无参构造,其中 property 是使用 setter 方法来给对象的属性注入值

当我们使用有参构造器来实例化 bean 时,需要传递构造器的参数,这时候的 xml 配置如下

//有参构造
public UserDaoImpl(String name) {
    System.out.println("有参构造");
    this.name = name;
}
<bean id="userDao" class="com.fang.dao.impl.UserDaoImpl">
    <constructor-arg name="name" value="张三"/>
</bean>

constructor-arg 标签用来传递构造器中的参数

xml 中使用有参构造实例化对象,传递的参数名字和个数必须和提供的有参构造的参数名字和个数一致

假设你的有参构造需要两个参数 id 和 name,那么你必须将两个参数都进行传递,只传递一个会报错,除非你还提供了只需要一个参数的构造器

注: constructor-arg 这个标签一般是用在构造器传参的 (其翻译也叫构造器参数) ,但是他并不仅仅只能用在构造器传参中,其余方法的传参也是可以使用这个标签来实现的,在后面的工厂方式实例化 bean 的时候,我们可以使用这个来传参

1.6.2 工厂方式

工厂方式实例化 bean 有三种做法:静态工厂、实例工厂、实现 FactoryBean

1.6.2.1 静态工厂方式

使用静态工厂实例化 bean 我们首先需要提供一个工厂类,内部提供一个静态方法,在静态方法中返回我们需要的对象

public class StaticFactory {
    public static UserDao getUserDao() {
        return new UserDaoImpl();
    }
}

之后我们需要在 xml 中配置 bean 标签,class 属性为静态工厂类的全路径

还需要加上 factory-method 属性,这个属性用来指定静态工厂类中的静态方法

<bean id="userDao" class="com.fang.factory.StaticFactory" factory-method="getUserDao"/>

之后就和前面一样,加载配置文件,并获取 bean

spring 解析 xml 时会进行检查,当发现 factory-method 属性时,spring 创建并存储的 bean 就不是 class 指定的类的对象了,而是静态方法中返回的对象

1.6.2.2 实例工厂

在使用实例工厂实例化 bean 时,我们需要先提供一个实例工厂类,内部就一个普通方法,返回我们需要的对象

public class GeneralFactory {
    public UserDao getUserDao() {
        return new UserDaoImpl();
    }
}

之后我们需要在 xml 中先配置一个工厂的 bean,使 spring 创建一个工厂的对象出来,然后再指定工厂对象内部的方法来创建我们需要的 UserDao 对象

<!--提供工厂的 bean 的配置信息,使 spring 可以实例化工厂对象-->
<bean id="generalFactory" class="com.fang.factory.GeneralFactory"/>
<!--指定工厂对象内部的方法获取我们要的对象-->
<bean id="userDao" factory-bean="generalFactory" factory-method="getUserDao"/>

实例工厂的方式和静态工厂的方式不同点在于,实例工厂需要 spring 先实例化工厂的对象,之后才可以指定工厂的方法获取我们要的对象,这个过程中,容器内会生成两个对象

而静态工厂对象不需要 spring 先实例化工厂的对象,直接指定 class 和静态方法就可以实例化我们需要的对象了,这个过程容器只会生成一个对象

1.6.2.3 FactoryBean

这个方式是需要我们实现一个叫做 FactoryBean 的接口,接口中提供了三个方法,当我们获取 bean 的时候,对象从其中一个方法中返回

public class MyFactoryBean implements FactoryBean<UserDao> {
    /**
     * 获取对象的方法
     * 当我们在 xml 中配置了这个类,通过 getBean 获取的时候,将会返回这个方法中返回的对象
     * @return
     * @throws Exception
     */
    @Override
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }

    /**
     * 获取对象的 class
     * @return
     */
    @Override
    public Class<?> getObjectType() {
        return UserDao.class;
    }

    /**
     * 设置是否是单例
     * 不设置默认就是 true
     * @return
     */
    @Override
    public boolean isSingleton() {
        // return FactoryBean.super.isSingleton();
        return true;
    }
}

在 xml 中配置这个类的 bean,当我们去 getBean 的时候,将会返回 getObject 方法中返回的对象

<bean id="myFactoryBean" class="com.fang.factory.MyFactoryBean"/>
//获取bean
Object bean = applicationContext.getBean("myFactoryBean");
System.out.println("bean = " + bean);

在这里插入图片描述

得到的结果可以看到返回的是一个 UserDaoImpl 而不是 MyFactoryBean

FactoryBean 的方法来实例化对象,会延迟实例化,当我们配置文件加载完成后,单例池中存储的对象是 MyFactoryBean 的对象

在这里插入图片描述

打断点可以看到单例池中没有存储 UserDao,这个时候 UserDao 还没被实例化

当你调用 getBean 方法时,UserDao 才会被实例化,但是也不会被存放在单例池中

在这里插入图片描述
在这里插入图片描述

那么我们获取的 UserDao 是哪里来的呢?

在 beanFactory 中存在一个 Map – factoryBeanObjectCache,factoryBean的缓存,UserDao 就被存放在这个缓存中

在这里插入图片描述

他的基本流程是这样的

1、在我们加载配置文件时,MyFactoryBean 会被实例化并且存放在单例池中

2、当我们第一次通过 beanName 去 getBean 时,由于 MyFactoryBean 实现了 FactoryBean 接口,所以会调用接口中的 getObject 方法,并将方法产生的对象存储到 factoryBean 的缓存中,然后给 getBean 方法返回

3、当再次调用 getBean 方法时,缓存中已经存在了符合条件的对象,这时候就直接会将对象返回,从始至终我们需要的 UserDao 的对象都不是存放在单例池中的,而是存放在 factoryBean 的缓存中

当我们在 isSingleton 方法中设置了单例模式为 false,那么和之前的策略一样,每调用一次就生成一个对象,且对象也不会存放到缓存中

那如果我们需要获取到单例池中的 MyFactoryBean 对象应该怎么操作呢?

这个时候就需要我们在 getBean 的时候,在 beanName 的前面加上一个 & 符号

Object bean = applicationContext.getBean("&myFactoryBean");

在这里插入图片描述

这样我们返回的对象就是一个 MyFactoryBean 对象了

1.6.2.4 带参数的工厂方法

工厂方式来实例化 bean 的三种方式就如上面说的一样,下面我们来补充一下带参数的工厂方法

//UserDaoImpl
public class UserDaoImpl implements UserDao {

    private String name;

    public UserDaoImpl() {
    }

    public void setName(String name) {
        this.name = name;
    }
}

//StaticFactory
public class StaticFactory {
    public static UserDao getUserDao(String name) {
        UserDaoImpl userDao = new UserDaoImpl();
        userDao.setName(name);
        return userDao;
    }
}

这里的静态工厂方法中需要传递一个参数,我们在 xml 中像之前那样配置是会出错的

在这里插入图片描述

出错的原因和构造器是一样的,当你的方法要求要传递参数时,就必须将参数给到,否则就会报错,这里我们就需要使用上 constructor-arg 标签来将参数传递给 getUserDao 方法,所以在上面使用构造器方式去实例化对象时,才会说明这个标签不仅仅只能用于构造器,而是所有需要传递参数的方法都可以使用这个标签

在这里插入图片描述

这样就不会报错了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值