Registrar究极案例

HeJin大约 3 分钟源码解析bean的注入

目标

我们期望实现类似Mybatis的效果:定义接口,接口上使用指定注解进行标识,然后就能生成对应的动态代理对象装载到容器中。

思路分析

  • 使用扫描器扫描使用了指定注解的接口。
  • 因为是在接口上加了注解,所以直接把BeanDefinition注册是没有作用的。因为BeanDefinition的注册是需要new对象的,接口无法直接new。
  • 封装成另外的BeanDefinition去注册:指定Spring用FactoryBean创建对象。
  • 在FactoryBean中使用动态代理来创建实现类对象。

扫描器修改

实现自己的扫描器,重写方法,符合接口放行:

public class MyClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
    
    public MyClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    public MyClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        super(registry, useDefaultFilters);
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        // 重写方法,符合接口放行
        return beanDefinition.getMetadata().isInterface();
    }
}

自定义注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMapper {
}

BeanDefinition转换后注册

MyMapperRegistrar

public class MyMapperRegistrar implements ImportBeanDefinitionRegistrar {
    public final static Logger logger = LoggerFactory.getLogger(MyMapperRegistrar.class);

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 使用自定义的扫描器
        MyClassPathBeanDefinitionScanner scanner = new MyClassPathBeanDefinitionScanner(registry, false);
        scanner.addIncludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                return metadataReader.getAnnotationMetadata().hasAnnotation(MyMapper.class.getName());
            }
        });
        // 扫描com.mapper包
        Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents("com.mapper");
        // 扫描到BeanDefinition之后进行转换,转换成MyMapperFactoryBean的BeanDefinition进行注册
        for (BeanDefinition beanDefinition : beanDefinitions) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyMapperFactoryBean.class);
            AbstractBeanDefinition factoryBeanDefinition = builder.addConstructorArgValue(beanDefinition.getBeanClassName()).getBeanDefinition();
            registry.registerBeanDefinition(beanDefinition.getBeanClassName(), factoryBeanDefinition);
        }
    }
}

MyMapperFactoryBean:

public class MyMapperFactoryBean implements FactoryBean {
    Logger logger = LoggerFactory.getLogger(MyMapperFactoryBean.class);

    private final Class clazz;

    public MyMapperFactoryBean(Class clazz) {
        this.clazz = clazz;
    }

    @Override
    public Object getObject() throws Exception {
        return Proxy.newProxyInstance(MyMapperFactoryBean.class.getClassLoader(),
                new Class[]{clazz}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if("select".equals(method.getName())){
                            logger.info("[{}]的动态代理对象的select被执行了", clazz.getName());
                        }
                        if("update".equals(method.getName())){
                            logger.info("[{}]的动态代理对象的update被执行了", clazz.getName());
                        }
                        return null;
                    }
                }
        );
    }

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

    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }
}

使用测试

ArticleMapper

@MyMapper
public interface ArticleMapper {
    void select();

    void update();
}

UserMapper

@MyMapper
public interface UserMapper {
    void select();

    void update();
}

BlogMapper

public interface BlogMapper {
    void select();

    void update();
}

主启动类导入注册器MyMapperRegistrar

@SpringBootApplication
@Import(MyMapperRegistrar.class) // 导入Mapper BeanDefinition注册器
public class SpringBootAnalysisApp {
    private static final Logger logger = LoggerFactory.getLogger(SpringBootAnalysisApp.class);

    public static void main(String[] args) {
        ApplicationContext applicationContext = SpringApplication.run(SpringBootAnalysisApp.class, args);
        UserMapper userMapper = applicationContext.getBean(UserMapper.class);
        userMapper.select();
        userMapper.update();
        ArticleMapper articleMapper = applicationContext.getBean(ArticleMapper.class);
        articleMapper.select();
        articleMapper.update();
        BlogMapper blogMapper = applicationContext.getBean(BlogMapper.class);
    }
}

结果:

com.registrar.MyMapperFactoryBean        : [com.mapper.UserMapper]的动态代理对象的select被执行了
com.registrar.MyMapperFactoryBean        : [com.mapper.UserMapper]的动态代理对象的update被执行了
com.registrar.MyMapperFactoryBean        : [com.mapper.ArticleMapper]的动态代理对象的select被执行了
com.registrar.MyMapperFactoryBean        : [com.mapper.ArticleMapper]的动态代理对象的update被执行了
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.mapper.BlogMapper' available

发现加了注解@MyMapper的mapper都被注册到了容器中,并且能够调用其中的方法。没有加注解@MyMapper的BlogMapper没有被扫描。

总结

  • 使用自定义注解@MyMapper来标识接口类。
  • MyMapperFactoryBean通过动态代理进行接口方法拦截,并做相应的业务处理。
  • MyMapperRegistrar通过自定义的扫描器MyClassPathBeanDefinitionScanner扫描指定的包,定义过滤规则,只扫描包含注解@MyMapper的类。将扫描到的接口的BeanDefinition作为参数传给MyMapperFactoryBean,通过动态代理创建代理对象,并将代理对象的BeanDefinition注册到容器中。

上面实现的功能和mybatis的相似。只不过我们的功能比较简单:只是通过自定义注解为接口生成了代理对象,并注册到容器中,动态代理的处理逻辑也只是输出了一条语句。在mybatis中,像扫描包的位置是可以自己配置的,还会解析注解中的参数,解析mapper文件等等。而且当我们调用mybatis接口方法的时候,直接返回的是数据库执行的结果。这些都是mybatis在背后做的大量的工作,对于使用方,只需要定义接口和参数就行了。但是,原理是相同的。mybatis在底层使用动态代理解析接口,然后执行数据库操作,封装结果返回给我们。