在特定的业务场景中,需要提供一个类似自定义实体的动态对象,并根据此对象生成相应的CRUD Repository。在这种场景中,与正常的domain对象不同,这个对象是在运行时,才定义出来,并产生相应的domain class和相应的repository class类。在业务体系中,需要将在运行时定义的对象重新加载至运行时中,并与普通的domain一样的操作。
如 普通的 AbcRepository ,可以使用如下的代码进行操作
@Autowired private AbcRepository abcRepository; abcRepository.findAll()
而新产生的repository,则可以通过如下的手法进行操作
val repository = (CrudRepository)applicationContext.getBean("newRepository"); repository.findAll();
本文即描述,通过 spring cloud 中自带的 RepositoryConfigurationDelegate, 使用新的自定义ClassLoader, 如何在运行时注册一个新的repository.
前提条件:
类名: com.iflym.repository.NewRepository, 此类通过字节码在运行时生成。实际定义为 NewRepository extends JpaRepository
存放位置: d:/tmp
扩展类加载器
为了数据隔离以及自定义实现,相应新产生的类都通过一个统一的扩展类加载器来完成,即将生成类统一放在一个指定的临时目录(也可以放在其它地方,取决于具体实现). 整个资源加载均通过此扩展ClassLoader来完成,后续在加载resource以及spring查找类时均使用此类来处理. 常规的扩展classLoader均通过继承自 URLClassLoader来处理.
简单定义如下
public class NewClassLoader extends URLClassLoader { public NewClassLoader() { super(new URL[]{new File("d:/temp").toURI().toURL())}, parentClassLoader); } }
扩展资源加载器
spring 体系中, classLoader用于加载类,而 resourceLoader 则用于加载资源。在注册过程中,spring 容器会尝试使用 resourceLoader加载类信息,以获取特定的信息,如 annotationMetadata 信息。因此这里,通过扩展类classLoader,同样定义 扩展resourceLoader
这里直接使用 new DefaultResourceLoader(NewClassLoader.CLASS_LOADER) 来完成.
JPA Repository标准扫描及定义机制
spring cloud中 jpa repository的扫描是通过 @EntityScan 和 @EnableJpaRepositories 来定义,同时 @EnableJpaRepositories 还会定义 用于构建 repository 的 facotryBeanClass, 以及定义出来的repository实现默认由哪个实现类来处理。在注册新的repository时,同样需要 由 @EnableJpaRepositories 标注的接口来描述这些信息,由 spring data 标注的 RepositoryConfigurationSource 需要由 相应的接口来查找meta 注解信息。
简单定义如下
@EnableJpaRepositories(repositoryFactoryBeanClass = NewJpaRepositoryFactoryBean.class, repositoryBaseClass = SimpleJpaRepository.class) public class NewRepositoryConfiguration { }
因为,由标注上并没有定义需要扫描的类信息,因此 spring data 中 AnnotationRepositoryConfigurationSource 可能并不能简单由 注解上拿到需要扫描的类。并且这里也并不适合扫描到并不该扫描的类,以避免重复注册bean的Exception产生。这里通过重写 getCandidates 方法来处理此问题。即通过定义一个新的 AnnotationRepositoryConfigurationSource,以产生自实现时所需要的 beanDefinition。
简单参考如下(以下代码有删减)
public class NewAnnotationRepositoryConfigurationSource extends AnnotationRepositoryConfigurationSource { private String className; public Streamable<BeanDefinition> getCandidates(@Nonnull ResourceLoader loader) { val classResourceName = className.replace('.', '/') + ".class"; val is = loader.getResource(classResourceName).getInputStream(); val classReader = new ClassReader(is); val annotationMetadata = new AnnotationMetadataReadingVisitor(NewClassLoader.CLASS_LOADER); classReader.accept(annotationMetadata, ClassReader.SKIP_DEBUG); val definition = new AnnotatedGenericBeanDefinition(annotationMetadata); return Streamable.of(definition); }); }
以上代码(主要为demo实现),仅返回所需要的定义信息. 并且为完整的包括注解信息的beanDefinition, 这里并不能简单地使用 RootBeanDefinition, 因为相应的类还没有加载,并且也没有相应 annotationMetadata 信息.
JPA Repository 类 构建扩展
通过jpa 扫描出来的类, 仅仅是接口信息,spring data 需要通过此进一步构建出 实现类的beanDefinition. 需要通过 BeanDefinitionBuilder 进一步处理,如注入transactionManager, entityManager 等。在spring data 中,此过程是通过 JpaRepositoryConfigExtension 来完成. 一般情况下, spring data 均能正常地注入相应的信息, 并通过spring 容器 autowire正常处理。
这里的问题在于,在构建 factoryBean JpaRepositoryFactoryBean时,其构造器需要 传递 repositoryInterface 对于默认的spring 容器处理时,这里传递的为接口的字符串形式,在构建对象时再进行从字符串到类的转换。转换时需要进行class.forName加载类,对于这里的扩展类,则直接会class not found Exception. 因此,这里进行调整,将相应的参数直接则默认的字符串调用为已经转换好的class形式。
参考代码如下:
public class NewJpaRepositoryConfigExtension extends JpaRepositoryConfigExtension { @Override public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) { super.postProcess(builder, source); //替代原参数值从 string 到 class,因为构建时无法class.forname, classLoader不一样 val argumentValue = builder.getRawBeanDefinition().getConstructorArgumentValues().getIndexedArgumentValue(0, String.class); String v = (String) Objects.requireNonNull(argumentValue.getValue()); val clazz = ClassUtils.forName(v, NewClassLoader.CLASS_LOADER); builder.getRawBeanDefinition().getConstructorArgumentValues().addIndexedArgumentValue(0, clazz); } }
注册新的repository
准备好上述工作之后,即直接使用 spring data 中的处理类,直接进行注册即可
RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(configurationSource, resourceLoader, environment); delegate.registerRepositoriesIn(registry, extension);
上述的registry和enviroment均为当前运行时spring 容器,而其它参数均根据扩展classLoader和要处理的类进行调整. 其中configurationSource进行 beanDefinition 查找, resourceLoader 进行类资源查找, extension 进行 beanDefinitonBuilder 扩展.
至此,整个类即完成相应的注册工作。在业务代码中,即可以通过 application.getBean(“new”) 拿到已经成功处理好的类,并且与正常bean 一样使用(包括事务等功能均工作正常).
总结
本文总共涉及到的扩展类和原始类如下:
RepositoryConfiguration 标记注解 EnableJpaRepositories
NewAnnotationRepositoryConfigurationSource 对应类 AnnotationRepositoryConfigurationSource
NewJpaRepositoryFactoryBean 对应类 JpaRepositoryFactoryBean
NewClassLoader 对应类 URLClassLoader
NewJpaRepositoryConfigExtension 对应类 JpaRepositoryConfigExtension
转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/201912140001.html
请问下transactionManager和entityManager是如何被动态加载的?
请问有源码吗,目前正需要动态加载JpaRepository,用registerSingleton,结果报错BeanDefinition不存在
请问下有源码可以参考下吗