현재 스프링 시큐리티를 사용하지 않기에 발생할 수 있는 문제이긴 하지만 이러한 고민은 다른 영역에서도 자주 일어날 수 있다고 생각한다.
게시판 프로젝트의 네비게이션 바가 구성되어 있으며, 해당 네비게이션 바는 th:fragment로 하여금 대부분의 템플릿에서 반복 사용된다. 문제는 이 네비게이션이 UserViewForm
DTO 객체와 바인딩되어 있다는 점이다. 즉 항상 UserViewForm을 필요로 한다.
수많은 컨트롤러가 최종적으로 템플릿 이름을 반환하는데, 이 과정에서 템플릿에서 활용할 객체를 모델에 담아야 한다.
헤더에 UserViewForm
객체를 전달하기 위해 매번 다음과 같은 코드를 작성해야 하는 상황이다.
// 세션에서 사용자 ID 가져오기
User sessionUser = (User) session.getAttribute("loginUser");
if (sessionUser == null) {
return; // 세션에 사용자 정보가 없으면 바로 반환
}
// User 엔티티를 데이터베이스에서 다시 조회하여 초기화된 상태로 가져오기
User fullyInitializedUser = userRepository.findById(sessionUser.getId())
.orElse(null);
if (fullyInitializedUser == null) {
return; // 데이터베이스에 사용자가 없으면 반환
}
// UserViewForm에 데이터 매핑
UserViewForm userViewForm = getUserViewForm(fullyInitializedUser);
// 모델에 추가
model.addAttribute("sessionUser", userViewForm);
다음은 UserViewForm
DTO 객체의 예시이다.
@Data
public class UserViewForm {
private String username;
private String email;
private String userDesc;
private String profilePic;
private String blogTitle;
}
모든 컨트롤러 메서드에 위와 같은 로직이 반복되는 문제를 해결하기 위하여, @ControllerAdvice
를 활용해 전역적으로 모델 속성을 설정할 수 있다. @ControllerAdvice
는 스프링 MVC에서 전역 예외 처리, 모델 속성 전역 설정, 데이터 바인딩 설정 등을 담당하는 어노테이션이다. 이를 통해 공통으로 사용되는 데이터를 중앙 집중화하여 코드 중복을 제거하고, 유지보수성을 향상시킬 수 있다.
아래는 @ControllerAdvice
를 활용하여 세션의 사용자 정보를 전역 모델 속성으로 추가하는 예시이다.
@ControllerAdvice
@RequiredArgsConstructor
public class GlobalControllerAdvice {
private final UserRepository userRepository;
@ModelAttribute
public void addSessionUserToModel(HttpSession session, Model model) {
// 세션에서 사용자 ID 가져오기
User sessionUser = (User) session.getAttribute("loginUser");
if (sessionUser == null) {
return; // 세션에 사용자 정보가 없으면 바로 반환
}
// User 엔티티를 데이터베이스에서 다시 조회하여 초기화된 상태로 가져오기
User fullyInitializedUser = userRepository.findById(sessionUser.getId())
.orElse(null);
if (fullyInitializedUser == null) {
return; // 데이터베이스에 사용자가 없으면 반환
}
// UserViewForm에 데이터 매핑
UserViewForm userViewForm = getUserViewForm(fullyInitializedUser);
// 모델에 추가
model.addAttribute("sessionUser", userViewForm);
}
private static UserViewForm getUserViewForm(User fullyInitializedUser) {
UserDesc userDesc = fullyInitializedUser.getUserDesc(); // UserDesc 가져오기
UserViewForm userViewForm = new UserViewForm();
// 필드 매핑
userViewForm.setUsername(fullyInitializedUser.getUsername());
userViewForm.setEmail(fullyInitializedUser.getEmail());
userViewForm.setProfilePic(userDesc != null && userDesc.getProfilePic() != null
? userDesc.getProfilePic()
: "/img/default-profile.png"); // 기본 프로필 이미지
userViewForm.setUserDesc(userDesc != null && userDesc.getDescription() != null
? userDesc.getDescription()
: "No description available"); // 기본 설명
userViewForm.setBlogTitle(userDesc != null && userDesc.getBlogTitle() != null
? userDesc.getBlogTitle()
: "Untitled Blog"); // 기본 블로그 제목
return userViewForm;
}
}
위와 같이 @ControllerAdvice
를 적용하면, 다음과 같이 간단하게 컨트롤러 메서드를 작성할 수 있다.
@GetMapping("/")
public String getMain() {
return "home";
}
이 경우에도 GlobalControllerAdvice
에 정의된 로직에 의해 UserViewForm
객체가 자동으로 모델에 담기게 된다.
이와 같이 @ControllerAdvice
를 활용하면 모든 컨트롤러에서 반복되는 사용자 정보 전달 로직을 중앙 집중화할 수 있다. 이를 통해 코드의 중복을 제거하고, 애플리케이션의 유지보수성을 높일 수 있다.