mybatis与spring整合总结

推荐官网  http://www.mybatis.org/spring/

    大家在使用mybatis的时候,都知道他是一个用于连接数据库的开源框架,在我们的配置文件中首先呢需要配置一个数据源dataSource,这个dataSource可以是我们任一个可以获取的dataSource,然后呢,将他注入到sqlSessionFactory中,之后呢在mybatis中的MapperScannerConfigurer注入我们需要扫描的mapper bean,但是呢,从始至终我们都没有看到我们将我们的mapper类交由spring管理,如果我们的bean没有交由spring管理,那么我们就不能用@Autowired,@Resource,@Inject等等关键字来自动注入,但是我们测试的时候发现,我们的mapper确实被实例化了,那么mybatis到底是怎么将我们的mapper文件交由spring管理的呢,这就是我想要探讨并记录的点。

  当我们的在测试的时候断点的时候,我们会看到以下的图:

可以看到,我们的SeckillDao是一个Proxy代理对象,是jdk提供的代理反射机制通过class文件为我们创建出来一个spring

jdk通过代理的方法是以下方法:

Object object  = (要代理的类)Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

第一个参数:loader 我们想要实例化的类的classLoader,第二个参数,是我们的实例化的class数组,第三个参数是一个InvocationHandler的继承类,我们可以实现接口中的invoke方法去实例化对象,这个是反射机制初始化的过程,而spring在扫描一个带有@Component注解的时候呢,是会在他的beanFactory的子接口ListableBeanFactory的子接口ConfigurableListableBeanFactory的实现DefaultListableBeanFactory类中去根据类的信息去存储所有的BeanDefinition并添加到DefaultListableBeanFactory的ConcurrentHashmap中,在初始化bean的时候,会去遍历我们的beanDefinition,然后去将他们加入到spring容器中,伪代码如下:

GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
genericBeanDefinition.setBeanClassName("");
genericBeanDefinition.setBeanClass(CI.class);
//添加到map中
//map.put(genericBeanDefinition)

在初始化中,我们也可以自定义去告诉spring按照我们的BeanFactoryPostProcessor去实现我们的定义的逻辑。

下一步来看一下mybatis的执行bean的过程,我们在配置一个sqlSessionFactory了么,这是mybatis为我们提供的一个session工厂,在session中我们可以获取一个session连接,然后在我们的session中我们可以获取到mapper文件

SqlSession sqlSession = sqlSessionFactory.openSession();
SeckillDao seckillDao1 = sqlSession.getMapper(SeckillDao.class);

而这个通过sqlsession中获取的mapper的seckillDao就是mybatis通过Proxy代理产生的对象,核心代码如下:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

在MapperProxyFactory中获取我们传入类型的dao,然后让我们的mapperProxyFactory去给我们构造出一个新的实例供我们使用,但是我们根据sqlsession获取的bean是没有交给spring管理的,是没有通过spring bean生命周期校验的,那mybatis是怎么样把我们的bean交给spring管理呢,那就是官网上的代码

 

mybaits通过他的MapperFactoryBean类继承Spring的FactoryBean将我们的实例返回给Spring容器管理, 简易原理代码

@Component
public class MyBeanFactory implements FactoryBean {
    //注入进来 将我们的实体业务注入进来
    Class mapperInterface; //

    @Override
    public Object getObject() throws Exception {
        //返回的对象会放到Spring容器中

        Class[] classes = new Class[]{mapperInterface};
        //生成一个代理对象
        Object Object = Proxy.newProxyInstance(SeckillDao.class.getClassLoader()
                ,classes,new MyInvoationHandler());

        return Object ;
    }

    @Override
    public Class<?> getObjectType() {
        return C2.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }

    public void setMapperInterface(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

这样就将我们配置的单例通过反射机制创建,并将他给我们的spring管理。

第二种方式扫描包mybatis官方如下提供

通过MapperScannerConfigurer配置扫描的包,进入MapperScannerConfigurer类中,发现继承第一个接口就是BeanDefinitionRegistryPostProcessor

第一个BeanDefinitionRegistryPostProcessor接口就是spring提供外部程序员可以人为干预spring bean初始化的BeanFactoryPostProcessor接口,他会返回一个void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)

我们已经注册的所有的bean的DefaultListableBeanFactory类,返回我们spring注册的map

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);

这个时候mybatis就可以将扫描到的包中的类通过反射返回到spring的 beanDefinitionMap中,就可以让我们的@Autowired,@Resource,@Inject等等注解生效了。