前面一篇文章写了
@Import
引入ImportSelector
的原理以及在SpringBoot自动装配技术中的应用:【spring源码】@Import在SpringBoot自动装配技术中的应用
本文讲
@Import
引入ImportBeanDefinitionRegistrar
,以及在Mybatis中的引用,本文会写一个Mybatis
的栗子,后面会把Spring源码的内容写完。
先看看ImportBeanDefinitionRegistrar
这个接口:
public interface ImportBeanDefinitionRegistrar {
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
这个接口中有一个方法,对比ImportSelector
接口中的方法,并没有返回值,但是参数多了一个BeanDefinitionRegistry
,看名称可以知道这是一个BeanDefinition注册器,那么就可以拿着这个注册器为所欲为了嘛!
下面就写一个简易版的mybatis框架,开整开整...
首先自定义一个ImportBeanDefinitionRegistrar
,去实现它的registerBeanDefinitions
:
package importbeandefinitionregistry.registrar;
import importbeandefinitionregistry.factorybean.MyMapperFactoryBean;
import importbeandefinitionregistry.myannotation.MyMapperScan;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义的ImportBeanDefinitionRegistrar
*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar , ResourceLoaderAware {
private static final String VALUE = "value";
private ResourceLoader resourceLoader;
/**
* 提供了注解信息(importingClassMetadata)和一个注册器(registry)
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//取到相关的注解信息(包名),并进行扫描,通过代理构造对象,注册到bean容器中
AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(
importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName()));
if(null!=annotationAttributes){
for(String path : getScanPath(annotationAttributes)){
//通过FactoryBean去动态生成接口的代理对象
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyMapperFactoryBean.class);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
//将接口的全路径 作为参数 传给FactoryBean 生成代理对象
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(path);
//将beanDefinition注册到容器中
registry.registerBeanDefinition(lowerFirst(path.substring(path.lastIndexOf(".")+1)),beanDefinition);
}
}
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* 得到注解中包下的接口全路径
* @param annotationAttributes 注解
* @return 注解中包下的接口全路径
*/
private List<String> getScanPath(AnnotationAttributes annotationAttributes){
List<String> scanPath = new ArrayList<>();
for(String basePackage:annotationAttributes.getStringArray(VALUE)){
ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resolver);
try {
Resource[] resources = resolver.getResources("classpath*:" + basePackage.replace('.', '/') + "/**/*.class");
for(Resource resource:resources){
MetadataReader metadataReader = readerFactory.getMetadataReader(resource);
scanPath.add(metadataReader.getClassMetadata().getClassName());
}
} catch (IOException e) {
e.printStackTrace();
}
}
return scanPath;
}
/**
* 首字母变小写
* @param oldStr 需要转换的string
* @return 转换后的string
*/
private String lowerFirst(String oldStr){
char[] charArray = oldStr.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);
}
}
这个自定义的ImportBeanDefinitionRegistrar
做了一些什么事情呢?主要是通过参数AnnotationMetadata
拿到注解里的参数,即需要扫描的包,然后去扫描这些包。看看自定义的MapperScan
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface MyMapperScan {
String[] value() default {};
}
我们在使用mybatis的时候,都是通过去调用mapper的方法,然后写SQL去查询数据库,但是我们定义的mapper都是接口,而接口怎么能直接调用呢?其实我们调用的mapper不是’接口‘,而是一个代理对象。下面来看看如何对这些mapper生成代理对象的:
package importbeandefinitionregistry.factorybean;
import org.apache.ibatis.annotations.Select;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 自定义的 FactoryBean
*
* @author cy
*/
public class MyMapperFactoryBean implements FactoryBean ,InvocationHandler{
private Class interfaceClass;
public MyMapperFactoryBean(Class interfaceClass){
this.interfaceClass = interfaceClass;
}
/**
*
* @return 返回代理对象
* @throws Exception 异常
*/
@Override
public Object getObject() throws Exception {
return Proxy.newProxyInstance(getClass().getClassLoader(),new Class[]{interfaceClass},this);
}
/**
*
* @return 返回class
*/
@Override
public Class<?> getObjectType() {
return interfaceClass;
}
/**
* 打印出注解中的sql
* @param proxy 代理
* @param method 方法
* @param args 参数
* @return object
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String sql = method.getDeclaredAnnotation(Select.class).value()[0];
//打印sql
System.out.println(sql);
//JDBC去查询数据库,然后返回结果集
return "哈哈哈哈";
}
}
这里用到了FactoryBean这个接口,本文先不对这个接口做讲解,其实这里就是去接受一个class,然后对这个class生成代理对象,并返回。这样我们在自定义的ImportBeanDefinitionRegistrar
中得到的对象就是一个代理对象,而不是接口,然后通过方法里提供的注册器参数,将这个代理对象注册到spring容器中(放到beanDefinitionMap中,在后续的过程中实例化bean,并注册到spring容器中)。
在invoke这个方法里,实际上就是去拿到注解@Select
的参数信息,即SQL,这里只是将它简单的打印出来了。既然这里已经拿到了SQL,我们就可以通过jdbc的方式,去查询数据库,最后将得到的结果返回,最后调用mapper的方法得到的就是你查询到的结果集。
下面贴一下mapper,一个简单的查询语句:
public interface UserMapper {
@Select("select * from user limit 1")
String queryUser();
}
配置类,扫描mapper包:
@Configuration
@MyMapperScan("importbeandefinitionregistry.mapper")
public class MyConfiguration {
}
最后写一下测试类:
public class DataSourceTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
UserMapper userMapper = (UserMapper) context.getBean("userMapper");
String s = userMapper.queryUser();
System.out.println(s);
}
}
执行结果:
可以看到在invoke方法中打印的方法和最后输出的结果—>"哈哈哈哈"
整个目录:
今天写了
@Import
注解引入ImportBeanDefinitionRegistrar
去手写一个简版的mybatis框架,觉得不错的可以自己动手试一试噢。写spring源码的文章到现在已经是第4篇了,因为会把spring中一些比较重要的点单独列出来嘛,后续的文章会写bean的创建过程,觉得不错的可以点赞收藏一下噢,欢迎关注后续的spring源码文章。