2009년 01월 08일
Spring Framework 비난에 대해
블로그들을 돌아다니다 spring framework에 대해 "비난"하는 포스팅을 봤다. "뭐 하나 고치려면 파일 뒤지는데 한 세월"이라면서 사이비가 아니냐는 말을 하고 있다.
그 포스팅을 보니 예전에 같이 일했다가 지금은 SI 쪽에서 일하고 있는 팀원이 한 말이 생각났다. 그 친구는 2004년 우리 팀에 spring framework를 도입했을 때 주도적으로 작업했던 친구다. 1.0 버전부터 최근까지 사용했던 친구라 spring framework에 대해선 아주 잘 안다 할 수 있다. 요즘은 spring framework가 꽤나 많이 알려지지 않았나 생각해서 SI 쪽에선 어떤가 물었더니 의외의 대답을 했다.
"JSP 하나로 처리할 수 있는 걸 복잡하게 여러 파일로 나눠놨다며 안쓰려고 해요. 그것 때문에 욕까지 들어먹은 적도 있는데요"
그 포스트의 말대로 "슬슬 사이비이고, 버려야 할 것이라는 움직임"이 SI 쪽에 있긴 한 거 같다. 하지만 정말로 그런 움직임이 있다면 그건 정말 슬픈 일이다.
spring framework는 참 잘 만든 프레임워크다. 나는 JSP + Bean 스타일이나 자작 Front Controller 스타일로 개발하다가 한계에 부딪친 상태에서 뭐가 뭔지 모를만큼 복잡했던 EJB는 쓰기 꺼려지던 때 spring framework를 발견했다. 그 이름처럼 산뜻한 느낌을 주는 framework였고 그때부터 지금까지 사용하면서 항상 즐거움과 신뢰를 느끼고 있다.
그런데 왜 이와 같은 말이 나오는 걸까? SI라면 나는 나쁜 기억 밖에 없다. 몇번 SI 업체에 개발 외주를 준 적이 있었고 다 별로였다. 최근 경우는 가관이었다. 구축 후 그 시스템을 우리 팀이 맡아서 유지 보수할 예정이었기 때문에 개발에 spring framework를 사용할 것을 요구했다. 그런데 그 결과물이라는 걸 검수하며 우리 팀원들은 욕을 무더기로 해댔다.
그것은 spring framework에 대한 최소한의 이해도 없는 상태에서 아니 그것보다 소프트웨어 설계에 대한 기본도 무시한 상태에서 작성한 코드였다. 데이타 접근 계층, 서비스 계층, 웹 처리 계층, 뷰 계층의 구분은 시늉만 냈을 뿐이지 아예 무시되고 뒤섞여 있었고 DI로 처리할 부분을 버젓이 singleton factory를 만들어 lookup하고 있었다. java collection 객체들을 엉터리로 사용하는가 하면 심지어 java naming convention도 지키지 않고 클래스를 정의하고 있었다. 메서드 시그내쳐를 정의하기 귀찮았는지 Map 객체만을 인자로 받는 메서드들이 허다했고 각 메서드 호출 계층마다 인자로 넘어온 Map 객체를 마구 조작해가며 다음 메서드에 인자로 넘기고 있었다. 정말로 뭐 하나 고치려면 파일들을 찾느라 한 세월인 경우였다.
말하자면 이런 거다. "추상화니 모듈화니 재사용성이니 그런 거 몰라. 어쨌든 돌아가면 되는 거 아냐. 어차피 copy & paste인데 복잡하게 뭐하러 여러 파일로 나눠. 그냥 JSP 하나면 족하지"
글쎄, 누구 말마따나 x 한번 푸짐하게 싸놓고 나올 수 있다면 몰라도 끝까지 자신의 결과물을 책임져야 하는 인하우스 개발팀이라면 위와 같은 생각을 가질 수 없다. 설령 copy & paste로 가능하더라도 그 얼마나 재미없는 일인가?
spring framework를 비판하는데 그게 추상화와 모듈화라는 소프트웨어 개발의 기본을 망각한 가운데 나온 거라면 들어야 할 일고의 가치도 없다. 그렇지만 spring framework 자체의 단점 또는 결함을 비판하는 거라면 정말로 한번 들어보고 싶다. 과연 그런 게 나올지 의문이긴 하지만.
# by | 2009/01/08 14:14 | 트랙백 | 덧글(2)
2008년 06월 25일
Spring Framework 2.5의 Annotation based Controller의 메서드 파라미터에서 주의점
Spring framework 버전 2.5에서는 어노테이션(Annotation)을 이용하여 URL과 웹 요청 처리 핸들러(handler)를 매핑할 수 있게 되었다. DefaultAnnotationHandlerMapping과 AnnotationHandlerAdapter 클래스로 이것이 가능해진 것인데, 이를 이용하면 핸들러 매핑을 편리하게 할 수 있을 뿐 아니라 웹 요청 파라미터(Request Parameter)에 대한 처리도 좀더 고수준으로 처리할 수 있게 된다. 즉 어노테이션을 이용하여 폼 커맨드(form command) 객체를 바인드(bind)하거나 원하는 웹 요청 파라미터를 추출하여 직접 핸들러 메서드의 파라미터로 넘길 수 있게 된 것이다. 이로써 웹 요청 처리 핸들러 메서드를 서블릿 API에 의존하지 않는 형태로 작성할 수 있게 되었다.
웹 요청 파라미터로 id 값을 받아서 그 값을 보여주는 기능을 구현하려면 이전에는 MultiActionController를 써서 다음과 같이 구현했다.
그러나 이제는 다음과 같이 처리할 수 있게 되었다.
핸들러 메서드의 파라미터로는 HttpServletRequest, HttpServletResponse, HttpSession 같은 서블릿 API의 객체, 폼 커맨드 객체, Map, Model 등의 모델 객체, InputStream, Reader, OutputStream, Writer 등의 IO 객체가 순서에 상관없이 올 수 있다. 확실히 이전 MultiActionController에 비하면 상당히 편해진 셈이다. 그외 일반적인 데이타 타입(정수 또는 문자열 등)은 @RequestParam 어노테이션으로 메서드의 파라미터를 표시하면 그 @RequestParam 어노테이션의 value와 같은 이름의 웹 요청 파라미터를 추출해서 메서드에 넘겨준다.
그런데 여기서 문제가 되는 것은, 특별한 경우 @RequestParam 어노테이션으로 표시하지 않아도 메서드 파라미터와 같은 이름의 웹 요청 파라미터의 값을 넘겨 줄 수 있다는 것이다. 즉 다음과 같은 일이 가능한 것이다.
많은 경우 - 특히 이클립스에서 실행해 보는 경우 - 이 기능은 잘 동작한다. 매우 편리한 기능인데 "와~ 스프링 킹왕짱!" 하고 감탄 한 번 한 다음 잘 써먹어도 되는지는 좀 더 따져봐야 한다. 왜냐하면 이 기능이 잘 동작한다면 다음 세 가지 물음에 대하여 어떤 식으로든 답이 있어야 하기 때문이다.
첫째로 그럴꺼면 왜 @RequestParam 어노테이션이 있냐는 것이다. 두 기능은 중복되며 어노테이션을 안 사용하는 게 훨씬 편하다. 중복된 기능이 그것도 더 불편한 방식이 존재하는 이유는 무얼까? 둘째로 왜 이러한 기능이 참조 문서나 API 문서에는 언급되어 있지 않느냐는 거다. 세째로는 과연 자바에서 이게 정상적으로 가능하냐는 거다. 자바의 reflection 기능을 생각해 보면 의문이 들 수밖에 없다. 자바 reflection에서는 메서드 파라미터의 갯수나 타입은 알 수 있지만 이름은 알 수 없기 때문이다. 이 물음에 답하기 위해 spring의 소스를 한번 뒤져 보았다.
Spring MVC 에서는 DispatcherServlet이 모든 웹 요청을 받아서 HandlerMapping 빈을 이용하여 요청에 해당하는 핸들러를 찾고 HandlerAdapter 빈을 이용하여 실제 요청을 처리한다. 어노테이션을 이용한 웹 요청 처리에는 DefaultAnnotationHandlerMapping 과 AnnotationHandlerAdpter 클래스가 관련되어 있다.
웹 요청은 AnnotationMethodHandlerAdpter 클래스의 handle 메서드가 처리하는데 이 메서드는 다시 invokeHandlerMethod를 호출한다. 이 메서드는 MethodResolver 빈을 이용하여 핸들러의 해당 메서드를 찾은 다음 이 메서드를 ServletHandlerMethodInvoker(HandlerMethodInvoker의 서브 클래스) 객체로 감싼 다음 이 객체의 invokeHandlerMethod 메서드를 호출한다. invokeHandlerMethod 메서드에서는 핸들러 메서드의 파라미터 타입 목록 정보를 이용하여 웹 요청 객체(HttpServletRequest)로부터 파라미터들을 생성하여 이를 파라미터로 핸들러 메서드를 호출한다. 파라미터들을 생성하는 메서드가 resolveHandlerArgument 메서드인데 여기에 비밀(?)이 숨겨져 있다.
이 메서드는 핸들러 메서드의 파라미터 타입 배열을 얻은 다음 배열 크기만큼 루프를 돈다. 루프 안에서 각 파라미터마다 처리를 하는데 처리 루틴은 다음과 같다.
152줄에서 파라미터에 표시되어 있는 어노테이션을 얻는다. 그 후 154줄에서 RequestParam 또는 ModelAttribute 어노테이션인지 알아내서 각각 paramName 또는 attrName 변수에 이름을 설정한다. 변수에 값이 설정되어 있지 않은 경우(174줄) resolveCommonArgument 메서드로 처리를 하여 파라미터 객체를 얻는다. 여기서 HttpServletRequest, HttpServletResponse, InputStream/OutputStream, HttpSession, Reader/Writer, Locale, Map, Model, SessionStatus, Errors 타입일 때 처리를 하는 것이다. 그리고 paramName 변수에 값이 설정되어 있으면(199줄) resolveRequestParam 메서드를 호출하여 RequestParameter 처리를 하고 attrName 변수에 값이 설정되어 있으면 (202줄) resolveModelAttribute 메서드를 호출하여 command 객체 바인딩을 한다.
파라미터에 어노테이션이 표시되어 있지 않고 그 타입이 미리 처리할 수 있는 경우(HttpSerlvetRequest 등등일 경우)가 아니라면 190줄, 193줄에서 처리하게 된다. 만약 파라미터의 타입이 단순 데이타 타입이면 paramName 변수를 빈이면 attrName 변수에 빈 문자열로 값을 설정한다. 그렇게 되어 이후 199, 202줄에서 처리되게 되는 것이다. 즉 만약 핸들러 메서드 형식이
과 같이 되어 있다면 190번 줄을 거쳐 199번으로 넘어가 resolveRequestParam으로 처리가 넘어가서 파라미터 값을 얻게 되는 것이다. 그렇다면 resolveRequestParam 메서드는 어떻게 되어 있을까? resolveRequestParam 메서드의 첫부분은 다음과 같다.
paramName 변수는 이 메서드의 인자로 RequestParam 어노테이션을 사용한 경우 이 어노테이션의 value() 값이며, 어노테이션이 표시되어 있지 않은 경우(즉 문제가 되는 경우)는 빈 문자열("")이다. paramName 변수 값이 빈 문자열이면 311줄이 실행된다. 여기서는 methodParam 객체의 getParameterName 메서드를 호출하여 파라미터의 이름을 얻어온다. 여기서 이름을 제대로 얻어오면 (위의 경우 "id") 이어지는 처리에서 제대로 웹 요청 인자값을 추출할 수 있게 된다. 그러면 methodParam 객체는 어떤 객체일까? 이 객체는 MethodParameter 클래스의 인스턴스이며 SpringFramework API 문서에서 getParamenterName 메서드는 다음과 같이 설명하고 있다.
그리고 ParameterNameDiscoverer 클래스의 설명을 보면 다음과 같이 되어 있다.
즉 getParameterName()는 메서드 파라미터의 이름을 컴파일할 때 컴파일러가 생성하는 디버그 정보나 어노테이션 등을 이용하여 얻어내는 것이다. 만약 클래스 파일로 컴파일할 때 디버그 정보를 포함시키지 않았다면 이 메서드는 null을 반환하게 된다.
정리하면 핸들러 메서드의 파라미터 이름으로 웹 요청에서 값을 추출할 수 있는 기능은 컴파일할 때 디버그 정보를 포함시켜 컴파일했을 경우에만 동작한다는 거다. 만약 디버그 옵션을 끄고 컴파일한다면 제대로 동작하지 않는 것이다.
이를 테스트해보기 위하여 간단히 실험을 해봤다. 값을 받아 단순히 출력하는 controller를 다음과 같이 작성하였다.
이를 일반적인 이클립스 환경에서 실행하면 잘 실행된다.

