Home > Net >  Register Spring reactive repository using Byte Buddy
Register Spring reactive repository using Byte Buddy

Time:10-06

I want to create and register at runtime using Byte Buddy a trio of three entities (a class for entity/table, a repository interface and controller class)

Class<?> tableClass =
    new ByteBuddy()
        .subclass(Object.class, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR)
        .name(className)
        .annotateType(getTableAnnotation(tableName))
        .defineField("id", UUID.class, Visibility.PRIVATE)
        .annotateField(getIdAnnotation())
        .defineMethod("getId", UUID.class, Visibility.PUBLIC)
        .intercept(FieldAccessor.ofBeanProperty())
        .defineMethod("setId", void.class, Visibility.PUBLIC)
        .withParameter(UUID.class)
        .intercept(FieldAccessor.ofBeanProperty());
        .defineField("name", String.class, Visibility.PRIVATE)
        .defineMethod("getName", String.class, Visibility.PUBLIC)
        .intercept(FieldAccessor.ofBeanProperty())
        .defineMethod("setName", void.class, Visibility.PUBLIC)
        .withParameter(String.class)
        .intercept(FieldAccessor.ofBeanProperty())
        .make()
        .load(getClass().getClassLoader())
        .getLoaded();

TypeDescription.Generic repositoryTypeDescription =
    TypeDescription.Generic.Builder.parameterizedType(
            R2dbcRepository.class, tableClass, UUID.class)

Class<? extends Object> repositoryClass =
    new ByteBuddy()
        .makeInterface(repositoryTypeDescription)
        .name(tableClass.getCanonicalName()   "Repository")
        .make()
        .load(tableClass.getClassLoader())
        .getLoaded();

I have also a service class that does all bean registration

@Service
public class BeanRegisterService {

  @Autowired private BeanFactory beanFactory;
  @Autowired private R2dbcEntityTemplate entityTemplate;

  public <T> void registerBean(String beanName, T bean) {
    ((ConfigurableBeanFactory) beanFactory).registerSingleton(beanName, bean);
  }

  public <T> void registerBeanDefinition(String beanName, Class<T> rootBeanClass) {
    BeanDefinitionBuilder beanDefinitionBuilder =
        BeanDefinitionBuilder.rootBeanDefinition(rootBeanClass);
    ((DefaultListableBeanFactory) beanFactory)
        .registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
  }

  public <T> void registerRepositoryBeanDefinition(
      String entityBean, Class<T> repositoryInterface) {
    BeanDefinitionBuilder beanDefinitionBuilder =
        BeanDefinitionBuilder.rootBeanDefinition(R2dbcRepositoryFactoryBean.class)
            .addConstructorArgValue(repositoryInterface)
            .addPropertyValue("databaseClient", entityTemplate.getDatabaseClient())
            .addPropertyValue("dataAccessStrategy", entityTemplate.getDataAccessStrategy());

    ((DefaultListableBeanFactory) beanFactory)
        .registerBeanDefinition(
            repositoryInterface.getSimpleName(), beanDefinitionBuilder.getBeanDefinition());
  }
}
beanService.registerBeanDefinition(tableClass.getSimpleName(), tableClass);
beanService.registerRepositoryBeanDefinition(tableClass.getSimpleName(), repositoryClass);

When I try to gain access to the repository bean via ApplicationContext I get an error: MyRepository referenced from a method is not visible from class loader.

Caused by: java.lang.IllegalArgumentException: com.example.MyRepository referenced from a method is not visible from class loader
        at java.base/java.lang.reflect.Proxy$ProxyBuilder.ensureVisible(Proxy.java:858) ~[na:na]
        at java.base/java.lang.reflect.Proxy$ProxyBuilder.validateProxyInterfaces(Proxy.java:681) ~[na:na]
        at java.base/java.lang.reflect.Proxy$ProxyBuilder.<init>(Proxy.java:627) ~[na:na]
        at java.base/java.lang.reflect.Proxy.lambda$getProxyConstructor$1(Proxy.java:426) ~[na:na]
        at java.base/jdk.internal.loader.AbstractClassLoaderValue$Memoizer.get(AbstractClassLoaderValue.java:329) ~[na:na]
        at java.base/jdk.internal.loader.AbstractClassLoaderValue.computeIfAbsent(AbstractClassLoaderValue.java:205) ~[na:na]
        at java.base/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:424) ~[na:na]
        at java.base/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1006) ~[na:na]
        at org.springframework.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:126) ~[spring-aop-5.3.22.jar:5.3.22]
        at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[spring-aop-5.3.22.jar:5.3.22]
        at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:371) ~[spring-data-commons-2.7.2.jar:2.7.2]
        at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:323) ~[spring-data-commons-2.7.2.jar:2.7.2]
        at org.springframework.data.util.Lazy.getNullable(Lazy.java:231) ~[spring-data-commons-2.7.2.jar:2.7.2]
        at org.springframework.data.util.Lazy.get(Lazy.java:115) ~[spring-data-commons-2.7.2.jar:2.7.2]
        at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:329) ~[spring-data-commons-2.7.2.jar:2.7.2]
        at org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactoryBean.afterPropertiesSet(R2dbcRepositoryFactoryBean.java:179) ~[spring-data-r2dbc-1.5.2.jar:1.5.2]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863) ~[spring-beans-5.3.22.jar:5.3.22]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800) ~[spring-beans-5.3.22.jar:5.3.22]
        ... 15 common frames omitted

The table class has a more dynamic definition than above example and it is not known at build time; so all the classes must be generated at runtime on demand.

What am I missing?

My code is based on answers from:

Byte Buddy Runtime generation for spring boot JPA repository classes

How to register custom JPA interface using bytebuddy

Edit:

If I load repositoryClass with this.getClass() it doesn't make it to ProxyBuilder. It crashes way sooner with ClassNotFound. Switching ClassLoadingStrategy to persistent does not help either. On the other hand tableClass bean gets loaded without any issues.

CodePudding user response:

You probably need to inject the class into the targeted class loader rather then creating a new one (which is the default). So instead of:

load(getClass().getClassLoader())

You'd use:

load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECT)

For Java 9 , you'd rather use ClassLoadingStrategy.UsingLookup. It has a factory that would work on either JVM version.

  • Related