주요글: 도커 시작하기
반응형

HTTP 요청 헤더를 이용해서 접속한 사용자 정보를 받기로 했다. 받아야 할 정보가 두 개여서 스프링 컨트롤러에서 다음과 같은 코드를 사용하게 되었다.


@RestController

public class HandoverApi {


    private HandoverService handoverService;


    @PostMapping("/api/handover")

    public HandoverPk postHandover(

            @RequestHeader(name = "employeeId", required = false) String employeeIdHeader,

            @RequestParam(name = "employeeId", required = false) String employeeIdParam,

            @RequestHeader(name = "cellphone", required = false) String cellphoneHeader,

            @RequestParam(name = "cellphone", required = false) String cellphoneParam,

            @Valid HandoverRequest req) throws ServletRequestBindingException {

        if (StringUtils.isEmpty(employeeIdHeader) && StringUtils.isEmpty(employeeIdParam)) {

            throw new ServletRequestBindingException("Missing employeeId");

        }

        if (StringUtils.isEmpty(cellphoneHeader) && StringUtils.isEmpty(cellphoneParam)) {

            throw new ServletRequestBindingException("Missing cellphoneParam");

        }

        String employeeId = employeeIdParam != null ? employeeIdParam : employeeIdHeader;

        String cellphone = cellphoneParam != null ? cellphoneParam : cellphoneHeader;

        req.setEmployeeId(employeeId);

        req.setCellphone(cellphone);


        return handoverService.handoverDigWork(req);

    }


employeeId와 cellphone 값을 요청 헤더나 요청 파라미터로 받을 수 있도록 했다. 헤더와 파라미터를 함께 사용할 수 있게 해서 코드가 다소 장황해졌다. 문제는 이런 코드가 계속해서 중복해서 출현하게 되었다는 것이다. employeeId와 cellphone 값이 필요한 API에서 이런 장황하면서 보기 싫은 코드를 중복해서 사용하게 되었다.


이런 중복을 없애기 위해 스프링의 커스텀 HandlerMethodArgumentResolver를 사용했다. 원하는 코드 모양은 다음과 같다.


@RestController

public class HandoverApi {


    @PostMapping("/api/handover")

    public HandoverPk postHandover(

            @MobileUser(check = MobileUserCheck.ALL) MobileUserInfo mobUserInfo,

            @Valid DigWorkHandoverRequest req) {

        req.setEmployeeId(mobUserInfo.getEmployeeId());

        req.setCellphone(mobUserInfo.getCellphone());


        return handoverService.handoverDigWork(req);

    }


@MobileUser 애노테이션이 붙은 MobileUserInfo 타입 파라미터에 자동으로 employeeId와 cellphone 값이 담기도록 스프링을 확장하는 것을 목표로 했다.


파라미터에 사용할 MobileUserInfo와 @MobileUser


먼저 employeeId와 cellphone을 담을 데이터 클래스를 하나 만들었다.


@Getter

@AllArgsConstructor

@ToString

public class MobileUserInfo {

    private String employeeId;

    private String cellphone;

    

    public void checkEmployeeId() throws ServletRequestBindingException {

        if (StringUtils.isEmpty(employeeId))

            throw new ServletRequestBindingException("Missing employeeId");

    }

    

    public void checkCellphone() throws ServletRequestBindingException {

        if (StringUtils.isEmpty(cellphone))

            throw new ServletRequestBindingException("Missing cellphone");

    }


    public void checkAll() throws ServletRequestBindingException {

        checkEmployeeId();

        checkCellphone();

    }

    

}


@MobileUser 애노테이션은 다음과 같이 정의했다.


@Target(value = { ElementType.PARAMETER })

@Retention(value = RetentionPolicy.RUNTIME)

public @interface MobileUser {


    MobileUserCheck check() default MobileUserCheck.EMPLOYEE_ID;


}


check 속성은 값을 어디까지 검사할지 여부를 지정하기 위한 용도로 사용한다. 이 예제의 경우 employeeId만 필요한 경우가 있고, cellphone만 필요한 경우가 있고, 둘 다 필요한 경우도 있다. 이를 필요에 따라 검사 대상을 지정할 수 있도록 check 속성을 추가했다. MobileUserCheck 열거 타입은 검사할 대상을 포함한다.


public enum MobileUserCheck {

