본문 바로가기

[Spring] Spring MVC - 스프링이 제공하는 컨트롤러

@xuv22025. 6. 23. 21:40

현대의 스프링이 제공하는 컨트롤러는 애노테이션 기반으로 동작하여 유연하고 실용적이다.


@RequestMapping

스프링의 애노테이션을 활용한 가장 실용적인 방법은 바로 @RequestMapping 애노테이션을 사용하는 컨트롤러이다.

@RequestMapping 애노테이션을 사용한 컨트롤러가 사용하는 핸들러 매핑은 RequestMappingHandlerMapping 이고, 이에 맞는 핸들러 어댑터는 RequestMappingHandlerAdapter을 사용한다.

기존에 직접 구현했던 MVC 패턴을 이제 스프링 MVC를 사용하여 구현해보자

그전에, 먼저 뷰 리졸버의 기능을 사용하기 위해 application.properties에 다음과 같은 코드를 반드시 추가해야한다.

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

해당 코드를 추가하면, 컨트롤러가 반환하는 ModelAndView에서 View 이름이 절대 경로로 매핑 되어 뷰가 실행 될 수 있다.


@RequestMapping 컨트롤러

회원 등록

package hello.servlet.web.springmvc.v1;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class SpringMemberFormControllerV1 {

    @RequestMapping( "/springmvc/v1/members/new-form")
    public ModelAndView process() {
        return new ModelAndView("new-form");
    }
}

@Controller 애노테이션은 컴포넌트 스캔 대상이다 -> 즉 해당 클래스는 자동으로 스프링 빈으로 등록된다.

@RequestMapping 에노테이션은 요청 정보를 매핑하는 에노테이션으로써, 지정한 URL이 호출되면 해당 process 메서드가 실행된다.

그리고 반환은 ModelAndView로 해주면 된다.

 

회원 저장

package hello.servlet.web.springmvc.v1;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class SpringMemberSaveControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/springmvc/v1/members/save")
    public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);

        ModelAndView mv = new ModelAndView("save-result");
        mv.addObject("member", member);
        return mv;
    }
}

스프링이 제공하는 ModelAndView에 있는 Model에 데이터를 저장하기 위해서는 mv.addObject() 메서드를 사용하면 된다.

회원 조회

package hello.servlet.web.springmvc.v1;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

@Controller
public class SpringMemberListControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/springmvc/v1/members")
    public ModelAndView process() {

        List<Member> members = memberRepository.findAll();
        ModelAndView mv = new ModelAndView("members");
        mv.addObject("members", members);

        return mv;
    }
}

세 코드 모두 RequestMapping에 설정된 URL주소로 요청이 오면 process() 메서드를 실행하여 Model 데이터를 통해 뷰를 렌더링한다.


컨트롤러 합치기 (@RequestMapping 편의 기능)

위 코드는 현재 3가지 기능을 분리해서 각각 클래스로 관리하고 있는데, 3개의 코드를 하나의 컨트롤러로 병합할 수 있다.

package hello.servlet.web.springmvc.v2;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

@Controller
@RequestMapping("/springmvc/v2/members")
public class SpringMemberControllerV2 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("new-form")
    public ModelAndView newForm() {
        return new ModelAndView("new-form");
    }

    @RequestMapping("save")
    public ModelAndView save(HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);

        ModelAndView mv = new ModelAndView("save-result");
        mv.addObject(member);
        return mv;
    }

    @RequestMapping
    public ModelAndView members() {
        List<Member> members = memberRepository.findAll();
        ModelAndView mv = new ModelAndView("members");
        mv.addObject("members", members);
        return mv;
    }
}

꿀팁 아닌 꿀팁은 @RequestMapping을 클래스 레벨에 적어두고, 이후 각 메서드별 @RequestMapping 에노테이션을 적으면, [클래스 레벨 URL + 메서드 레벨 URL] 을 통해 URL 계층을 편리하게 관리할 수 있다.


실용적인 방식의 Spring MVC - HTTP 메서드 에노테이션

스프링 MVC는 개발자 친화적으로 설계되어 수많은 편의 기능이 존재한다.

package hello.servlet.web.springmvc.v3;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

import static org.springframework.web.bind.annotation.RequestMethod.*;

@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @GetMapping("/new-form")
    public String newForm() {
        return "new-form";
    }

    @PostMapping("/save")
    public String save(
            @RequestParam("username") String username,
            @RequestParam("age") int age,
            Model model) {

        Member member = new Member(username, age);
        memberRepository.save(member);

        model.addAttribute("member", member);
        return "save-result";
    }

    @GetMapping
    public String members(Model model) {
        List<Member> members = memberRepository.findAll();

        model.addAttribute("members", members);
        return "members";
    }
}

기존에는 반드시 ModelAndView를 반환 받았는데, 이를 해소하는 편의 기능들이 존재한다.

먼저, save()나 members() 를 보면 Model을 파라미터로 받는다. ModelAndView의 addObject() 메서드를 쓰지 않고도 단순 모델만 호출하여 데이터를 저장해둘 수 있다.

또한 반환 타입을 String으로 하여 뷰의 논리적인 이름만 반환하면 뷰 리졸버를 통해 절대 경로로 매핑이 가능하다.

그리고 @RequestParam("username") 과 같은 에노테이션을 제공하여 HTTP 요청 파라미터를 바로 사용할 수 있다. (request.getParameter("username")이랑 비슷하다)

 

또 중요한 기능 중 하나는 HTTP 메서드별로 에노테이션을 제공한다는 점인데, 기존에 @RequestMapping 에노테이션은 HTTP 메서드를 구분하는 옵션을 제공했다. 예를 들면 아래처럼 쓸 수 있다.

@RequestMapping(value = "/new-form", method = RequestMethod.GET)

하지만 스프링은 가독성을 위해 HTTP 메서드의 이름을 딴 에노테이션을 각각 제공한다.

@GetMapping("/new-form")
@PostMapping("/save")

참고로 HTTP 메서드별 에노테이션은 모두 안에 @RequestMapping 에노테이션을 포함하고 있다.

xuv2
@xuv2 :: xuvlog

폭싹 늙었수다

목차