Spring Boot:HATEOAS 和自定义 JacksonObjectMapper

Posted

技术标签:

【中文标题】Spring Boot:HATEOAS 和自定义 JacksonObjectMapper【英文标题】:Spring Boot: HATEOAS and custom JacksonObjectMapper 【发布时间】:2020-06-21 19:16:07 【问题描述】:

将 HATEOAS 的依赖项添加到 Maven 后,Spring Boot 无法启动:

添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

完整的 pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>ru.example</groupId>
    <artifactId>testapp</artifactId>
    <version>1.0</version>
    <name>testapp</nAfter I added dependency for HATEOAS to Maven, Spring Boot does not startame>
    <description>Test</description>

    <properties>
        <java.version>1.8</java.version>
        <h2.version>1.4.200</h2.version>
        <jackson-json.version>2.10.2</jackson-json.version>
        <jsoup.version>1.12.1</jsoup.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-hateoas</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.17.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>$jsoup.version</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <version>5.3.0.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>$h2.version</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>$jackson-json.version</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-hibernate5</artifactId>
            <version>$jackson-json.version</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>$jackson-json.version</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

SecurityConfig.class:

package ru.example.testapp;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import ru.example.testapp.dao.UserRepository;
import ru.example.testapp.service.UserServiceImpl;

@Configuration
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter 
    private final UserRepository userRepository;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
        auth
                .userDetailsService(new UserServiceImpl(userRepository))
                .passwordEncoder(new BCryptPasswordEncoder());
    

    protected void configure(final HttpSecurity http) throws Exception 
        http
                .authorizeRequests()
                .antMatchers("/rest/admin/**").hasRole("ADMIN").and().httpBasic().and()
                .authorizeRequests()
                .antMatchers("/rest/user/**").hasAnyRole("USER","ADMIN").and().httpBasic().and()
                .authorizeRequests().and()
                .csrf().ignoringAntMatchers("/rest/**");
    

JacksonObjectMapper.class:

package ru.example.testapp.util.json;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.stereotype.Component;

@Component
public class JacksonObjectMapper extends ObjectMapper 

    private static final ObjectMapper MAPPER = new JacksonObjectMapper();

    public static ObjectMapper getMapper() 
        return MAPPER;
    

    private JacksonObjectMapper() 
        registerModule(new Hibernate5Module());
        registerModule(new JavaTimeModule());
        configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
        setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        setSerializationInclusion(JsonInclude.Include.NON_NULL);
    

在控制台我有以下错误:

org.springframework.beans.factory.UnsatisfiedDependencyException: 创建名为“securityConfig”的 bean 时出错:不满足的依赖关系 通过方法'setContentNegotationStrategy'参数0表示; 嵌套异常是 org.springframework.beans.factory.UnsatisfiedDependencyException: 创建具有名称的 bean 时出错 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration': 通过方法“setConfigurers”表达的不满足的依赖关系 参数0;嵌套异常是 org.springframework.beans.factory.UnsatisfiedDependencyException: 创建名称为“hypermediaWebMvcConfigurer”的 bean 时出错 类路径资源 [org/springframework/hateoas/config/WebMvcHateoasConfiguration.class]: 通过方法表达的不满足的依赖关系 'hypermediaWebMvcConfigurer' 参数 0;嵌套异常是 org.springframework.beans.factory.BeanCreationException:错误 创建类中定义的名称为“hypermediaWebMvcConverters”的bean 路径资源 [org/springframework/hateoas/config/HateoasConfiguration.class]:豆 通过工厂方法实例化失败;嵌套异常是 org.springframework.beans.BeanInstantiationException:失败 实例化 [org.springframework.hateoas.config.WebConverters]: 工厂方法“hypermediaWebMvcConverters”抛出异常;嵌套的 异常是 java.lang.IllegalStateException:复制失败(): ru.example.wmanage.util.json.JacksonObjectMapper(版本:2.10.2) 不覆盖 copy();它必须

可能是什么问题? 错误 - securityConfig、JacksonObjectMapper 和 hatoas 的问题。 如果我在依赖项中删除 spring-boot-starter-hateoas ,那么一切正常。但我需要仇恨。 请帮忙。

更新: 使用带有注释 @Component 的自定义 JacksonObjectMapper 时会出现问题。只要 spring-boot-starter-hateoas 添加到依赖项,Spting Boot 就不会启动。

问题:如何一起使用自定义 JacksonObjectMapper 和 hatoas?

问题未解决

【问题讨论】:

【参考方案1】:

我认为您没有任何理由扩展 ObjectMapper。您应该像往常一样实例化 ObjectMapper,然后通过其公开的方法对其进行配置,并将其注册为配置中的 bean。

@Bean
public ObjectMapper createMapper() 
    return new ObjectMapper().registerModule(new Hibernate5Module());
            .registerModule(new JavaTimeModule());
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
            .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
            .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
            .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
            .setSerializationInclusion(JsonInclude.Include.NON_NULL);

【讨论】:

我需要自定义映射器 使用带有注解@Component 的自定义JacksonObjectMapper 时会出现问题。一旦 spring-boot-starter-hateoas 添加到依赖项中,那么 Spting Boot 就不会启动 这是因为在您自定义的 ObjectMapper 实现中,您没有实现复制方法。我将重申您不需要扩展 ObjectMapper 来自定义它,事实上我不相信您应该扩展它。【参考方案2】:

我不知道为什么不应该扩展 ObjectMapper。 可能 Deadron Mar 可以解释,但这个类不是最终的。 就我而言,它在我们的实体框架中得到了深入扩展,我得到了同样的错误消息。

我刚刚做了另一个扩展

@Component
public class MyCustomizedMapper extends AnotherExtendedMapper 
    public MyCustomizedMapper copy() 
        return new MyCustomizedMapper(); // we have default constructor for this
`   

并且想知道 - 它有效)

【讨论】:

以上是关于Spring Boot:HATEOAS 和自定义 JacksonObjectMapper的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring Boot 应用程序中使用 API 网关时,HATEOAS 路径无效

我可以让自定义控制器镜像 Spring-Data-Rest / Spring-Hateoas 生成的类的格式吗?

Spring 安全性和自定义 AuthenticationFilter 与 Spring boot

spring boot spring security 基于自定义令牌的身份验证和自定义授权

具有基本身份验证和自定义 UserDetailsS​​ervice 的 Spring Boot OAuth2

强制spring hateoas生成https链接而不是http