Как добавить в Boot-проект свой кастомный Formatter – так, чтобы это работало?

У меня веб-проект на Spring Boot. В нём есть несколько вью с формами для апдейта моих энтити, в том числе такая (включил только саму форму)

<form class="forms" th:action="@{/save-user}" th:object="${user}" method="post">
            <input type="hidden" th:field="*{id}">

            <input type="hidden" th:field="*{id}">

            <input type="hidden" th:field="*{username}">

            <input type="hidden" th:field="*{password}">

            <input type="hidden" th:field="*{name}">

            <input type="hidden" th:field="*{lastName}">

            <fieldset>
                <label for="departments">Department: </label>
                <select id="departments" th:field="*{department}">
                    <option th:value="accounting">Accounting</option>
                    <option th:value="sales">Sales</option>
                    <option th:value="'information technology'">IT</option>
                    <option th:value="'human resources'">HR</option>
                </select>
            </fieldset>

            <fieldset>
                <label for="salary">Salary: </label>
                <input id="salary" th:field="*{salary}"/>
            </fieldset>

            <input type="hidden" th:field="*{age}">

            <input type="hidden" th:field="*{email}">

            <input type="hidden" th:field="*{enabled}">

            <input type="hidden" th:field="*{authorities}">

            <input class="main-button" type="submit" value="Submit">
        </form>

В каждой из вью, редактирующих энтити, для изменения доступны только некоторые поля. Для остальных присутствуют скрытые формы, чтобы значения нередактируемых полей не обнулялись при выходе из вью. При опущении этих хидденов такое происходит

Теперь обратите внимание на эту строчку

<input type="hidden" th:field="*{authorities}">

Вот, что такое authorities

@Entity
@Table(name = "users")
public class User implements UserDetails {
// ...
    @ManyToMany
    @JoinTable(name = "user_role",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> authorities;  // ← ← ← ← ← поле authorities
    @Entity
    @Table(name = "roles")
    public class Role implements GrantedAuthority {
    // ...
    @ManyToMany
    @JoinTable(name = "user_role",
            joinColumns = @JoinColumn(name = "role_id"),
            inverseJoinColumns = @JoinColumn(name = "user_id"))
    private Set<User> userList;
    // ...
    @Override
    public String toString() {
        return new StringJoiner(", ", Role.class.getSimpleName() + "[", "]")
                .add("id=" + id)
                .add("role='" + authority + "'") //  ← ← ← ← кавычки
                .toString();
    }

Теперь при чём здесь Formatter. Как я понял, из вью из скрытого инпута authorities у меня будет возвращаться что-то вроде

[Role[id=1, authority='USER'], Role[id=2, authority='ADMIN']]

Чтобы этот стринг конвертировался в Set<Role>, я добавил свой кастомный форматтер так (идею с форматтером подсказал бот)

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    private final UserRoleService service;

    public WebMvcConfig(UserRoleService service) {
        this.service = service;
    }
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(new Formatter<Set<Role>>() {

            @Override
            public Set<Role> parse(String text, Locale locale) {
                Set<Role> roleSet = new HashSet<>();
                String[] roles = text.split("^\\[|\\]$|(?<=\\]),\\s?");
                for (String roleString : roles) {
                    String authority =
                            roleString.substring(roleString.lastIndexOf("=") + 1,
                                    roleString.indexOf("]") - 1);
                    roleSet.add(service.getRoleByName(authority));
                }
                return roleSet;
            }

            @Override
            public String print(Set<Role> object, Locale locale) {
                return null;
            }
        });
    }
}

Пробовал ещё так, через отдельный бин

// ...
    @Bean
    public Formatter<Set<Role>> roleSetFormatter() {
        return new Formatter<>() {
// ...

Бот мне сказал импортировать MVC-конфиг в конфиг Security, но, кажется, погоды это не делает

@Configuration
@Import(WebMvcConfig.class)
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

Проблема: роли всё равно обнуляются при апдейте (сам апдейт работает)

введите сюда описание изображения

введите сюда описание изображения

Ожидаемое поведение: после выхода из формы нередактируемые поля сохраняют свои значения, в том числе authorities, конвертер StringSet<Role> работает корректно

Я пока не уверен, что конвертер вообще применяется. А если применяется, мне непонятно, что из него выходит. Ставил брейкпоинт на parse(), он не срабатывал вообще. Дебаг-логгинг тоже включал

Минимальный пример: я не знаю, что мне включить в минимальный пример, поэтому вот репо


Ответы (0 шт):