[스프링] 스프링5 프로그래밍 입문 - 11장 MVC 1: 요청 매핑, 커맨드 객체, 리다이렉트, 폼 태그, 모델

June·2021년 6월 11일
0

프로젝트 준비

요청 매핑 애노테이션을 이용한 경로 매핑

웹 어플리케이션을 개발하는 것은 다음 코드를 작성하는 것이다.

  1. 특정 요청 URL을 처리할 코드
  2. 처리 결과를 HTML과 같은 형식으로 응답하는 코드

첫 번째는 @Controller 애노테이션을 사용한 컨트롤러 클래스를 이용해서 구현한다. 컨트롤러 클래스는 요청 매핑 애노테이션을 사용해서 메서드가 처리할 요청 경로를 지정한다.

RegisterController

@Controller
public class RegisterController {

    @RequestMapping("/register/step1")
    public String handleStep1() {
        return "register/step1";
    }
}

GET과 POST 구분: @GetMapping, @PostMapping

스프링 MVC는 별도 설정이 없으면 GET과 POST 방식에 상관없이 @RequestMapping에 지정한 경로와 일치하는 요청을 처리한다.

@GetMapping 애노테이션과 @PostMapping 애노테이션은 스프링 4.3버전에 추가된 것으로 이전 버전까지는 다음 코드처럼 @RequestMapping 애노테이션의 method 속성을 사용해서 HTTP 방식을 제한했다.

public class LoginController {
    @RequestMapping(value = "/member/login", method = RequestMethod.GET)
    public String form() {
     ...
    }
}

요청 파라미터 접근

step1.jsp의 코드를 보면 약관에 동의할 경우 값이 true인 'agree'요청 파라미터의 값을 POST 방식을 전송한다.

컨트롤러 메서드에서 요청 파라미터를 사용하는 첫 번째 방법은 HttpServletRequest를 직접 이용하는 것이다.

@Controller
public class RegisterController {

	...

    @PostMapping("/register/step2")
    public String handleStep2(HttpServletRequest request) {
        String agreeParam = request.getParameter("agree");
        if (agreeParam == null || !agreeParam.equals("true")) {
            return "register/step1";
        }
        return "register/step2";
    }
}

요청 파라미터에 접근하는 또 다른 방법은 @RequestParam 애노테이션을 사용하는 것이다. 요청 파라미터 개수가 몇 개 안되면 이 애노테이션을 사용해서 간단하게 요청 파라미터의 값을 구할 수 있다.

@Controller
public class RegisterController {

	...

    @PostMapping("/register/step2")
    public String handleStep2(@RequestParam(value = "agree", defaultValue = "false") Boolean agree) {
        if (!agree) {
            return "register/step1";
        }
        return "register/step2";
    }
}

agree 요청 파라미터의 값을 읽어와 agree 하라미터에 할당한다. 요청 파라미터의 값이 없으면 "false" 문자열을 값으로 사용한다.

스프링 MVC는 파라미터 타입에 맞게 String 값을 변환해준다. 위 코드는 agree 요청 파라미터의 값을 읽어와 Boolean 타입으로 변환해서 agree 파라미터에 전달한다.

리다이렉트 처리

RegisterController 클래스의 handleStep2() 메서드는 POST 방식만을 처리하기 때문에 웹 브라우저에 직접 주소를 입력할 때 사용되는 GET 방식 요청은 처리하지 않는다. 그러면 사용자는 405 코드를 보게 된다.

잘못된 전송 방식으로 요청이 왔을 때 에러 화면보다 알맞은 경로로 리다이렉트하는 것이 더 좋을 때가 있다. 예를 들어 에러 화면 대신 약관 동의 화면으로 이동시키는 것이다.

"redirect:경로"를 뷰 이름으로 리턴하면 리다이렉트 된다.

@Controller
public class RegisterController {
	...
	
    // /register/step2 경로를 GET 방식으로 접근하면 리다이렉트 된다. 
    @GetMapping("/register/step2")
    public String handleStep2Get() {
        return "redirect:/register/step1";
    }
}

"redirect:" 뒤의 문자열이 "/"로 시작하면 웹 어플리케이션을 기준으로 이동 경로를 설정한다.
"/"로 시작하지 않으면 현재 경로를 기준으로 상대 경로를 사용한다.

request.getParamater() 방식은 요청 파라미터 개수가 많아지면 불편하다. 그래서 스프링은 요청 파라미터의 값을 커맨드(command) 객체에 담아주는 기능을 제공한다.

RegisterController

@Controller
public class RegisterController {
    
    private MemberRegisterService memberRegisterService;

    public void setMemberRegisterService(MemberRegisterService memberRegisterService) {
        this.memberRegisterService= memberRegisterService;
    }
    
   	...
    
    @PostMapping("/register/step3")
    public String handleStep3(RegisterRequest regReq) {
        try {
            memberRegisterService.regist(regReq);
            return "register/step3";
        } catch (DuplicateMemberException ex) {
            return "register/step2";
        }
    }
}

ControllerConfig

@Configuration
public class ControllerConfig {

    @Autowired
    private MemberRegisterService memberRegSvc;

    @Bean
    public RegisterController registerController() {
        RegisterController controller = new RegisterController();
        controller.setMemberRegisterService(memberRegSvc);
        return controller;
    }
}

뷰 JSP 코드에서 커맨드 객체 사용하기

스프링 MVC는 커맨드 객체의 (첫 글자를 소문자로 바꾼) 클래스 이름과 동일한 속성 이름을 사용해서 커맨드 객체를 뷰에 전달한다.

