@TOC# Spring系列
记录在程序走的每一步___auth:huf
该篇章讲解架构整合的思路; 讲解该篇章主要是为了将之前的知识点进行一个串联; 有很多接口 我仅仅是介绍了它们的使用;并没有实战场景;所以有很多同学想知道 这些接口 到底在什么场景下使用; 怎么使用; 能达到什么样的效果;例如我们整合Mybatis的时候,怎么去整合的? 如果有疑问的同学;可私信我; 回复不一定及时;请各位同学谅解;
我们学习了Spring 那么多功能 主要围绕是围绕 Bean 做一些事情; 创建Bean 初始化Bean 运行Bean 销毁Bean. 我们Java程序 主要围绕着 对象 构成一个一个的模块 最后组合成庞大的系统; 做一些计算机的处理. 我们架构整合的时候 主要是整合 对象 , 对象的创建; 对象集合的管理; 如果将一个不相关的框架 跟Spring整合; 主要整合其内部的功能集; 也就是对象; 例如 我们要整合Mybatis; 我们应该如何进行整合? 也许 在别的 大神文章里面 我们也能看到 别人是 怎么整合的 复制粘贴之后 我们也能行; 但是这样能给我们技术带来实际上的提升吗? 这篇文章 我就带大家一起手写一个超简易版本 mybatis-spring.jar
1:我们Mybatis 使用流程 大致如下
创建一个流 把配置文件传入
InputStream is = Resources.getResourceAsStream("MybatisConfig.xml");
创建SqlSessionBeanFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
利用sqlSessionBeanFactory 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
通过SqlSession 拿到相对应的Mapper
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
最后执行我们的sql语句;
List list = mapper.selectAll();
2 :我们大致准备一个架子结构;
简简单单的 一个扫描的Config 一个main启动 类 一个Service 一个Mapper
注意这个Mapper 是一个接口
这就是我们平时使用Mapper的时候 最常见的方式; 直接注入Mapper 然后直接调用即可;
# 我们的Mapper 是一个接口 此时 在我们Service 注入了这个接口; 并且进行sql执行; 在Mybatis中 它会使用代理 进行接口实现; 并且最终把该对象的实现类 注入到这里来; 我们现在这里并没做这个类的实现; 现在执行肯定是报错的; 我们该如何把这个接口变成Bean呢? 答:使用FactoryBean 进行对象的创建. 我们知道 Spring内部使用了Cglib代理 所以不能代理接口;
1:我们利用FactoryBean 间接的创建Bean:
/**
* auth :huf
*/
@Component
public class HufFatoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
Object object = Proxy.newProxyInstance(HufFatoryBean.class.getClassLoader(), new Class[]{StudentMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName());
return null;
}
});
return object;
}
@Override
public Class<?> getObjectType() {
return StudentMapper.class;
}
}
这样我们就可以通过FactoryBean 再利用 JDK 代理; 把接口变成对象 并且注入到容器中;
为什么是null 因为我Factory返回出来就是Null;
我们继续扩展; 假设我们有多个Mapper 一个StudentMapper 一个 TeacherMapper … 多个Mapper.那怎么办?
答: 我们把FactoryBean 变成 BeanDefinition 就可以了; 以下是演进展示;
我们先把HufFatoryBean 写活; 通过构造方法传入即将要代理的类Class. 这样 我们就可以只用一个FactoryBean 进行全部Mapper的代理.
/**
* auth :huf
*/
@Component
public class HufFatoryBean implements FactoryBean {
private Class clazz;
public HufFatoryBean(Class clazz) {
this.clazz = clazz;
}
@Override
public Object getObject() throws Exception {
Object object = Proxy.newProxyInstance(HufFatoryBean.class.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName());
return null;
}
});
return object;
}
@Override
public Class<?> getObjectType() {
return clazz;
}
}
然后 我们注册BeanDefinition
第一种方式
/**
* Application启动类;
* auth : huf
*/
public class ApplictionMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ap = new AnnotationConfigApplicationContext();
ap.register(AppConfig.class);
-------------
创建一个beanDefinition
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
传入我们的FactoryBean
beanDefinition.setBeanClass(HufFatoryBean.class);
通过构造方法 把Student传入
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(StudentMapper.class);
注册BeanDefinition 把创建好的 BeanDefinition 传入进去;
ap.registerBeanDefinition("studentMapper",beanDefinition);
-------------------------以下也是一样
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.setBeanClass(HufFatoryBean.class);
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(TeacherMapper.class);
ap.registerBeanDefinition("teacherMapper",beanDefinition1);
启动
ap.refresh();
StudentService studentService = (StudentService) ap.getBean("studentService");
studentService.test();
}
}
这里 同学们应该都理解了吧? 到这里 其实思路就已经出来了; 我们不可能以这种方式去加载BeanDefinition; 以下 我介绍2种加载BeanDefinition的方式; 其实以前说过 再之前的篇章里边; 这里我写一次;
第二种BeanDefinitionRegistryPostProcessor
/**
* auth : huf
*/
public class HufBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
创建一个beanDefinition
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
传入我们的FactoryBean
beanDefinition.setBeanClass(HufFatoryBean.class);
通过构造方法 把Student传入
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(StudentMapper.class);
注册BeanDefinition 把创建好的 BeanDefinition 传入进去;
ap.registerBeanDefinition("studentMapper",beanDefinition);
-------------------------以下也是一样
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.setBeanClass(HufFatoryBean.class);
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(TeacherMapper.class);
ap.registerBeanDefinition("teacherMapper",beanDefinition1);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
这样是否是合理很多? 我们场景再继续往下深入一下; 如果我们之后想加一个Mapper 我们还得再这里写多一个BeanDefinition 是不是又突然觉得不太合理了?
答: 我们可以利用 Scan 扫描的方式 进行Mapper扫描. 凡是再某个包下面的所有接口 我都认为它是Mapper 是否OK? 我们定义一个注解;
现在我们注入了扫描 我们会发现 之前用的BeanDefinitionRegistryPostProcessor 无法拿到我们的配置;我们就引出了另外一个
第三种ImportBeanDefinitionRegistrar
这样 我们在配置文件中 可以通过@Inport(HufImportBeanDefinitionRegistrar.class) Spring捕捉到配置文件中的Import后 就可以调度这个方法 把BeanDefinition 加入容器中去; 这样我们继续改造我们的HufImportBeanDefinitionRegistrar 让他通过扫描的方式得到所有MapperClass 然后通过工厂的方式 生产BeanDefinition 注册到容器中; 以下是改造后的代码:
/**
*
* auth : huf
*/
public class HufImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(HufMapperScan.class.getName());
String path = MapUtils.getString(annotationAttributes,"value");
//Spring 原生扫描的方式 因为我们使用的是接口 所以我们必须继承这个类 并且进行改造
// ClassPathBeanDefinitionScanner scan = new ClassPathBeanDefinitionScanner(registry);
// scan.scan(path);
创建自己的MapperBeanDefinitionScanner
HufMapperBeanDefinitionScanner scanner = new HufMapperBeanDefinitionScanner(registry);
该步骤是为了在扫描其中 如果有@Component 才会加入到容器 默认false 我改成true 就是 不管有没有 都是Bean
scanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
scanner.scan(path);
}
}
HufMapperBeanDefinitionScanner
中间的doScan方法重写 是为了什么? 我们通过path路径扫描到的 是Mapper本身 并不是Factory 现在我们变成Factory 所以要从写Scan 方法 这时候 我们得到的BeanDefinition 就是我们想要的了; 这样我们的扫描组件就写完了
以上就是我们的扫描组件; 但是我们留了一个问题; 我们的对象 Mapper对象 并没有实现我们 sql的执行; 没有访问数据库; 我们继续往下扩展:
我们只需要把之前我们流程所说的Mybatis Sqlsession.getMapper 对象 返回出去; 即可;
改造后代码为:
这样就彻底的整合完毕;实际上 mybatis-spring 就是为我们做了这么一件事; 真正的代码 核心就是这样 但是因为更丰富的功能实现 以及兼容 . 实际代码肯定是比这里更复杂;
总结:
1: Import 注解使用场景
2: FactoryBean 使用场景
3: BeanDefinitionRegistryPostProcessor使用场景;
4:ImportBeanDefinitionRegistrar使用场景
5:ClassPathBeanDefinitionScanner使用场景
6: Beandefinition 的使用场景;