Issue
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.
Solution
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.
Answered By - Rafael Winterhalter
Answer Checked By - Timothy Miller (JavaFixing Admin)