Spring/SpringMVC

스프링 MVC를 시작과 컨트롤러 통합 & 실용적인 방식

느리지만 꾸준하게 2022. 4. 28. 15:26

@RequestMapping

  • @RequestMapping 애노테이션 컨트롤러가 등장하면서, MVC 부분도 스프링의 한 부분이다.

 

@RequestMapping
  • RequestMappingHandlerMapping
  • RequestMappingHandlerAdapter

 

이제 본격적으로 컨트롤러를 @RequestMapping 기반의 스프링 MVC 컨트롤러로 변경해보자.

 

 

 

 

회원 등록 폼을 만들어보자.

SpringMemberFormControllerV1

package hello.setvlet.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() {
        // ModelAndView에 viewName을 넘겨주었다.
        return new ModelAndView("new-form");
    }
}

 

@RequestMapping url로 가보면 나온다.

  • @Controller
  • 스프링이 자동으로 스프링 빈으로 등록한다. (내부에 @Component 애노테이션이 있어서 컴포넌트 스캔의 대상이 된다.)
  • 스프링 MVC에서 애노테이션 기반 컨트롤러로 인식한다.

 

  • @RequestMapping: 요청 정보를 매핑한다. 해당 URL이 호출되면 메서드가 호출이 되고
  • 애노테이션을 기반으로 동작하기 때문에 메서드의 이름은 임의로 지으면 된다.
  • ModelAndView: 모델과 뷰 정보를 담아서 반환하면 된다.

 

 

 

컴포넌트 스캔을 통해 스프링 빈으로 등록해도 동작한다.

  • RequestMappingHandlerMapping은 스프링 빈 중에서 @RequestMapping 또는 @Controller가 클래스 레벨에 붙어 있는 경우에 매핑 정보로 인식한다.
// SpringMemberControllerV1

package hello.setvlet.web.springmvc.v1;

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

//@Controller
@Component
@RequestMapping
public class SpringMemberFormControllerV1 {
    @RequestMapping("/springmvc/v1/members/new-form")
    public ModelAndView process() {
        // ModelAndView에 viewName을 넘겨주었다.
        return new ModelAndView("new-form");
    }
}

 

 

그리고 SpringMemberFormControllerV1을 그대로 두고

package hello.setvlet.web.springmvc.v1;

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

@RequestMapping
public class SpringMemberFormControllerV1 {
    @RequestMapping("/springmvc/v1/members/new-form")
    public ModelAndView process() {
        // ModelAndView에 viewName을 넘겨주었다.
        return new ModelAndView("new-form");
    }
}

 

 

ServletApplication에 SpringBean을 직접 등록해도 된다.

// ServletApplication

package hello.setvlet;

import hello.setvlet.web.springmvc.v1.SpringMemberFormControllerV1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.view.InternalResourceView;
import org.springframework.web.servlet.view.InternalResourceViewResolver;


@ServletComponentScan // 서블릿 자동 등록
@SpringBootApplication
public class SetvletApplication {

   public static void main(String[] args) {
      SpringApplication.run(SetvletApplication.class, args);
   }

   @Bean
   SpringMemberFormControllerV1 springMemberFormControllerV1() {
      return new SpringMemberFormControllerV1();
   }

}

 

근데 컨트롤러는 그냥 SpringMemberFormControllerV1에 Controller라고 등록하는게 제일 깔끔하다.

// SpringMemberFormControllerV1

package hello.setvlet.web.springmvc.v1;

import org.springframework.stereotype.Component;
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");
    }
}

 

// ServletApplication Class

package hello.setvlet;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;


@ServletComponentScan
@SpringBootApplication
public class SetvletApplication {

   public static void main(String[] args) {
      SpringApplication.run(SetvletApplication.class, args);
   }
}

 

 

회원 저장 기능을 만들어보자.

 

MemberSaveControllerV3에서 아래 코드를 가져와서 MemberSaveControllerV1에 붙여주자.

ModelView => ModelAndView로 바꿈

// MemberSaveControllerV3

private MemberRepository memberRepository = MemberRepository.getInstance();

