前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >IOC容器创建bean实例的4种方式

IOC容器创建bean实例的4种方式

作者头像
可为编程
发布2023-11-14 11:38:01
2000
发布2023-11-14 11:38:01
举报

戳上方蓝字“可为编程” 点击右上角选择“设为星标”,好文不错过!

了解了IOC是什么,接下来我们看IOC容器能做什么,首先其最最主要的功能就是对Bean进行管理和创建,IOC容器创建bean实例共有4种方式,具体如下:

  1. 通过反射调用构造方法创建bean对象
  2. 通过静态工厂方法创建bean对象
  3. 通过实例工厂方法创建bean对象
  4. 通过FactoryBean创建bean对象

Spring容器内部创建bean实例对象常见的有4种方式,这四种又可以分为两大种,一是基于反射机制,二是基于工厂模式,我将基于此并结合案例深入说明一下两者的区别和原理。

1、通过反射调用构造方法创建bean对象

调用类的构造方法获取对应的bean实例,是使用最多的方式,这种方式只需要在xml bean元素中指定class属性,spring容器内部会自动调用该类型的构造方法来创建bean对象,将其放在容器中以供使用。

如果是采用注解形式创建和管理Bean,同样也是采用反射的机制,随着Spring的发展,注解(Annotation)逐渐成为主流的配置方式。使用注解可以减少配置文件的代码量,并且把相关的配置信息和代码放在一起,提高了可维护性。例如,使用@Component、@Service、@Repository、@Controller等注解可以自动创建Bean。

代码语言:javascript
复制
<bean id="bean名称" name="bean名称或者别名" class="bean的完整类型名称">
<constructor-arg index="0" value="bean的值" ref="引用的bean名称" />
<constructor-arg index="1" value="bean的值" ref="引用的bean名称" />
....
<constructor-arg index="n" value="bean的值" ref="引用的bean名称" />
</bean>

constructor-arg用于指定构造方法参数的值 index:构造方法中参数的位置,从0开始,依次递增 value:指定参数的值 ref:当插入的值为容器内其他bean的时候,这个值为容器中对应bean的名称

举个例子:这里我采用两种方式,首先采用Xml配置文件形配置并式定义Bean,二是采用注解形式生成Bean。

2、. IOC容器初始化细节

Person类
代码语言:javascript
复制
public class Person {

    public String name;
    public Integer age;
    Wife wife;

    public Person(String s, Integer s2, Wife wife) {
        System.out.println("反射通过调用构造函数进行实例创建...");
        this.name = s;
        this.age = s2;
        this.wife = wife;
    }
...省略属性的get() set()方法
}

beans.xml配置

代码语言:javascript
复制
<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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        ">

    <!--构造方法中增加妻子对象-->
    <bean class="org.kewei.pojo.Person" id="person">
        <constructor-arg type="org.kewei.pojo.Wife" ref="wife"/>
        <constructor-arg index="0" value="可为编程" />
        <constructor-arg index="1" value="18" />
    </bean>
    <bean class="org.kewei.pojo.Wife" id="wife" autowire-candidate="true">
        <property name="age" value="18"/>
        <property name="name" value="可为"/>
    </bean>

</beans>

spring容器创建Person的时候,会通过反射的方式去调用Person类中对应的构造函数来创建Person对象。

代码语言:javascript
复制
public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("test.xml");
        Person person = (Person) classPathXmlApplicationContext.getBean("person");
        System.out.println(person.name+person.age);
        System.out.println(person.getWifeName() + person.getWifeAge());
    }
}

采用注解@Service定义Bean,如果没有指定BeanId,系统会自动以类名的首字母小写作为Bean名称进行生成。

代码语言:javascript
复制
@Service
public class KeWeiService {
    public KeWeiService() {

        System.out.println("基于注解形式创建正在创建KeWeiService---   " + this);
        System.out.println("反射通过调用构造函数进行实例创建...---   " + this);
    }
}
代码语言:javascript
复制
//获取Bean
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(KeWeiService.class);
        KeWeiService kw = (KeWeiService) annotationConfigApplicationContext.getBean("keWeiService");
        System.out.println(kw);
    }
}

