@MapperScan简易实现

331 阅读3分钟
  •  本文要实现的功能
仿照Mybatis与spring相结合,自定义注解通过实现指定接口方法数据库的查询.
预期的输入方式如下;

@Sl4j
public class AnnotationRunner {
  public static void main(String[] args) {
    //
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
      context.register(MyConfig.class);
      context.refresh();
      CustomerQuery bean = context.getBean("customerQuery", CustomerQuery.class);
      System.out.println(bean.query());
      
  }
}
只需要关心最后一步 bean.query().该方法其实并未实现,而是在实例化bean的时候我们进行了修改.

预期的输出方式是,查询数据库,返回了所有客户的信息.

实现上述的功能,分解成如下几个子问题
  1. 该接口交给spring管理,生成bean如何实现.
  2. 生成的bean如何增加默认的实现方式,查询数据库.


先把需要的类创建好

public interface CustomerQuery {
  String query();
}

@Configuration
@MyScanner
public class MyConfig {
}


自定的注解创建

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({MyImportSelector.class})
public @interface MyScanner {
}
这里需要新增一个@Import注解.根据spring的官方翻译成中文大概就是 ,相当于一个import 元素在xml中,允许添加config.xml文件/ImportSelector 实现类/ImportBeanDefinitionRegistrar类是一个很常规的component classes.它的作用就是在BeanDefinition放入map的时候对BeanDefinition进行处理.处理具体的实现就是通过MyImportSelector实现的.

这里需要穿插一个spring中bean的创建过程,只有了解该过程才能实现动态创建bean.在创建bean之前,有一个BeanDefinition,描述bean,然后根据这个描述创建bean.所有的BeanDefinition 都放在了一个map中,k是name,v是BeanDefinition.

由此引出MyBenDefinitionRegistrar,根据上面@Import的解释,该类需要实现ImportBeanDefinitionRegistrar.这样该类就会被spring调用.从而实现自定义bean的创建.

public class MyBenDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  @Override
  public void registerBeanDefinitions(
      AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(CustomerQuery.class);
    GenericBeanDefinition beanDefinition = (GenericBeanDefinition) bdb.getBeanDefinition();
    beanDefinition
        .getConstructorArgumentValues()
        .addGenericArgumentValue(beanDefinition.getBeanClassName());
//    System.out.println("---->" + beanDefinition.getBeanClassName());
    beanDefinition.setBeanClass(MyFactoryBean.class);
    registry.registerBeanDefinition("customerQuery",beanDefinition);
  }
}

BeanDefinitionRegistry:根据源码的注释,中文意思就是.这个接口包含了Beandefinition构建信息的类,它负责把对应BeanDefinition放入map中,这个过程称之为Registry.
BeanDefinitionBuilder:builder BeanDefinition, 通过调用build方法,生成各种BeanDefinition
/这里需要扩展一下,BeanDefinition有很多种,可以查看BeanDefinition的实现类/
此时需要修改这个bean 的定义,使得通过name获取的类,是我们所指定的.设置beanClassName.这里的name其实是CustomerQuery.
beanDefinition.setBeanClass(MyFactoryBean.class); 修改了对应的BeanClass,这里是我们生成的工厂类,该类是实际是实现类.暂不讨论,放在下面细说.
registry.registerBeanDefinition("customerQuery",beanDefinition);调用注册方法.


MyFactoryBean的具体实现

public class MyFactoryBean<T> implements FactoryBean {
  private Class clazz;


  public MyFactoryBean(Class<T> clazz) {
    this.clazz = clazz;
  }

  @Override
  public Object getObject() throws Exception {
    return Proxy.newProxyInstance(
        this.getClass().getClassLoader(),
        new Class[] {CustomerQuery.class},
        (proxy, method, args) -> queryCustomer());
  }

  @Override
  public Class<?> getObjectType() {
    return clazz;
  }

  public String queryCustomer(){
      String name = null;
      try {
          Statement statement = HikariCPDataSource.getConnection().createStatement();
          ResultSet resultSet = statement.executeQuery("select  name from customer;");
resultSet.next();
          name = resultSet.getString(1);
      } catch (SQLException e) {
          e.printStackTrace();
      }
      return  name;
  }

}

自定义一个FactoryBean,当实例化bean的时候调用getObject,然后我们在这里做了一个代理类.


新建对应的连接数据库的类DataSource

public class DataSource {
  private static HikariConfig config;
  private static HikariDataSource dataSource;
static  {
    config = new HikariConfig();
    config.setJdbcUrl("jdbc:mysql://localhost:3306/test?serverTimezone=UTC");
    config.setDriverClassName("com.mysql.jdbc.Driver");
    config.setUsername("root");
    config.setPassword("admin123");
    dataSource = new HikariDataSource(config);
  }
  public static Connection getConnection() {
    Connection connection = null;
    try {
      connection = dataSource.getConnection();
    } catch (SQLException e) {
      e.printStackTrace();
    }
    return connection;
  }
}
执行开头的方法,查看结果是否符合预期.


如果真有人需要,会考虑把代码上传git.