헤르메스 LIFE

[Spring Boot] HandlerMethodArgumentResolver를 이용한 중복 Source 제거 방법 본문

Spring Boot Framework

[Spring Boot] HandlerMethodArgumentResolver를 이용한 중복 Source 제거 방법

헤르메스의날개 2023. 4. 4. 23:31
728x90

요즘 JPA를 공부하고 있습니다. MyBatis 만 하다가, 개인적으로 공부를 진행하고 있습니다. 하다보니 막히고, 이해가 안되는 부분이 많네요. 그때마다 검색과 삽질로 해결하고 있습니다. 여기 그 삽질의 자취를 남겨봅니다.


개발환경

Spring Boot 2.7.9

H2 2.1.214

p6spy 1.8.1

slf4j 1.7.36

swagger2 2.6.1

lombok

devtools

postgresql


데이터를 저장하는 로직을 구현할 때 다음과 같은 소스 코드를 작성할 수 있습니다.

CodeDController.java

	private final HttpSession session;
    
    ....
    
    @ApiOperation(value = "공통코드 저장", notes = "공통코드 정보를 저장합니다.")
	@PostMapping("/code")
	public SingleResult<TCodeDDto> save(
			@ApiParam(value = "대분류코드", required = true) @RequestParam(required = true) String pCd,
			@ApiParam(value = "공통코드", required = true) @RequestParam(required = true) String cd,
			@ApiParam(value = "코드명", required = true) @RequestParam(required = true) String cdNm,
			@ApiParam(value = "사용여부", required = true) @RequestParam(required = true) String useYn,
			@ApiParam(value = "비고", required = false) @RequestParam(required = false) String rmk
	) {

		UserSessionDto userDto = (UserSessionDto) session.getAttribute("user");

		TCodeDDto tCodeDDto = TCodeDDto.builder()
                                       .pCd(pCd).cd(cd).cdNm(cdNm)
                                       .useYn(useYn).rmk(rmk).build();

		tCodeDDto.setCrtId(userDto.getUserId());
		tCodeDDto.setMdfId(userDto.getUserId());

		log.debug("tCodeMDto :: {}", tCodeDDto);

		TCodeD tCodeD = codeDService.save(tCodeDDto);

		return responseService.getSingleResult(TCodeDDto.makeDto(tCodeD));
	}

이 코드의 중복 코드는 아래와 같습니다.

		UserSessionDto userDto = (UserSessionDto) session.getAttribute("user");
		tCodeDDto.setCrtId(userDto.getUserId());
		tCodeDDto.setMdfId(userDto.getUserId());

저장(save, update) 시 Session 으로 부터 사용자 정보를 가져와서 User ID 를 Setting 해줘야 합니다.


HandlerMethodArgumentResolver 

위와 같은 상황에서 사용할 수 있는, Spring 3.1부터 제공되는 'HandlerMethodArgumentResolver'라는 인터페이스가 있습니다.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/method/support/HandlerMethodArgumentResolver.html

 

HandlerMethodArgumentResolver (Spring Framework 6.0.7 API)

Resolves a method parameter into an argument value from a given request.

docs.spring.io

 

Strategy interface for resolving method parameters into argument values in the context of a given request.
주어진 요청을 처리할 때, 메소드 파라미터를 인자값들에 주입 해주는 전략 인터페이스.

Controller에 공통으로 입력되는 parameter들을 추가하거나 수정하는 등의 여러 공통적인 작업(예를 들어, Session 으로 부터 사용자 정보를 가져올 때..)들을 한 번에 처리하고 싶을 경우 HandlerMethodArgumentResolver를 사용하면, 중복코드를 배제할 수 있습니다.

LoginUser.java

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

@Target(ElementType.PARAMETER) // @LoginUser 어노테이션이 생성될 수 있는 위치를 지정한다.
@Retention(RetentionPolicy.RUNTIME) // 런타임까지 보존한다.
public @interface LoginUser { // 어노테이션 클래스로 지정한다.
}

UserSessionDto.java

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@Builder
@AllArgsConstructor // 모든인자를 가지는객체 생성
@NoArgsConstructor // 인자 없이 객체 생성
@ToString
public class UserSessionDto implements Serializable {
	private static final long serialVersionUID = 1L;

	private String userId;
	private String userNm;
}

LoginUserArgumentResolver.java

import javax.servlet.http.HttpSession;

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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import octopus.base.anotation.LoginUser;
import octopus.base.dto.UserSessionDto;

/**
 * <pre>
 * @LoginUser Annotation 을 사용할 수 있다.
 * </pre>
 */
@Slf4j
@RequiredArgsConstructor
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {

	private final HttpSession session;

	/**
	 * Parameter가 @LoginUser Annotation이고, UserSessionDto Type이면..
	 */
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null;
		boolean isUserClass = UserSessionDto.class.equals(parameter.getParameterType());

		return isLoginUserAnnotation && isUserClass;
	}

	/**
	 * 사용자 정보를 Return 한다.
	 */
	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

		// TODO Session 객체가 없음. Redis 에서 사용자의 정보를 가져와야 할 지도..
		// TODO 사용자 정보를 Return 한다.
		// UserSessionDto userDto = (UserSessionDto) session.getAttribute("user");
		UserSessionDto userDto = UserSessionDto.builder().userId("admin").userNm("어드민").build();

		log.debug("userDto :: {}", userDto);

		return userDto;
	}
}

WebConfig.java

import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import lombok.RequiredArgsConstructor;
import octopus.base.resolver.LoginUserArgumentResolver;

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {

	private final LoginUserArgumentResolver loginUserArgumentResolver;

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

CodeDController.java

	@ApiOperation(value = "공통코드 저장", notes = "공통코드 정보를 저장합니다.")
	@PostMapping("/code")
	public SingleResult<TCodeDDto> save(
			@ApiParam(value = "대분류코드", required = true) @RequestParam(required = true) String pCd,
			@ApiParam(value = "공통코드", required = true) @RequestParam(required = true) String cd,
			@ApiParam(value = "코드명", required = true) @RequestParam(required = true) String cdNm,
			@ApiParam(value = "사용여부", required = true) @RequestParam(required = true) String useYn,
			@ApiParam(value = "비고", required = false) @RequestParam(required = false) String rmk,
			@LoginUser UserSessionDto userDto) {

		TCodeDDto tCodeDDto = TCodeDDto.builder()
                                       .pCd(pCd).cd(cd).cdNm(cdNm)
                                       .useYn(useYn).rmk(rmk).build();

		tCodeDDto.setCrtId(userDto.getUserId());
		tCodeDDto.setMdfId(userDto.getUserId());

		log.debug("tCodeMDto :: {}", tCodeDDto);

		TCodeD tCodeD = codeDService.save(tCodeDDto);

		return responseService.getSingleResult(TCodeDDto.makeDto(tCodeD));
	}

 

728x90