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


    🎈个人公众号:🎈 :✨✨✨ 可为编程✨ 🍟🍟
    🔑个人信条:🔑 知足知不足 有为有不为 为与不为皆为可为🌵
    🍉本篇简介:🍉 本篇记录IOC容器创建bean实例的4种方式,如有出入还望指正。

    关注公众号【可为编程】回复【面试】领取年度最新面试题!!!

    了解了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。

    关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!

    1. "bean名称" name="bean名称或者别名" class="bean的完整类型名称">
    2. "0" value="bean的值" ref="引用的bean名称" />
    3. "1" value="bean的值" ref="引用的bean名称" />
    4. ....
    5. "n" value="bean的值" ref="引用的bean名称" />

    constructor-arg用于指定构造方法参数的值

    index:构造方法中参数的位置,从0开始,依次递增

    value:指定参数的值

    ref:当插入的值为容器内其他bean的时候,这个值为容器中对应bean的名称

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

    2、 IOC容器初始化细节

    Person类
    1. public class Person {
    2. public String name;
    3. public Integer age;
    4. Wife wife;
    5. public Person(String s, Integer s2, Wife wife) {
    6.         System.out.println("反射通过调用构造函数进行实例创建...");
    7. this.name = s;
    8. this.age = s2;
    9. this.wife = wife;
    10. }
    11. ...省略属性的get() set()方法

    beans.xml配置

    1. "http://www.springframework.org/schema/beans"
    2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3. xsi:schemaLocation="http://www.springframework.org/schema/beans
    4. https://www.springframework.org/schema/beans/spring-beans.xsd
    5. ">
    6. "org.kewei.pojo.Person" id="person">
    7. "org.kewei.pojo.Wife" ref="wife"/>
    8. "0" value="可为编程" />
    9. "1" value="18" />
    10. "org.kewei.pojo.Wife" id="wife" autowire-candidate="true">
    11. "age" value="18"/>
    12. "name" value="可为"/>

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

    1. public class Main {
    2.     public static void main(String[] args) {
    3. ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("test.xml");
    4. Person person = (Person) classPathXmlApplicationContext.getBean("person");
    5. System.out.println(person.name+person.age);
    6. System.out.println(person.getWifeName() + person.getWifeAge());
    7. }
    8. }

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

    1. @Service
    2. public class KeWeiService {
    3. public KeWeiService() {
    4. System.out.println("基于注解形式创建正在创建KeWeiService--- " + this);
    5. System.out.println("反射通过调用构造函数进行实例创建...--- " + this);
    6. }
    7. }
    1. //获取Bean
    2. public class Main {
    3.     public static void main(String[] args) {
    4. AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(KeWeiService.class);
    5. KeWeiService kw = (KeWeiService) annotationConfigApplicationContext.getBean("keWeiService");
    6.         System.out.println(kw);
    7. }
    8. }

    测试结果如下所示,可见都是采用了反射的机制进行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以供后续使用。

    1. "bean名称" name="" class="静态工厂完整类名" factory-method="静态工厂的方法">
    2. "0" value="bean的值" ref="引用的bean名称" />
    3.     "1" value="bean的值" ref="引用的bean名称" />
    4. ....
    5. "n" value="bean的值" ref="引用的bean名称" />

    class:指定静态工厂完整的类名

    factory-method:静态工厂中的静态方法,返回需要的对象。

    constructor-arg用于指定静态方法参数的值,用法和上面介绍的构造方法一样。

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

    定义静态工厂

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

    1. public class PersonStaticFactory {
    2. /**
    3. * 静态无参方法创建Person
    4. *
    5. * @return
    6. */
    7. public static Person build() {
    8. System.out.println(PersonStaticFactory.class + ".buildPerson1()");
    9. Person person = new Person();
    10. person.setName("我是无参静态构造方法创建的!");
    11. return person;
    12. }
    13. /**
    14. * 静态有参方法创建Person
    15. *
    16. * @param name 名称
    17. * @param age 年龄
    18. * @return
    19. */
    20. public static Person build2(String name, int age) {
    21. System.out.println(PersonStaticFactory.class + ".buildPerson2()");
    22. Person person2 = new Person();
    23. person2.setName(name);
    24. person2.setAge(age);
    25. return person2;
    26. }
    27. }
    beans.xml配置
    1. "createBeanByStaticFactoryMethod1" class="org.kewei.service.PersonStaticFactory"
    2. factory-method="build"/>
    3. "createBeanByStaticFactoryMethod2" class="org.kewei.service.PersonStaticFactory"
    4. factory-method="build2">
    5.    "0" value="通过工厂静态有参方法创建UerModel实例对象"/>
    6.    "1" value="30"/>

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

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

    1. ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("test.xml");
    2. Person createBeanByStaticFactoryMethod1 = (Person) classPathXmlApplicationContext.getBean("createBeanByStaticFactoryMethod1");
    3. Person createBeanByStaticFactoryMethod2 = (Person) classPathXmlApplicationContext.getBean("createBeanByStaticFactoryMethod2");
    4. System.out.println(createBeanByStaticFactoryMethod1.name+createBeanByStaticFactoryMethod1.age);
    5. System.out.println(createBeanByStaticFactoryMethod2.name+createBeanByStaticFactoryMethod2.age);
    1. ---------------------------------------------
    2. 我是无参静态构造方法创建的!null
    3. 通过工厂静态有参方法创建Person实例对象30

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

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

    关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!

    注解配置

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

    1. @Configuration
    2. public class PersonStaticFactory {
    3. /**
    4. * 静态无参方法创建Person
    5. *
    6. * @return
    7. */
    8. @Bean("createBeanByStaticFactoryMethod1")
    9. public static Person build() {
    10. System.out.println(PersonStaticFactory.class + ".buildPerson1()");
    11. Person person = new Person();
    12. person.setName("我是无参静态构造方法创建的!");
    13. return person;
    14. }
    15. /**
    16. * 静态有参方法创建Person
    17. *
    18. * @return
    19. */
    20. @Bean("createBeanByStaticFactoryMethod2")
    21. public static Person build2() {
    22. System.out.println(PersonStaticFactory.class + ".buildPerson2()");
    23. Person person2 = new Person();
    24. person2.setName("通过工厂静态有参方法创建");
    25. person2.setAge(18);
    26. return person2;
    27. }
    28. }

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

    1. AnnotationConfigApplicationContext annotationConfigApplicationContext1 = new AnnotationConfigApplicationContext(PersonStaticFactory.class);
    2. Person createBeanByStaticFactoryMethod1 = (Person) annotationConfigApplicationContext1.getBean("createBeanByStaticFactoryMethod1");
    3. Person createBeanByStaticFactoryMethod2 = (Person) annotationConfigApplicationContext1.getBean("createBeanByStaticFactoryMethod2");
    4. System.out.println(createBeanByStaticFactoryMethod1.name+createBeanByStaticFactoryMethod1.age);
    5. System.out.println(createBeanByStaticFactoryMethod2.name+createBeanByStaticFactoryMethod2.age);

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

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

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

    1. "bean名称" factory-bean="需要调用的实例对象bean名称" factory-method="bean对象中的方法">
    2. "0" value="bean的值" ref="引用的bean名称" />
    3.     "1" value="bean的值" ref="引用的bean名称" />
    4. ....
    5. "n" value="bean的值" ref="引用的bean名称" />

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

    定义一个实例工厂

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

    1. public class PersonFactory {
    2. /**
    3. * 静态无参方法创建Person
    4. *
    5. * @return
    6. */
    7. public Person build() {
    8. System.out.println(PersonFactory.class + ".buildPerson1()");
    9. Person person = new Person();
    10. person.setName("我是无参静态构造方法创建的!");
    11. return person;
    12. }
    13. /**
    14. * 静态有参方法创建Person
    15. * 关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!
    16. *
    17. * @return
    18. */
    19. public Person build2(String name, int age) {
    20. System.out.println(PersonFactory.class + ".buildPerson2()");
    21. Person person2 = new Person();
    22. person2.setName(name);
    23. person2.setAge(age);
    24. return person2;
    25. }
    26. }
    beans.xml配置
    1.   
    2.   "personFactory" class="org.kewei.service.PersonFactory"/>
    3.   
    4.   "createBeanByBeanMethod1" factory-bean="personFactory" factory-method="build"/>
    5.   
    6.   "createBeanByBeanMethod2" factory-bean="personFactory" factory-method="build2">
    7.       "0" value="通过bean实例有参方法创建UserModel实例对象"/>
    8.       "1" value="30"/>
    9.   

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

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

    1. ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("test.xml");
    2. Person createBeanByStaticFactoryMethod1 = (Person) classPathXmlApplicationContext.getBean("createBeanByBeanMethod1");
    3. Person createBeanByStaticFactoryMethod2 = (Person) classPathXmlApplicationContext.getBean("createBeanByBeanMethod2");
    4. System.out.println(createBeanByStaticFactoryMethod1.name+createBeanByStaticFactoryMethod1.age);
    5. System.out.println(createBeanByStaticFactoryMethod2.name+createBeanByStaticFactoryMethod2.age);
    1. ---------------------------------------------
    2. 我是无参静态构造方法创建的!null
    3. 通过bean实例有参方法创建UserModel实例对象30

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

    注解配置
    1. @Service
    2. public class PersonFactory {
    3. /**
    4. * 静态无参方法创建Person
    5. *
    6. * @return
    7. */
    8. public Person build() {
    9. System.out.println(PersonFactory.class + ".buildPerson1()");
    10. Person person = new Person();
    11. person.setName("我是无参静态构造方法创建的!");
    12. return person;
    13. }
    14. /**
    15. * 静态有参方法创建Person
    16. *
    17. * @return
    18. */
    19. public Person build2(String name, int age) {
    20. System.out.println(PersonFactory.class + ".buildPerson2()");
    21. Person person2 = new Person();
    22. person2.setName(name);
    23. person2.setAge(age);
    24. return person2;
    25. }
    26. }

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

    1. AnnotationConfigApplicationContext annotationConfigApplicationContext1 = new AnnotationConfigApplicationContext(PersonFactory.class);
    2.  PersonFactory personFactory = (PersonFactory) annotationConfigApplicationContext1.getBean("personFactory");
    3.  System.out.println(personFactory.build().name);
    1. ---------------------------------------------
    2. class org.kewei.service.PersonFactory.buildPerson1()
    3. 我是无参静态构造方法创建的!

    4、通过FactoryBean来创建bean对象

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

    关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!

    FactoryBean接口源码:

    1. public interface FactoryBean {
    2. /**
    3. * 返回创建好的对象
    4. */
    5. @Nullable
    6. T getObject() throws Exception;
    7. /**
    8. * 返回需要创建的对象的类型
    9. */
    10. @Nullable
    11. Class getObjectType();
    12. /**
    13. * bean是否是单例的
    14. **/
    15. default boolean isSingleton() {
    16. return true;
    17. }
    18. }

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

    <bean id="bean名称" class="FactoryBean接口实现类" />
    创建一个FactoryBean实现类​​​​​​​
    1. public class PersonFactoryBean implements FactoryBean {
    2. int count = 1;
    3. @Nullable
    4. @Override
    5.     public Person getObject() { //1
    6. Person person = new Person();
    7.         person.setName("我是通过FactoryBean创建的第" + count++ + "对象");//4
    8. return person;
    9. }
    10. @Nullable
    11. @Override
    12. public Class getObjectType() {
    13.         return Person.class; //2
    14. }
    15. @Override
    16. public boolean isSingleton() {
    17.         return true//3
    18. }
    19. }

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

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

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

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

    bean xml配置
    1. "createByFactoryBean" class="org.kewei.service.PersonFactoryBean"/>

    启动类增加如下代码:

    1. System.out.println("-------------以下是FactoryBean创建的Bean对象-------------");
    2. //1.bean配置文件位置
    3. String beanXml = "classpath:/test.xml";
    4. //2.创建ClassPathXmlApplicationContext容器,给容器指定需要加载的bean配置文件
    5. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
    6. System.out.println("spring容器中所有bean如下:");
    7. //getBeanDefinitionNames用于获取容器中所有bean的名称
    8. //关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!
    9. for (String beanName : context.getBeanDefinitionNames()) {
    10.      System.out.println(beanName + ":" + context.getBean(beanName));
    11. }
    1. //多次获取createByFactoryBean看看是否是同一个对象
    2. System.out.println("createByFactoryBean:" + context.getBean("createByFactoryBean"));
    3. System.out.println("createByFactoryBean:" + context.getBean("createByFactoryBean"));

    图片

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

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

    1. @Override
    2. public boolean isSingleton() {
    3. return false;
    4. }

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

    图片

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

    总结

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

    图片

    由表及里分析Spring-IOC容器始末

    Spring中的核心概念

    不要称之为卷土重来:为什么 Java 仍然会是冠军!

    关于高并发你必须知道的几个概念

    线程的创建方式对比与线程池相关原理剖析

    BigDecimal对象的日常使用汇总

    图片

  • 相关阅读:
    【用户实践】openGauss5.0在某省医保局实时数仓应用
    Codeforces 1255B. Fridge Lockers
    C++/Qt 小知识记录
    View菜单解析
    聊聊logback的MarkerFilter
    Redis—听说你速度跟甲斗一样快?——主从复制
    Mysql备份工具innobackupex简单操作及参数说明
    BaaS、FaaS、Serverless 都是什么?
    pgzrun 拼图游戏制作过程详解(6,7)
    数组 冒泡排序
  • 原文地址:https://blog.csdn.net/weixin_42103983/article/details/134257355