目录
一、BeanFactory
BeanFactory,以Factory结尾,表示它是一个工厂类(接口), 它负责生产和管理bean的一个工厂,我们可以通过它获取工厂管理的对象。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。它定义了getBean()、containsBean()等管理Bean的通用方法。但BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 :
- DefaultListableBeanFactory
- XmlBeanFactory
- ApplicationContext
其中XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。
1.1 源码
public interface BeanFactory {
/**
用于区分factoryBean和bean,后面会讲到
/*String FACTORY_BEAN_PREFIX = "&";
/**
返回byName返回bean的实例
*/
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
/**
* Return a provider for the specified bean, allowing for lazy on-demand retrieval
* of instances, including availability and uniqueness options.
* @param requiredType type the bean must match; can be an interface or superclass
* @return a corresponding provider handle
* @since 5.1
* @see #getBeanProvider(ResolvableType)
*/
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
/**
判断工厂中是否包含给定名称的bean定义,若有则返回true
*/
boolean containsBean(String name);
/**
判断bean是否为单例
*/
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
/**
判断bean是否为多例
*/
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
/**
检查具有给定名称的bean是否匹配指定的类型。
*/
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
/**
返回给定名称的bean的Class,如果没有找到指定的bean实例,则排除*/
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
/**
返回给定bean名称的所有别名
*/
String[] getAliases(String name);
}
1.2 使用场景
- 从Ioc容器中获取Bean(byName or byType)
- 检索Ioc容器中是否包含指定的Bean
- 判断Bean是否为单例
二、FactoryBean
使用XML配置spring容器的时候,Spring通过反射机制利用<bean>的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在<bean>中提供大量的配置信息。配置方式的灵活性是受限的,比如一个类大量依赖了其他的对象属性,此时就算是使用自动装配,不需要再显式的写出bean之间的依赖关系,但是其依赖的对象也需要将其装配到spring容器中,也需要为它所依赖的多有对象都创建bean标签将他们注入,如果这个类依赖了上百个对象,那么这个工作量无疑是非常大的。
Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean<T>的形式。
Spring中共有两种bean,一种为普通bean,另一种则为工厂bean
以Bean结尾,表示它是一个Bean,不同于普通Bean的是:它是实现了FactoryBean<T>接口的Bean,根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取。
2.1 源码
public interface FactoryBean<T> {
//从工厂中获取bean
@Nullable
T getObject() throws Exception;
//获取Bean工厂创建的对象的类型
@Nullable
Class<?> getObjectType();
//Bean工厂创建的对象是否是单例模式
default boolean isSingleton() {
return true;
}
}
从它定义的接口可以看出,FactoryBean表现的是一个工厂的职责。 即一个Bean A如果实现了FactoryBean接口,那么A就变成了一个工厂,根据A的名称获取到的实际上是工厂调用getObject()返回的对象,而不是A本身,如果要获取工厂A自身的实例,那么需要在名称前面加上'&'符号。
- getObject('name')返回工厂中的实例
- getObject('&name')返回工厂本身的实例
通常情况下,bean 无须自己实现工厂模式,Spring 容器担任了工厂的角色;但少数情况下,容器中的 bean 本身就是工厂,作用是产生其他 bean 实例。由工厂 bean 产生的其他 bean 实例,不再由 Spring 容器产生,因此与普通 bean 的配置不同,不再需要提供 class 元素。
2.2 示例
我们现在要将下面这个TempDaoFactoryBean类交给工厂去创建管理
public class TempDaoFactoryBean {
private String msg1;
private String msg2;
private String msg3;
public void test() {
System.out.println("FactoryBean");
}
public void setMsg1(String msg1) {
this.msg1 = msg1;
}
public void setMsg2(String msg2) {
this.msg2 = msg2;
}
public void setMsg3(String msg3) {
this.msg3 = msg3;
}
public String getMsg1() {
return msg1;
}
public String getMsg2() {
return msg2;
}
public String getMsg3() {
return msg3;
}
}
我们有两种方法可以选择:
方法一:通过spring的xml的方式对其进行配置.
方法二:定义一个CarProxy类,实现factoryBean接口.
2.2.1 方法一
如果使用传统方式配置下面Car的<bean>时,Car的每个属性分别对应一个<property>元素标签,就算是使用自动装配也要写很多<bean>标签,十分的麻烦
2.2.2 方法二
定义DaoFactoryBean实现FactoryBean接口
/**
* FactoryBean由名字可以看出,是以bean结尾的,就说明这是一个bean,是由IOC容器管理的一个bean对象
*
* 如果你的类实现了FactoryBean
* 那么spring容器当中会存储两个对象:一个是getObject()方法返回的对象(TempDaoFactoryBean),还有一个就是当前对象(DaoFactoryBean)
*
* getObject()返回的对象(TempDaoFactoryBean)存储在spring容器中给这个对象设置的beanName是当前类指定的对象,也就是 @Component("daoFactoryBean") 中的daoFactoryBean
* 当前对象(DaoFactoryBean)在spring容器中设置的beanName是在@Component("")指定name的基础上加一个“&”,这里也就是&daoFactoryBean
*
* ClassCastException类型转换异常
*/
public class DaoFactoryBean implements FactoryBean {
// DaoFactoryBean这个工厂bean管理的对象
private String msg;
// 使用setter方法将其注入
public void setMsg(String msg) {
this.msg = msg;
}
public void testBean() {
System.out.println("testBean");
}
@Override
public Object getObject() throws Exception {
// 在FactoryBean内部创建对象实例
TempDaoFactoryBean temp = new TempDaoFactoryBean();
String[] msfArray = msg.split(",");
temp.setMsg1(msfArray[0]);
temp.setMsg2(msfArray[1]);
temp.setMsg3(msfArray[2]);
return temp;
}
@Override
public Class<?> getObjectType() {
return TempDaoFactoryBean.class;
}
/**
* 是否是单例
* @return
*/
@Override
public boolean isSingleton() {
return true;
}
}
使用xml将这个factoryBean装配到spring容器中
<?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="daoFactory" class="priv.cy.dao.DaoFactoryBean">
<property name="msg" value="msg1,msg2,msg3"></property>
</bean>
</beans>
测试类:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext
= new AnnotationConfigApplicationContext(AppConfig.class);
TempDaoFactoryBean tempDaoFactoryBean = (TempDaoFactoryBean) annotationConfigApplicationContext.getBean("daoFactory");
System.out.println(tempDaoFactoryBean.getMsg1());
System.out.println(tempDaoFactoryBean.getMsg2());
System.out.println(tempDaoFactoryBean.getMsg3());
}
}
执行结果:
因为当我们getBean时,spring对实现了FactoryBean接口的类实现了特殊处理
当调用getBean("daoFactory")时,Spring通过反射机制发现DaoFactoryBean实现了FactoryBean的接口,
这时Spring容器就调用接口方法中的getObject()方法返回。如果希望获取CarFactoryBean的实例,
则需要在使用getBean(beanName)方法时在beanName前显示的加上"&"前缀:如getBean("&car");
2.3 FactoryBean的两种用法
2.3.1 简化xml配置,隐藏细节
如果一个类有很多的属性,我们想通过Spring来对类中的属性进行值的注入,势必要在配置文件中书写大量属性配置,造成配置文件臃肿,那么这时可以考虑使用FactoryBean来简化配置
新建bean
public class Student {
/** 姓名 */
private String name;
/** 年龄 */
private int age;
/** 班级名称 */
private String className;
public Student() {
}
public Student(String name, int age, String className) {
this.name = name;
this.age = age;
this.className = className;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + ", className='" + className + '\'' + '}';
}
}
实现FactoryBean接口
public class StudentFactoryBean implements FactoryBean<Student> {
private String studentInfo;
@Override
public Student getObject() throws Exception {
if (this.studentInfo == null) {
throw new IllegalArgumentException("'studentInfo' is required");
}
String[] splitStudentInfo = studentInfo.split(",");
if (null == splitStudentInfo || splitStudentInfo.length != 3) {
throw new IllegalArgumentException("'studentInfo' config error");
}
Student student = new Student();
student.setName(splitStudentInfo[0]);
student.setAge(Integer.valueOf(splitStudentInfo[1]));
student.setClassName(splitStudentInfo[2]);
return student;
}
@Override
public Class<?> getObjectType() {
return Student.class;
}
public void setStudentInfo(String studentInfo) {
this.studentInfo = studentInfo;
}
}
新建day03.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--注意:class是StudentFactoryBean而不是Student-->
<bean id="student" class="com.lyc.cn.day03.StudentFactoryBean" p:studentInfo="张三,25,三年二班"/>
</beans>
测试类
public class MyTest {
@Before
public void before() {
System.out.println("---测试开始---\n");
}
@After
public void after() {
System.out.println("\n---测试结束---");
}
@Test
public void testStudentFactoryBean() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("day03.xml");
System.out.println(applicationContext.getBean("student"));
System.out.println(applicationContext.getBean("&student"));
}
}
运行
---测试开始---
Student{name='张三', age=25, className='三年二班'}
org.springframework.beans.factory_bean.StudentFactoryBean@1ae369b7
---测试结束---
这样我们就实现了通过BeanFactory接口达到了简化配置文件的作用。另外大家也可以发现getBean(“student”)返回的Student类的实例;而getBean("&student")返回的是StudentFactoryBean实例,即工厂bean其本身。
2.3.2 返回不同Bean的实例
既然FactoryBean是一种工厂bean,那么我们就可以根据需要的类型,返回不同的bean的实例,通过代码简单说明一下
新建bean
public interface Animal {
void sayHello();
}
public class Cat implements Animal {
@Override
public void sayHello() {
System.out.println("hello, 喵喵喵...");
}
}
public class Dog implements Animal {
@Override
public void sayHello() {
System.out.println("hello, 汪汪汪...");
}
}
创建了一个Animal接口极其两个实现类Cat和Dog,并进行简单输出,那么如何通过FactoryBean来通过配置返回不同的Animal实例呢
新建AnimalFactoryBean
public class AnimalFactoryBean implements FactoryBean<Animal> {
private String animal;
@Override
public Animal getObject() throws Exception {
if (null == animal) {
throw new IllegalArgumentException("'animal' is required");
}
if ("cat".equals(animal)) {
return new Cat();
} else if ("dog".equals(animal)) {
return new Dog();
} else {
throw new IllegalArgumentException("animal type error");
}
}
@Override
public Class<?> getObjectType() {
if (null == animal) {
throw new IllegalArgumentException("'animal' is required");
}
if ("cat".equals(animal)) {
return Cat.class;
} else if ("dog".equals(animal)) {
return Dog.class;
} else {
throw new IllegalArgumentException("animal type error");
}
}
public void setAnimal(String animal) {
this.animal = animal;
}
}
修改day03.xml配置文件,增加bean
<bean id="animal" class="com.lyc.cn.day03.AnimalFactoryBean" p:animal="cat"/>
在MyTest中添加测试用例
@Test
public void testAnimalFactoryBean() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("day03.xml");
Animal animal = applicationContext.getBean("animal", Animal.class);
animal.sayHello();
}
运行
---测试开始---
hello, 喵喵喵...
---测试结束---
可以看到,配置文件里我们将animal配置成了cat,那么返回的就是cat的实例,也是简单工厂的一个实现
2.4 使用场景
说了这么多,为什么要有FactoryBean这个东西呢,有什么具体的作用吗?
FactoryBean在Spring中最为典型的一个应用就是用来创建AOP的代理对象。
我们知道AOP实际上是Spring在运行时创建了一个代理对象,也就是说这个对象,是我们在运行时创建的,而不是一开始就定义好的,这很符合工厂方法模式。更形象地说,AOP代理对象通过Java的反射机制,在运行时创建了一个代理对象,在代理对象的目标方法中根据业务要求织入了相应的方法。这个对象在Spring中就是——ProxyFactoryBean。
所以,FactoryBean为我们实例化Bean提供了一个更为灵活的方式,我们可以通过FactoryBean创建出更为复杂的Bean实例。
还有比如我们spring需要整合mybatis,在没有spring-mybatis的情况下(spring-mybatis会帮助你将MyBatis 代码无缝地整合到Spring 中),我们需要将mybatis核心类SqlSessionFactory注入到spring容器,那么思考使用最常用的两种方式:
- 注解,可是mybatis是个我们引用的独立的项目.与我们自己的项目源码无关,我们无法去修改它的源码,在它的源码上添加注解,所以不能使用注解的方法
- xml,sqlSessionFacory需要注入许多的依赖,如果使用XML来配置,需要我们写大量的配置标签,非常不方便维护。
所以可以选择一个代理类去处理sqlSessionFacory,也就是我们在整合spring+mybatis时使用的SqlSessionFactoryBean,这个类是由mybatis提供的用来方便我们快速配置mybatis的factoryBean,通过这个类把很多繁琐的配置代码封装了起来,类似于装饰者模式,SqlSessionFactoryBean里面管理了sqlSessionFacory并且对他进行相关配置设置操作,我们只需要将SqlSessionFactoryBean注入到spring容器中,并在在xml向这个factoryBean传入一些简单的配置信息,SqlSessionFactoryBean就会帮我们自动配置好sqlSessionFacory,很多复杂的配置都帮我们填充好了,然后我们就可以通过SqlSessionFactoryBean获取已经配置完成的sqlSessionFacory。
三、BeanFactory和FactoryBean的区别以及共同点
共同点:都是接口
区别:
- BeanFactory 以Factory结尾,表示它是一个工厂类,用于管理Bean的一个工厂。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。该接口是IoC容器的顶级接口,是IoC容器的最基础实现,也是访问Spring容器的根接口,负责对bean的创建,访问等工作
- 对FactoryBean而言,以Bean结尾,说明这是一个交给容器去管理的bean。这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。
参考资料:
https://blog.csdn.net/lyc_liyanchao/article/details/82424122