测试结果如下所示,可见都是采用了反射的机制进行Bean的生成创建。只不过两种不同的方式,根本原理上来说还是基于Java的反射原理。

Spring框架在创建Bean时,使用了Java反射(Reflection)机制。这种机制允许Spring在运行时检查和修改对象的一些行为。比如AOP就是采用了反射机制,对Bean可以在运行时对其进行行为修改,比如切面(Aspect),可以拦截目标对象的方法调用,并在调用前后加入额外的逻辑处理,Spring使用反射获取目标方法的信息,并动态地织入额外的代码逻辑。诚然,这块也用到了动态代理技术。

总之,Spring容器使用反射来实例化、配置和管理Bean。当Spring容器启动时,它会读取配置文件(例如XML或Java类注释),并根据这些配置信息创建Bean实例。在这个过程中,Spring使用反射来调用对象的构造函数或静态方法来创建Bean,对于属性,Spring还使用反射来设置Bean的属性,当配置文件中定义了一个Bean的属性时,Spring会使用反射调用对象的setter方法来设置这些Bean的属性值。其实反射在Spring的很多地方都有体现,利用Java反射机制Spring实现了延迟加载、依赖注入以及AOP等核心功能。

2、 通过静态工厂方法创建Bean对象

我们还可以采用工厂模式,创建静态工厂,内部提供一些静态方法来生成所需要的对象,将这些静态方法创建的对象交给spring以供后续使用。

代码语言:javascript
复制
<bean id="bean名称" name="" class="静态工厂完整类名" factory-method="静态工厂的方法">
    <constructor-arg index="0" value="bean的值" ref="引用的bean名称" />
    <constructor-arg index="1" value="bean的值" ref="引用的bean名称" />
    ....
    <constructor-arg index="n" value="bean的值" ref="引用的bean名称" />
</bean>

class:指定静态工厂完整的类名 factory-method:静态工厂中的静态方法,返回需要的对象。 constructor-arg用于指定静态方法参数的值,用法和上面介绍的构造方法一样。

spring容器会自动调用静态工厂的静态方法获取指定的对象,将其放在容器中以供使用。

定义静态工厂

创建一个静态工厂类,用于生成Person对象。

代码语言:javascript
复制
public class PersonStaticFactory {

    /**
     * 静态无参方法创建Person
     *
     * @return
     */
    public static Person build() {
        System.out.println(PersonStaticFactory.class + ".buildPerson1()");
        Person person = new Person();
        person.setName("我是无参静态构造方法创建的!");
        return person;
    }

    /**
     * 静态有参方法创建Person
     *
     * @param name 名称
     * @param age  年龄
     * @return
     */
    public static Person build2(String name, int age) {
        System.out.println(PersonStaticFactory.class + ".buildPerson2()");
        Person person2 = new Person();
        person2.setName(name);
        person2.setAge(age);
        return person2;
    }
}
beans.xml配置
代码语言:javascript
复制
<!-- 通过工厂静态无参方法创建bean对象 -->
<bean id="createBeanByStaticFactoryMethod1" class="org.kewei.service.PersonStaticFactory"
factory-method="build"/>
<!-- 通过工厂静态有参方法创建bean对象 -->
<bean id="createBeanByStaticFactoryMethod2" class="org.kewei.service.PersonStaticFactory"
factory-method="build2">
   <constructor-arg index="0" value="通过工厂静态有参方法创建UerModel实例对象"/>
   <constructor-arg index="1" value="30"/>
</bean>

上面配置中,spring容器启动的时候会自动调用PersonStaticFactory中的build()静态方法获取Person对象,将其作为createBeanByStaticFactoryMethod1名称对应的bean对象放在IOC容器当中。

