目录
Bean的实例化
Bean的实例化有四种方式,这里的实例化Bean和之前学的注入是两回事,
依赖注入有两种方式,一种是构造方法注入另一种是set方法注入,这里的注入是指给类方法中的属性赋值,我们可以在构造方法运行的同时通过含参数构造方法注入属性的值,也可以在对象创建之后通过set方法给对象的属性赋值;这里创建对象是通过反射技术
然而实例化是创建对象也就是new对象,在Spring中提供的可以创建对象的方式有四种
通过构造方法实例化Bean;
这个方法是最开始接触Spring时使用的创建对象的方式,只需要在Spring.xml文件配置Bean标签中配置,然后在test方法中直接通过ApplicationContext调用getbean()方法;这是在底层调用了反射技术创建对象;
<bean id="userBean" class="com.lypcode.spring.bean.user"/>
通过简单工厂模式创建对象;
这个方式是提供了一个工厂类,通过工厂类来间接获取对象,工厂类中会提供一个静态方法;然后通过工厂类中的静态方法创建Bean对象,相当于给了一个专门创建对象的工厂一样;但是需要再spring.xml文件中的Bean声明中指出factory-method属性;
<bean id="vipBean" class="com.powernode.spring6.bean.VipFactory" factory-method="get"/>
这种方式:是在Spring容器读取xml文件时,Spring容器根据配置的Bean检测到,这个类配置了Factory-method属性是个工厂类,那么Spring容器会在创建类时调用class对应的这个类中的factory-method指定的静态方法来实例化Bean;其中工厂类的构造方法不执行
MyBean myBean = MyBeanFactory.createInstance();
这样做的好处是;它允许你在创建Bean的过程中执行更复杂的逻辑,例如根据条件返回不同的实例;
如何理解:既然他叫工厂类,呢么显然工厂不可能只生产一个东西,它是可以生产同一系列多种多样的产品,应用到实际需求中,会根据你请求参数的不同给你返回不同的对象,这里主要理解,工厂类起加工作用,或者说可以将被创建类加工成不同功能的类的一个功能;
使用案例
public class MyBeanFactory {
public static MyBean createInstance(String type) {
if ("type1".equals(type)) {
return new Type1Bean();
} else if ("type2".equals(type)) {
return new Type2Bean();
} else {
throw new IllegalArgumentException("Unsupported type: " + type);
}
}
}
public class Type1Bean implements MyBean {
// Type1Bean implementation
}
public class Type2Bean implements MyBean {
// Type2Bean implementation
}
在这个例子中,MyBeanFactory
的 createInstance
方法接受一个参数 type
,根据不同的 type
返回不同类型的 MyBean
实例。你可以根据实际需求扩展这个逻辑,例如从配置文件中读取参数,执行一些计算,或者根据其他条件动态选择要返回的Bean类型。
<bean id="myBean" class="com.example.MyBeanFactory" factory-method="createInstance">
<constructor-arg value="type1"/>
</bean>
这样,当Spring容器需要实例化myBean
时,它会调用MyBeanFactory
类的createInstance
方法,并传递"type1"作为参数,从而获取并返回相应的Type1Bean
实例。
通过使用工厂方法模式
这种方法中是摒弃了之前的Bean中定义class属性,而是定义了一个Factory-Bean
,这个就是工厂类,和之前不同的是,这个工厂类中提供的方法是非静态方法;这个Factory-Bean
又指向另一个Bean标签,这个被指向的的类则作为工厂类;与简单工厂类实例化Bean方式相比较:这个过程中工厂类的无参构造会执行;
这种方式利用另一个 Bean 的实例作为工厂来创建目标 Bean, 当 Spring 容器启动并读取配置文件时,它会实例化配置的工厂 Bean。当需要创建 myBean
时,容器将调用 myBeanFactory
的 createInstance
方法,而不是直接调用 MyBean
的构造方法创建MyBean对象。
使用 factory-bean
的方式,你可以借助现有的 Bean 实例作为工厂来创建其他 Bean 实例。这种方法适用于在实例化过程中可能需要其他实例或外部资源的情况;
<bean id="orderFactory" class="com.lypcode.spring.bean.orderFactory"/>
<bean id="orderBean" factory-bean="orderFactory" factory-method="get"/>
使用案例
假设你的目标是在实例化过程中根据某些条件选择不同的实例化逻辑,而这些条件可能需要从外部环境中获取。例如,你可能有一个数据库连接工厂,根据配置中的数据库类型创建不同的数据库连接。
-
定义工厂 Bean: 创建一个工厂 Bean,该工厂可能需要一些外部条件。
public class DatabaseConnectionFactory { private String databaseType; public DatabaseConnectionFactory(String databaseType) { this.databaseType = databaseType; } public DatabaseConnection createConnection() { // 根据数据库类型创建不同的连接实例 if ("mysql".equals(databaseType)) { return new MySQLConnection(); } else if ("oracle".equals(databaseType)) { return new OracleConnection(); } else { throw new IllegalArgumentException("Unsupported database type: " + databaseType); } } }
-
在 Spring 配置文件中配置工厂 Bean: 在配置文件中定义工厂 Bean,传递外部条件。
<bean id="databaseConnectionFactory" class="com.example.DatabaseConnectionFactory"> <constructor-arg value="mysql"/> <!-- 可以从配置文件中读取 --> </bean>
-
配置使用
factory-bean
实例化目标 Bean: 配置目标 Bean,使用factory-bean
指定工厂 Bean 的 ID,并通过factory-method
指定工厂方法。<bean id="databaseConnection" factory-bean="databaseConnectionFactory" factory-method="createConnection"/>
在这个例子中,当容器需要创建 databaseConnection
时,它将调用 databaseConnectionFactory
的 createConnection
方法,根据外部条件(数据库类型)选择不同的连接实例。这种方式提供了更大的灵活性,允许你根据外部条件动态选择实例化逻辑。
通过FactoryBean接口实例化
以上的第三种方式中,factory-bean是我们自定义的,factory-method也是我们自己定义的。在Spring中,当你编写的类直接实现FactoryBean接口之后,factory-bean不需要指定了,factory-method也不需要指定了。factory-bean会自动指向实现FactoryBean接口的类,factory-method会自动指向getObject()方法。
在Spring中,通过工厂模式方法实例化Bean通常是通过使用工厂方法模式和Spring的工厂bean(FactoryBean)接口来实现的。下面是底层原理的简要解释:
-
定义工厂接口或类: 创建一个接口或类,定义工厂方法(工厂接口)或普通方法(工厂类),用于创建和返回Bean实例。这个接口或类可以是普通的Java类,也可以实现Spring的
FactoryBean
接口。public interface MyBeanFactory { MyBean createInstance(); }
-
实现工厂接口或类: 创建一个实现了工厂接口或类的具体实现,实现工厂方法来创建Bean实例。
public class MyBeanFactoryImpl implements MyBeanFactory { @Override public MyBean createInstance() { return new MyBean(); } }
-
在Spring配置文件中配置FactoryBean: 使用
<bean>
标签配置工厂bean,指定class
属性为工厂实现类,Spring容器在需要创建Bean时会调用工厂方法来获取Bean实例。<bean id="myBeanFactory" class="com.example.MyBeanFactoryImpl"/>
-
配置使用FactoryBean创建Bean: 在配置文件中配置实际的Bean,使用
factory-bean
属性指定工厂bean的ID,使用factory-method
属性指定工厂方法。<bean id="myBean" factory-bean="myBeanFactory" factory-method="createInstance"/>
这告诉Spring容器在需要创建
myBean
Bean时,调用myBeanFactory
的createInstance
方法来获取Bean实例。 -
Spring容器实例化Bean: 当Spring容器启动并读取配置文件时,它将根据配置实例化工厂bean和实际的Bean。当需要创建
myBean
时,它将调用myBeanFactory
的createInstance
方法,而不是直接调用Bean的构造方法。
使用案例
注入Date日期类
public class Student {
private Date birth;
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Student{" +
"birth=" + birth +
'}';
}
}
java.util.Date在Spring中被当做简单类型,简单类型在注入的时候可以直接使用value属性或value标签来完成。但我们之前已经测试过了,对于Date类型来说,采用value属性或value标签赋值的时候,对日期字符串的格式要求非常严格,必须是这种格式的:Mon Oct 10 14:30:26 CST 2022。其他格式是不会被识别的;
public class DateFactoryBean implements FactoryBean<Date> {
// 定义属性接收日期字符串
private String date;
// 通过构造方法给日期字符串属性赋值
public DateFactoryBean(String date) {
this.date = date;
}
@Override
public Date getObject() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.parse(this.date);
}
@Override
public Class<?> getObjectType() {
return null;
}
}
<bean id="dateBean" class="com.powernode.spring6.bean.DateFactoryBean">
<constructor-arg name="date" value="1999-10-11"/>
</bean>
<bean id="studentBean" class="com.powernode.spring6.bean.Student">
<property name="birth" ref="dateBean"/>
</bean>
如果不使用FactoryBean,那么根据xml文件中的配置,直接使用注入外部Bean的方式, 就是让Spring容器管理并返回对象new java.util.Date,该对象使用的是默认的构造方法进行初始化,所以它的初始值是系统当前的时间,使用FactoryBean相当于可以在返回对象之前,对这个对象进行加工处理, 这样的话,就可以先使用注入简单类型的方法,先给FactoryBean注入strDate的值,然后在重写的getObject()方法内将Date类型的值改为自定义的时间,再将其返回。