java - Spring HATEOAS HalResourcesSerializer 未找到默认构造函数

标签 java spring spring-boot spring-data-rest spring-hateoas

我正在使用 Spring Boot 2、Spring Data REST、Spring HATEOAS 创建一个 Spring REST 应用。

我创建了这个 Controller :

@Api(tags = "City Entity")
@RepositoryRestController
@RequestMapping(path = "/api/v1")
@PreAuthorize("isAuthenticated()")
public class CityController {

    @Autowired
    private LocalValidatorFactoryBean validator;

    @Autowired
    private PagedBeanResourceAssembler<City> pagedBeanResourceAssembler;

    @Autowired
    private CityService cityService;


    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addValidators(validator);
    }

    @GetMapping(path = "/cities/search/autocomplete")
    public ResponseEntity<?> autocomplete(@RequestParam(name = "city") String city, @RequestParam(name = "country", required = false) String country, Pageable pageable, Locale locale) {
        return new ResponseEntity<>(pagedBeanResourceAssembler.toResource(cityService.autocomplete(city, country, pageable)), HttpStatus.OK);
    }

}

服务方式为:

@Transactional(readOnly = true)
    public Page<City> autocomplete(String text, String country, Pageable pageable) {
        //my logic
        return elasticSearchManager.search(ElasticSearchUtil.getIndexName(City.class), null, City.class, filters, null, pageable);
    }

如您所见,City bean 未存储在数据库中。事实上,bean 是:

public class City implements Persistable<Long> {

    private Long id;

    @NotBlank
    private String name;

    private String district;

    private String region;

    private String zipCode;

    @NotNull
    @Size(min = 2, max = 2)
    private String country;
}

最后这是我的 PagedBeanResourceAssembler:

@Component
public class PagedBeanResourceAssembler<T> implements ResourceAssembler<Page<T>, PagedResources<T>> {

    @Autowired
    private EntityLinks entityLinks;

    @Override
    public PagedResources<T> toResource(Page<T> page) {
        PagedResources<T> pagedResources = new PagedResources<T>(page.getContent(), asPageMetadata(page));
        return pagedResources;
    }

   private PagedResources.PageMetadata asPageMetadata(Page<?> page) {
        Assert.notNull(page, "Page must not be null!");
        return new PagedResources.PageMetadata(page.getSize(), page.getNumber(), page.getTotalElements(), page.getTotalPages());
    }
}

当我进行 http 调用时,我在控制台中看到一条警告消息:

08/02/2019 11:09:35,526  WARN http-nio-8082-exec-1 RepositoryRestMvcConfiguration$ResourceSupportHttpMessageConverter:205 - Failed to evaluate Jackson serialization for type [class org.springframework.hateoas.PagedResources]: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer.<init>()
08/02/2019 11:09:35,527  WARN http-nio-8082-exec-1 MappingJackson2HttpMessageConverter:205 - Failed to evaluate Jackson serialization for type [class org.springframework.hateoas.PagedResources]: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer.<init>()

不使用 PagedResources 错误就消失了。我不明白我哪里做错了。我知道 HalResourcesSerializer 没有默认构造函数,但我没有直接使用它,我也不明白为什么 Entity 在数据库中持久存在这样一个 Controller ,这样工作正常。 如何解决此问题并继续使用 PagedResource?

========更新==========

我添加我的配置以提供更详细的 View :

自定义配置:

@Configuration
@EnableRetry
@EnableTransactionManagement
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
public class CustomConfiguration {
    public static CustomConfiguration INSTANCE;

    @PostConstruct
    public void init() {
        INSTANCE = this;
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public static SpringSecurityAuditorAware springSecurityAuditorAware() {
        return new SpringSecurityAuditorAware();
    }

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasenames("classpath:/i18n/messages");
        // messageSource.setDefaultEncoding("UTF-8");
        // set to true only for debugging
        messageSource.setUseCodeAsDefaultMessage(false);
        messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
        messageSource.setFallbackToSystemLocale(false);
        return messageSource;
    }

