文章目录
1、回顾
前面连续开了两篇,写了Spring容器初始化的调用链。
第一篇主要写了,AnnotationConfigApplicationContext
对象创建完成。AnnotatedBeanDefinitionReader(this)
这个对象会在DefaultListableBeanFactory
放入6个BD(BeanDefinition的简称
)。其中有一个特别重要的BeanDefinitionRegistryPostProcessor
有源码基础的老哥应该知道这个类有多重要。
第二篇主要写的,配置类的注册,以及DefaultListableBeanFactory
的初始化。其中有一个非常重要的方法invokeBeanFactoryPostProcessors
这个方法调用链实在太长了。有机会会专门开一篇来,针对它来写。
2、目标—手写mybatis
今天主要是利用前面讲到的一些知识,手写一个mybatis部分代码。
3、环境准备!
1、当然是先建一个工程,
2、引入依赖。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
3、添加配置文件信息。(这些信息大家基本上开发中都是用烂了的)
@Configuration
@ComponentScan(value = "com.spring")
@MapperScan(value = "com.spring")
public class AppConfig {
@Bean
@Autowired
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean
public DataSource dataSource(){
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setUsername("root");
driverManagerDataSource.setPassword("root");
driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/mydb?characterEncoding=utf8&verifyServerCertificate=false&useSSL=true");
driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
return driverManagerDataSource;
}
}
4、添加一个 Mapper接口(这个大家也都懂无须多说吧。)
public interface IndexDao {
@Select(value = "select * from user where name like '%${name}%'")
public List<Map<Integer,String>> list(@Param(value = "name") String name);
}
5、添加一个Service方法,这个也不解释。
@Component
public class IndexService {
@Autowired
IndexDao indexDao;
public void list(String str){
System.out.println(indexDao.list(str));
System.out.println(str);
}
}
4、一阶段测试。
执行测试类,输出数据库要查的信息,这个大家也都懂吧。
public static void main(String[] args) {
org.springframework.context.annotation.AnnotationConfigApplicationContext annotationConfigApplicationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
IndexService bean = annotationConfigApplicationContext.getBean(IndexService.class);
bean.list("兵哥");
}
------------------------------控制台输出----------------------------------
[{name=兵哥, id=1}]
兵哥
5、疑问?IndexDao什么时候实例化的?
不知道大伙,有没有疑问,我都没实例化,IndexDao这个Mapper spring是怎么注入的,怎么拿到这个对象,且执行这个方法的?
IndexDao这是一个接口,也没办法实例化。我加个注解。然后引到Service中,凭什么就能执行了?
我们就在配置类,加了这一样注解。@MapperScan(value = "com.spring")
啊哈,它就可以执行了。
我们去掉这一样 它就报错了。
1、先来看一下这个注解它干了什么
2、它@Import(MapperScannerRegistrar.class) import了一个MapperScannerRegistrar
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
3、再来看 MapperScannerRegistrar
实现了 ImportBeanDefinitionRegistrar
诶,这不就是上一篇写到的。导入一个BeanDefinition
注册器
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
6、(利用)ImportBeanDefinitionRegistrar,手写mybatis
mybatis 源码比较简单,大家可以自己去看。今天这一篇,源码肯定也是写不了。
接上面可以看到mybatis实现了ImportBeanDefinitionRegistrar
这个类完成 mybatis的功能。
代码如下,我自己写一个类,实现它。然后完成包扫描。然后拿到我们要去实例化的接口信息。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//第一步 先完成扫描部分
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName()));
//获取到扫描的包
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
System.out.println("拿到注解中的包"+pkg);
basePackages.add(pkg);
}
}
//拿到代码的当前路劲
String rootPath = this.getClass().getResource("/").getPath();
String basePackagePath =basePackages.get(0).replaceAll("\\.","\\\\");
List<Class> classes = new ArrayList<>();
//获取到包路劲下的所有类
Scanner(basePackagePath, rootPath,classes);
for (Class aClass : classes) {
//第一步得到一个BeanDefinition
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(aClass);
AbstractBeanDefinition definition = beanDefinitionBuilder.getBeanDefinition();
//第二步为MyFactoryBean生成一个构造方法 并且设置MyFactoryBean信息
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
definition.setBeanClass(MyFactoryBean.class);
//注册一个BeanDefinition
registry.registerBeanDefinition(Introspector.decapitalize(aClass.getSimpleName()),definition);
}
}
public List<Class> Scanner(String basePackagePath ,String rootPath, List<Class> classes){
//当前路劲加上我们 传过来的包名,就是我们要扫描的类信息
File file = new File(rootPath+"//"+basePackagePath);
String names[]=file.list();
for (String name : names) {
if (name.endsWith(".class")) {
name = name.replaceAll(".class", "");
try {
String ba =basePackagePath.replaceAll("\\\\","\\.");
System.out.println("拿到类的全称"+ba + "." + name);
Class aClass = Class.forName(ba + "." + name);
// //判断是否是一个接口类,且在Dao下
if (aClass.isInterface() && ba.contains("dao")) {
classes.add(aClass);
}
} catch (Exception e) {
e.printStackTrace();
}
}else {
Scanner(basePackagePath+"\\"+name,rootPath,classes);
}
}
return classes;
}
}
2、我们只需要将上面,扫描出来的类。然后传过来的,字节码生成一个代理对象。
3、MyFactoryBean 实现FactoryBean 至于FactoryBean的作用就不说了。前面的博客已有写到,大家可以去翻一番。mybatis框架就是利用这两个东西来完成与spring整合的。
3、然后让它执行这个代理方法。
4、代理方法中拿到注解。并且拿到sql语句。。
public class MyFactoryBean implements FactoryBean,InvocationHandler {
Class classzz;
public MyFactoryBean( Class classzz){
this.classzz = classzz;
}
@Override
public Object getObject() throws Exception {
//反射,拿到传入进来的字节码 生成代理对象。
Class [] classes = new Class[]{classzz};
Object dao = Proxy.newProxyInstance(this.getClass().getClassLoader(),classes,this);
return dao;
}
@Override
public Class<?> getObjectType() {
return classzz;
}
/**
* 代理对象要执行的代理方法。
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("aaaa");
//获取注解上的sql语句
Class<?>[] classes = proxy.getClass().getInterfaces();
for (Class<?> aClass : classes) {
for (Object arg : args) {
Method classMethod = aClass.getMethod(method.getName(), arg.getClass());
String[] value = classMethod.getDeclaredAnnotation(Select.class).value();
for (String s : value) {
System.out.println(s);
}
}
}
return null;
}
}
6、为了B格,我们自己写一个自定义的注解类。
注解类中然后,导入我们写的那个 MyImportBeanDefinitionRegistrar
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface MyMapperScan {
String[] value() default {};
}
7、替换成,我们自己写的这个 注解。传入要扫描的包。B格瞬间就上来了。有没有
@Configuration
@ComponentScan(value = "com.spring")
//@MapperScan(value = "com.spring")
@MyMapperScan(value = "com.spring")
public class AppConfig {
7、测试 mybatis低配版
如下图所示,sql语句是不是已经拿到。那我们要怎么让sql语句执行呢?
public static void main(String[] args) {
org.springframework.context.annotation.AnnotationConfigApplicationContext annotationConfigApplicationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
IndexService bean = annotationConfigApplicationContext.getBean(IndexService.class);
bean.list("兵哥");
}
------------------------------控制台输出----------------------------------
aaaa
select * from user where name like '%${name}%'
null
兵哥
剩下的步骤。很简单。
第一步:替换参数。得到一条可以执行的sql语句
怎么替换,无须多说,太简单
第二步:执行sql语句 获取结果。这个也不多说。用过 JDBC c3p0这种基础框架的大佬。基本都懂!