🎈个人公众号:🎈 :✨✨✨ 可为编程✨ 🍟🍟
🔑个人信条:🔑 知足知不足 有为有不为 为与不为皆为可为🌵
🍉本篇简介:🍉 本篇记录IOC容器创建bean实例的4种方式,如有出入还望指正。
关注公众号【可为编程】回复【面试】领取年度最新面试题!!!
了解了IOC是什么,接下来我们看IOC容器能做什么,首先其最最主要的功能就是对Bean进行管理和创建,IOC容器创建bean实例共有4种方式,具体如下:
-
通过反射调用构造方法创建bean对象
-
通过静态工厂方法创建bean对象
-
通过实例工厂方法创建bean对象
-
通过FactoryBean创建bean对象
Spring容器内部创建bean实例对象常见的有4种方式,这四种又可以分为两大种,一是基于反射机制,二是基于工厂模式,我将基于此并结合案例深入说明一下两者的区别和原理。
1、通过反射调用构造方法创建bean对象
调用类的构造方法获取对应的bean实例,是使用最多的方式,这种方式只需要在xml bean元素中指定class属性,spring容器内部会自动调用该类型的构造方法来创建bean对象,将其放在容器中以供使用。
如果是采用注解形式创建和管理Bean,同样也是采用反射的机制,随着Spring的发展,注解(Annotation)逐渐成为主流的配置方式。使用注解可以减少配置文件的代码量,并且把相关的配置信息和代码放在一起,提高了可维护性。例如,使用@Component、@Service、@Repository、@Controller等注解可以自动创建Bean。
关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!
<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类
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配置
<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对象。
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名称进行生成。
@Service
public class KeWeiService {
public KeWeiService() {
System.out.println("基于注解形式创建正在创建KeWeiService--- " + this);
System.out.println("反射通过调用构造函数进行实例创建...--- " + this);
}
}
//获取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以供后续使用。
<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对象。
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配置
<!-- 通过工厂静态无参方法创建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容器中。
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);
---------------------------------------------
我是无参静态构造方法创建的!null
通过工厂静态有参方法创建Person实例对象30
从输出中可以看出,两个静态方法都被调用了,都输出了对应的信息,第一行为build()方法生成的Bean,第二行为build2()方法生成的Bean对象。
上面是通过配置文件的形式获取Bean对象,接下来我再演示一下通过注解的方式如何通过静态工厂生成Bean对象。
关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!
注解配置
当然,方法是静态的我们就可以直接调用,但为了演示注解的作用,交给Spring做管理,因此我们通过加注解的形式,获取Bean对象。
@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中获取它。
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对象放在容器中以供使用。
<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对象。
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配置
<!-- 定义一个工厂实例 -->
<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方法生成的。
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);
---------------------------------------------
我是无参静态构造方法创建的!null
通过bean实例有参方法创建UserModel实例对象30
同样我们可以改成注解的形式
注解配置
@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注解,就可以直接调用里面的方法。
AnnotationConfigApplicationContext annotationConfigApplicationContext1 = new AnnotationConfigApplicationContext(PersonFactory.class);
PersonFactory personFactory = (PersonFactory) annotationConfigApplicationContext1.getBean("personFactory");
System.out.println(personFactory.build().name);
---------------------------------------------
class org.kewei.service.PersonFactory.buildPerson1()
我是无参静态构造方法创建的!
4、通过FactoryBean来创建bean对象
前面我们学过了BeanFactory接口,BeanFactory是Spring容器的顶层接口,而这里要说的是FactoryBean,也是一个接口,这两个接口很容易搞混淆,FactoryBean可以让Spring容器通过这个接口的实现来创建我们需要的bean对象。
关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!
FactoryBean接口源码:
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对象。
<bean id="bean名称" class="FactoryBean接口实现类" />
创建一个FactoryBean实现类
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配置
<!-- 通过FactoryBean 创建Person对象 -->
<bean id="createByFactoryBean" class="org.kewei.service.PersonFactoryBean"/>
启动类增加如下代码:
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
@Override
public boolean isSingleton() {
return false;
}
当这个方法返回false的时候,表示由这个FactoryBean创建的对象是多例的,那么我们每次从容器中getBean的时候都会去重新调用FactoryBean中的getObject方法获取一个新的对象,再运行一下Client,最后3行输出:
很明显这3次获取的对象不一样,这也是SpringBean的作用域不同,下一篇进行讲解SpringBean的作用域。
总结
SpringIOC容器提供了4种创建bean实例的方式,除了构造函数的方式,其他几种方式可以让我们手动去控制对象的创建,这几种方式大家都掌握一下,能够灵活使用。需要源码的联系我获取。
由表及里分析Spring-IOC容器始末
Spring中的核心概念
不要称之为卷土重来:为什么 Java 仍然会是冠军!
关于高并发你必须知道的几个概念
线程的创建方式对比与线程池相关原理剖析
BigDecimal对象的日常使用汇总