- 本文要实现的功能
仿照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的时候我们进行了修改.
预期的输出方式是,查询数据库,返回了所有客户的信息.
实现上述的功能,分解成如下几个子问题
- 该接口交给spring管理,生成bean如何实现.
- 生成的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.