IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring系列3:bean实例化的方式知多少 -> 正文阅读

[Java知识库]Spring系列3:bean实例化的方式知多少

本文内容

  1. 通过构造函数实例化bean
  2. 通过静态工厂方法实例化bean
  3. 通过实例工厂方法实例化bean

通过构造函数实例化bean

Spring中可以通过空构造函数或是默认构造函数来实例bean,直接上案例。

定义一个简单类

package com.crab.spring.ioc.demo01;

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/13 12:05
 * @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列
 */
public class ConstructorBean {
}

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--默认构造函数实例化-->
    <bean class="com.crab.spring.ioc.demo01.ConstructorBean" id="constructorBean"/>
</beans>

来个测试测试进行测试

package com.crab.spring.ioc.demo01;
import static org.junit.Assert.*;

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/13 13:32
 * @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列
 */
public class Test {

    @org.junit.Test
    public void test() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo01/spring.xml");
        ConstructorBean constructorBean = context.getBean(ConstructorBean.class);
        System.out.println(constructorBean);
        context.close();
    }

}

运行结果

com.crab.spring.ioc.demo01.ConstructorBean@371a67ec

通过静态工厂方法实例化bean

在定义使用静态工厂方法创建的 bean 时,使用 class 属性来指定包含静态工厂方法的类,并使用名为 factory-method 的属性来指定工厂方法本身的名称。

定义一个类,包含一个静态工厂方法返回该类的实例

package com.crab.spring.ioc.demo01;

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/13 13:38
 * @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列
 */
public class StaticBean {

    private static StaticBean instance = new StaticBean();

    private StaticBean() {
    }

    /**
     * 静态工厂方法返回实例
     *
     * @return
     */
    public static StaticBean factoryMethod() {
        return instance;
    }
}

配置文件中配置静态工厂方法实例化bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--静态工厂方法-->
    <bean class="com.crab.spring.ioc.demo01.StaticBean" id="staticBean" factory-method="factoryMethod"/>

</beans>

测试类

    @org.junit.Test
    public void test() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo01/spring.xml");
        ConstructorBean constructorBean = context.getBean(ConstructorBean.class);
        System.out.println(constructorBean);
        System.out.println("测试静态工厂方法实例化类");
        StaticBean staticBean = context.getBean(StaticBean.class);
        System.out.println(staticBean); // 实例地址
        System.out.println(staticBean == StaticBean.factoryMethod()); // true
        context.close();
    }

通过实例工厂方法实例化bean

与通过静态工厂方法进行实例化类似,使用实例工厂方法进行实例化从容器中调用现有 bean 的非静态方法来创建新 bean。 要使用此机制, class 属性留空,并在 factory-bean 属性中指定当前(或父级或祖先)容器中包含要调用以创建对象的实例方法的 bean 的名称。 使用 factory-method 属性设置工厂方法本身的名称。

定义一个类及其工厂类

package com.crab.spring.ioc.demo01;

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/13 13:48
 * @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列
 */
public class MyBean {
    private String name;

    public MyBean(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "MyBean{" +
                "name='" + name + '\'' +
                '}';
    }
}
package com.crab.spring.ioc.demo01;

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/13 13:48
 * @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列
 */
public class MyBeanFactory {

    /**
     * 生成一个 MyBean
     * @return
     */
    public MyBean factoryMethod() {
        return new MyBean("xxx");
    }
}

配置文件中进行bean定义

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--默认构造函数实例化-->
    <bean class="com.crab.spring.ioc.demo01.ConstructorBean" id="constructorBean"/>

    <!--静态工厂方法-->
    <bean class="com.crab.spring.ioc.demo01.StaticBean" id="staticBean" factory-method="factoryMethod"/>

    <!--实例工厂方法-->
    <!--工厂-->
    <bean class="com.crab.spring.ioc.demo01.MyBeanFactory" id="myBeanFactory"/>
    <!--    通过实例工厂方法创建的bean-->
    <bean id="myBean" factory-bean="myBeanFactory" factory-method="factoryMethod"/>
</beans>

测试类输出通过实例工厂方法创建的bean

package com.crab.spring.ioc.demo01;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import static org.junit.Assert.*;

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/13 13:32
 * @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列
 */
public class Test {

    @org.junit.Test
    public void test() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo01/spring.xml");
        ConstructorBean constructorBean = context.getBean(ConstructorBean.class);
        System.out.println(constructorBean);
        System.out.println("测试静态工厂方法实例化类");
        StaticBean staticBean = context.getBean(StaticBean.class);
        System.out.println(staticBean);
        System.out.println(staticBean == StaticBean.factoryMethod());
        System.out.println("测试实例工厂方法实例化类");
        MyBean myBean = context.getBean(MyBean.class);
        System.out.println(myBean);
        context.close();
    }

}

