Open7

Spring

DellgreenDellgreen

DIで実クラスではなくインターフェースを指定する理由

  1. 切り替えの融通が聞く(実装A→実装Bにしたい場合、アノテーションでプライムを宣言すれば、実装Bのクラス追加のみで対応できる)
  2. UTする際にモックで書き換えしやすい。実クラスを指定していた場合むり
  3. 環境によって(dev,honban)などをプロパティで切り替えて制御できる
DellgreenDellgreen

spring security 闇
これは Spring Security の「セッション固定攻撃」対策の機能の一つで、認証前にアクセスしようとしていたURLを記憶し、ログイン成功後にそのURLにリダイレクトする仕組みが働いているためです。

この動作を修正するには以下の2つの方法があります:

SecurityConfig で明示的にデフォルトのサクセスURLを設定する:
ログインフォームで明示的にリダイレクト先を指定する:
defaultSuccessUrlの第2引数にtrueを指定することで、常に指定したURLにリダイレクトされるようになり、以前アクセスしていたURLは無視されます。

DellgreenDellgreen

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.formLogin(login -> login
.loginProcessingUrl("/login")
.loginPage("/login")
.defaultSuccessUrl("/", true) // 第2引数のtrueを追加
.failureUrl("/login?error")
.usernameParameter("accountId")
.passwordParameter("pw")
.permitAll()
)
// ...existing code...
}

DellgreenDellgreen

<form action="#" th:action="@{/login}" method="post">
<input type="hidden" name="redirect_uri" value="/" />

</form>

DellgreenDellgreen

Spring MVCでこのような画面を作る場合、表示専用の値と入力項目を1つのFormクラスにまとめることをお勧めします

推奨アプローチ:1つのFormクラス

public class UserEditForm {
    // 表示専用(ユーザが変更不可)
    private String userId;
    private String userName;
    private LocalDateTime createdAt;
    
    // 入力項目(ユーザが変更可能)
    @NotBlank
    private String email;
    
    @Size(max = 100)
    private String description;
    
    // getter/setter...
}

この方法の利点

画面との対応が明確になります。Thymeleafテンプレートでth:object="${form}"として、表示項目も入力項目も同じオブジェクトから参照できるため、画面構造と一致します。

コントローラーがシンプルになります。1つのオブジェクトをModelに渡すだけで済み、画面表示時と更新処理時で同じFormクラスを使えます。

保守性が高いです。画面項目の追加や変更時に、関連するすべての項目が1つのクラスにまとまっているため、修正箇所が分散しません。

実装例

@Controller
public class UserController {
    
    @GetMapping("/user/edit/{id}")
    public String editForm(@PathVariable String id, Model model) {
        UserEditForm form = new UserEditForm();
        
        // DBから値を取得して設定
        User user = userService.findById(id);
        form.setUserId(user.getId());
        form.setUserName(user.getName());
        form.setCreatedAt(user.getCreatedAt());
        form.setEmail(user.getEmail());
        form.setDescription(user.getDescription());
        
        model.addAttribute("form", form);
        return "user/edit";
    }
    
    @PostMapping("/user/edit")
    public String update(@Valid @ModelAttribute UserEditForm form, 
                        BindingResult result) {
        if (result.hasErrors()) {
            return "user/edit";
        }
        
        // 入力項目のみを更新処理に使用
        userService.updateUser(form.getUserId(), 
                              form.getEmail(), 
                              form.getDescription());
        
        return "redirect:/user/list";
    }
}

この方法により、画面の構造とコードの構造が一致し、開発・保守の両面で効率的になります。