1. 背景
手写一下spring-mybatis 包,方便理解spring的加载过程。
(希望能够耐着心看完,学习源码毕竟枯燥,只有死磕才是终点)
2. 代码环境
引入mybatis 的jar 包
compile group: 'org.mybatis', name: 'mybatis', version: '3.4.5'
3. 准备基础代码
3.1 :手写mapper类----DogAppleMapper
public interface DogAppleMapper {
@Select("select 'apple'")
String query();
}
3.2: 准备测试类 service
@Service
public class DogUserService {
@Autowired
private DogAppleMapper dogAppleMapper;
public String getUser(){
System.out.println(dogAppleMapper.query());
return "success";
}
}
3.3 : 准备容器
@ComponentScan("com.dog.dog_mybatis")
public class DogMybatisTest {
public static void main(String[] args) {
/** 获得上下文容器 **/
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(DogMybatisTest.class);
context.refresh();
DoguserService dogUserService = (DogUserService) context.getBean("dogUserService");
dogUserService.getUser();
}
}
3.4: 启动后分析
启动后报错 如下:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.dog.dog_mybatis.mapper.DogAppleMapper' available:
原因很简单,就是DogAppleMapper 没有实现类,在spring 找到注入点的时候,因为没有对应的实现类,所以生成不了对应的BeanDefinition,所以就会报错 NoSuchBeanDefinition。
所以需要给DogAppleMapper 找到一个实现类/代理类/或则直接把DogAppleMapper手动 注册到BeanDefinitionMap中。
4. 改造,使用BeanDefinition直接注册
4.1: 尝试手动注册到BeanDefinition (❌)
@ComponentScan("com.dog.dog_mybatis")
public class DogMybatisTest {
public static void main(String[] args) {
/** 获得上下文容器 **/
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(DogMybatisTest.class);
//获取一个新的beanDefinition
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(DogAppleMapper.class);
context.registerBeanDefinition(DogAppleMapper.class.getName(), beanDefinition);
context.refresh();
DoguserService dogUserService = (DogUserService) context.getBean("dogUserService");
dogUserService.getUser();
}
}
这样也是不行的,因为DogAppleMapper是一个 interface, 不能直接注册成 beanDefinition. 我们只能考虑给 DogAppleMapper 生成一个代理类。这里就要使用FactoryBean了
5. FactoryBean接手
5.1: 关于FactoryBean
FactoryBean可以理解为是一个spring的扩展类,我们可以用来生成一些复杂的bean, 或则就是为接口,类生成代理类使用。 在实例化刚开始的时候一段代码如下
/** 只有非懒加载的单例才会走这里 **/
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
/** 这里是处理特殊的对象 FactoryBean的逻辑 **/
if (isFactoryBean(beanName)) {
// 获取FactoryBean对象,这个 Bean获取是要加一个 &
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (){...}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());}
if (isEagerInit) {
// 创建真正的Bean对象(getObject()返回的对象)
getBean(beanName);
}
}
也就是我们如果实现的是FactoryBean接口,在Spring启动的时候是不会执行getBean()方法的,也就是不会生成内部的getObject()产生的对象。 只有实现SmartFactoryBean的时候,并且重写了isEager()方法返回true的时候,才会在Spring启动过程中生成getObject()中的bean.
接着往下看getBean(). 里面调用的是doGetBean()。 这个先处理FactoryBean实现类,如果不是,再去处理正常的业务Bean等。处理后面业务Bean的时候会先扫描生成BeanDefinition。这里就不再描述了。直接看doGetBean()方法中对FactoryBean的处理。
if (sharedInstance != null && args == null) {
......
// 如果sharedInstance是FactoryBean,那么就调用getObject()返回对象
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
....
object = postProcessObjectFromFactoryBean(object, beanName);
代码太长了,就是用两行看一下。 先从factoryBeanObjectCache 缓存中获取,获取不到调用FactoyBean实现类中的getObject()方法获取对象,然后存到 factoryBeanObjectCache缓存中。所以只要factoryBean是单例的,我们getObject()获取的对象也是单例的,因为后续获取getBean(factoryBean)的时候,都是走的缓存。
结论就是:通过factoryBean接口getObject()方法创建的对象,不会放入到单例池中,也不会生成BeanDefinition. 只是在需要该对象的时候通过getObject()获取然后放入缓存。 factoryBean接口getObject()创建的实例,是不拥有spring生命周期的。但是实现了factoryBean的类是在容器中的。
5.2: 使用FactoryBean获取DogAppleMapper接口的代理
@Component
public class DogFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
Object proxyInstance = Proxy.newProxyInstance(DogFactoryBean.class.getClassLoader(), new Class[]{DogAppleMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("factory bean 中的getObject()");
return null;
}
});
return proxyInstance;
}
@Override
public Class<?> getObjectType() {
return DogAppleMapper.class;
}
}
这样我们使用基础代码测试,就能返回 null. 说明DogAppleMapper的代理类是生成成功了。
按照上面写,会存在两个问题
- 我们会有很多的mapper,不能把 DogAppleMapper.class写死,写死的话,需要创建很多的FactoryBean
- DogFactoryBean不能加@Component注解,加了就是单例,单例的对象是固定一个。但是会有多mapper,需要很多的mapper代理对象factoryBean.
更改上面的代码如下
public class DogFactoryBean implements FactoryBean {
private Class instance;
public DogFactoryBean(Class instance) {
this.instance = instance;
}
@Override
public Object getObject() throws Exception {
Object proxyInstance = Proxy.newProxyInstance(DogFactoryBean.class.getClassLoader(), new Class[]{instance}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("factory bean 中的getObject()");
return null;
}
});
return proxyInstance;
}
@Override
public Class<?> getObjectType() {
return instance;
}
}
改动了两个地方,第一使用动态的class. 使用方mapper通过构造方法设置
第二去掉了@Component注解,使用方可以创建不同的实例对象,作为mapper代理类
这样还是存在问题,我们的代理类DogFactoryBean没有了@Component,没办法注册到spring容器中,但是作为mapper的代理对象,spring初始化的时候是要加载创建的。这个时候可以使用BeanDefinition去实例化这个factoryBean
我们更改启动类代码如下:
@ComponentScan("com.dog.dog_mybatis")
public class DogMybatisTest {
public static void main(String[] args) {
/** 获得上下文容器 **/
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(DogMybatisTest.class);
/** 生成一个新的BeanDefinition **/
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
/** 设置beanClass, beanClass 就是一个BeanDefinition实例化的时候要去找的对象 **/
beanDefinition.setBeanClass(DogFactoryBean.class);
/** 设置构造方法的参数引用,spring会根据推断 构造方法机型匹配 **/ beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(DogAppleMapper.class);
/** 注册到BeanDifinitionMap中 **/
context.registerBeanDefinition("dogAppleMapper", beanDefinition);
context.refresh();
DogUserService dogUserService = (DogUserService) context.getBean("dogUserService");
dogUserService.getUser();
}
跑一下测试,可以拿到DogAppleMapper的代理类的。
5.3 插入一点BeanDefinition 说明
上面的插入的代码拿出来
/** 生成一个新的BeanDefinition **/
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
/** 设置beanClass, beanClass 就是一个BeanDefinition实例化的时候要去找的对象 **/
beanDefinition.setBeanClass(DogFactoryBean.class);
/** 注册到BeanDifinitionMap中 **/
context.registerBeanDefinition("dogAppleMapper", beanDefinition);
巩固一下这个流程,Spring启动过程中,对Bean的初始化大概的过程是
- 创建BeanFactory,默认是DefaultListableBeanFactory
- 创建注解对象BeanDefinition 读取器 AnnotationBeanDefinitionReader
- 创建扫描对象注册BeanDefinition 注册器 ClasspathBeanDefinitionScanner
- 把class文件加载到 JVM中,Class.forName();
- 扫描配置类,在invokerBeanFactoryPostProcessor()方法中,先通过@Component,@ComponentScaner,@Import,@ImportSource, @Bean找到配置类。
- 解析配置类,把配置类都转换成BeanDefinition, 并且注册到BeanDefinitionMap<String name, beanDefinition>中,这个时候我们可以随便指定一个name,随表起名字,只不过后续使用的时候也要用这个名字getBean()。
- 然后进行实例化,实例化的时候会根据BeanDefinitionMap中的BeanDefinition的 beanClass属性找到类,对类进行实例化。Class.invoke()
- 实例化后,进行属性填充,属性填充包含配置文件,依赖注入。 寻找注入点,然后注入实例
- 实例化完成后,进行初始化。初始化后会把实例放入到单例池中。
6. 引入ImportBeanDefinitionRegistrar 注册BeanDefinition
在看一下5.2 步骤的代码,可以看到,如果存在多个Mapper,我们是要生成多个FactoryBean实现类的代理类,需要生成很多的BeanDefinition 去处理。我们不可能在启动类上无限的添加BeanDefinition是吧。所以需要考虑一下注册BeanDefinition还有什么办法。
6.1: 使用BeanDefinitionRegistryPostProcessor 来注册BeanDefinition
@Component
public class DogBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
/** 用不到,不实现 **/
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(DogFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(DogAppleMapper.class);
registry.registerBeanDefinition("dogAppleMapper", beanDefinition);
}
}
把5.2步骤中的 BeanDefinition 那块去掉,测试发现DogAppleMapper依然可以注册。
自此,创建BeanDefinition的工作交给了 BeanDefinitionRegistryPostProcessor
还是要注册不同mapper的BeanDefinition。 如果使用扫描的方式,循环赋值就可以规避,扫描包下面的类。Spring中有一个 ClassPathBeanDefinitionScanner。
上面代码面临什么问题呢?我们接下来改造需要使用扫描。但是这个postProcessor不支持获取注解,想一下还有什么方式可以注册BeaDefinition呢?可以使用@Import注解范畴内 ImportBeanDefinitionRegistrar。
6.2:自定义MapperScanner注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(DogImportBanDefinitionRegistrar.class)
public @interface DogMapperScanner {
String value() default "";
}
自定义的注解需要加到3.3步骤容器类上 @DogMapperScanner("com.dog.mybatis.mapper")指定到mapper所在的包。
注解上上面还多了一个注解 @Import(DogImportBeanDefinitionRegistrar.class) ,需要知道前提@Import的注解会把类的信息生成BeanDefinition。
6.3:自定义 ClassPathBeanDefinitionScanner
上面我们自定义了MapperScanner,还需要注解解析器,也就是说需要这个注解做什么事情,需要自定义一个注解解析器,告诉程序这个注解接下来需要做什么。 这个场景下我们就是需要把扫描到mapper 注册成BeanDefinition。
其实Spring源码中已经提供了类似的功能,例如 @ComponentScanner注解。就是通过扫描包下面的类,然后注册成BeanDefinition,所以这里只需要集成Spring中的ClassPathBeanDefinitionScanner,重写内部的方法就可以了。
public class DogClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
/** 构造方法,集成ClassPathBeanDefinitionScanner 必选的 **/
public DogClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
super(registry);
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
/** 获取到扫描到的 单个BeanDefinition, 此处就不用我们手动生成新的BeanDefinition了,扫描会得到一个 **/
BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
/** 设置构造方法参数,这个是要和下一行放一起分析的 **/ beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
/** 替换了这个 BeanDefinition的 beanClass。 下面这个set就是该beanClass的。 接下来根据BeanDefinition实例化对象的时候,就会使用DogFactoryBean, 推断的出来的构造方法就是上面一句。 上面这一句就可以把扫描到的mapper 传进去生成 **/
beanDefinition.setBeanClassName(DogFactoryBean.class.getName());
}
return beanDefinitionHolders;
}
上面代码中的doScan()方法就是直接调用CalssPathBeanDefinitionScanner生成 BeanDefinition。
Spring 后续流程中会处理这个set集合中对象 到BeanDefinitionMap中。
6.4:处理 ImportBeanDefinitionRegistrar。
这个类中实现的方法先去获取 @DogMapperScanner注解上的path, 根据path, 使用自定义的DogClassPathBeanDefinitionScanner去扫描。代码如下
public class DogImportBanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
/** 获取注解 **/
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(DogMapperScanner.class.getName());
/** 获取注解属性值**/
String path = (String)annotationAttributes.get("value");
/** 使用自定义扫描器扫描,参数就是一个 beanFactory **/
DogClassPathBeanDefinitionScanner scanner = new DogClassPathBeanDefinitionScanner(registry);
/** 这个扫描就是我们重新的扫描方法 **/
scanner.scan(path);
}
这个类上面是不需要添加 @Componnet的。因为注解 @Import的是会被Spring自己解析加载到容器的。
启动尝试,这个时候会报错。这是因为Spring 扫描在解析配置类的时候,是会忽略接口的,mapper包下都是接口,所以不行。看一下这块的源码:
doScan()---->findCondidateComponent()----->scanCondidateComponent()在这个方法中,是寻找配置类。
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// excludeFilters、includeFilters判断
if (isCandidateComponent(metadataReader)) { // @Component-->includeFilters判断
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
isCandidateCOmponent() 源码中就是判断 includefilter, excludeFilter, Conditional这些注解。 而@Component注解在Spring刚开始启动的时候,都加到includerFilter。 也就是说,这个地方会判断是不是配置类,mapper接口上面没有添加@Component注解,是没办法成为配置类的。当然是可以在mapper类上加上@Component注解的,也就是配置类了,但是一般没有这样写的。所以我们要重写这个判断。
先看第二个isCandidateComponent(),我们在6.3 步骤中的DogClassPathBeanDefinitionScanner中重写方法
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
/** 只要是接口就返回 true **/
return beanDefinition.getMetadata().isInterface();
}
还有第一个isCandidateComponent()判断,我们有很多种方法解决,也可以重写,直接返回true。 但是在源码中是在DogImportBeanDefinitionRegistrar中处理的,代码如下:
scanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
也就是直接返回true了。也就是校验这个类的时候返回true,直接理解为忽略。
现在启动测试,发现还是没问题的。经过上面的处理,我们把spring 怎么引入 mybatis的代码写完了。主要就是完成了怎么无感注册代理类。接下来就是mybatis相关的了
7. 引入SqlSession
7.1: 再看DogFactoryBean
public class DogFactoryBean implements FactoryBean {
private Class instance;
public DogFactoryBean(Class instance) {
this.instance = instance;
}
@Override
public Object getObject() throws Exception {
Object proxyInstance = Proxy.newProxyInstance(DogFactoryBean.class.getClassLoader(), new Class[]{instance}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("factory bean 中的getObject()");
return null;
}
});
return proxyInstance;
}
@Override
public Class<?> getObjectType() {
return instance;
}
}
在这里,只是生成了一个代理类,也就是在代码中注入dogAppleMapper的时候,会找到一个factoryBean,具体的执行对象,还是要通过getObject()方法获得,但是现在的这个是null.
此时你可以在启动类上添加下面代码就能看到效果。
Object dogAppleMapper = context.getBean("dogAppleMapper");
这样,还没有生成真正的代理类。
接下来就可以直接使用,mybatis中操作mapper的代理类了,也就是getMapper()方法。
7.2: 使用mybatis 包中的代理类
这个DogFactoryBean就要改造成下面的代码了
public class DogFactoryBean implements FactoryBean {
private Class mapperInstance;
/** sql session连接**/
private SqlSession sqlSession;
/** 这个是构造方法,是BeanDefinition 推断构造方法的时候使用 **/
public DogFactoryBean(Class instance) {
this.mapperInstance = instance;
}
@Autowired
public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
sqlSessionFactory.getConfiguration().addMapper(mapperInstance);
this.sqlSession = sqlSessionFactory.openSession();
}
@Override
public Object getObject() throws Exception {
return sqlSession.getMapper(mapperInstance);
}
@Override
public Class<?> getObjectType() {
return mapperInstance;
}
}
上面的代码,在setSqlSession()方法上添加了@Autowired,就是要注入这个属性了。属性注入byName,byType等等这些,不重点说哈。
这里这样注入 sqlSession也是可以的。但还有另外一种方式,就是使用BeanDefinition的 setAutowiredMode() 设置注入类型。在 DogClassPathBeanDefinitionScanner类中的for循环中方改成如下. 记得去掉上面set方法的@Autowired注解
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition)beanDefinitionHolder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
/** 添加了属性注入模型,按照byType的形式,也就是会调用setSqlSession()方法了 **/
beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
beanDefinition.setBeanClassName(DogFactoryBean.class.getName());
通过上面的set方法,可以看到还有一个类,就是SqlSessionFactory还没有初始化,这个类对象是读取配置文件 mybatis.xml生成的。在启动类上注入一个@Bean
@Bean
public SqlSessionFactory sqlSessionFactory() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
return factory;
}
另外,mybatis.xml文件附上
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC"/>
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://127.0.0.1:3306/atm?characterEncoding=utf-8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
8. 完成
启动测试类,这个时候已经可以正常使用了。
再提一下,这里最终使用的链接数据库session是 DefaultSession,应该是这个名字吧。这里面的操作线程是不安全的。spring-mybatis源码中使用的sessionTemplate,里面用ThreadLocal封装了一下。
over. 个人总结。