结果输出,输出了myBeanname 符合预期

//省略
测试实例工厂方法实例化类
MyBean{name='xxx'}

扩展: 上面的工厂bean指的是在 Spring 容器中配置的 bean,它通过实例或静态工厂方法创建对象。Spring中还有一个强大的接口FactoryBean,同样可以产生bean,但2者不是同一个概念,感兴趣的可以了解下,后续有机会我们再详细讲。

总结

本篇主要说明了3中实例化bean的方式,可以按照实际需要进行选择使用。下一篇介绍bean中的依赖是如何注入的。

本篇源码地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo01

Spring系列4:依赖注入的2种方式

本文内容

  1. 基于构造器的依赖注入
  2. 基于setter的依赖注入

基于构造器的依赖注入

案例

定义2个简单的bean类,BeanOne 和 BeanTwo,前者依赖后者。

package com.crab.spring.ioc.demo02;

public class BeanTwo {
}
package com.crab.spring.ioc.demo02;

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/12 16:59
 */
public class BeanOne {
    private int age;
    private String name;
    private BeanTwo beanTwo;

    /**
     * 构造函数,用于依赖注入,定义3个依赖
     * @param age
     * @param name
     * @param beanTwo
     */
    public BeanOne(int age, String name, BeanTwo beanTwo) {
        this.age = age;
        this.name = name;
        this.beanTwo = beanTwo;
    }

    @Override
    public String toString() {
        return "BeanOne{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", beanTwo=" + beanTwo +
                '}';
    }
}

通过xml配置文件实现bean定义和依赖注入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bean2" class="com.crab.spring.ioc.demo02.BeanTwo"/>

    <bean id="bean1" class="com.crab.spring.ioc.demo02.BeanOne">
        <constructor-arg name="age" index="0" type="int" value="20"/>
        <constructor-arg name="name" index="1" type="java.lang.String" value="xxx"/>
        <constructor-arg name="beanTwo" index="2" type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/>
    </bean>
</beans>

来个测试类验证下注入

package com.crab.spring.ioc.demo02;
/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/12 17:09
 */
public class demo02Test {

    @Test
    public void test_construct() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo02/spring1.xml");
        BeanOne bean1 = context.getBean("bean1", BeanOne.class);
        System.out.println(bean1);
        context.close();
    }

输出如下

BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@5204062d}

对照配置文件BeanOne的3个依赖都通过构造器的方式进行注入了,符合预期,很简单。

constructor-arg详解

标签constructor-arg支持的元素列表如下。

元素名作用
name参数名
index参数索引,0开始
type参数类型
value
refbean引用

例如案例中的配置

<bean id="bean1" class="com.crab.spring.ioc.demo02.BeanOne">
    <constructor-arg name="age" index="0" type="int" value="20"/>
    <constructor-arg name="name" index="1" type="java.lang.String" value="xxx"/>
    <constructor-arg name="beanTwo" index="2"                             type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/>
</bean>

注意: 在没有引起歧义的情况下,上面的部分元素并不是都必须配置的。如指定了index时可以定位参数位置,那么name是可以不配置的,又如通过ref引用依赖bean,type可以省略

<bean id="bean1" class="com.crab.spring.ioc.demo02.BeanOne">
    <constructor-arg index="0" type="int" value="20"/>
    <constructor-arg index="1" type="java.lang.String" value="xxx"/>
    <constructor-arg  index="2" ref="bean2"/>
</bean>

注意事项

当在<constructor />使用 name元素指定构造函数中的参数名时,尤其要方法参数名编译后是否保留的情况。举个例子

// 编译前的方法参数
public BeanOne(int age, String name, BeanTwo beanTwo)
// 编译后的方法参数
public BeanOne(int var1, String var2, BeanTwo var3)

编译后方法参数被编译成var1这种形式会导致仅指定name的构造注入失效。

如何解决?提供2种方法

  1. Maven编译插件添加编译参数,保留参数名,pom文件下增加如下插件配置

        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <encoding>UTF8</encoding>
                        <compilerArgs>
                            <arg>-parameters</arg>
                        </compilerArgs>
                    </configuration>
                </plugin>
    
            </plugins>
        </build>
  2. 使用 @ConstructorProperties JDK 注释显式命名构造函数参数

        /**
         * 构造函数,用于依赖注入,定义3个依赖
         * @param age
         * @param name
         * @param beanTwo
         */
        @ConstructorProperties({"age", "name", "beanTwo"}) // 显式声明构造参数名称
        public BeanOne(int age, String name, BeanTwo beanTwo) {
            this.age = age;
            this.name = name;
            this.beanTwo = beanTwo;
        }

