spring官方给出了三个拓展点:
a、基于实现BeanPostProcessor。
b、基于BeanFactoryPostProcessor。
c、基于FactoryBean。(如果想要知道这三个拓展点的使用和原理可以留言可肝)
今天我们就使用@Import和FactoryBean实现一个青春mini版的Mybatis,实现从sql到entity。
1、配置MySQL的连接
@Configuration
public class MysqlConfig {
@Bean
public JdbcTemplate jdbcTemplate(final DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
return dataSource;
}
}
不了解@Configuration注解符的可以看这篇文章,现在我们已经能从spring拿到MySQL的连接了。
2、自定义需要到的注解符
@Import(HxlMapperScannerRegistrar.class) //我们用Import和ImportBeanDefinitionRegistrar来加载自己生成的bean
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HxlMapperScan {
String value();
}
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface HxlParam {
String value();
}
@Target(METHOD)
@Retention(RUNTIME)
public @interface HxlQuery {
String value();
}
不了解元注解的看这个,不了解@Import的看这个,我们想要的效果就是在启动类上加了HxlMapperScan就能生效去掉就不生效,升不生效完全由程序员自己决定。
实现ImportBeanDefinitionRegistrar来动态往beanFactory注册bean
public class HxlMapperScannerRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//spring会调用这个方法,importingClassMetadata是注解当中的信息,我们可以利用registry来修改添加和删除BeanDefinition
Set<BeanDefinitionHolder> bds = scan(importingClassMetadata, registry); //先打到需要注册的dao接口
bds.forEach(this::processBeanDefinition); //给这些bd添加beanClass
}
//这个方法用来扫描需要我们自己手动注册的dao接口
private Set<BeanDefinitionHolder> scan(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annotation = fromMap(importingClassMetadata.getAnnotationAttributes(HxlMapperScan.class.getName()));
return ofNullable(annotation).map(a -> scan(annotation, registry)).orElseGet(HashSet::new);
}
private Set<BeanDefinitionHolder> scan(final AnnotationAttributes annotation, final BeanDefinitionRegistry registry) {
String basePackage = annotation.getString("value"); // value里面放的是包的路径,这个地方获得注解里面value里面的值用来扫描
CustomScanner scanner = new CustomScanner(registry); //用register生成一个我们自己的scanner
return scanner.doScan(basePackage);
}
private static class CustomScanner extends ClassPathBeanDefinitionScanner {
public CustomScanner(BeanDefinitionRegistry registry) {
super(registry, false);
}
public Set<BeanDefinitionHolder> doScan(String basePackage) {
super.addIncludeFilter((metadataReader, metadataReaderFactory) -> true); //修改默认的筛选规则,
return super.doScan(basePackage);
}
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
//要修改这个判断规则,因为我们要扫描的是一些接口,原来的规则,要求他是一个具体类或者是一个抽象的类但是要加@lockUp注解这个点想看源码留言可肝
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
}
private void processBeanDefinition(final BeanDefinitionHolder bd) {
//前面扫描的那些bd都是一些接口,我们需要为这些接口提供实现方法,这个时候我们就用到了FactoryBean来扩展spring
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) bd.getBeanDefinition();
beanDefinition.setBeanClass(HxlFactoryBean.class); //把自定义的FactoryBean class文件传进去
//给构造方法添加参数,我们要用HxlFactoryBean给这些接口添加实现也是需要告诉HxlFactoryBean这个接口是什么样子的
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(HxlDao.class.getName());
}
}
实现FactoryBean来给我们的接口添加实现
//这个类不仅实现了FactoryBean还实现了InvocationHandler
public class HxlFactoryBean<T> implements FactoryBean<T>, InvocationHandler {
private final Class<T> clazz;
@Autowired
private JdbcTemplate template; //想要实现查询你要获得一个连接
public HxlFactoryBean(final Class<T> clazz) { //构造函数可以接收传进来的接口
this.clazz = clazz;
}
@Override
public T getObject() {
//使用jdk动态代生成代理对象
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{clazz}, this);
}
@Override
public Class<?> getObjectType() {
return clazz;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
//代理对象的生成其实就是一堆判断然后再调用jdbcTemplate的查询方法
HxlQuery query = method.getAnnotation(HxlQuery.class);
String sql = assembleSql(query.value(), args, method.getParameterAnnotations());
Class<?> returnType = method.getReturnType();
if (returnType.getTypeName().equals("void")) {
template.execute(sql);
return null;
} else if (returnType.getSimpleName().equals("Integer") || returnType.getSimpleName().equals("int")) {
return template.queryForObject(sql, Integer.class);
}
List<?> result = template.query(sql, new BeanPropertyRowMapper<>(returnType));
return result.size() > 0 ? result.get(0) : null;
}
private String assembleSql(String sql, final Object[] args, final Annotation[][] parameterAnnotations) {
//从dao层的方法中组装一个sql
if (args != null) {
for (int i = 0; i < args.length; i++) {
HxlParam param = (HxlParam) parameterAnnotations[i][0];
String value = args[i].toString();
value = args[i].getClass().getSimpleName().equals("String") ? "'" + value + "'" : value;
sql = sql.replace("#{" + param.value() + "}", value);
}
}
return sql;
}
}
好了到现在我们已经实现了一个简陋原始版的Mybatis
4、测试
@HxlMapperScan("com.example.hxl.dao")
public class DemoApplication { //启动类
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@Component
public interface HxlDao { //我们的dao
@HxlQuery("delete from user where 1=1")
void deleteAll();
@HxlQuery("select count(*) from user")
Integer countAll();
@HxlQuery("insert into user values(#{id}, #{name})")
void save(@HxlParam("id") Integer id, @HxlParam("name") String name);
@HxlQuery("select * from user where id = #{id} and name = #{name}")
User findByIdAndName(@HxlParam("id") Integer id, @HxlParam("name") String name);
}
public class User { //entity
private int id;
private String name;
}
@Autowired
private HxlDao hxlDao;
@Test
public void should_hxl_batis() { //测试方法
hxlDao.deleteAll();
System.out.println(hxlDao.countAll());
hxlDao.save(1, "hxl");
System.out.println(hxlDao.findByIdAndName(1, "hxl"));
System.out.println(hxlDao.countAll());
}
测试的结果
好了大功告成,题外话不用纠结hxl这是我名字的缩写,欢迎评论区见。