BeanFactory:
理解为一个加工工厂:当程序员把类地址,类别名,类中的属性放在配置文件中后,他就去取这些数据并验证,最终为这些类创建对象。
他的作用就是把所有需要管理的对象存储起来,统一管理,最终提供一个统一的getBean()方法。
如果你模拟过xml方式实现spring的ioc功能,就知道我们把所有需要spring管理的类,都需要配置到xml中;
例如:applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="demoController" class="com.wqm._02annotation.controller.DemoController">
<property name="demoService" ref="demoService"></property>
</bean>
<bean id="demoService" class="com.wqm._02annotation.service.impl.DemoServiceImpl"></bean>
</beans>
如果你模拟过用注解的方式,实现spring的ioc功能,也就会知道注解的作用,其实就只是标识
@Serivce
@Controller
@Component
等等
他们最终都调用了BeanFactory接口,提供的getBean方法;
不管是xml还是注解的方式,他们都是把需要管理的类,先获取到,然后将这些类存储起来,再通过静态代理的方式,为这些类,提供一个统一的方法。而提供统一的规范的这个类,就叫BeanFactory。
下面一段代码展示:BeanFactory的最终作用:(就是spring为了管理对象,写了一个接口,接口的所有实现,都是围绕拿到正确的对象所展开的)
@org.junit.Test
public void getOne() {
/**
* 方式一: 最原始的方式,获取xml配置,创建对象
*/
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:springioc.xml");
OneController oneController1 = (OneController) beanFactory.getBean("oneController");
oneController1.getOne();
/**
* 方式二: 被封装后的方式,获取xml配置,创建对象
*/
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("classpath:springioc.xml");
OneController oneController = (OneController) classPathXmlApplicationContext.getBean("oneController");
oneController.getOne();
/**
* 方式一: 最原始的方式,获取注解配置,创建对象
*/
BeanFactory beanFactory = new AnnotationConfigApplicationContext(SpringConfigDemo.class);
DemoController demoController = (DemoController) beanFactory.getBean("demoController");
demoController.getDemo();
/**
* 方式二: 被封装后的方式,获取注解配置,创建对象
*/
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(SpringConfigDemo.class);
DemoController demoController2 = (DemoController) annotationConfigApplicationContext.getBean("demoController");
demoController2.getDemo();
}
现在知道BeanFactory的作用了吧。就是你把对象的位置,名字给我,我就帮你创建一个对象,并把对象的地址放在spring容器中。(spring容器可以理解为一个map集合)
现在好了,比如我把DataServiceImpl这个对象放在了map集合中,我如何来取呢 ?
现在就出来了,@Autowired、@Resource,比如
@Autowired
private DataService dataService;
上面的注解,就标识了,这个类,可以去spring容器中去取(map集合中去取),取map集合中,拿到对象,就可以使用对象了。
说了这么多:BeanFactory就是用来创建对象的。
FactoryBean:
这是一个非常重要的知识点,知道这个之后,就可以写mybatis、hibernate、shiro或者其他你自己想写的组件,最终整合spring。
看不懂FactoryBean就看不懂mybatis源码真正的运行过程。
这里我拿mybatis为例,整合spring,并且拿大多数人熟悉的xml配置方式开头讲解
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据库连接池 -->
<property name="dataSource" ref="dataSource" />
<!-- 加载mybatis的全局配置文件 -->
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
<!-- 配置pojo别名 -->
<property name="typeAliasesPackage" value="com.wqm.step.pojo"/>
</bean>
看这个配置,大家应该都很熟悉,我们要mybaits整合spring,就必须要配置bean:SqlSessionFactoryBean
也就是说,用Bean配置的方式,让spring管理mybatis提供的SqlSessionFactoryBean。
通常情况下,我们自己写的代码,会在需要被spring管理的类上,加上@Component注解,表示这个类会被spring管理。
但是第三方框架,不会直接加上@Component注解,并且第三方框架也不知道我们到底要用它的那些功能。
在我们直接使用第三方框架(有些框架不开源),且不能修改第三方框架的前提下,spring提供了一个技术。
叫 FactoryBean。
下面是mybatis为了整合spring,自己提供的代码
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
......
}
这段代码我们发现,mybatis提供了一个SqlSessionFactoryBean,并且实现了spring提供的FactoryBean。
其实不光是mybatis,所有可以整合spring的第三方框架,都需要实现FactoryBean。
上面的例子证明了,FactoryBean的使用范围,和设计初衷。下面用模拟代码来讲讲spring的FactoryBean的使用
package com.wqm._12FactoryBean;
import com.wqm._12FactoryBean.temp.HelloServiceImpl;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;
/**
* @author: wangqinmin
* @date : 2020/4/18
* @description: 仰天大笑出门去,我辈岂是蓬蒿人
*/
@Component
public class DaoFactoryBean implements FactoryBean {
/**
* BeanFactory:是用来存储对象的,它提供了一个getBean方法,是用来获取Bean的。
* FactoryBean:是用来修改的,它可以让你在获取Bean之前,把要获取的Bean修改成其他的Bean
* <p>
* 正常来说,我们可以在Test类中,获取DaoFactoryBean这个类,并可以直接调用这里面的下面方法。
*
*/
public void test() {
System.out.println("DaoFactoryBean 的 test方法执行 ");
}
/**
* 这个方法可以获取对象,可以理解为获取Bean
*
* @return
* @throws Exception
*/
public Object getObject() throws Exception {
return new HelloServiceImpl();
}
/**
* 返回对象类型
*
* @return
*/
public Class<?> getObjectType() {
return HelloServiceImpl.class;
}
/**
* 设置对象是否单例
*
* @return
*/
public boolean isSingleton() {
return true;
}
}
我自定义了一个DaoFactoryBean,并且实现了FactoryBean
下面测试:
package com.wqm._12FactoryBean;
import com.wqm._12FactoryBean.temp.HelloServiceImpl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author: wangqinmin
* @date : 2020/4/15
* @description: 仰天大笑出门去,我辈岂是蓬蒿人
*/
public class Test {
/**
* FactoryBean的作用:
*/
@org.junit.Test
public void test() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
DaoFactoryBean daoFactoryBean = (DaoFactoryBean) applicationContext.getBean("daoFactoryBean");
/**
* java.lang.ClassCastException: com.wqm._12FactoryBean.temp.HelloServiceImpl cannot be cast to com.wqm._12FactoryBean.DaoFactoryBean
*
* 为什么呢 ?
* 因为DaoFactoryBean实现了FactoryBean;
*
* spring源码写到: 如果实现FactoryBean,那么获取这个类的Bean,就必须加上&符号。
* 否则,就是获取这个类中,实现getObject()方法和getObjectType()方法的的那个类的Bean
*/
daoFactoryBean.test();
}
}
结果是报错了,类类型转换异常。上面的注释说明了原因。
所以我们需要将上面的
DaoFactoryBean daoFactoryBean = (DaoFactoryBean) applicationContext.getBean("daoFactoryBean");
改为:
DaoFactoryBean daoFactoryBean = (DaoFactoryBean) applicationContext.getBean("&daoFactoryBean");
多加一个&符号:结果是调用成功。
那我们没有加&符号的时候,applicationContext.getBean("daoFactoryBean");
返回类型,到底是什么呢 ?
我们在看看上面
public class DaoFactoryBean implements FactoryBean{}
这个类,实现了FactoryBean后,重写三个方法。
其中有两个方法,一个是getObject(),一个是getObjectType(),重写的时候,我就指定了spring要管理 DaoFactoryBean的时候,实际上是管理一个叫HelloServiceImpl的类。
所以进行如下测试:
package com.wqm._12FactoryBean;
import com.wqm._12FactoryBean.temp.HelloServiceImpl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author: wangqinmin
* @date : 2020/4/15
* @description: 仰天大笑出门去,我辈岂是蓬蒿人
*/
public class Test {
/**
* FactoryBean的作用:
*/
@org.junit.Test
public void test() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
HelloServiceImpl helloServiceImpl = (HelloServiceImpl) applicationContext.getBean("daoFactoryBean");
helloServiceImpl.hello();
}
}
结果是成功的。
结论:FactoryBean是用来指定外部某个类,被spring创建对象(管理对象)。
完工。
疑问: (其实这是我自己的疑问,不过我一轮测试下来,发现了问题及原因的所在,还是对spring基础了解不够,不过也很开心,至少知道问题出在什么地方,并知道怎么解决。)
疑问来源于,为什么第三方框架必须实现FactoryBean,难道不能直接用@Bean注解,强行将第三方框架中的类交给spring管理吗?
比如,拿JavaConfig方式举例:
1. 要让spring管理,必须在spring的扫描包下。这时候,我把mybatis的包也扫描到。
2. 要让spring管理,在JavaConfig下用注解@Bean,将这类初始化。
上面两步都没有问题。但是有一个问题,spring管理的类,都有一个类似别名的东西,也就是spring的ioc功能。
比如:
@Service
public void DemoService(){}
没有ioc的加持,我们怎么来DI ?所以还是会报错。
这样下来,就必须在被管理的类上加 @Component。
由于是第三方框架,所以必须下载源码,重新编译,并加上 @Component注解
结论: 饭吃饱了。
spring的3种使用方式:
1. xml配置,管理对象
2. xml配置+开启注解,管理对象
3. javaConfig配置+注解,管理对象
用第一种方式,确实可以解决上面的问题,因为没有注解,直接配置就好了。
但2、3种方式,是不能解决的。
但是第1、2种方式在springboot坐江山的时候,已经被淘汰了。
上面那个疑问不成立的原因就是:
spring管理Bean的过程,有4个步骤;
1. 包扫描Bean
2. 初始化实例化Bean
3. 管理Bean(ioc)
4. 应用过程: 依赖注入(di)