    @Bean
    public MessageSourceAccessor messageSourceAccessor() {
        return new MessageSourceAccessor(messageSource());
    }

    /**
     * Enable Spring bean validation https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation
     *
     * @return
     */
    @Bean
    public LocalValidatorFactoryBean validator() {
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        factoryBean.setValidationMessageSource(messageSource());
        return factoryBean;
    }

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
        methodValidationPostProcessor.setValidator(validator());
        return methodValidationPostProcessor;
    }

    /**
     * Utility class from Google to work with phone numbers {@link https://github.com/googlei18n/libphonenumber}
     *
     * @return
     */
    @Bean
    public PhoneNumberUtil phoneNumberUtil() {
        return PhoneNumberUtil.getInstance();
    }

    /**
     * To enable SpEL expressions
     *
     * @return
     */
    @Bean
    public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
        return new SecurityEvaluationContextExtension();
    }

    /**
     * Define the specific storage manager to use (disk, S3, etc)
     *
     * @return
     */
    @Bean
    public StorageManager storageManager() {
        return new S3StorageManager();
    }

    /**
     * GRACEFUL SHUTDOWN
     */
    @Bean
    public GracefulShutdown gracefulShutdown() {
        return new GracefulShutdown();
    }

    @Bean
    public ConfigurableServletWebServerFactory webServerFactory(final GracefulShutdown gracefulShutdown) {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.addConnectorCustomizers(gracefulShutdown);
        return factory;
    }

}

GlobalRepositoryRestConfigurer:

@Configuration
public class GlobalRepositoryRestConfigurer implements RepositoryRestConfigurer {
    private Logger log = LogManager.getLogger();

    @Autowired(required = false)
    private Jackson2ObjectMapperBuilder objectMapperBuilder;

    @Autowired
    private Validator validator;

    @Value("${cors.mapping}")
    private String corsMapping;

    @Value("#{'${cors.allowed.headers}'.split(',')}")
    private String[] corsAllowedHeaders;

    @Value("#{'${cors.exposed.headers}'.split(',')}")
    private String[] corsExposedHeaders;

    @Value("#{'${cors.allowed.methods}'.split(',')}")
    private String[] corsAllowedMethod;

    @Value("#{'${cors.allowed.origins}'.split(',')}")
    private String[] corsAllowedOrigins;

    @Value("${cors.max.age}")
    private int corsMaxAge;

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.getCorsRegistry().addMapping(corsMapping).exposedHeaders(corsExposedHeaders).allowedOrigins(corsAllowedOrigins)
                .allowedHeaders(corsAllowedHeaders).allowedMethods(corsAllowedMethod).maxAge(corsMaxAge);

    }

    @Override
    public void configureConversionService(ConfigurableConversionService conversionService) {

    }

    /**
     * ValidationException serialiazer
     *
     * @return
     */
    @Bean
    public ValidationExceptionSerializer validationExceptionSerializer() {
        return new ValidationExceptionSerializer();
    }

    @Bean
    public CustomValidationExceptionSerializer customValidationExceptionSerializer() {
        return new CustomValidationExceptionSerializer();
    }

    @Bean
    public ConstraintViolationExceptionSerializer constraintViolationExceptionSerializer() {
        return new ConstraintViolationExceptionSerializer();
    }

    /**
     * Customize Object Mapper
     */
    @Override
    public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
        if (this.objectMapperBuilder != null) {
            /**
             * Custom serializer for ConstraintViolationException
             * (https://jira.spring.io/browse/DATAREST-593)
             */
            try {
                SimpleModule constraintExceptionModule = new SimpleModule();
                constraintExceptionModule.addSerializer(ConstraintViolationException.class, constraintViolationExceptionSerializer());

                constraintExceptionModule.addSerializer(ValidationException.class, validationExceptionSerializer());
                constraintExceptionModule.addSerializer(cloud.optix.server.exceptions.ValidationException.class, customValidationExceptionSerializer());
                objectMapper.registerModule(constraintExceptionModule);
                this.objectMapperBuilder.configure(objectMapper);
            } catch (Exception e) {
                log.error("", e);
            }
        }
    }

    @Override
    public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
        validatingListener.addValidator("beforeCreate", validator);
        validatingListener.addValidator("beforeSave", validator);
    }

    @Override
    public void configureExceptionHandlerExceptionResolver(ExceptionHandlerExceptionResolver exceptionResolver) {

    }

    /**
     * Adding converter to donwload files in{@link org.springframework.web.bind.annotation.RestController}
     *
     * @param messageConverters
     */
    @Override
    public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        // super.configureHttpMessageConverters(messageConverters);
        messageConverters.add(new ResourceHttpMessageConverter());
    }
}

