어노테이션 기반 개선 && 세션 저장소

17098 단어 SpringbootSpringboot

스프링 부트를 오랜만에 다시 하려고 하니까 자꾸 포트 중복 에러가 떴다.
근데 netstat -ano로 확인해보면 8080 중복이 안 떴다.
임시로 9090으로 바꾸고 userName은 loginName으로 변경해 이름이 아닌 user로 뜨는 오류를 해결했다.

어노테이션 기반 개선

코드가 반복되면 유지보수성이 떨어진다.
이전에 스프링 부트 프로젝트에서 문제가 되는 부분은 IndexController의 세션 값을 가져오는 부분이다.

이렇게 하면 세션 값이 필요할 때마다 저 코드를 붙여넣게 된다.
이 부분을 메소드 인자로 세션 값을 바로 받도록 변경하겠다.


  • LoginUser
package com.chanmi.book.springboot.config.auth;

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

//1. 이 어노테이션이 생성될 수 있는 위치를 지정, PARAMETER로 지정하면 메소드의 파라미터로 선언된 객체에서만 사용
//2. 어노테이션을 런타임시에까지 사용할 수 있음
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {//@interface : 이 파일을 어노테이션 클래스로 지정, LoginUser라는 이름을 가진 어노테이션 생성된 것
}

  • LoginUserArgumentResolver
package com.chanmi.book.springboot.config.auth;

import com.chanmi.book.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 {
    //LoginUserArgumentResolver라는 HandlerMethodArgumentResolver 인터페이스 구현
    
    //HandlerMethodArgumentResolver : 조건에 맞는 경우 메소드가 있다면 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");
    }
}
  • supportParameter()
    컨트롤러 메서드의 특정 파라미터를 지원하는지 판단
    위에서는 @LoginUser 어노테이션이 붙어 있고, 파라미터 클래스 타입이 SessionUser.class인 경우 true 반환
  • resolveArgument
    파라미터에 전달할 객체 생성
    여기서는 세션에서 객체 가져온다.

이제 @LoginUser를 사용할 환경을 구성했다.
이렇게 생성된 LoginUserArgumentResolver가 스프링에서 인식될 수 있도록 WebMvcConfigurer에 추가한다.


  • WebConfig
package com.chanmi.book.springboot.config;

import com.chanmi.book.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;

    //HandlerMethodArgumentResolver는 항상 WebMvcConfigurer의 addArgumentResolvers()를 통해 추가해야 한다.
    //다른 HandlerMethodArgumentResolver가 필요하다면 같은 방식으로 추가해주면 된다.
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers){

        argumentResolvers.add(loginUserArgumentResolver);
    }
}


이제 @LoginUser를 IndexController에서 이용해보겠다.

  • IndexController


세션 저장소로 데이터베이스 사용

현재 프로젝트는 애플리케이션 재실행을 하면 로그인이 풀린다.
그 이유는 세션이 내장 톰캣 메모리에 저장되기 때문이다. 기본적으로 세션은 실행되는 WAS의 메모리에서 저장되고 호출된다. 메모리에 저장되니 애플리케이션 실행 시(톰캣 재시작) 초기화 된다.
또 다른 문제는 2대 이상의 서버에서 서비스하고 있다면 톰캣마다 세션 동기화 설정을 해야 한다.

  • 세션 저장소 사용 방식
  1. 톰캣 세션 사용
    일반적으로 별다른 설정 없이 선택되는 방식
    이럴 경우 초기화 문제와 여러 WAS 구동 시 톰캣들 간의 세션 공유를 위한 추가 설정 필요

  2. MySql과 같은 데이터베이스를 세션 저장소로 사용
    여러 WAS간 공용 세션을 사용할 수 있는 가장 쉬운 방법
    많은 설정 필요 없지만, 로그인 요청 때마다 DB IO가 발생해 성능상 이슈가 발생할 수 있음
    보통 로그인 요청이 많이 없는 백오피스, 사내 시스템 용도에서 사용

  3. Redis, Memcached와 같은 메모리 DB를 세션 저장소로 사용
    B2C 서비스에서 가장 많이 사용
    실제 서비스로 사용하기 위해서 Embedded Redis와 같은 방식이 아닌 외부 메모리 서버 필요

난 위에서 두 번째 방식을 선택하겠다.


  • build.gradle
    spring-session-jdbc 의존성 등록

  • application.properties
    세션 저장소를 jdbc로 선택하도록 코드 추가

이제 애플리케이션을 재실행하고 로그인 해본다.
잘 되는 걸 확인하고, h2-console에 접속한다.

이제 세션 저장소를 데이터베이스로 교체했다. 하지만 지금은 기존과 동일하게 스프링을 재시작하면 세션이 풀린다. 그 이유는 H2 기반으로 스프링이 재실행될 때 H2도 재시작되기 때문이다.
이후 AWS로 배포하게 되면 RDS(Relational Database Service)를 사용하게 되니 이 때부터는 세션이 풀리지 않는다.

좋은 웹페이지 즐겨찾기