그러나 이클립스의 빌드 설정을 다음과 같이 바꿔서 파라미터 이름에 대한 디버그 정보를 포함하지 않고 컴파일한 후 실행하면 에러가 발생한다.


정리하면 웹 요청 핸들러 메서드에서 웹 요청에서 값을 추출하여 메서드 파라미터로 받으려면 반드시 RequestParam 어노테이션을 사용하는 것이 좋다. 메서드 파라미터와 같은 이름의 웹 요청 파라미터를 추출할 수 있는 경우도 있지만 이는 언제나 그런 것은 아니기 때문에 주의해야 한다. 무심코 사용했다가는 개발 환경과 테스트 환경에서는 잘 동작하다가도(빌드시 디버그 옵션을 켜 놓았기 때문에) 실 환경에서 에러가 발생하게 되는 경우(실 환경을 위해 빌드할 경우 보통 디버그 정보를 끄고 컴파일한다)가 생긴다.
이 경우도 그렇지만 자바로 개발할 때에는 항상 Specification 문서와 참조 매뉴얼, API 문서 등을 확인하여 규약대로 기능을 구현해야 한다. 해보니까 동작하더라는 식으로 개발하여 구현에 의존하게 되면 환경이 바뀌면서 다르게 동작하여 찾기 힘든 버그가 발생하게 된다. 또한 자바의 경우 많은 라이브러리들이 오픈 소스이므로 미진한 사항은 항상 소스를 참조하여 해결하는 게 좋을 것 같다.
웹 요청 파라미터로 id 값을 받아서 그 값을 보여주는 기능을 구현하려면 이전에는 MultiActionController를 써서 다음과 같이 구현했다.
public ModelAndView read(HttpServletRequest request, HttpServletResponse response) throws Exception {
int id = Integer.parseInt(request.getParameter("id"));
PrintWriter out = response.getWriter();
out.println("value: id=" + id);
}
그러나 이제는 다음과 같이 처리할 수 있게 되었다.
@RequestMapping("/read")
public void read(@RequestParam("id") int id, Writer out) throws Exception {
out.write("value: id=" + id);
}
핸들러 메서드의 파라미터로는 HttpServletRequest, HttpServletResponse, HttpSession 같은 서블릿 API의 객체, 폼 커맨드 객체, Map, Model 등의 모델 객체, InputStream, Reader, OutputStream, Writer 등의 IO 객체가 순서에 상관없이 올 수 있다. 확실히 이전 MultiActionController에 비하면 상당히 편해진 셈이다. 그외 일반적인 데이타 타입(정수 또는 문자열 등)은 @RequestParam 어노테이션으로 메서드의 파라미터를 표시하면 그 @RequestParam 어노테이션의 value와 같은 이름의 웹 요청 파라미터를 추출해서 메서드에 넘겨준다.
그런데 여기서 문제가 되는 것은, 특별한 경우 @RequestParam 어노테이션으로 표시하지 않아도 메서드 파라미터와 같은 이름의 웹 요청 파라미터의 값을 넘겨 줄 수 있다는 것이다. 즉 다음과 같은 일이 가능한 것이다.
@RequestMapping("/read")
public String read(int id, Writer out) throws Exception {
out.write("value: id=" + id);
}
많은 경우 - 특히 이클립스에서 실행해 보는 경우 - 이 기능은 잘 동작한다. 매우 편리한 기능인데 "와~ 스프링 킹왕짱!" 하고 감탄 한 번 한 다음 잘 써먹어도 되는지는 좀 더 따져봐야 한다. 왜냐하면 이 기능이 잘 동작한다면 다음 세 가지 물음에 대하여 어떤 식으로든 답이 있어야 하기 때문이다.
첫째로 그럴꺼면 왜 @RequestParam 어노테이션이 있냐는 것이다. 두 기능은 중복되며 어노테이션을 안 사용하는 게 훨씬 편하다. 중복된 기능이 그것도 더 불편한 방식이 존재하는 이유는 무얼까? 둘째로 왜 이러한 기능이 참조 문서나 API 문서에는 언급되어 있지 않느냐는 거다. 세째로는 과연 자바에서 이게 정상적으로 가능하냐는 거다. 자바의 reflection 기능을 생각해 보면 의문이 들 수밖에 없다. 자바 reflection에서는 메서드 파라미터의 갯수나 타입은 알 수 있지만 이름은 알 수 없기 때문이다. 이 물음에 답하기 위해 spring의 소스를 한번 뒤져 보았다.
Spring MVC 에서는 DispatcherServlet이 모든 웹 요청을 받아서 HandlerMapping 빈을 이용하여 요청에 해당하는 핸들러를 찾고 HandlerAdapter 빈을 이용하여 실제 요청을 처리한다. 어노테이션을 이용한 웹 요청 처리에는 DefaultAnnotationHandlerMapping 과 AnnotationHandlerAdpter 클래스가 관련되어 있다.
웹 요청은 AnnotationMethodHandlerAdpter 클래스의 handle 메서드가 처리하는데 이 메서드는 다시 invokeHandlerMethod를 호출한다. 이 메서드는 MethodResolver 빈을 이용하여 핸들러의 해당 메서드를 찾은 다음 이 메서드를 ServletHandlerMethodInvoker(HandlerMethodInvoker의 서브 클래스) 객체로 감싼 다음 이 객체의 invokeHandlerMethod 메서드를 호출한다. invokeHandlerMethod 메서드에서는 핸들러 메서드의 파라미터 타입 목록 정보를 이용하여 웹 요청 객체(HttpServletRequest)로부터 파라미터들을 생성하여 이를 파라미터로 핸들러 메서드를 호출한다. 파라미터들을 생성하는 메서드가 resolveHandlerArgument 메서드인데 여기에 비밀(?)이 숨겨져 있다.
이 메서드는 핸들러 메서드의 파라미터 타입 배열을 얻은 다음 배열 크기만큼 루프를 돈다. 루프 안에서 각 파라미터마다 처리를 하는데 처리 루틴은 다음과 같다.
146: MethodParameter methodParam = new MethodParameter(handlerMethod, i);
....
152: Object[] paramAnns = methodParam.getParameterAnnotations();
154: for (int j = 0; j < paramAnns.length; j++) {
Object paramAnn = paramAnns[j];
if (RequestParam.class.isInstance(paramAnn)) {
RequestParam requestParam = (RequestParam) paramAnn;
paramName = requestParam.value();
paramRequired = requestParam.required();
break;
}
else if (ModelAttribute.class.isInstance(paramAnn)) {
ModelAttribute attr = (ModelAttribute) paramAnn;
attrName = attr.value();
}
}
....
Class paramType = methodParam.getParameterType();
174: if (paramName == null && attrName == null) {
175: Object argValue = resolveCommonArgument(methodParam, webRequest);
if (argValue != WebArgumentResolver.UNRESOLVED) {
args[i] = argValue;
}
else {
if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
args[i] = implicitModel;
....
190: else if (BeanUtils.isSimpleProperty(paramType)) {
paramName = "";
}
193: else {
attrName = "";
}
}
}
199: if (paramName != null) {
args[i] = resolveRequestParam(paramName, paramRequired, methodParam, webRequest, handler);
}
202: else if (attrName != null) {
WebDataBinder binder = resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
....
152줄에서 파라미터에 표시되어 있는 어노테이션을 얻는다. 그 후 154줄에서 RequestParam 또는 ModelAttribute 어노테이션인지 알아내서 각각 paramName 또는 attrName 변수에 이름을 설정한다. 변수에 값이 설정되어 있지 않은 경우(174줄) resolveCommonArgument 메서드로 처리를 하여 파라미터 객체를 얻는다. 여기서 HttpServletRequest, HttpServletResponse, InputStream/OutputStream, HttpSession, Reader/Writer, Locale, Map, Model, SessionStatus, Errors 타입일 때 처리를 하는 것이다. 그리고 paramName 변수에 값이 설정되어 있으면(199줄) resolveRequestParam 메서드를 호출하여 RequestParameter 처리를 하고 attrName 변수에 값이 설정되어 있으면 (202줄) resolveModelAttribute 메서드를 호출하여 command 객체 바인딩을 한다.
파라미터에 어노테이션이 표시되어 있지 않고 그 타입이 미리 처리할 수 있는 경우(HttpSerlvetRequest 등등일 경우)가 아니라면 190줄, 193줄에서 처리하게 된다. 만약 파라미터의 타입이 단순 데이타 타입이면 paramName 변수를 빈이면 attrName 변수에 빈 문자열로 값을 설정한다. 그렇게 되어 이후 199, 202줄에서 처리되게 되는 것이다. 즉 만약 핸들러 메서드 형식이
public String read(int id) throws Exception { .... }
과 같이 되어 있다면 190번 줄을 거쳐 199번으로 넘어가 resolveRequestParam으로 처리가 넘어가서 파라미터 값을 얻게 되는 것이다. 그렇다면 resolveRequestParam 메서드는 어떻게 되어 있을까? resolveRequestParam 메서드의 첫부분은 다음과 같다.
309: Class paramType = methodParam.getParameterType();
if ("".equals(paramName)) {
311: paramName = methodParam.getParameterName();
if (paramName == null) {
throw new IllegalStateException("No parameter specified for @RequestParam argument of type [" +
paramType.getName() + "], and no parameter name information found in class file either.");
}
}
Object paramValue = null;
if (webRequest.getNativeRequest() instanceof MultipartRequest) {
paramValue = ((MultipartRequest) webRequest.getNativeRequest()).getFile(paramName);
}
if (paramValue == null) {
322: String[] paramValues = webRequest.getParameterValues(paramName);
if (paramValues != null) {
paramValue = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
paramName 변수는 이 메서드의 인자로 RequestParam 어노테이션을 사용한 경우 이 어노테이션의 value() 값이며, 어노테이션이 표시되어 있지 않은 경우(즉 문제가 되는 경우)는 빈 문자열("")이다. paramName 변수 값이 빈 문자열이면 311줄이 실행된다. 여기서는 methodParam 객체의 getParameterName 메서드를 호출하여 파라미터의 이름을 얻어온다. 여기서 이름을 제대로 얻어오면 (위의 경우 "id") 이어지는 처리에서 제대로 웹 요청 인자값을 추출할 수 있게 된다. 그러면 methodParam 객체는 어떤 객체일까? 이 객체는 MethodParameter 클래스의 인스턴스이며 SpringFramework API 문서에서 getParamenterName 메서드는 다음과 같이 설명하고 있다.
Return the name of the method/construct parameter
Returns:
the parameter name (may be null if no parameter name metadata is contained in the class file or no ParameterNameDiscoverer has been set to begin with)
그리고 ParameterNameDiscoverer 클래스의 설명을 보면 다음과 같이 되어 있다.
Parameter name discovery is not always possible, but various strategies are available to try, such as looking for debug information that may have been emitted at compile time, and looking for argname annotation values optionally accompanying AspectJ annotated methods.
즉 getParameterName()는 메서드 파라미터의 이름을 컴파일할 때 컴파일러가 생성하는 디버그 정보나 어노테이션 등을 이용하여 얻어내는 것이다. 만약 클래스 파일로 컴파일할 때 디버그 정보를 포함시키지 않았다면 이 메서드는 null을 반환하게 된다.
정리하면 핸들러 메서드의 파라미터 이름으로 웹 요청에서 값을 추출할 수 있는 기능은 컴파일할 때 디버그 정보를 포함시켜 컴파일했을 경우에만 동작한다는 거다. 만약 디버그 옵션을 끄고 컴파일한다면 제대로 동작하지 않는 것이다.
이를 테스트해보기 위하여 간단히 실험을 해봤다. 값을 받아 단순히 출력하는 controller를 다음과 같이 작성하였다.
@Controller
public class ExampleController {
@RequestMapping("/index")
public void index(Writer out, int id) throws Exception {
out.write("value id=" + id);
}
}
이를 일반적인 이클립스 환경에서 실행하면 잘 실행된다.

그러나 이클립스의 빌드 설정을 다음과 같이 바꿔서 파라미터 이름에 대한 디버그 정보를 포함하지 않고 컴파일한 후 실행하면 에러가 발생한다.


정리하면 웹 요청 핸들러 메서드에서 웹 요청에서 값을 추출하여 메서드 파라미터로 받으려면 반드시 RequestParam 어노테이션을 사용하는 것이 좋다. 메서드 파라미터와 같은 이름의 웹 요청 파라미터를 추출할 수 있는 경우도 있지만 이는 언제나 그런 것은 아니기 때문에 주의해야 한다. 무심코 사용했다가는 개발 환경과 테스트 환경에서는 잘 동작하다가도(빌드시 디버그 옵션을 켜 놓았기 때문에) 실 환경에서 에러가 발생하게 되는 경우(실 환경을 위해 빌드할 경우 보통 디버그 정보를 끄고 컴파일한다)가 생긴다.
이 경우도 그렇지만 자바로 개발할 때에는 항상 Specification 문서와 참조 매뉴얼, API 문서 등을 확인하여 규약대로 기능을 구현해야 한다. 해보니까 동작하더라는 식으로 개발하여 구현에 의존하게 되면 환경이 바뀌면서 다르게 동작하여 찾기 힘든 버그가 발생하게 된다. 또한 자바의 경우 많은 라이브러리들이 오픈 소스이므로 미진한 사항은 항상 소스를 참조하여 해결하는 게 좋을 것 같다.
# by | 2008/06/25 15:02 | 트랙백 | 핑백(3) | 덧글(1)
◀ 이전 페이지다음 페이지 ▶



