WebMvcConfiguration:

@Configuration
// Enable entity links for Spring HATEOAS
@EnableHypermediaSupport(type = {HypermediaType.HAL})
public class WebMvcConfiguration implements WebMvcConfigurer {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private TenantRestClient tenantRestClient;

    @Value("${cors.mapping}")
    private String corsMapping;

    @Value("#{'${cors.allowed.headers}'.split(',')}")
    private String[] corsAllowedHeaders;

    @Value("#{'${cors.exposed.headers}'.split(',')}")
    private String[] corsExposedHeaders;

    @Value("#{'${cors.allowed.methods}'.split(',')}")
    private String[] corsAllowedMethod;

    @Value("#{'${cors.allowed.origins}'.split(',')}")
    private String[] corsAllowedOrigins;

    @Value("${cors.max.age}")
    private int corsMaxAge;

    @Autowired
    public WebMvcConfiguration() {
    }

    @Bean
    public LocaleResolver localeResolver() {
        return new SmartLocaleResolver();
    }

    public class SmartLocaleResolver extends CookieLocaleResolver {
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            String acceptLanguage = request.getHeader("Accept-Language");
            if (acceptLanguage == null || acceptLanguage.trim().isEmpty()) {
                return super.determineDefaultLocale(request);
            }
            return request.getLocale();
        }
    }

    /**
     * Custom exception in WEB MVC
     *
     * @return
     */
    @Bean
    public CustomErrorAttributes myCustomErrorAttributes() {
        return new CustomErrorAttributes();
    }

    /**
     * Global CORS security configuration
     *
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping(corsMapping).exposedHeaders(corsExposedHeaders).allowedOrigins(corsAllowedOrigins).allowedHeaders(corsAllowedHeaders)
                .allowedMethods(corsAllowedMethod).maxAge(corsMaxAge);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TenantInterceptor());
    }

}

最佳答案

尝试在您的配置中注释掉这一行:

this.objectMapperBuilder.configure(objectMapper);

我认为 RepositoryRestConfigurer 可以很好地为自身配置 objectMapper。

如果您需要它从类路径中自动添加更多模块,请手动添加/配置这些模块。

关于java - Spring HATEOAS HalResourcesSerializer 未找到默认构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54590203/

相关文章:

java - Spring Boot 中使用相同存储库和实体的多个数据源

java - Mockito,如何验证被测试类的方法是否被调用?

spring - 如何在 Spring Security 中允许单个 URL

java - Spring MVC Web 应用程序中的模板

java - JPA transient 注释和 JSON

java - 每次迭代为任务安排一个新日期 - Java Spring

spring-security - Spring Boot,Spring Security 覆盖 UserDetailsS​​ervice

java - JPA 多对多关系 : delete child entity not possible with active relation

java - 执行线程队列

java.lang.NoSuchMethodError : org. springframework.web.context.ConfigurableWebApplicationContext.setId(Ljava/lang/String;)V