今天我们以Mybatis,一步步了解Spring和其他依赖的集成原理。
在Spring中,集成Mybatis需要引入一个mybatis-spring的一个jar包。这个jar包会帮我们做好bean的注册等等。但我们今天就先不引入这个jar,只引入mybatis的原生依赖,我们试着自己实现两者的集成。
现在代码如下:
1 2 3 4 5 6
| @Autowired private UserMapper userMapper;
public void test(){ System.out.println(userMapper); }
|
显然会报错吧,因为没有useMapper这个bean。因为@Autowired默认require是true的。
那我们就得考虑一下把这个bean放入IOC容器里面。但是我们知道,Mapper层都是接口对吧,所以如果我们是Mybatis,应该要为其去创建动态代理对象,再把动态代理对象放入IOC。
那什么东西能为一个接口来生成动态代理呢?我们想到factoryBean,虽然@Bean也可以注册,但是对于mapper层,肯定不止一个接口吧,一个个注册就有问题了,再说,mybatis作为一个框架,是不知道你业务代码里面的具体mapper有多少,有什么,所以采用factoryBean是一个再合适不过的方法。
FactoryBean具体详解可见单独文章:FactoryBean?BeanFactory?Spring中的那些有意思的内容
那我们就建一个自己的SwissFactoryBean来继承FactoryBean吧。然后实现里面的getObject方法,为其生成一个动态代理返回就好了。然后再在下面的getObjectType里面声明一下userMapper.class,这样为UserMapper就注册了一个动态代理对象的bean。
现在最开始的userMapper就能成功注入bean了。
知道了这个,我们就可以尝试实现Mybatis和Spring的整合了。我们知道Mybatis里面有一个SqlSession,它可以为指定的接口生成动态代理。
1 2 3 4 5 6
| private SqlSession sqlSession;
@Override public Object getObject(){ return sqlSession.getMapper(UserMapper.class); }
|
那么,这个SqlSession要怎么注入呢?我们可以通过SqlSessionFactory去得到:
1 2 3 4
| @Autowired public void setSqlSession(SqlSessionFactory sqlSessionFactory){ this.sqlSession = sqlSessionFactory.openSession(); }
|
SqlSessionFactory是可以通过我们的配置得到。我们可以通过xml文件配置内容,得到输入流;然后根据输入流创建出SqlSessionFactory,用@Bean交给Spring容器管理。
那现在我们就可以用sqlSession来执行了吗,不,我们还得告诉MyBatis的全局配置里面有哪些mapper。
1 2 3 4 5
| @Autowired public void setSqlSession(SqlSessionFactory sqlSessionFactory){ sqlSessionFactory.getConfiguration().addMapper(UserMapper.class); this.sqlSession = sqlSessionFactory.openSession(); }
|
现在我们就可以使用调用UserMapper层里面的方法,实现动态代理下的查询了。
那又有新的问题了:我们目前的mapper类有哪些是写死的,如果的mapper有很多,总不能让使用者一个个去注册吧?并且每注册一个就得重新写一个类去实现FactoryBean,这种写死的方式肯定是不合理的。
假设我们现在又有了一个OrderMapper,那我们就先开始尝试解耦合吧。先把SwissFactoryBean里面写死的地方提取出来,变成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private Class mapperClass;
public SwissFactoryBean(Class mapperClass){ this.mapperClass = mapperClass; }
@Autowired public void setSqlSession(SqlSessionFactory sqlSessionFactory){ sqlSessionFactory.getConfiguration().addMapper(mapperClass); this.sqlSession = sqlSessionFactory.openSession(); }
@Override public Object getObject(){ return sqlSession.getMapper(mapperClass); }
|
现在我们就可以通过构造函数去传入具体的类,然后对其进行动态代理bean的创建了。这样的话,我们就可以做到这样:
1 2 3 4 5 6 7 8 9 10
| AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.register(AppConfig.class);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition(); beanDefinition.setBeanClass(SwissFactoryBean.class); beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
applicationContext.registerBeanDefinition("userMapper", beanDefinition);
applicationContext.refresh();
|
通过容器去注册UserMapper的beanDefinition。我们只需要重复写这样的代码,对于我们的SwissFactoryBean,就可以创建多个mapper的代理了。这里面每一个bean的名字都是不重复的,所以不用担心看似多例的情况(如果有疑惑,可以看手写Spring框架Day3)。
但是,这样看起来还是太乱了,有没有更优雅的方式呢?
又一个扩展类来了:ImportBeanDefinitionRegistrar接口。至于这个接口具体是什么,和一些其他扩展类有什么关系,请看FactoryBean?BeanFactory?Spring中的那些有意思的内容。
我们定义出自己的SwissImportBeanDefinitionRegistrar,实现ImportBeanDefinitionRegistrar接口及其方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){ AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition(); beanDefinition.setBeanClass(SwissFactoryBean.class); beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
registry.registerBeanDefinition("userMapper", beanDefinition);
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition(); beanDefinition1.setBeanClass(SwissFactoryBean.class); beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
registry.registerBeanDefinition("orderMapper", beanDefinition1); }
|
这样我们就可以在这里面优雅的定义出所有的mapper,接下来只要让Spring知道这个类:
1 2 3 4 5
| @ComponentScan(...) @Import(SwissImportBeanDefinitionRegistrar.class) public class APPConfig{ ... }
|
然后把原来main里面的模拟构造方法改回去,因为我们已经通过配置类引入了我们想要声明的mapper:
1
| AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(APPConfig.class);
|
通过@Import注解引入了这个ImportBeanDefinitionRegistrar,这样Spring就会知道我们的自定义的SwissImportBeanDefinitionRegistrar,帮我们通过我们定义的SwissFactoryBean去实现每一个mapper的动态代理了。
Ok,到这里,耦合就解开了,只要我们想,就可以方便的编码声明哪些mapper。
接下来就是解决动态寻找mapper的问题了。我们会发现现在我们mapper有哪些仍然是写死的,只是解耦合,搭建好了方便的用一个FactoryBean去构造每个mapper动态代理的架构。那我们怎么实现动态的去识别有哪些mapper,然后自动的生成动态代理呢?
很明显就是扫描吧。我们可以定义一个注解@SwissScan标记在APPConfig上面来指定扫描的包路径是什么。那既然这个注解标记在APPConfig上面了,那我们也就可以把@Import(SwissImportBeanDefinitionRegistrar.class)加到@SwissScan这个注解声明的上面,因为这是我们提供的一套,就没必要重复声明,都放在一起就好,并且他还提供了一个好处,那就是我们可以拿到@MapperScan里面用户定义的扫描路径。那现在我们就可以开始搭建扫描的逻辑了。
在写死的SwissImportBeanDefinitionRegistrar下,我们开始定义:
1 2 3 4 5 6 7 8
| @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(SwissScan.class.getName()); String path = (String) annotationAttributes.get("value"); ... }
|
现在我们要进行包路径下的扫描了。我们完全可以借助Spring已经定义好的扫描器帮我们进行扫描,但是Spring定义好的扫描器只会扫描Bean对象,并不会扫描到接口。所以我们需要对扫描器进行一些改造。
我们先定义一个自定义的扫描器SwissScanner,重写一些方法:(具体Spring底层扫描逻辑详见之后的手写Spring系列)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class SwissScanner extends ClassPathBeanDefinitionScanner {
public SwissScanner(BeanDefinitionRegistry registry) { super(registry); }
@Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isInterface(); }
@Override protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages); for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) { BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition(); beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName()); beanDefinition.setBeanClassName(SwissFactoryBean.class.getName()); } return beanDefinitionHolders; } }
|
这么一来,我们的特定扫描器就构建好了,并且在扫描的同时就已经帮我们把BeanDefinition注册好了。回到ImportBeanDefinitionRegistrar,直接删除原先的逻辑,执行扫描即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(SwissScan.class.getName()); String path = (String) annotationAttributes.get("value");
SwissScanner swissScanner = new SwissScanner(registry); swissScanner.addIncludeFilter(new TypeFilter() { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return true; } }); swissScanner.scan(path); }
|
为什么要添加addIncludeFilter详见之后的手写Spring系列。
到这里,我们构建的集成框架就实现完毕了,这就是mybatis和spring集成的方法。