我使用Byte Buddy在Spring Boot应用程序中生成一些DTO类。我还使用Orika映射器库将实体映射到DTO类或从DTO类映射。该库使用另一个运行时代码生成工具来生成映射器类,即Javassist。
我的问题是,应该映射到Byte Buddy生成的类的Orika映射器无法找到它们。此line in the Orika JavasssistCompilerStrategy class会抛出异常。
我不确定这里发生了什么,因为Class<?> type
参数不为null。以下是我的DTO类的生成方式:
final ClassLoader classLoader = getClass().getClassLoader();
// This object holds the information required to generate classes
// for a use case (it also contains information about a generated
// @Repository, @Service and @RestController)
final DynamicBeansDefinition def = ...
// Create the DTO class from the entity class
final Class<?> entityClass = ClassUtils.forName(def.getEntityClassName(), classLoader);
final Builder<BaseDto> dtoBuilder =
new ByteBuddy().subclass(BaseDto.class).name(def.getDtoClassName());
// Copy all the entity class properties (excluding inherited fields),
// adding @JsonView(Views.Public.class) on each field.
final Field[] fields = entityClass.getDeclaredFields();
for (final Field field : fields) {
dtoBuilder
.defineProperty(field.getName(), field.getType())
.annotateField(
AnnotationDescription.Builder.ofType(JsonView.class)
.defineTypeArray("value", Views.Public.class)
.build());
}
final Class<?> dtoClass = dtoBuilder.make().load(classLoader).getLoaded();
我尝试(但不是密集)为Orika创建一个ByteBuddyCompilerStrategy,这将是一个很好的和干净的解决方案,但我无法从它们的字符串表示创建字段和方法,就像在{{3}中完成的那样}。 Orika在Javaassist类中保存一个生成的类定义,该类仅包含字段和方法的源代码(字符串表示)。
修改
这是(非动态)基类和接口的快速表示。这将是一个很长的帖子,但也许它可以在将来帮助其他人:
@MappedSuperclass
public class BaseEntity {
@Id private Long id;
@Version private Long version;
// Getters and setters omitted for brevity
}
@Entity
public class SomeEntityA extends BaseEntity {
// Fields, getters and setters omitted for brevity
}
@Entity
public class SomeEntityB extends BaseEntity {
// Fields, getters and setters omitted for brevity
}
public class BaseDto {
@JsonView(Views.Summary.class)
private Long id;
@JsonView(Views.Summary.class)
private Long version;
// Getters and setters omitted for brevity
}
@NoRepositoryBean
public interface BaseRepository<E extends BaseEntity> extends JpaRepository<E, Long> {
// Methods omitted for brevity
}
@Repository
public interface SomeRepositoryA extends BaseRepository<SomeEntityA> {}
@Repository
public interface SomeRepositoryB extends BaseRepository<SomeEntityB> {}
public interface BaseService<E extends BaseEntity, D extends BaseDto> {
// Methods omitted for brevity
}
public class BaseMapper<E extends BaseEntity, D extends BaseDto> {
private SomeRepositoryA someRepositoryA;
private SomeRepositoryB someRepositoryB;
protected BaseMapper(SomeRepositoryA someRepositoryA, SomeRepositoryB someRepositoryB) {
super();
this.someRepositoryA = someRepositoryA;
this.someRepositoryB = someRepositoryB;
}
// Implementation omitted for brevity
}
public class BaseServiceImpl<E extends BaseEntity, D extends BaseDto>
implements BaseService<E, D> {
private BaseRepository<E> repository;
private SomeRepositoryA someRepositoryA;
protected BaseServiceImpl(BaseRepository<E> repository, SomeRepositoryA someRepositoryA) {
super();
this.repository = repository;
this.someRepositoryA = someRepositoryA;
}
// Implementation omitted for brevity
}
public class BaseController<E extends BaseEntity, D extends BaseDto> {
private BaseService<E, D> service;
protected BaseController(final BaseService<E, D> service) {
super();
this.service = service;
}
// Implementation omitted for brevity
}
现在,我希望能够从实体类生成DTO,Repository,Mapper,Service声明,Service实现和RestController。例如,从中:
@Entity
public class FooEntityA extends BaseEntity {
@Column private String columnA;
// Getters and setters omitted for brevity
}
我想生成这个:
public class FooDtoA extends BaseDto {
@JsonView(Views.Public.class)
private String columnA;
// Getters and setters omitted for brevity
}
@Repository(value = "fooRepositoryA")
public interface FooRepositoryA extends BaseRepository<FooEntityA> {
// WILL ALWAYS BE EMPTY, everything is defined in the base interface
}
@Component(value = "fooMapperA")
public class FooMapperA extends BaseMapper<FooEntityA, FooDtoA> {
public FooMapperA(SomeRepositoryA someRepositoryA, SomeRepositoryB someRepositoryB) {
super(someRepositoryA, someRepositoryB);
}
// WILL ALWAYS BE EMPTY, everything is defined in the base class
}
public interface FooServiceA extends BaseService<FooEntityA, FooDtoA> {
// WILL ALWAYS BE EMPTY, everything is defined in the base interface
}
@Service(value = "fooServiceAImpl")
public class ServiceAImpl extends BaseServiceImpl<FooEntityA, FooDtoA> implements FooServiceA {
public ServiceAImpl(
@Autowired @Qualifier(value = "fooRepositoryA") BaseRepository<FooEntityA> repository,
SomeRepositoryA someRepositoryA) {
super(repository, someRepositoryA);
}
// WILL ALWAYS BE EMPTY, everything is defined in the base class
}
@RestController(value = "fooControllerA")
@RequestMapping(path = "fooPathA")
public class FooControllerA extends BaseController<FooEntityA, FooDtoA> {
public FooControllerA(
@Autowired @Qualifier(value = "fooServiceAImpl") BaseService<FooEntityA, FooDtoA> service) {
super(service);
}
// WILL ALWAYS BE EMPTY, everything is defined in the base class
}
这是尝试这样做的方法(随意指出可能更好的部分):
public void createBeans(
final ConfigurableListableBeanFactory beanFactory, final BeansDefinition def) {
final ClassLoader classLoader = getClass().getClassLoader();
try {
// Create the DTO class from the entity class
final Class<?> entityClass = ClassUtils.forName(def.getEntityClassName(), classLoader);
// final Class<?> dtoClass = ClassUtils.forName(def.getDtoClassName(), classLoader);
final Builder<BaseDto> dtoBuilder =
new ByteBuddy().subclass(BaseDto.class).name(def.getDtoClassName());
// Copy all the entity class properties, adding
// @JsonView(Views.Public.class) on each field.
final Field[] fields = entityClass.getDeclaredFields();
for (final Field field : fields) {
dtoBuilder
.defineProperty(field.getName(), field.getType())
.annotateField(
AnnotationDescription.Builder.ofType(JsonView.class)
.defineTypeArray("value", Views.Public.class)
.build());
}
final Class<?> dtoClass =
dtoBuilder
.make()
// .load(classLoader)
.load(classLoader, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
.getLoaded();
// Create the repository
new ByteBuddy()
.makeInterface(
TypeDescription.Generic.Builder.parameterizedType(BaseRepository.class, entityClass)
.build())
.name(def.getRepositoryClassName())
.annotateType(
AnnotationDescription.Builder.ofType(Repository.class)
.define("value", def.getRepositoryBeanName())
.build())
.make()
// .load(classLoader)
.load(classLoader, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
.getLoaded();
// This is an ugly hack in order to create the same BeanDefinition for
// our created Repository as if it was auto configured by spring
// boot. There is no other way (AFAIK) to do this, since Spring
// won't scan the dynamically created classes. See:
// https://stackoverflow.com/questions/37402782
// So the hack is to create a RootBeanDefinition from a known
// existing repository RootBeanDefinition, and then to change
// the argument that will be used to create the
// JpaRepositoryFactoryBean.
final RootBeanDefinition repositoryABeanDefinition =
(RootBeanDefinition) beanFactory.getBeanDefinition("someRepositoryA");
final RootBeanDefinition repositoryBeanDefinition =
new RootBeanDefinition(repositoryABeanDefinition);
repositoryBeanDefinition.getConstructorArgumentValues().clear();
repositoryBeanDefinition
.getConstructorArgumentValues()
.addIndexedArgumentValue(0, def.getRepositoryClassName());
((DefaultListableBeanFactory) beanFactory)
.registerBeanDefinition(def.getRepositoryBeanName(), repositoryBeanDefinition);
// Create the service mapper
final Class<?> mapperClass =
new ByteBuddy()
.subclass(
TypeDescription.Generic.Builder.parameterizedType(
BaseMapper.class, entityClass, dtoClass)
.build(),
ConstructorStrategy.Default.NO_CONSTRUCTORS)
.name(def.getMapperClassName())
.annotateType(
AnnotationDescription.Builder.ofType(Component.class)
.define("value", def.getMapperBeanName())
.build())
.defineConstructor(Modifier.PUBLIC)
.withParameters(SomeRepositoryA.class, SomeRepositoryB.class)
.intercept(
MethodCall.invoke(
BaseMapper.class.getDeclaredConstructor(
SomeRepositoryA.class, SomeRepositoryB.class))
.withArgument(0, 1))
.make()
// .load(classLoader)
.load(classLoader, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
.getLoaded();
final BeanDefinition mapperBeanDefinition = new RootBeanDefinition(mapperClass);
((DefaultListableBeanFactory) beanFactory)
.registerBeanDefinition(def.getMapperBeanName(), mapperBeanDefinition);
// Create the service interface
final Class<?> serviceInterfaceClass =
new ByteBuddy()
.makeInterface(
TypeDescription.Generic.Builder.parameterizedType(
BaseService.class, entityClass, dtoClass)
.build())
.name(def.getServiceInterfaceClassName())
.make()
//.load(classLoader)
.load(classLoader, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
.getLoaded();
// Create the service implementation
final Class<?> serviceImplClass =
new ByteBuddy()
.subclass(
TypeDescription.Generic.Builder.parameterizedType(
BaseServiceImpl.class, entityClass, dtoClass)
.build(),
ConstructorStrategy.Default.NO_CONSTRUCTORS)
.name(def.getServiceImplementationClassName())
.implement(serviceInterfaceClass)
.annotateType(
AnnotationDescription.Builder.ofType(Service.class)
.define("value", def.getServiceBeanName())
.build())
.defineConstructor(Modifier.PUBLIC)
.withParameter(BaseRepository.class)
.annotateParameter(
AnnotationDescription.Builder.ofType(Autowired.class).build(),
AnnotationDescription.Builder.ofType(Qualifier.class)
.define("value", def.getRepositoryBeanName())
.build())
.withParameter(SomeRepositoryA.class)
.intercept(
MethodCall.invoke(
BaseServiceImpl.class.getDeclaredConstructor(
BaseRepository.class, SomeRepositoryA.class))
.withArgument(0, 1))
.make()
//.load(classLoader)
.load(classLoader, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
.getLoaded();
final BeanDefinition serviceBeanDefinition = new RootBeanDefinition(serviceImplClass);
((DefaultListableBeanFactory) beanFactory)
.registerBeanDefinition(def.getServiceBeanName(), serviceBeanDefinition);
// Create the rest controller
final Class<?> controllerClass =
new ByteBuddy()
.subclass(
TypeDescription.Generic.Builder.parameterizedType(
BaseController.class, entityClass, dtoClass)
.build(),
ConstructorStrategy.Default.NO_CONSTRUCTORS)
.name(def.getControllerClassName())
.annotateType(
AnnotationDescription.Builder.ofType(RestController.class)
.define("value", def.getControllerBeanName())
.build(),
AnnotationDescription.Builder.ofType(RequestMapping.class)
.defineArray("value", def.getControllerPath())
.build())
.defineConstructor(Modifier.PUBLIC)
.withParameter(BaseService.class)
.annotateParameter(
AnnotationDescription.Builder.ofType(Autowired.class).build(),
AnnotationDescription.Builder.ofType(Qualifier.class)
.define("value", def.getServiceBeanName())
.build())
.intercept(
MethodCall.invoke(
BaseController.class.getDeclaredConstructor(BaseService.class))
.withArgument(0))
.make()
//.load(classLoader)
.load(classLoader, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
.getLoaded();
final BeanDefinition controllerBeanDefinition = new RootBeanDefinition(controllerClass);
((DefaultListableBeanFactory) beanFactory)
.registerBeanDefinition(def.getControllerBeanName(), controllerBeanDefinition);
} catch (Exception ex) {
throw new FatalBeanException("Unable to create beans for entity " + def.getEntityName(), ex);
}
}
现在,当我使用load(classLoader)
时,我遇到了Orika / Javassist问题。当我使用load(classLoader, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
方式时,make()
调用在创建服务实现类时抛出异常:
java.lang.TypeNotPresentException: Type com.example.FooDtoA not present
也许它与SourceCodeContext有关?现在我的解决方案是以与Entity类相同的方式创建DTO类,并使用load(classLoader)
。但我想以与所有其他类相同的方式生成DTO类。
答案 0 :(得分:0)
Javassist可能依赖于定位类文件来完成其工作。对于Byte Buddy,这不一定是可能的,因为类文件被注入,使得类加载器无法使用.getResource
API从jar文件中找到类文件。
你试过.load(classLoader, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
吗?此策略保留类文件,以便Javassist可以找到它。