스프링부트와 AWS로 혼자 구현하는 웹서비스 따라하기


시작 👊

만들어놓은 웹페이지에 구글 로그인을 얹어보았다. 웹페이지를 완성하는 것도 힘들었지만, 구글 로그인을 만드는 과정은 매우 힘든 과정이었다. 낯선 코드 낯선 방식 쉽지 않다.

오늘은 코드를 개선해볼거다.

개선하기

왜? 어디를 개선하지?

일반적인 프로그래밍에서 개선이 필요한 나쁜 코드는 같은 코드가 반복되는 부분입니다. 같은 코드를 반복해서 만들어놓으면 나중에 수정이 필요한 상황에서 또 반복해서 수정해야합니다. 그러면 유지보수성이 떨어질 수 밖에 없고, 한 부분을 놓쳐 수정이 이뤄지지 않으면 문제가 발생할 수 밖에 없습니다.
그렇다면 앞서 우리가 만든 코드에서 개선할 부분은 어디일까요?

흐음... 모르게씁니다.. 완벽했던 거 같아요 허헣허😅

필자는 IndexController 에서 세션값을 가져오는 부분이라고 생각합니다.

// IndexController.java
...

SessionUser user = (SessionUser) hyttpSession.getAttribute("user");

...

그게 왜 문제인가요..😔

index 컨트롤러 외에 다른 컨트롤러와 메서드에서 세션값이 필요하면 그때마다 직접 세션에서 값을 가져와야합니다. 같은 코드가 계속해서 반복되는 것은 불필요합니다. 그래서 이 부분을 메서드 인자로 세션값을 바로 받을 수 있도록 변경해보겠습니다.

넵!

근데 세션값이 다른 컨트롤러에서 왜 필요하지?
하지만 저자가 만들어 놓으면 좋다는데 좋은 거지!

ㄱㄱ

코드를 개선해보자

  1. config/auth 에 LoginUser 라는 인터페이스를 만든다.
// LoginUser.java

package com.prac.webservice.springboot.config.auth;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}

@interface 는 뭘까?

그리고 인터페이스 위에 선언된 어노테이션들은 @interface 가 아니면 오류가 나던데.

@Target(ElementType.PARAMETER) : 이 어노테이션이 생성될 수 있는 위치를 지정합니다. PARAMETER 로 지정했으니 메서드의 파라미터로 선언된 객체에서만 사용할 수 있습니다. 이 외에도 클래스 선언문에 쓸 수 있는 type 등이 있습니다.
@interface : 이 파일을 어노테이션 클래스로 지정합니다. LoginUser라는 일므을 가진 어노테이션이 생성되었다고 보면 됩니다.

오..!

  1. 같은 위치에 LoginUserArgumentResolver 를 생성합니다. LoginUserArgumentResolver 라는 HandlerMethodArgumentResolver 인터페이스를 구현한 클래스입니다.
// LoginUserArgumentResolver.java

package com.prac.webservice.springboot.config.auth;

import com.prac.webservice.springboot.config.auth.dto.SessionUser;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpSession;

@RequiredArgsConstructor
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
    
    private final HttpSession httpSession;
    
    @Override
    public boolean supportsParameter(MethodParameter parameter){
        boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null;
        boolean isUserClass = SessionUser.class.equals(parameter.getParameterType());
        return isLoginUserAnnotation && isUserClass;
    }
    
    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) throws Exception{
        return httpSession.getAttribute("user");
    }
}

HandlerMethodArgumentResolver 는 조건에 맞는 경우 메서드가 있다면 HandlerMethodArgumentResolver 의 구현체가 지정한 값을 해당 메서드의 파라미터로 넘길 수 있습니다.

흑흑 이해하지 못했답니다 흑흑

supportsParameter() : 컨트롤러 메서드의 특정 파라미터를 지원하는지 판단합니다. 여기서는 파라미터에 @LoginUser 어노테이션이 붙어 있고, 파라미터 클래스 타입이 SessionUser.class 인 경우 true 를 반환합니다.

신기하게도 if 문을 쓰지 않고 boolean 타입을 반환할 수도 있구나!

@resolveArgument() : 파라미터에 전달할 객체를 생성합니다. 여기서는 세션에서 객체를 가져옵니다.

  1. config 에 WebConfig.java 를 만든다. config.auth 가 아님.

@LoginUser 를 사용하기 위한 환경 구성은 끝났고, 이렇게 만들어진 LoginUserArgumentResolver 를 스프링에서 인식될 수 있도록 WebMvcConfigurer 에 추가하겠습니다.

// WebConfig.java

package com.prac.webservice.springboot.config;

import com.prac.webservice.springboot.config.auth.LoginUserArgumentResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final LoginUserArgumentResolver loginUserArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers){
        argumentResolvers.add(loginUserArgumentResolver);
    }
}

HandlerMethodArgumentResolver 는 항상 WebMvcConfigureraddArgumentResolvers() 를 통해 추가 해야합니다. 다른 HandlerMethodArgumentResolver 가 필요하다면 같은 방식으로 추가하면 됩니다.

  1. 이제 다시 IndexController 의 코드에서 반복이 예상되는 부분들을 모두 @LoginUser 로 개선해보자
// IndexController.java

	...
    
        @GetMapping("/")
    public String index(Model model, @LoginUser SessionUser user){
        model.addAttribute("posts", postsService.findAllDesc());
        if (user != null) {
            model.addAttribute("userName", user.getName());
        }
        return "index";
    }
    
    ...

index 메서드 인자로 @LoginUser 가 추가되고,
위에 선언했던 private final HttpSession httpSession; 없애고, 메서드 내부에 SessionUser user = (SessionUser) httpSession.getAttribute("user");부분 없앴다.

@LoginUser SessionUser user : 기존에 (User) httpSession.getAttribute("user") 로 가져오던 세션 정보값이 개선 되었습니다. 이제는 어느 컨트롤러든 @LoginUser 만 사용하면 세션 정보를 가져올 수 있습니다!

오오...

개선한 코드 테스트

로그인 - h2-console 에서 GUEST 를 USER 로 바꿔주기 - 로그아웃 후 다시 로그인 - 글 등록 확인

✅ 성공


좋습니다.

개운합니다. 사실 이해하지는 못했지만, 개운합니다.

추가로 개선을 해볼까요?

예!!!

매우 기대됩니다

profile
BEAT A SHOTGUN

0개의 댓글