    ALL, EMPLOYEE_ID, CELLPHONE, NONE

}


커스텀 HandlerMethodArgumentResolver 구현


스프링이 컨트롤러 메서드의 인자로 MobileUserInfo 타입 객체를 받을 수 있으려면 HandlerMethodArgumentResolver의 구현체를 알맞게 제공해야 한다. 예제를 위한 구현체는 다음과 같다.


public class MobileUserInfoResolver implements HandlerMethodArgumentResolver {

    @Override

    public boolean supportsParameter(MethodParameter parameter) {

        MobileUser mobUserAnnot = parameter.getParameterAnnotation(MobileUser.class);

        return mobUserAnnot != null && 

                 MobileUserInfo.class.isAssignableFrom(parameter.getParameterType());

    }


    @Override

    public Object resolveArgument(MethodParameter parameter, 

            ModelAndViewContainer mavContainer,

            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) 

            throws Exception {

        String employeeIdHeader = webRequest.getHeader("employeeId");

        String employeeIdParam = webRequest.getParameter("employeeId");

        String cellphoneHeader = webRequest.getHeader("cellphone");

        String cellphoneParam = webRequest.getParameter("cellphone");


        String employeeId = employeeIdParam != null ? employeeIdParam : employeeIdHeader;

        String cellphone = cellphoneParam != null ? cellphoneParam : cellphoneHeader;


        MobileUser mobUserAnnot = parameter.getParameterAnnotation(MobileUser.class);

        MobileUserInfo mobileUserInfo = new MobileUserInfo(employeeId, cellphone);

        switch (mobUserAnnot.check()) {

        case ALL:

            mobileUserInfo.checkAll();

            break;

        case EMPLOYEE_ID:

            mobileUserInfo.checkEmployeeId();

            break;

        case CELLPHONE:

            mobileUserInfo.checkCellphone();

            break;

        default:

            break;

        }

        return mobileUserInfo;

    }

}


supportsParameter() 메서드는 컨트롤러 메서드의 특정 파라미터를 지원하는지 여부를 리턴한다. 이 예제의 경우 파라미터에 MobileUser 애노테이션이 붙이 있고 파라미터 타입이 MobileUserInfo인 경우 true를 리턴하도록 구현했다.


resolveArgument() 메서드는 파라미터에 전달할 객체를 생성한다. 이 예에서는 employeeId와 cellphone 값을 요청 헤더나 요청 파라미터에서 읽어와 MobileUserInfo를 생성하고, @MobileUser 애노테이션의 check 값에 따라 값 검사를 수행한 뒤에, 검사에 통과하면 MobileUserInfo를 리턴한다.


WebMvcConfigurer로 설정하기


마지막으로 준비할 작업은 커스텀 HandlerMethodArgumentResolver를 사용하도록 스프링 MVC를 설정하는 것이다. @EnableWebMvc나 스프링 부트를 사용한다면 다음과 같이 WebMvcConfigurer 구현 클래스를 사용해서 커스텀 HandlerMethodArgumentResolver를 등록하면 된다.


@Configuration

public class WebMvcCustomConfiguration extends WebMvcConfigurerAdapter {


    @Override

    public void addArgumentResolvers(

            List<HandlerMethodArgumentResolver> argumentResolvers) {

        argumentResolvers.add(new MobileUserInfoResolver());

    }

    

}



커스텀 인자 사용


이제 컨트롤러 메서드의 파라미터로 커스텀 인자 타입을 사용하면 된다.


@PostMapping("/api/handover")

public HandoverPk postHandover(

        @MobileUser(check = MobileUserCheck.ALL) MobileUserInfo mobUserInfo,

        @Valid DigWorkHandoverRequest req) {

    req.setEmployeeId(mobUserInfo.getEmployeeId());

    req.setCellphone(mobUserInfo.getCellphone());


    return handoverService.handoverDigWork(req);

}


@GetMapping(value = "/api/checks")

public yCheckData getCheckData(@MobileUser MobileUserInfo userInfo) {

    CheckData checks = checkService.getChecks(userInfo.getEmployeeId());

    return checks;

}


+ Recent posts