@Override
public ModelView process(Map<String, String> paramMap) {
    String username = paramMap.get("username");
    int age = Integer.parseInt(paramMap.get("age"));

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

    ModelView mv = new ModelView("save-result");
    mv.getModel().put("member", member);
    return mv;
}

 

// SpringMemberSaveControllerV1

package hello.setvlet.web.springmvc.v1;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;


@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;
    }

}

 

 

 

List도 만들어주자.

// SpringMemberSaveControllerV1

package hello.setvlet.web.springmvc.v1;

import hello.setvlet.domain.member.Member;
import hello.setvlet.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;
import java.util.Map;

@Controller
public class SpringMemberListControllerV1 {

    private final 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;
    }
}

 

성공적이다.

 

 

 

스프링 MVC - 컨트롤러를 통합해보자.

각 파일에서 가져와서 통합한게 끝이다.

  • RequestMapping은 메서드 단위로 되기 때문에 원하는 만큼 넣어줄 수 있다.
// SpringMemberControllerV2

package hello.setvlet.web.springmvc.v2;


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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

@Controller
public class SpringMemberControllerV2 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

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

    @RequestMapping("/springmvc/v2/members/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", member);
        return mv;
    }

    @RequestMapping("/springmvc/v2/members")
    public ModelAndView members() {

        List<Member> members = memberRepository.findAll();

        ModelAndView mv = new ModelAndView("members");
        mv.addObject("members", members);
        return mv;
    }
}

 

결과가 잘 나온다.

 

 

획기적인 방법으로 @RequestMapping을 맨위에 두고 중복되는 url을 간편하게 지정할 수 있다.

// SpringMemberControllerV2

package hello.setvlet.web.springmvc.v2;


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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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", member);
        return mv;
    }

//  /springmvc/v2/members가 되니까 이 부분은 안적어줘도 된다.
    @RequestMapping
    public ModelAndView members() {

        List<Member> members = memberRepository.findAll();

        ModelAndView mv = new ModelAndView("members");
        mv.addObject("members", members);
        return mv;
    }
}

 

결과가 잘 나온다.

 

정리를 해보자.

  • 컨트롤러 클래스를 통합하는 것을 넘어서 조합도 가능
  • 다음 코드는 /springmvc/v2/members라는 부분에 중복이 있다.

그래서 위에서 SpringMemberControllerV2에서 RequestMapping에 중복되는 url을 한번에 등록해 주었다.

@RequestMapping("/springmvc/v2/members/new-form")
@RequestMapping("/springmvc/v2/members")
@RequestMapping("/springmvc/v2/members/save")
@Controller
  @RequestMapping("/springmvc/v2/members")
  public class SpringMemberControllerV2 {}

 

 

 

실용적인 방식을 한 번 알아보자.

기존의 SpringMemberControllerV3에서 코드를 더 간단히 했다.

// SpringMemberV3 Class

package hello.setvlet.web.springmvc.v3;

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

import java.util.List;


