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等等注解生效了。