Registrar究极案例
大约 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在底层使用动态代理解析接口,然后执行数据库操作,封装结果返回给我们。