调用PersonStaticFactory的build2()方法,并且会传入2个指定的参数,得到返回的Person对象,将其作为createBeanByStaticFactoryMethod2名称对应的bean对象放在IOC容器中。

代码语言:javascript
复制
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("test.xml");
Person createBeanByStaticFactoryMethod1 = (Person) classPathXmlApplicationContext.getBean("createBeanByStaticFactoryMethod1");
Person createBeanByStaticFactoryMethod2 = (Person) classPathXmlApplicationContext.getBean("createBeanByStaticFactoryMethod2");
System.out.println(createBeanByStaticFactoryMethod1.name+createBeanByStaticFactoryMethod1.age);
System.out.println(createBeanByStaticFactoryMethod2.name+createBeanByStaticFactoryMethod2.age);
代码语言:javascript
复制
---------------------------------------------
我是无参静态构造方法创建的!null
通过工厂静态有参方法创建Person实例对象30

从输出中可以看出,两个静态方法都被调用了,都输出了对应的信息,第一行为build()方法生成的Bean,第二行为build2()方法生成的Bean对象。

上面是通过配置文件的形式获取Bean对象,接下来我再演示一下通过注解的方式如何通过静态工厂生成Bean对象。

注解配置

当然,方法是静态的我们就可以直接调用,但为了演示注解的作用,交给Spring做管理,因此我们通过加注解的形式,获取Bean对象。

代码语言:javascript
复制
@Configuration
public class PersonStaticFactory {

    /**
     * 静态无参方法创建Person
     *
     * @return
     */
    @Bean("createBeanByStaticFactoryMethod1")
    public static Person build() {
        System.out.println(PersonStaticFactory.class + ".buildPerson1()");
        Person person = new Person();
        person.setName("我是无参静态构造方法创建的!");
        return person;
    }

    /**
     * 静态有参方法创建Person
     *
     * @return
     */
    @Bean("createBeanByStaticFactoryMethod2")
    public static Person build2() {
        System.out.println(PersonStaticFactory.class + ".buildPerson2()");
        Person person2 = new Person();
        person2.setName("通过工厂静态有参方法创建");
        person2.setAge(18);
        return person2;
    }
}

通过@Bean注解使用一个静态方法创建一个Bean,并通过Bean的名称(在这里是createBeanByStaticFactoryMethod1)来获取它。首先要保障配置类被Spring扫描到使用@Configuration注解来标记配置类。在非Spring管理的类中直接通过名称获取Bean,需要手动从Spring上下文AnnotationConfigApplicationContext中获取它。

代码语言:javascript
复制
AnnotationConfigApplicationContext annotationConfigApplicationContext1 = new AnnotationConfigApplicationContext(PersonStaticFactory.class);
Person createBeanByStaticFactoryMethod1 = (Person) annotationConfigApplicationContext1.getBean("createBeanByStaticFactoryMethod1");
Person createBeanByStaticFactoryMethod2 = (Person) annotationConfigApplicationContext1.getBean("createBeanByStaticFactoryMethod2");
System.out.println(createBeanByStaticFactoryMethod1.name+createBeanByStaticFactoryMethod1.age);
System.out.println(createBeanByStaticFactoryMethod2.name+createBeanByStaticFactoryMethod2.age);

很显然,比我们用Xml配置的形式,少了好多代码,这也是Spring后期推行的主流方式。

3、通过实例工厂方法创建bean对象

让spring容器去调用某些对象的某些实例方法来生成bean对象放在容器中以供使用。

代码语言:javascript
复制
<bean id="bean名称" factory-bean="需要调用的实例对象bean名称" factory-method="bean对象中的方法">
    <constructor-arg index="0" value="bean的值" ref="引用的bean名称" />
    <constructor-arg index="1" value="bean的值" ref="引用的bean名称" />
    ....
    <constructor-arg index="n" value="bean的值" ref="引用的bean名称" />
</bean>

spring容器以factory-bean的值为bean名称查找对应的bean对象,然后调用该对象中factory-method属性值指定的方法,将这个方法返回的对象作为当前bean对象放在容器中供使用。