基于setter的依赖注入

基于 Setter 的 DI 是通过容器在调用无参数构造函数或无参数静态工厂方法来实例化 bean 后调用 bean 上的 setter 方法来完成的。

来一个简单类BeanThree依赖BeanOneBeanTwo并提供了Setter方法来设置。

package com.crab.spring.ioc.demo02;

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/13 8:18
 */
public class BeanThree {
    private BeanTwo beanTwo;
    private BeanOne beanOne;

    public void setBeanTwo(BeanTwo beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setBeanOne(BeanOne beanOne) {
        this.beanOne = beanOne;
    }
}

对应的配置文件可以通过标签property中的refname来设置属性引用或是属性值

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bean2" class="com.crab.spring.ioc.demo02.BeanTwo"/>

    <!--构造函数注入-->
    <bean id="bean1" class="com.crab.spring.ioc.demo02.BeanOne">
        <constructor-arg name="age" index="0" type="int" value="20"/>
        <constructor-arg name="name" index="1" type="java.lang.String" value="xxx"/>
        <constructor-arg name="beanTwo" index="2" type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/>
    </bean>

    <!--setter注入-->
    <bean id="bean3" class="com.crab.spring.ioc.demo02.BeanThree">
        <!-- 1 ref元素-->
        <property name="beanOne" ref="bean1"></property>
        <!-- 2 ref标签-->
        <property name="beanTwo">
            <ref bean="bean2"></ref>
        </property>
         <property name="name" value="xxxx"/>
    </bean>
</beans>

运行测试类和结果,可见依赖注入成功

public class demo02Test {

    @Test
    public void test_construct() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo02/spring1.xml");
        BeanOne bean1 = context.getBean("bean1", BeanOne.class);
        System.out.println(bean1);

        System.out.println("演示Setter注入");
        BeanThree beanThree = context.getBean(BeanThree.class);
        System.out.println(beanThree);
        context.close();
    }
}
BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24}
演示Setter注入
BeanThree{name='xxxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24, beanOne=BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24}}

依赖解决过程

  • ApplicationContext是用描述所有bean的配置元数据创建和初始化的。配置元数据可以通过XML、Java代码或注释指定。
  • 对于每个bean,它的依赖项都以属性、构造函数参数或静态工厂方法的参数的形式表示(如果使用静态工厂方法而不是普通构造函数的话)。当bean实际创建时,这些依赖项被提供给bean。
  • 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。
  • 每个具有值的属性或构造函数参数都将从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring可以将字符串格式提供的值转换为所有内置类型,如int、long、string、boolean等。

什么是循环依赖,如何处理?

如果主要使用构造函数注入,则可能会创建一个不可解析的循环依赖场景。

例如:类A需要类B的一个实例通过构造函数注入,类B需要类A的一个实例通过构造函数注入。如果将类A和类B的bean配置为相互注入,Spring IoC容器会在运行时检测此循环引用,并抛出BeanCurrentlyInCreationException

一个可能的解决方案是编辑一些由setter而不是构造函数配置的类的源代码。或者,避免构造函数注入,只使用setter注入。换句话说,可以使用setter注入配置循环依赖项。

与典型情况(没有循环依赖关系)不同,bean A 和 bean B 之间的循环依赖关系强制其中一个 bean 在完全初始化之前注入另一个 bean(典型的先有鸡还是先有蛋的场景)。

萝卜青菜各有所爱

选择用构造器注入还是Setter注入?

Spring官方的推荐,构造函数用于强制依赖项,将 setter 方法或配置方法用于可选依赖项。请注意,在 setter 方法上使用 @Required 注释可用于使属性成为必需的依赖项;然而,带有参数的编程验证的构造函数注入是更可取的。

Spring 团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。作为旁注,大量的构造函数参数是一种不好的代码气味,这意味着该类可能有太多的职责,应该重构以更好地解决适当的关注点分离。 Setter 注入应该主要只用于可以在类中分配合理默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。 setter 注入的一个好处是 setter 方法使该类的对象可以在以后重新配置或重新注入。

有时,在处理没有源代码的第三方类时,如果第三方类没有公开任何 setter 方法,那么构造函数注入可能是 DI 的唯一可用形式。

总结

本文演示2种依赖注入的方式:构造函数注入和Setter方法注入,并对比如何选择这2种方式。下一篇继续深入依赖注入。

本篇源码地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo02

知识分享,转载请注明出处。学无先后,达者为先!

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-01-16 12:54:46  更:2022-01-16 12:56:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:16:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码