Hello, Freakin world!

기능 추가 - URL에 포함된 정보를 자동으로 메서드 인수에 바인딩하기 본문

Toy Project/URL Mapping 프레임워크 구현하기

기능 추가 - URL에 포함된 정보를 자동으로 메서드 인수에 바인딩하기

johnna_endure 2020. 4. 1. 16:52

글의 제목만으로는 추가하려는 기능의 정의가 와닿지 않는다.

 

간단하게 코드를 이용해 추가하려는 기능을 정의하자.

기능 정의

   @RequestMapping(method = Method.GET, url = "/room/{id}")
    public Response canJoinRoom(Request request, int id) {
		...
    }

@RequestMapping의 url 속성의 값을 살펴보자. 

특이한 점은 "{ id }" 라는 중괄호가 포함된 url을 가지고 있다. 이는 스프링 프레임워크를 써본 사람이라면 한번 쯤은 써본 기능이다.

중괄호는 일종의 유연한 url 매핑이 가능하도록 해준다.

예를 들어, "/room/12", "/room/idstring" 은 모두 위의 url을 만족한다. 다만, 아래 인수의 id의 타입이 int이므로 이 경우엔 "/room/12" 이 해당된다.

그리고 { id }에 할당된 값은 자동으로 메서드의 인수 id에 바인딩된다.  그리고 이 시점에 타입이 맞지 않을 경우 예외를 발생시킨다.

 

Work flow

이 흐름대로 작은 기능들을 구현하면 된다.

 

1. 우선 순수하게 reqeustUrl이 urlFormat 에 만족하는지 판정해야 한다.

중괄호에 포함되는 부분은 어떤 값도 가질 수 있다고 가정하자. 이렇게 가정한다면 중괄호 이외의 urlFormat과 requestUrl 부분이 같아야 한다.

 

2. 1번이 만족한다면 requestUrl과 urlFormat을 비교해 중괄호에 어떤 값이 해당되는지 데이터를 뽑아내야 한다.

 

3. 2번에서 생성한 데이터와 reflection api를 이용해 메서드를 invoke한다. 

 

 

구현 코드 

 

1번 기능 - urlForamt과 requestUrl의 매칭 여부

public class URLParser {
    public static boolean validateUrl(String urlFormat, String url) {
        Pattern braceConvertPattern = Pattern.compile("\\{[\\w\\d]+\\}");
        String braceConvertedUrlFormat = braceConvertPattern.matcher(urlFormat)
                .replaceAll("[\\\\w\\\\d]+");

        return Pattern.compile(braceConvertedUrlFormat)
                .matcher(url).find();
    }
}

urlFormat 에 정규표현식을 적용해 중괄호가 있는 부분을 찾아낸다.

다시 찾아낸 부분을 어떤 문자열이라도 대응될 수 있는 부분으로 바꾼 뒤, requestUrl과 비교해 적합성을 판단한다.

(해당 기능은 URLParser의 static 메서드 형태로 빼냈다.)

 

2번 기능 - 중괄호에 대응하는 부분의 데이터를 맵에 저장하는 기능

    private Map<String, String> getInnerBraceKeyMap(String urlFormat, String requestUrl) {
        String[] formatFractions = urlFormat.split("/");
        String[] requestUrlFractions = requestUrl.split("/");

        return Stream.iterate(0, i -> i+1)
                .limit(formatFractions.length)
                .filter(i -> formatFractions[i].contains("{"))
                .collect(Collectors.toMap(i -> formatFractions[i].substring(1,formatFractions[i].length()-1),
                        i -> requestUrlFractions[i]));
    }

이 코드는 urlFormat과 requestUrl을 "/" 단위로 분절한다. 

그리고 분절 단위로 비교 작업을 수행해, 분절에 "{"가 포함되는 경우에 분절 내의 이름을 key, 실제 요청의 값을 value로 하는 맵을 반환한다.

 

 3번 기능 - 메서드의 인수를 추가해 invoke 하기

  private Object[] mappingParameter( java.lang.reflect.Method method,
                                       Request request, Map<String, String> innerBraceKeyMap) {
        Parameter[] declaredParameters = method.getParameters();
        Object[] parameters = new Object[declaredParameters.length];

        Iterator<Map.Entry<String, String>> entryIterator = innerBraceKeyMap.entrySet().iterator();
        while(entryIterator.hasNext()) {
            Map.Entry<String, String> entry = entryIterator.next();
            String key = entry.getKey();
            String value = entry.getValue();
            // url 포맷의 중괄호 값 매핑
            Stream.iterate(0, i -> i+1)
                    .limit(declaredParameters.length)
                    .filter(i -> declaredParameters[i].getName().equals(key))
                    .forEach(i -> {
                        if(declaredParameters[i].getType() == int.class) {
                            parameters[i] = Integer.parseInt(value);
                        }else if(declaredParameters[i].getType() == long.class){
                            parameters[i] = Long.parseLong(value);
                        }else if(declaredParameters[i].getType() == double.class){
                            parameters[i] = Double.parseDouble(value);
                        }else{ //객체형 파라미터의 경우
                            parameters[i] = value;
                        }
                    });
        }
        // 파라미터에 Request 있을 경우 매핑
        Stream.iterate(0, i -> i+1)
                .limit(declaredParameters.length)
                .filter(i -> declaredParameters[i].getType() == Request.class)
                .forEach(i -> {
                    parameters[i] = request;
                });
        return parameters;
    }

2번에서 생성한 맵을 이용해 url에 포함된 데이터를 메서드 인수에 매핑하고 있다.

추가적으로 url과는 무관하게 인수의 타입 중 Request가 있다면 Request 정보도 매핑한다.

Comments