보통 Web 개발 하면 Spring 이 자연스럽게 떠오른다. 그 만큼 보편화 되어 있다. 개발 경험이 있다면 @ModelAttribute 라는 어노테이션이 친숙할 것 이다. 아래 그림을 보자.



Spring 을 사용하면 대표적으로 떠오르는 Servlet 이 있다. Servlet 를 직접 개발 하면 HttpServletRequest.getParameter(String name) 을 직접 사용하지만, DispatcherServlet 는 name 값과 Model Class 의 field 가 mapping 되어 값을 넣어준다. 그 시점은 Controller 호출 되기 직전 이라고 보면 된다. 참 편리한 기능 이다.


여기서 한번 더 생각 해보자. List 로 받을 수는 없을 까? 여러 번의 시도 끝에 방법을 찾았다.


@RequestMapping(value="/sample", method=RequestMethod.POST)
public ModelAndView sample(List<Sample> sampleList) throws Exception {
    try {
        ModelAndView mav = new ModelAndView();

        // Doing...

        return mav;
    } catch (Exception e) {
        throw e;
    }
}


Controller 소스 중 일부 이다. @ModelAttribute 는 생략이 가능 한 어노테이션 이다. 방금 설명 했던 mapping 작업은 Object 기준으로 이루어 진다. 결국 위 소스는 무용지물 이다. 다른 방법으로 접근 해 보자.


public class Sample {

    private String sample1;
    private String sample2;
    private String sample3;
    private String files;

    private List<Sample> sampleList;

    // Doing...

}


Model Class 에 List 로 선언 하면 mapping 이 가능하다.


<table class="sample_table" border="0" cellspacing="0" summary="">
    <tr>
        <th scope="row">Sample 1</th>
        <td class="long">
            <input type="text" name="sampleList[index].sample1" />
        </td>
    </tr>
    <tr>
        <th scope="row">Sample 2</th>
        <td class="long">
            <input type="text" name="sampleList[index].sample2" />
        </td>
    </tr>
    <tr>
        <th scope="row">Sample 3</th>
        <td>
            <input type="text" name="sampleList[index].sample3" />
        </td>
    </tr>
    <tr>
        <th scope="row">Sample File</th>
        <td class="long">
            <input type="file" name="sampleList[index].files" />
        </td>
    </tr>
</table>


값은 전달 하는 Html 이다. name 을 주시 하자. Model Class 에서 선언 했던 List 값과 동일 하다. 배열을 사용하듯 name 값을 작성 한다.


@RequestMapping(value="/sample", method=RequestMethod.POST)
public ModelAndView sample(Sample sample) throws Exception {
    try {
        ModelAndView mav = new ModelAndView();

        logger.debug(sample.toString());

        // Doing...

        return mav;
    } catch (Exception e) {
        throw e;
    }
}


실제 요청을 받아 보자. log 을 보면 값을 확인 할 수 있을 것 이고, 이로서 흔히 게시글 N개 를 동시에 등록할 때 사용하면 편리하게 처리가 가능 하다.


참고 사이트


@Controller, @Service, @Repository, @Component 를 사용하여 Bean 을 생성 하는 도중 중복 되어 충돌이 나는 현상 이다.


Annotation-specified bean name 'class' for bean class [class] conflicts with existing, non-compatible bean definition of same name and class [class]


해당 Annotation 의 경우 value 값을 지정 하지 않으면 Class 의 첫번째 문자열을 소문자로 하여 Bean ID 값을 생성 한다. 같은 Class 를 value 값을 지정하지 않았기 때문 이다.


@Controller("myController") 이와 같이 value 값을 설정 하여 해결 하자.


Spring MVC 기능을 기반으로 개발 하면, 핵심 기능인 DI(Dependency Injection) 기능을 많이 사용하게 된다. 의존성 주입 이라고 보면 되겠다. setSample(new Sample()) 강제적으로 setter 할 필요 없이, Spring Container 가 @Inject, @Autowired, @Resource 를 사용하여 대신 setter 를 해준다고 보면 된다.


생각을 반대로 바꿔보자. Spring Container 에게 특정 객체를 달라고 요청 하는 것이다. 반대로 객체를 받아오는 경우를 DL(Dependency Lookup) 이라 한다. Spring ApplicationContext 에게 getBean(String id, class<?> clazz) 함수를 호출하여 반대로 필요한 객체를 받게 된다.


객체는 Bean Scope 의 영향을 받는다.


  • singleton: 그 말대로 하나의 객체만 존재 한다.
  • prototype: 객체를 매번 생성 하기 때문에, 다른 객체가 제공 된다.
  • request: http 요청을 기준으로 객체를 제공 한다. http 요청 생명 주기에 단 하나의 객체만 존재 한다. 다른 http 요청에 영향 받지 않는다.
  • session: session 발생을 기준으로 객체를 제공 한다. session 생명 주기에 단 하나의 객체만 존재 한다.
  • global session: 테스트를 해봐야 할 것 같다. (WAS의 ServletContext 와 같은 개념인듯 싶다.)


DL(Dependency Lookup) 의 사용을 위해서는 ApplicationContextAware 인터페이스 구현을 필요로 한다.


public class Sample implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void sample() {
        Target target = this.applicationContext.getBean("ID", Target.class); // DL

        // Doing...
    }
}


위 샘플 소스를 참조 하면 ApplicationContext 를 받아 왔다. 이제 반대로 객체를 받아서 사용해 보자. 중앙에서 관리 하는 기능을 구현해야 할 일이 있다면, 편하게 사용되는 것 같다.


설명


Controller 에 @InitBinder 기능을 추가로 사용 한다. WebDataBinder.class 를 인자로 받게 된다.

Sample


binder.registerCustomEditor(requiredType, field, propertyEditor);
  • requireType: 파라미터 Class
  • field: 파라미터 명
  • propertyEditor: 파라미터 처리 핸들러

해당 코드는 Integer[] 타입의 tests=123&tests=124&tests=125 요청을 처리 하는 핸들러 이다. 필요에 따라 setAsText 함수를 Override 하여 값 수정 후 setValue(text) 처리를 하자.


@InitBinder
protected void initBinder(WebDataBinder binder) {

    binder.registerCustomEditor(Integer[].class, "tests", new PropertyEditorSupport() {

        public void setAsText(String text) throws IllegalArgumentException {
            // text: field value

            if (text.indexOf("&") == -1) {
                setValue(Integer.valueOf(text.split("=")[1]));
                return;
            }

            List<Integer> tests = new ArrayList<Integer>();

            for (String test : text.split("&")) {
                tests.add(Integer.valueOf(test.split("=")[1]));
            }

            setValue(tests.toArray());
        }
    });
}


확인 하기


@RequestMapping(value="/request", method=RequestMethod.POST)
public @ResponseBody String request(Test test) throws Exception {
    return Arrays.toString(test.getTests());
}

참고 사이트


+ Recent posts