Byte Buddy生成的类对Orika(Javaassist)

时间:2018-04-10 13:11:07

标签: java byte-buddy orika

我使用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类。

1 个答案:

答案 0 :(得分:0)

Javassist可能依赖于定位类文件来完成其工作。对于Byte Buddy,这不一定是可能的,因为类文件被注入,使得类加载器无法使用.getResource API从jar文件中找到类文件。

你试过.load(classLoader, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)吗?此策略保留类文件,以便Javassist可以找到它。