Import注解
我们知道在spring中Import注解可以实现bean的注入,对于Import注解可以传入的参数其实不只有需要注入的类,一般可以传入以下三种参数
- 普通类(需要注入的类)
- ImportSelect的实现类
- ImportBeanDefinitionRegistrar的实现类
对于第一种对象我们肯定已经很熟悉了,但是对于后面两种对象来说,也许并不常用到,对于第二种在之前的博文中有过介绍,就不再赘述,这里主要是介绍第三种对象。
ImportBeanDefinitionRegistrar
实现这个接口主要可以得到BeanDefinitionRegistry,也就是bean工厂的注册器,可以直接向bean工厂注册bean,准确来说是bd(BeanDefinition)。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
简单模拟mybatis的注解实现
我们知道对于mybatis的注解开发可以直接在接口的方法上加上注解就可以实现sql查询功能,最主要的是可以直接在后面得到这个接口的实现类。
我们是没有实现这个接口的,那么能从spring容器中得到实现类说明肯定是spring和mybatis帮我们去实现它了。我们有那么多接口肯定不是mybatis一个一个去实现的,必然是通过动态生成的。
这里就必须借助一个重要的接口ImportBeanDefinitionRegistrar,实现它就可以拿到bean工厂的注册器了,通过这个注册器就可以动态地向bean工厂中注册bean了,不过注意这里的注册的还是BeanDefinition,也就是bean的描述对象。
同时还需要一个重要的接口的帮助就是FactoryBean,这个接口的作用之前的博文也做过介绍。
主要的功能就是这个类如果注入spring容器中最后从容器中拿到的对象并不是它本身,而是它实现的一个方法的返回的对象。
所以我们可以通过注册这个类并将需要实现的接口通过动态代理的方法返回回来,在动态代理中也可以实现注解上的sql语句。
不过这里还有一个问题就是如何动态获取需要注入的接口,这就需要spring的自动装配来自动实现,这里是通过spring的构造器的自动装配来实现的。
因为spring通过构造器的自动装配是通过参数的名字来实现,所以在创建bd时可以通过更改参数名来实现动态的自动装配。
这里只是简单实现了mybatis的动态注入bean的过程,对于sql语句的查询和包的扫描这里并没有模拟。
自定义select注解
@Retention(RetentionPolicy.RUNTIME)
public @interface MySelect {
public String value();
}
查询的dao接口
public interface MyDao {
@MySelect("select * from mydao")
public void select();
}
ImportBeanDefinitionRegistrar的实现类
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//假设前面做了包的扫描得到了需要实现的接口
//假如已经得到了MyDao这个接口
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyDao.class);
//这里得到了一个普通的bd,但是这还没完,因为没有实现类,所以spring是无法实例化bean的
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) builder.getBeanDefinition();
//这里是一个代理对象
beanDefinition.setBeanClass(MyFactoryBean.class);
//但是如何在spring实例化bean的时候知道需要实例化哪个接口呢
//这里通过设置了构造器的参数名字来使spring能够找到需要实现的接口,注意这里传入的是接口的全名,包括包名
//因为spring自动注入如果是通过构造器来注入的话是根据参数名来注入的
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue("com.zdd.spring.mybatis.MyDao");
//这里第一个参数是beanName,也是初始化时通过上下文对象拿到bean的名字
//第二个参数是需要注册的bd
registry.registerBeanDefinition("myDao",beanDefinition);
}
}
FactoryBean的实现类
public class MyFactoryBean implements FactoryBean , InvocationHandler {
//动态注入的接口
Class clazz;
//到时候接口需要通过构造器传过来
public MyFactoryBean(Class clazz) {
this.clazz = clazz;
}
@Override
public Object getObject() throws Exception {
//真正的实现类
return 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) throws Throwable {
//在这里可以实现对注解的解析和实现
if (method.isAnnotationPresent(MySelect.class)){
//将注解的内容返回出去
//模拟sql查询
return method.getAnnotation(MySelect.class).value();
}
return null;
}
}
注入ImportBeanDefinitionRegistrar的实现类
这里的办法很多,这里直接使用了Import注解,当然也可以通过自定义注解的方式来实现
@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)
public class Springcfg {
调用
就可以通过之前设置的beanName来拿到bean
MyDao myDao = (MyDao) annotationConfigApplicationContext.getBean("myDao");
System.out.println(myDao.select());
当然也可以通过接口的类对象来获取
MyDao myDao = (MyDao) annotationConfigApplicationContext.getBean(MyDao.class);
System.out.println(myDao.select());
结果
select * from mydao