EvideShow
EvideShow

Reputation: 39

Spring Boot: HATEOAS and custom JacksonObjectMapper

After I added dependency for HATEOAS to Maven, Spring Boot does not start:

Added dependency:

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

Full 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);
    }
}

In console I have following error:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityConfig': Unsatisfied dependency expressed through method 'setContentNegotationStrategy' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration': Unsatisfied dependency expressed through method 'setConfigurers' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'hypermediaWebMvcConfigurer' defined in class path resource [org/springframework/hateoas/config/WebMvcHateoasConfiguration.class]: Unsatisfied dependency expressed through method 'hypermediaWebMvcConfigurer' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'hypermediaWebMvcConverters' defined in class path resource [org/springframework/hateoas/config/HateoasConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.hateoas.config.WebConverters]: Factory method 'hypermediaWebMvcConverters' threw exception; nested exception is java.lang.IllegalStateException: Failed copy(): ru.example.wmanage.util.json.JacksonObjectMapper (version: 2.10.2) does not override copy(); it has to

What could be the problem? In error - something with securityConfig, JacksonObjectMapper and hateoas. If I delete spring-boot-starter-hateoas at dependencies, then all works. But I need hateoas. Please help.

UPDATED: The problem occurs when using custom JacksonObjectMapper with annotation @Component. As soon as spring-boot-starter-hateoas added to dependencies, then Spting Boot does not startup.

QUESTION: How to use custom JacksonObjectMapper and hateoas together?

PROBLEM NOT RESOLVED

Upvotes: 0

Views: 2440

Answers (2)

Eugene Sirikh
Eugene Sirikh

Reputation: 1

I dont know why one should not extend ObjectMapper. probably Deadron Mar can explain, but this class is not final. In my case it is extended deeply in our corporal framework and i got same error message.

i just did another extension

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

and wonder - it works)

Upvotes: 0

Deadron
Deadron

Reputation: 5289

I don't think you have any reason to be extending ObjectMapper. You should instantiate an ObjectMapper like normal and then configure it by its exposed methods and register it as a bean in your configuration.

@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);
}

Upvotes: 1

Related Questions