定义一个实例工厂

内部写2个方法用来创建Person对象。

代码语言:javascript
复制
public class PersonFactory {

    /**
     * 静态无参方法创建Person
     *
     * @return
     */
    public Person build() {
        System.out.println(PersonFactory.class + ".buildPerson1()");
        Person person = new Person();
        person.setName("我是无参静态构造方法创建的!");
        return person;
    }

    /**
     * 静态有参方法创建Person
     *
     * @return
     */
    public Person build2(String name, int age) {
        System.out.println(PersonFactory.class + ".buildPerson2()");
        Person person2 = new Person();
        person2.setName(name);
        person2.setAge(age);
        return person2;
    }
}
beans.xml配置
代码语言:javascript
复制
  <!-- 定义一个工厂实例 -->
  <bean id="personFactory" class="org.kewei.service.PersonFactory"/>
  <!-- 通过userFactory实例的无参user方法创建UserModel对象 -->
  <bean id="createBeanByBeanMethod1" factory-bean="personFactory" factory-method="build"/>
  <!-- 通过userFactory实例的有参user方法创建UserModel对象 -->
  <bean id="createBeanByBeanMethod2" factory-bean="personFactory" factory-method="build2">
      <constructor-arg index="0" value="通过bean实例有参方法创建UserModel实例对象"/>
      <constructor-arg index="1" value="30"/>
  </bean>

createBeanByBeanMethod1对应的bean是通过personFactory的build方法生成的。

createBeanByBeanMethod2对应的bean是通过personFactory的build2方法生成的。

代码语言:javascript
复制
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("test.xml");
Person createBeanByStaticFactoryMethod1 = (Person) classPathXmlApplicationContext.getBean("createBeanByBeanMethod1");
Person createBeanByStaticFactoryMethod2 = (Person) classPathXmlApplicationContext.getBean("createBeanByBeanMethod2");
System.out.println(createBeanByStaticFactoryMethod1.name+createBeanByStaticFactoryMethod1.age);
System.out.println(createBeanByStaticFactoryMethod2.name+createBeanByStaticFactoryMethod2.age);
代码语言:javascript
复制
---------------------------------------------
我是无参静态构造方法创建的!null
通过bean实例有参方法创建UserModel实例对象30

同样我们可以改成注解的形式

注解配置
代码语言:javascript
复制
@Service
public class PersonFactory {

    /**
     * 静态无参方法创建Person
     *
     * @return
     */
    public Person build() {
        System.out.println(PersonFactory.class + ".buildPerson1()");
        Person person = new Person();
        person.setName("我是无参静态构造方法创建的!");
        return person;
    }

    /**
     * 静态有参方法创建Person
     *
     * @return
     */
    public Person build2(String name, int age) {
        System.out.println(PersonFactory.class + ".buildPerson2()");
        Person person2 = new Person();
        person2.setName(name);
        person2.setAge(age);
        return person2;
    }
}

我们只需要在上面加一个@Service注解,就可以直接调用里面的方法。

代码语言:javascript
复制
 AnnotationConfigApplicationContext annotationConfigApplicationContext1 = new AnnotationConfigApplicationContext(PersonFactory.class);
 PersonFactory personFactory = (PersonFactory) annotationConfigApplicationContext1.getBean("personFactory");
 System.out.println(personFactory.build().name);
代码语言:javascript
复制
---------------------------------------------
class org.kewei.service.PersonFactory.buildPerson1()
我是无参静态构造方法创建的!

4、通过FactoryBean来创建bean对象

前面我们学过了BeanFactory接口,BeanFactory是Spring容器的顶层接口,而这里要说的是FactoryBean,也是一个接口,这两个接口很容易搞混淆,FactoryBean可以让Spring容器通过这个接口的实现来创建我们需要的bean对象。

FactoryBean接口源码:

