一,MyBatis整合Spring原理介绍:
1,在mybatis-spring的源码中,有一个把spring和mybatis结合起来的类:MapperScannerConfigurer
这个类实现了Spirng的BeanDefinitionRegistryPostProcessor接口,这是利用了spring的一个扩展点,Spring在初始化容器的扫描注册BeanDefinition之后,会调用所有实现了BeanDefinitionRegistryPostProcessor接口的类的postProcessBeanDefinitionRegistry方法,给第三方或者程序员一次修改BeanDefinition集合的机会。那么mybatis就是在这里把Mapper接口的定义注册到Spring中,交由Spring来管理的。
2,但是有一个问题,就是Mapper都是接口,接口是不能直接new的,这里就需要另外一个类:MapperFactoryBean
这个类实现了Spring的FactoryBean接口,说明这个一个工厂bean。在把Mapper接口信息封装为BeanDefinition注册到Spring之后,又修改了这些由Mybatis注册的BeanDefinition,把原来的Mapper接口的Class替换成了MapperFactoryBean这个工厂bean的Class,并标识这个一个工厂bean。这样Spring在通过BeanDefinition实例化的时候就会实例化出一个MapperFactoryBean对象,然后由MapperFactoryBean来创建Mapper实例。
补充说明:Spring的获取bean的时候,如果这是一个工厂bean,则会调用工厂bean的getObject()方法来获取由工厂bean生成的bean实例。
而在MapperFactoryBean这个工厂bean中,getObject()方法里面调用的就是mybatis提供的获取mapper对象的方法,源码如下:
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
总结:一句话描述就是:mybatis利用Spring的后置处理器扩展点注册了MapperFactoryBean,MapperFactoryBean可以生产mapper对象。
二,代码实现逻辑
基于之前写的springIOC容器和mybatis代码来实现整合。
实现逻辑:
1,SpringIOC容器提供一个BeanDefinitionRegistryPostProcessor接口,然后找出实现了这个接口的类,调用其中的postProcessBeanDefinitionRegistry()方法。
2,mybatis中创建一个类MapperBeanDefinitionRegistryPostProcessor实现BeanDefinitionRegistryPostProcessor接口,将mapper接口注册到Spring的BeanDefinition集合中,并修改class为工厂bean。
3,修改之前Spring的代码,在创建bean的时候判断是否为工厂bean,如果是,则创建工厂bena之后调用getObject方法返回bean对象。
三,写代码实现
1,基于之前的Spring和SpringMVC整合的项目,新建一个用于实现mybatis的模块,把之前写的mybatis简单实现的代码放到这个模块中继续开发。
2,实现Spring中BeanDefinitionRegistryPostProcessor接口扩展点的功能:
2.1 创建BeanDefinitionRegistryPostProcessor这个接口:
package com.salulu.minisp.context.support;
import com.salulu.minisp.context.implement.AnnotatedBeanDefinitionReader;
public interface BeanDefinitionRegistryPostProcessor{
void postProcessBeanDefinitionRegistry(AnnotatedBeanDefinitionReader registry);
}
2.2 修改之前的AnnotatedBeanDefinitionReader类
在扫描包完成之后,找出实现了BeanDefinitionRegistryPostProcessor接口的类,然后执行类中的postProcessBeanDefinitionRegistry()方法。
/**
* 进行包扫描,并封装beanDefinition对象放入BeanFactory的bdMap中
*/
public void doScanner(){
String basePackage = findScanPath();
// 扫描指定包的Class文件
List<Class> classList = new ArrayList<>();
scanClassFile(basePackage,classList);
// 过滤需要由SpingIOC容器管理的class
List<Class> filterClassList = filterBeanDefinition(classList, new Class[]{Component.class, Controller.class, Service.class});
// 注册到BeanDefinitionMap中
registryBeanDefinition(filterClassList);
System.out.println("包扫描完成");
// 找出BeanDefinitionRegistry的后置处理器放到一个list集合中
findBeanDefinitionRegistryPostProcessorImpl();
// 执行实现了BeanDefinitionRegistryPostProcessor接口的类
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(this,beanFactoryPostProcessors);
}
2.2.1找出BeanDefinitionRegistry的后置处理器放到一个list集合中
/**
* 找出BeanDefinitionRegistry的后置处理器放到一个list集合中
*/
private void findBeanDefinitionRegistryPostProcessorImpl() {
// 获取所有的.class
List<File> classFileList = getAllClassFile(rootPath);
// 根据.class文件得到Class对象
List<Class> classList = getClassListByFile(classFileList);
// 遍厉Class对象,找出实现了BeanDefinitionRegistryPostProcessor接口的后置处理器类,并把这个类实例化,然后放入一个list集合中
findBeanDefinitionRegistryPostProcessorImpl(classList);
}
//获取所有的.class
private List<File> getAllClassFile(String path){
List<File> classFileList = new ArrayList<>();
File rootFile = new File(path);
for (File file : rootFile.listFiles()) {
if(file.isDirectory()){
List<File> allClassFile = getAllClassFile(file.getAbsolutePath());
classFileList.addAll(allClassFile);
}else {
if(file.getName().endsWith(".class")){
classFileList.add(file);
}
}
}
return classFileList;
}
// 将.class文件转为Class对象
private List<Class> getClassListByFile(List<File> classFileList){
List<Class> classList = new ArrayList<>();
for (File file : classFileList) {
String absolutePath = file.getAbsolutePath();
int index = absolutePath.indexOf("com/");
if(index<0) continue;
String substring = absolutePath.substring(index);
String className = substring.replace(File.separator,".").replace(".class","");
// 得到class
Class<?> aClass = null;
try {
aClass = Class.forName(className);
classList.add(aClass);
} catch (ClassNotFoundException e) {
//e.printStackTrace();
}
}
return classList;
}
/**
* 找出BeanDefinitionRegistryPostProcessor的后置处理器放到一个list集合中
* @param classList
*/
private void findBeanDefinitionRegistryPostProcessorImpl(List<Class> classList){
for (Class aClass : classList) {
Class[] interfaces = aClass.getInterfaces();
for (Class anInterface : interfaces) {
if(anInterface == BeanDefinitionRegistryPostProcessor.class){
try {
Object obj = aClass.newInstance();
beanFactoryPostProcessors.add((BeanDefinitionRegistryPostProcessor)obj);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
2.2.2 执行实现了BeanDefinitionRegistryPostProcessor接口的类的postProcessBeanDefinitionRegistry()方法
// 执行实现了BeanDefinitionRegistryPostProcessor接口的类
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(this,beanFactoryPostProcessors);
新建一个PostProcessorRegistrationDelegate类来调用执行:
package com.salulu.minisp.context.support;
import com.salulu.minisp.context.implement.AnnotatedBeanDefinitionReader;
import java.util.List;
public class PostProcessorRegistrationDelegate {
public static void invokeBeanFactoryPostProcessors(AnnotatedBeanDefinitionReader registry, List<BeanDefinitionRegistryPostProcessor> beanDefinitionRegistryPostProcessorList) {
for (BeanDefinitionRegistryPostProcessor beanDefinitionRegistryPostProcessor : beanDefinitionRegistryPostProcessorList) {
beanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry(registry);
}
}
}
2.3 修改实例化bean的逻辑,加一个判断,如果是工厂bean的话需要做特殊处理:
/**
* 创建bean实例
* @param beanName
* @return
*/
private Object doCreateBean(String beanName) {
Object newInstance = null;
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
// 判断是否为工厂bean
if (beanDefinition.isFactoryBean()) { // 工厂bean
Constructor<?> factoryBeanConstructor = beanDefinition.getFactoryBeanConstructor();
try {
FactoryBean factoryBean = (FactoryBean)factoryBeanConstructor.newInstance(beanDefinition.getBeanClass());
// 通过工厂bean生成出对应的bean对象
newInstance = factoryBean.getObject();
// 封装工厂bean对象后放入二级缓存中
ObjectFactory objectFactory = new ObjectFactory();
objectFactory.setBeanDefinition(beanDefinition);
objectFactory.setBean(newInstance);
singletonFactories.put(beanName,objectFactory);
} catch (Exception e) {
e.printStackTrace();
}
}else{// 为非工厂bean
Class beanClass = beanDefinition.getBeanClass();
// Spring 先推断构造方法,然后反射调用构造方法来实例化
// Constructor declaredConstructor = beanClass.getDeclaredConstructor();
// declaredConstructor.setAccessible(true);
// Object o = declaredConstructor.newInstance(null);
// 这里直接不用那么麻烦,直接反射生产实例
try {
newInstance = beanClass.newInstance();
// 先放入三级缓存
earlySingletonObjects.put(beanName,newInstance);
Field[] declaredFields = beanClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
Autowirted autowirted = declaredField.getAnnotation(Autowirted.class);
if(autowirted==null) continue;
Class<?> fieldType = declaredField.getType();
String simpleName = BeanDefinitionReaderUtils.getBeanByClass(fieldType);
Object fieldBean = doGetBean(simpleName);
declaredField.setAccessible(true);
declaredField.set(newInstance,fieldBean);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return newInstance;
}
3,实现mybatis与Spring整合的代码:
在原来的com.salulu包下新建一个spring的包,用来存放整合的代码:
3.1 创建MapperFactoryBean类,实现工厂bean接口的方法。
主要是实现getObject()方法:从SqlSession中获取Mapper对象
package com.salulu.spring;
import com.salulu.minisp.context.factory.FactoryBean;
public class MapperFactoryBean<T> implements FactoryBean<T> {
private Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
// 从SqlSession中获取Mapper对象
T mapper = SqlSessionSupport.sqlSession.getMapper(this.mapperInterface);
return mapper;
}
@Override
public Class<?> getObjectType() {
return this.mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
}
这个SqlSessionSupport中其实是放了一个可以全局方法问的SqlSession对象。
package com.salulu.spring;
import com.salulu.baits.session.SqlSession;
public class SqlSessionSupport {
public static SqlSession sqlSession = null;
}
3.2 创建MapperBeanDefinitionRegistryPostProcessor类并实现BeanDefinitionRegistryPostProcessor接口:
在postProcessBeanDefinitionRegistry()方法中主要完成一下逻辑:
1,扫描mapper接口注册到Spring中
2,读取mybatis的配置文件
3,构建session工厂
4,打开sqlsession得到SqlSession对象
5,给SqlSessionSupport的SqlSession赋值
代码如下:
package com.salulu.spring;
import com.salulu.baits.annotations.Mapper;
import com.salulu.baits.io.Resources;
import com.salulu.baits.session.SqlSession;
import com.salulu.baits.session.SqlSessionFactory;
import com.salulu.baits.session.SqlSessionFactoryBuilder;
import com.salulu.minisp.context.implement.AnnotatedBeanDefinitionReader;
import com.salulu.minisp.context.support.BeanDefinition;
import com.salulu.minisp.context.support.BeanDefinitionRegistryPostProcessor;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
public class MapperBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
private static Class<?> factoryBeanClass = MapperFactoryBean.class;
@Override
public void postProcessBeanDefinitionRegistry(AnnotatedBeanDefinitionReader registry){
// 扫描指定包的Class文件
List<Class> classList = new ArrayList<>();
registry.scanClassFile("com",classList);// 这里写死com开头的包
// 过滤需要由SpingIOC容器管理的class
classList = registry.filterBeanDefinition(classList, new Class[]{Mapper.class});
// 注册到BeanDefinitionMap中
for (Class aClass : classList) {
BeanDefinition beanDefinition = new BeanDefinition(aClass);
beanDefinition.setFactoryBean(true);// 标示为工厂bean
beanDefinition.setFactoryBeanClass(factoryBeanClass);
Constructor<?> constructor = null;
try {
constructor = factoryBeanClass.getConstructor(Class.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
beanDefinition.setFactoryBeanConstructor(constructor);
registry.registryBeanDefinition(beanDefinition);// 注册工厂bean的定义
}
// 1,读取配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 2,构建session工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3,打开sqlsession
SqlSession session = sqlSessionFactory.openSession();
// 给SqlSessionSupport的SqlSession赋值
SqlSessionSupport.sqlSession = session;
}
}
4,测试
4.1 在测试模块中加入salulu-batis模块和mysql驱动的依赖
4.2 将之前batis中的mybatis-config.xml文件和UserMapper.xml文件拷贝到resource目录下
4.3 完成config,mapper,service,controller类的测试代码
controller中调用userService:
package com.salulu.test.controller;
import com.salulu.minisp.context.annotate.Autowirted;
import com.salulu.minisp.context.annotate.Controller;
import com.salulu.test.entity.User;
import com.salulu.test.service.UserServiceA;
import com.salulu.web.annotate.RequestMapping;
import com.salulu.web.annotate.RequestParam;
import com.salulu.web.domain.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/user")
public class UserController {
@Autowirted
private UserServiceA userServiceA;
//模拟返回json数据
@RequestMapping("/getUserJson")
public String getUserJson(HttpServletRequest request, HttpServletResponse response,@RequestParam("id") Integer id){
User user = userServiceA.getById(id);
return user.toString();
}
userService中调用userMapper
package com.salulu.test.service;
import com.salulu.minisp.context.annotate.Autowirted;
import com.salulu.minisp.context.annotate.Component;
import com.salulu.test.entity.User;
import com.salulu.test.mapper.UserMapper;
@Component
public class UserServiceA {
@Autowirted
private UserMapper userMapper;
public UserServiceA(){
System.out.println("UserServiceA 构造方法被调用");
}
public User getById(int id){
User user = userMapper.selectByPrimaryKey(id);
return user;
}
}
4.4 使用postMan请求接口:
请求成功:成功获取到数据库中id等于2的数据。