커맨드 객체이 사용할 속성 이름을 변경하고 싶다면 커맨드 객체로 사용할 파라미터에 @ModelAttribute 애노테이션을 적용하면 된다.

    @PostMapping("/register/step3")
    public String handleStep3(@ModelAttribute("formData") RegisterRequest regReq) {

    }

컨트롤러 구현 없는 경로 매핑

첫 화면이 단순히 호나영 문구와 회원 가입으로 이동할 수 있는 링크만 제공한다고 하자. 이것을 위해 컨트롤러를 만들면, 이 컨트롤러 코드는 요청 경로와 뷰 이름을 연결해주는 것에 불과하다. 단순 연결을 위해 특별한 로직이 없는 컨트롤러 클래스를 만드는 것은 성가시다.

WebMvcConfigurer 인터페이스의 addViewControllers() 메서드를 사용하면 이런 성가심을 없앨 수 있다.

MvcConfig

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

    ...

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/main").setViewName("main");
        
    }
}

커맨드 객체 : 중첩, 콜렉션 프로퍼티

Respondent

package survey;

public class Respondent {

    private int age;
    private String location;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }
}

AnsweredData

package survey;

import java.util.List;

public class AnsweredData {

    private List<String> responses;
    private Respondent res;

    public List<String> getResponses() {
        return responses;
    }

    public void setResponses(List<String> responses) {
        this.responses = responses;
    }
    
    public Respondent getRes() {
        return res;
    }

    public void setRes(Respondent res) {
        this.res = res;
    }
}

스프링 MVC는 커맨드 객체가 리스트 타입의 프로퍼티를 가졌거나 중첩 프로퍼티를 가진 경우에도 요청 파라미터의 값을 알맞게 커맨드 객체에 설정해주는 기능을 제공하고 있다.

  1. HTTP 요청 파라미터 이름이 "프로퍼티이름[인덱스]" 형식이면 List 타입 프로퍼티의 값 목록으로 처리한다.
  2. HTTP 요청 파라미터 이름이 "프로퍼티이름.프로퍼티이름"과 같은 형식이면 중첩 프로퍼티 값을 처리한다

SurveyController

@Controller
@RequestMapping("/survey")
public class SurveyController {

    @GetMapping
    public String form() {
        return "survey/surveyForm";
    }

    @PostMapping
    public String submit(@ModelAttribute("ansData") AnsweredData data) {
        return "survey/submitted";
    }
}

클래스의 @RequestMapping에만 경로를 지정했다. 이 경우 from() 메서드와 submit() 메서드가 처리하는 경로는 "/survey"가 된다. 즉 form() 메서드는 GET 방식의 "/survey" 요청을 처리하고 submit() 메서드는 POST 방식의 "/survey" 요청을 처리한다.

Model을 통해 컨트롤러에서 뷰에 데이터 전달하기

HelloController

@RequestMapping
public class HelloController {

    @RequestMapping("/hello")
    public String hello(Model model, @RequestParam(value = "name", required = false) String name) {
        model.addAttribute("greeting", "안녕하세요, " + name);
        return "hello";
    }
}

뷰에 데이터를 전달하는 컨트롤러는 두 가지를 하면 된다.

  • 요청 매핑 애노테이션이 적용된 메서드의 파라미터로 Model을 추가
  • Model 파라미터의 addAttribute() 메서드로 뷰에서 사용할 데이터 전달

addAttribute() 메서드의 첫 번째 파라미터는 속성 이름이다. 뷰 코드는 이 이름을 사용해서 데이터에 접근한다.

Question

public class Question {

    private String title;
    private List<String> options;

    public Question(String title, List<String> options) {
        this.title = title;
        this.options = options;
    }

    public Question(String title) {
        this(title, Collections.<String>emptyList());
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public List<String> getOptions() {
        return options;
    }

    public void setOptions(List<String> options) {
        this.options = options;
    }

    public boolean isChoice() {
        return options != null && !options.isEmpty();
    }
}

개별 설문 항목 데이터를 담기 위한 클래스다.

SurveyController

@Controller
@RequestMapping("/survey")
public class SurveyController {

    @GetMapping
    public String form(Model model) {
        List<Question> questions = createQuestions();
        model.addAttribute("questions", questions);
        return "survey/surveyForm";
    }

    private List<Question> createQuestions() {
        Question q1 = new Question("당신의 역할은 무엇입니까?", Arrays.asList("서버", "프론트", "풀스택"));
        Question q2 = new Question("많이 사용하는 개발도구는 무엇입니까?", Arrays.asList("이클립스", "인텔리J", "서브라임"));
        Question q3 = new Question("하고 싶은 말을 적어 주세요");

        return Arrays.asList(q1, q2, q3);
    }

    @PostMapping
    public String submit(@ModelAttribute("ansData") AnsweredData data) {
        return "survey/submitted";
    }
}

ModelAndView를 통한 뷰 선택과 모델 전달

지금까지 구현한 클래스는

  • Model을 이용해서 뷰에 전달한 데이터 설정
  • 결과를 보여줄 뷰 이름을 리턴

ModelAndView를 사용하면 이 두 가지를 한 번에 처리할 수 있다. ModelAndView는 모델과 뷰 이름을 함께 제공한다.

    @GetMapping
    public ModelAndVeiw form() {
        List<Question> questions = createQuestions();
        ModelAndView mav = new ModelAndView();
        mav.addObject("questions", questions);
        mav.setViewName("survey/surveyForm");
        return mav;
    }

0개의 댓글