代码语言:javascript
复制
public interface FactoryBean<T> {
    /**
     * 返回创建好的对象
     */
    @Nullable
    T getObject() throws Exception;
    /**
     * 返回需要创建的对象的类型
     */
    @Nullable
    Class<?> getObjectType();
    /**
    * bean是否是单例的
    **/
    default boolean isSingleton() {
        return true;
    }
}

接口中有3个方法,前面2个方法需要我们去实现,getObject方法内部由开发者自己去实现对象的创建,然后将创建好的对象返回给Spring容器;getObjectType需要指定我们创建的bean的类型;最后一个方法isSingleton表示通过这个接口创建的对象是否是单例的,如果返回false,那么每次从容器中获取对象的时候都会调用这个接口的getObject() 去生成bean对象。

代码语言:javascript
复制
<bean id="bean名称" class="FactoryBean接口实现类" />
创建一个FactoryBean实现类
代码语言:javascript
复制
public class PersonFactoryBean implements FactoryBean<Person> {
    int count = 1;

    @Nullable
    @Override
    public Person getObject() { //1
        Person person = new Person();
        person.setName("我是通过FactoryBean创建的第" + count++ + "对象");//4
        return person;
    }

    @Nullable
    @Override
    public Class<?> getObjectType() {
        return Person.class; //2
    }

    @Override
    public boolean isSingleton() {
        return true; //3
    }
}

//1:返回了一个创建好的Person对象。

//2:返回对象的Class对象。

//3:返回true,表示创建的对象是单例的,那么我们每次从容器中获取这个对象的时候都是同一个对象。

//4:此处用到了一个count,通过这个一会可以看出isSingleton不同返回值的时候从容器获取的bean是否是同一个。

bean xml配置
代码语言:javascript
复制
<!-- 通过FactoryBean 创建Person对象 -->
 
<bean id="createByFactoryBean" class="org.kewei.service.PersonFactoryBean"/>
 

启动类增加如下代码:

代码语言:javascript
复制
System.out.println("-------------以下是FactoryBean创建的Bean对象-------------");
//1.bean配置文件位置
String beanXml = "classpath:/test.xml";
//2.创建ClassPathXmlApplicationContext容器,给容器指定需要加载的bean配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
System.out.println("spring容器中所有bean如下:");
//getBeanDefinitionNames用于获取容器中所有bean的名称
for (String beanName : context.getBeanDefinitionNames()) {
     System.out.println(beanName + ":" + context.getBean(beanName));
}

//多次获取createByFactoryBean看看是否是同一个对象
System.out.println("createByFactoryBean:" + context.getBean("createByFactoryBean"));
System.out.println("createByFactoryBean:" + context.getBean("createByFactoryBean"));

注意最后3行输出,输出的都是同一个createByFactoryBean,并且对象唯一,程序中通过getBean从IOC容器中查找createByFactoryBean3次,3次结果都是相同对象,说明返回的都是同一个Person对象。

下面我们将UserFactoryBean中的isSingleton调整一下,返回false

代码语言:javascript
复制
@Override
 
public boolean isSingleton() 
 
 return false;
 
}
 

当这个方法返回false的时候,表示由这个FactoryBean创建的对象是多例的,那么我们每次从容器中getBean的时候都会去重新调用FactoryBean中的getObject方法获取一个新的对象,再运行一下Client,最后3行输出:

很明显这3次获取的对象不一样,这也是SpringBean的作用域不同,下一篇进行讲解SpringBean的作用域。

总结

SpringIOC容器提供了4种创建bean实例的方式,除了构造函数的方式,其他几种方式可以让我们手动去控制对象的创建,这几种方式大家都掌握一下,能够灵活使用。需要源码的联系我获取。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-11-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 可为编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Person类
  • 定义静态工厂
  • beans.xml配置
  • 注解配置
  • 3、通过实例工厂方法创建bean对象
    • 定义一个实例工厂
      • beans.xml配置
        • 注解配置
        • 4、通过FactoryBean来创建bean对象
          • 创建一个FactoryBean实现类
            • bean xml配置
            • 总结
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档