/**
 * v3
 * Model 도입
 * ViewName 직접 반환
 * @RequestParam 사용
 * @RequestMapping -> @GetMapping, @PostMapping */
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {

        private MemberRepository memberRepository = MemberRepository.getInstance();

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

        @RequestMapping("/save")
        public String save(
                // username이라는 요청 파라미터 받고싶다.
                @RequestParam("username") String username,
                @RequestParam("age") int age,
                Model model) {

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

            // view에 이름 반환해준다.
            model.addAttribute("member", member);
            return "save-result";
        }

        @RequestMapping
        public String members(Model model) {

            List<Member> members = memberRepository.findAll();

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

 

결과가 SpringMVC3에서도 잘 나온다.

 

 

SpringMVC를 이제 Postman으로 api 테스트를 해보자. POST GET 둘다 아래와 같은 BODY 값이 나온다.

// SpringMemberControllerV3

package hello.setvlet.web.springmvc.v3;

import hello.setvlet.domain.member.Member;
import hello.setvlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;


/**
 * v3
 * Model 도입
 * ViewName 직접 반환
 * @RequestParam 사용
 * @RequestMapping -> @GetMapping, @PostMapping */
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {

        private MemberRepository memberRepository = MemberRepository.getInstance();

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

        @RequestMapping("/save")
        public String save(
                // username이라는 요청 파라미터 받고싶다.
                @RequestParam("username") String username,
                @RequestParam("age") int age,
                Model model) {

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

            // view에 이름 반환해준다.
            model.addAttribute("member", member);
            return "save-result";
        }

        @RequestMapping
        public String members(Model model) {

            List<Member> members = memberRepository.findAll();

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

 

 

 

아래와 같이 해주면 GET인 경우에만 호출이 된다.

 

처음 형태에서

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

 

value값을 지정해주고 method값을 넣어줬다.

@RequestMapping(value = "/new-form", method = RequestMethod.GET)
        public String newForm() {
            return "new-form";
        }
// MemberControllerV3 Class

package hello.setvlet.web.springmvc.v3;

import hello.setvlet.domain.member.Member;
import hello.setvlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;


/**
 * v3
 * Model 도입
 * ViewName 직접 반환
 * @RequestParam 사용
 * @RequestMapping -> @GetMapping, @PostMapping */
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {

        private MemberRepository memberRepository = MemberRepository.getInstance();

        @RequestMapping(value = "/new-form", method = RequestMethod.GET)
        public String newForm() {
            return "new-form";
        }

        @RequestMapping("/save")
        public String save(
                // username이라는 요청 파라미터 받고싶다.
                @RequestParam("username") String username,
                @RequestParam("age") int age,
                Model model) {

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

            // view에 이름 반환해준다.
            model.addAttribute("member", member);
            return "save-result";
        }

        @RequestMapping
        public String members(Model model) {

            List<Member> members = memberRepository.findAll();

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

 

 

그러면 save는? POST로 해주자. save 하는데 GET으로 하면? 어색하다. save는 조회하는 거니까 POST로 해주자.

@RequestMapping(value = "/save", method = RequestMethod.POST)
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";
}

 

 

members는?

GET으로 해주자.

@RequestMapping(method = RequestMethod.GET)
public String members(Model model) {

    List<Member> members = memberRepository.findAll();

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

 

잘 나온다.

 

 

정말 간단하게 한번 더 변경해보자. getmapping / postmapping를 써준다.

package hello.setvlet.web.springmvc.v3;

import hello.setvlet.domain.member.Member;
import hello.setvlet.domain.member.MemberRepository;
import org.apache.coyote.Request;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;


/**
 * v3
 * Model 도입
 * ViewName 직접 반환
 * @RequestParam 사용
 * @RequestMapping -> @GetMapping, @PostMapping */
@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";
        }
}

 

Model 파라미터

  • save(), members()를 보면 Model을 파라미터로 받는 것을 확인할 수 있다. 스프링 MVC도 이런 편의 기능을 제공한다.

 

 

ViewName 직접 반환

  • 뷰의 논리 이름을 반환할 수 있다.

 

@RequestParam 사용

  • 스프링은 HTTP 요청 파라미터를 @RequestParam으로 받는다.
  • @RequestParam("username")은 request.getParameter("username")와 같은 코드라고 생각하면 된다.

 

@RequestMapping @GetMapping, @PostMapping

@RequestMapping은 URL만 매칭하는게 아니고, HTTP Method도 함께 구분한다.

예를 들어 URL /new-form이고, HTTP Method가 GET인 경우 모두 만족하는 매핑을 하려면 다음과 같이 처리.

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

 

 

이것을 @GetMapping, @PostMapping으로 더 편리하게 사용할 수 있다.

Get, Post, Put, Delete, Patch 모두 애노테이션이 준비되어 있다.

 

@GetMapping 코드를 열어서 @RequestMapping 애노테이션을 내부에 가지고 있는 모습을 확인하자.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

<출처 김영한: 스프링 MVC 1편 - 벡앤드 웹 개발 핵심 기술>

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., -

www.inflearn.com

 

'Spring > SpringMVC' 카테고리의 다른 글

스프링MVC 기본기능  (0) 2022.04.29
SpringMVC 구조 정리  (0) 2022.04.28
뷰 리졸버  (0) 2022.04.28
핸들러 매핑과 핸들러 어댑터  (0) 2022.04.28
스프링 MVC 전체 구조  (0) 2022.04.25