개발자로 후회없는 삶 살기

spring PART.스프링 MVC 시작 본문

[백엔드]/[spring | 학습기록]

spring PART.스프링 MVC 시작

몽이장쥰 2023. 4. 13. 00:53

서론

※ 이 포스트는 다음 강의의 학습이 목표임을 밝힙니다.

https://www.inflearn.com/roadmaps/373

 

우아한형제들 최연소 기술이사 김영한의 스프링 완전 정복 - 인프런 | 로드맵

Spring, MVC 스킬을 학습할 수 있는 개발 · 프로그래밍 로드맵을 인프런에서 만나보세요.

www.inflearn.com

 

본론

- 스프링 MVC 전체 구조

우리가 만든 MVC와 실제 스프링 MVC와 비교하면서 알아보겠습니다.

 

그림을 보면 스프링 MVC와 똑같은 구조입니다. 이름에 좀 차이가 있는데 구조는 완전 똑같고 완전 똑같이 동작합니다.

 

1) 프론트가 디스패쳐 서블렛이고 스프링에서 제일 중요한 프론트 컨트롤러입니다.

2) 프론트에 구현한 핸들러를 찾기 위한 핸들러 맵핑 맵이 핸들러 맵핑입니다. 핸들러 어댑터가 있습니다.

3) 모델뷰는 모델엔뷰이고 뷰 리졸버를 우리는 메서드로 만들었었는데 스프링은 인터로 만들어놨습니다. 스프링 용 뷰 리졸버가 따로 있습니다. Myview는 view로 인터페이스입니다.

 

 

- 디스패쳐 서블릿 구조 살펴보기

스프링도 프론트 컨트롤러 패턴으로 구현되어있습니다. 디스패쳐 서블릿도 서블릿으로 extends HttpServlet을 하고 있습니다. 스프링 부트가 디스패쳐를 컨테이너에 서블릿으로 자동으로 등록합니다. urlPatterns를 "/"로 해서 모든 경로 호출이 다 얘가 호출이 됩니다. 원래도 프론트가 모든 요청을 받고 핸들러 맵핑에서 자세한 요청으로 그에 맞는 컨트롤러를 찾았었습니다.

 

-> 요청 흐름

중요한 건 디스패쳐 서블릿의 doDispatch 메서드가 실행이 되고 이게 제일 중요한 핵심 코드입니다.

> 살짝 보면 getHandler가 있습니다.

 

그 다음에 이 찾은 핸들러를 넘겨서 핸들러 어댑터를 찾습니다.

 

핸들러 어댑터를 찾는 로직도 for로 루프 돌면서 어댑터의 서포트로 T, F해서 똑같이 어댑터를 반환합니다.

 

> 핸들러 어댑터도 서포트와 핸들 메서드를 가집니다. 서포트가 어댑터 찾는 거였고 핸들 메서드가 내부에 프론트가 했던 비즈니스 로직 호출을 대신 호출해주는 녀석이었습니다. 그리고 핸들러 어댑터로 실제 컨트롤러를 호출합니다. 반환을 모델 and 뷰를 받습니다.

 

받은 모델 엔 뷰로 View 객체를 만들고 render로 뷰를 호출합니다.

 

-> SpringMVC 구조

1) 핸들러 조회 : 핸들러 맵핑을 통해 요청 URL에 매핑된 핸들러를 조회합니다. 프론트로 들어오는 요청으로 실제 실행될 핸들러(컨트롤러)를 찾는 것입니다.

 

2) 핸들러 어댑터 조회 : 핸들러 목록에서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터를 조회합니다. 우리가 만든 MVC에서는 MyAdapters List가 있어서 거기서 for문으로 어댑터들의 서포트 메서드로 찾은 핸들러에 맞는 어댑터를 찾습니다.

 

3) 핸들러 어댑터 실행 : 실제 핸들러(실제 컨트)를 실행합니다. 그때 실행하는 핸들 메서드는 그 안에 실제 컨트롤러의 로직이 있었고 핸들 메서드가 실제 컨트롤러의 로직을 대신 호출하고 무조건 모델 and 뷰를 반환하도록 설계가 되어있습니다. 무조건 모델 뷰를 반환합니다.

 

4) 뷰 리졸버 호출 : 우리가 만든 것에서는 받은 모델 뷰에서 viewname을 찾고 그것을 뷰 리졸버에 인자로 줘서 myview인 뷰 호출 전담 마크하는 애를 반환했습니다.

 

5) 뷰 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고 뷰 객체를 반환합니다.

 

6) 뷰 랜더링 : 뷰를 통해서 뷰를 렌더링하고 html 응답을 합니다.

 

※ 스프링 MVC의 강점은 디스패쳐 서플릿 코드 변경없이 원하는 기능을 확장할 수 있다는 것입니다. 내가 원하는 컨트롤러, 컨트롤러 어댑터를 구현하고 디스패쳐에 사용할 수 있게하면 나만의 컨트롤러를 만들 수도 있습니다.

 

-> 정리

어댑터도 진짜 많고 디스패쳐도 코드가 많습니다. 이 복잡한 내부 구조를 다 파악하는 것은 쉽지 않습니다. 사실 나만의 컨트롤러를 만드는 일은 없습니다. 왜냐면 스프링이 다 만들어놨습니다. 개발자가 원하는 기능은 스프링에 다 만들어져 있으니 검색하면 다 찾을 수 있습니다.

> 근데도 핵심 동작 방식을 알아두어야 하는 이유는 향후 문제가 발생했을 때 " 아 이거는 핸들러 어댑터 문제구나!"처럼 문제를 파악하기 쉽기 때문이고 확장 포인트가 필요할 때 큰 그림을 알아야 어떤 부분을 확장해야 하는지 감을 잡을 수 있습니다.

 

- 핸들러 맵핑과 어댑터

스프링이 제공하는 핸들러와 어댑터를 보자 여기에 어노테이션 기반 핸들러도 있습니다. 그 이전에 사용하던 핸들러를 한번 보겠습니다.

 

- 컨트롤러 인터페이스 방식

옛날에는 이 컨트롤러가 컨트롤러 인터페이스를 구현해 사용했습니다. 우리 v3 버전과 유사합니다. 이는 @controller를 쓰는 핸들러 방식과 아예 다른 것입니다. 그냥 이걸 구현해보면서 스프링의 핸들러의 동작 방식을 이해해보겠습니다.

 

-> 구현

패키지를 springmvc라고 만듭니다. Old 패키지를 만들고 옛날에 이렇게 했음을 알아보겠습니다. Old 컨트롤러를 구현할 것이고 이게 진짜 우리가 이전에 인터를 구현한 실제 컨트롤러입니다.

 

public class MemberFromControllerV3 implements ControllerV3 {
    @Override
    public ModelView process(Map<String, String> paramMap) {
        return new ModelView("new-form");
    }
}

V3 방식에서 실제 컨트롤러는 이 모양이었고 핸들러 어댑터에서 핸들 메서드에서 저 process를 호출했었습니다.

 

@Component("/springmvc/old-controller")
public class OldController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("OldController.handleRequest");
        return null;
    }
}

그러니 우리 MVC에서 컨트롤러를 구현할 때처럼 옛날에 사용하던 Controller(@ 아님)를 implements 합니다. 핸들 req 메서드를 구현해야 합니다. 그냥 찍어만 보겠습니다. 스프링 컨트롤러이니 @comp로 빈에 등록해야합니다. 이때 빈 이름을 component(name = "url")로 맞춥니다. 이거 경로 아닙니다. 그냥 빈 이름을 url로 맞춘 것입니다.

 

handlerMappingMap.put(스프링 빈 Name, new OldController());

경로는 디스패쳐에 핸들러 맵퍼에 있을 것입니다.

 

실행해보면 컨트롤러 이름으로 url 엔터로 호출해보면 얘가 호출이 됩니다. 어떻게 된거냐면 이 url을 호출하면 http 요청이 들어갑니다. 저 url로 일단 핸들러 맵핑에서 핸들러를 찾아야합니다. 지금 구현한 컨트롤러를 찾아와야합니다.

 

-> 찾으려면 2가지가 필요합니다.

1) 핸들러 맵핑 : 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 맵핑이 필요합니다. 옛날 버전 컨트롤러를 사용하는 핸들러 사용 방식은 이렇게 스프링 빈 이름으로 핸들러를 찾도록 특수한 핸들러 맵핑 구현체가 필요한 것입니다. 우리가 구현한 MVC에서는 URL 요청으로 핸들러를 찾았는데 옛날 방식 컨트롤러는 스프링 빈 이름으로 핸들러 맵퍼에서 핸들러를 찾는 것입니다.

 

2) 어댑터 : 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요합니다. 이 controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 합니다.

 

+ 스프링 부트는 자동으로 핸들러 맵핑과 핸들러 어댑터를 여러가지를 등록해줍니다.

 

-> 핸들러 맵핑

0순위는 어노테이션 기반의 컨트롤러, 1순위는 스프링 빈의 이름으로 핸들러를 찾는 것으로 이게 아까 한 url의 이름과 똑같은 이름의 스프링 빈을 찾는 것입니다.

 

디스패쳐가 0순위로 어노테이션 방식으로 보는데 @Controller에 @RequestMapping이 있나 봤는데 없습니다. "/springmvc/old-controller"로 되어있는 RequestMapping 메서드가 없습니다. 그래서 무시하고 1순위로 스프링 빈의 이름으로 봅니다. 그렇게 핸들러 맵핑을 끄짚어냅니다. 이 맵핑이 OldController를 반환합니다.

 

-> 핸들러 어댑터

이제 찾은 핸들러 맵핑에 맞는 어댑터를 찾아야합니다. 스프링 MVC가 디스패쳐에서 0순위로 어노테이션 어댑터 1순위 HttpRequest 어댑터를 찾는 데 둘 다 for문 돌려보고 서포트 메서드를 실행했는데 F여서 패스하고 2순위로 심플 컨트롤러가 지원이 되어서 어댑터도 찾았습니다.

 

// 실제 컨트롤러 process 메서드
public class MemberFromControllerV3 implements ControllerV3 {
    @Override
    public ModelView process(Map<String, String> paramMap) {
        return new ModelView("new-form");
    }
}


// 어댑터 핸들 메서드
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
    ControllerV4 controller = (ControllerV4) handler;

    Map<String, String> paramMap = createParamMap(request);
    Map<String, Object> model = new HashMap<>();

    String viewName = controller.process(paramMap, model);
    ModelView mv = new ModelView(viewName);
    mv.setModel(model);

    return mv;
}

이제 디스패쳐가 2순위 어댑터의 핸들을 호출한다. 그 핸들은 실제 핸들러를 호출하니 메서드인 handlerequest가 호출되고 모델 뷰를 반환합니다. (이전에도 어댑터의 핸들 메서드에서 컨트롤러의 process 메서드를 호출했었습니다.)

 

 

- HttpRequestHandler 인터페이스 방식

@Component("/springmvc/hsb")
public class RequestController implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("RequestController.handleRequest");
    }
}

또 다른 방식의 핸들러와 핸들러 어댑터를 스프링이 어떻게 해놨나 보겠습니다. 얘를 실제 컨트롤러로 하여 구현해보겠습니다. sout를 해보겠습니다. 얘도 스프링이니 component를 해야합니다. 얘도 이름을 url로 하겠습니다.

 

-> 핸들러 맵핑

얘는 어떻게 되어있냐면 얘도 핸들러를 url로 찾습니다.

 

-> 핸들러 어댑터

그리고 어댑터를 찾는데 1순위 어댑터 핸들러에 서포트에 T가 됩니다. 이 어댑터가 핸들러를 디스패쳐에서 호출합니다.

 

 

엄청 간단합니다. 우리가 MVC를 만들어 봤기에 쉽게 이해가 되는 것입니다. 실제 우리가 개발에 사용하는 것은 @Controller 핸들러와 핸들러 어댑터를 사용합니다. RequestMappingHandlerMapping과 RequestMappingHandlerMappingAdapter입니다.

 

 

- 뷰 리졸버

// 우리 MVC
public class MemberFromControllerV3 implements ControllerV3 {
    @Override
    public ModelView process(Map<String, String> paramMap) {
        return new ModelView("new-form");
    }
}

// 스프링 MVC
@Component("/springmvc/old-controller")
public class OldController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("OldController.handleRequest");
        return new ModelAndView("new-form");
    }
}

뷰 리졸버를 알아보기 위해서 oldcont를 return을 모델엔 뷰로 하고 new-form으로 가게 하겠습니다. 원래 모델 뷰는 논리 경로를 매개로 받았었습니다.

 

logging.level.org.apache.coyote.http11=debug
spring.devtools.livereload.enabled=true
spring.devtools.restart.enabled=true
spring.mvc.view.prefix=/WEB-INF/views
spring.mvc.view.suffix=.jsp

뷰 리졸버에서 물리 경로로 바꿉니다. 실행해보면 오류페이가 납니다. 이 뷰를 못 찾았답니다. 뷰 리졸버를 만들어줘야합니다. 프로퍼티스에 spring.mvc.view.prifix, suffix를 넣어줘야합니다. 돌리면 정상 작동합니다.

 

 

-> 뷰 리졸버 설명

@ServletComponentScan
@SpringBootApplication
public class ServletApplication {

	public static void main(String[] args) {
		SpringApplication.run(ServletApplication.class, args);
	}
	
	@Bean
	InternalResourceViewResolver internalResourceViewResolver() {
		return new InternalResourceViewResolver("/WEB-INF/views", ".jsp");
	}
}

스프링이 어플이 올라올 때 자동으로 InternalResourceViewResolver라는 뷰 리졸버를 자동 등록하는데 그때 프로퍼티스에 설정 정보를 가져와서 등록합니다. 이를 스프링 부트가 자동으로 해줍니다.

 

-> 동작 방식

핸들러 어댑터에서 모델 뷰를 반환합니다. 여기서 논리적 이름으로 뷰 리졸버가 호출이 됩니다. 스프링 부트가 자동으로 여러 뷰 리졸버를 등록해주는데 0순위가 빈 이름으로 하는 것이고 우리는 이게 아닙니다. "new-form" 논리 이름이 빈 이름이 아닙니다. 1순위로 인터널 뷰 리졸버가 반환이 됩니다.

> 뷰 리졸버가 뷰를 반환해야 하는데 뷰가 인터페이스라고 했습니다. 인터널 리소스 뷰라는 것을 반환합니다. 이 뷰는 jsp forward하는 기능이 있는 것입니다. view.render를 하면 인터널 리소스 뷰는 forward를 실행해서 jsp를 호출합니다. 타임리프를 쓸 때는 타임리프 뷰 리졸버가 있고 타임리프 뷰가 있습니다.

 

- MVC 시작하기

본격적으로 스프링을 이용하여 회원 등록, 목록을 만들어보겠습니다. 이전에는 이론만 했고 실제고 만들어보는 첫 시간입니다.

 

> 스프링이 제공하는 컨트롤러는 @기반으로 동작합니다. 클래스에 @Controller를 붙이고 메서드에 @RequestMapping만 넣으면 굉장히 편하게 됐습니다. 생각해보면 MVC를 배우는 건 어려운데 배워놓으면 개발은 엄청 쉬울 것 같습니다.

 

 

-> @RequestMapping

스프링이 @를 사용하는 매우 실용적인 컨트롤러를 만들었는데 바로 @RequestMapping을 사용하는 컨트롤러입니다.

 

그러면 요청이 오면 RequestMapping을 보고 우리의 컨트롤러를 누군가 찾아주어야 합니다. 그 찾은 컨트롤러를 실행할 어댑터도 필요합니다. RequestMappingHandlerMapping과 어댑터가 가장 우선순위가 높습니다. 이것이 바로 스프링이 사용하는 '@' 기반 핸들러 맵핑과 핸들러 어댑터입니다.

이 '@RequestMapping'이 있으면 0순위가 이 핸들러 맵핑이므로 이 핸들러가 사용됩니다. '@RequestMapping'이 있으면 0순위가 이 핸들러 맵핑 어댑터이므로 이 어댑터가 사용됩니다.

 

- 회원 등록 폼

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

패키지에 v1을 만듭니다. 위처럼 implements받을 거 하나 없고 @를 붙이면 됩니다. RequestMapping도 만들고 반환은 모델 뷰에 메서드 명은 process이고 모델 뷰에 논리 경로를 넣습니다. 조회이니 process의 매개도 넣을게 없습니다. 이러면 뷰 리졸버에서 jsp를 처리하기 위한 인터널 리소스 뷰가 찾아져서 render가 됩니다.

 

실행해보면 RequestMapping에 있는 url로 요청하면 잘 나오고 model 뷰에 논리 경로를 넘겨서 jsp가 잘 나온 것을 알 수 있습니다.

 

 

-> 코드 분석

1) @Controller : 스프링이 자동으로 @Controller가 있으면 스프링 빈으로 등록합니다. 지금 만든 class가 빈 객체로 등록되는 것입니다. @Component가 있어서 컴포넌트 스캔의 대상이 됩니다.

+ 또한 스프링 MVC에서 에노기반 핸들러로 인식합니다. @Cont가 있으면 RequestMappingHandlerMapping에서 "아 이것은 RequestMapping 핸들러 정보구나" 하고 지금 만든 SpringMemberFormControllerV1 컨트롤러를 핸들러로 꺼낼 수 있는 대상이 되는 것입니다. (아까 빈 이름 컨트롤러를 사용할 때도 빈 이름으로 봐서 OldController를 꺼냈었습니다. 우리 MVC에서도 URL을 봐서 MemberFromControllerV4을 new로 꺼냈었습니다. 근데 이제는 아마 getBean으로 꺼낼 수도 있습니다.)

그래서 디스패쳐에서 getHandler할 때 SpringMemberFormControllerV1 가 꺼내집니다. 우리가 만든 MVC에서는 HandlerMappingMap에 putputput해서 넣었는데 이제는 HandlerMappingMap에 RequestMappingHandlerMapping 이런게 있고 @RequestMapping이 있으면 RequestMappingHandlerMapping 이것을 핸들러 맵핑 맵에서 찾아오고 getHandler하면 이 맵핑에서 핸들러를 찾아오나 봅니다.

 

 

2) @RequestMapping : 요청 정보를 맵핑합니다. 해당 URL이 호출되면 이 메서드가 호출됩니다. @기반으로 동작하기에 메서드의 이름은 아무거나 지으면 됩니다.

RequestMappingHandlerMapping은 스프링 빈 중에서 @Controller 또는 @RequestMapping이 클래스 레벨에 붙어 있는 경우 맵핑 정보로 인식해서 아 @기반 맵핑이 필요하구나 해서 RequestMappingHandlerMapping을 선택하고 얘의 getHandler를 해서 핸들러를 가져옵니다. 그냥 @RequestMapping이 있으면  RequestMappingHandlerMapping 핸들러 맵핑이 선택이 되는 것입니다.

 

RequestMappingHandlerMapping 내부 코드를 보면 isHandler에 Controller, RequestMapping이 .class로 class 레벨에 있으면 RequestMappingHandlerMapping가 "아 너는 내가 처리할 수 있는 핸들러구나" 합니다. 그러면 그 클래스를 컨트롤러로 꺼내올 수 있습니다.

 

> 어댑터는 실제 컨트롤러의 메서드를 대신 호출해줍니다. 예전에는 process를 어댑터의 핸들 메서드에서 호출했습니다. 근데 이제는 그것만 되는게 아닙니다. @기반 컨트롤러는 뭐를 impl하는게 아니라서 어떤 이름의 메서드든 작성할 수 있고 어댑터는 그 어떤 메서드든 대신 호출할 수 있습니다.

 

-> 회원 저장

@Controller
public class SpringMemberSaveControllerV1 {
    MemberRepository memberRepository = MemberRepository.getInstance();
    @RequestMapping("/springmvc/v1/members/save")
    public ModelAndView 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);

        ModelAndView modelView = new ModelAndView("save-result");
        modelView.addObject("member", member);

        return modelView;
    }

또 클래스를 만들고 @를 다 붙입니다. 모델뷰에 모델을 넣어야하는데 모델 뷰의 addObj 메서드를 하면 이전에는 모델 뷰의 모델을 get해서 모델에 데이터를 넣어야했는데 이제는 자동으로 모델 뷰의 모델에 데이터가 들어갑니다.

 

-> 회원 목록

@Controller
public class SpringMemberListControllerV1 {
    MemberRepository memberRepository = MemberRepository.getInstance();
    @RequestMapping("/springmvc/v1/members")
    public ModelAndView process() {
        List<Member> members = memberRepository.findAll();

        ModelAndView modelView = new ModelAndView("members");
        modelView.addObject("members", members);

        return modelView;
    }
}

paramMap이 필요가 없습니다. @기반이라서 메서드의 인자를 맞출 필요가 없습니다. 뭔가를 구현하면 매개를 맞춰야하는데 이제는 안그래도 됩니다.

 

- 컨트롤러 통합

@Controller
@RequestMapping("/springmvc/v2/members")
public class SpringMemberControllerV2 {
    MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/new-form")
    public String newForm() {
        return "new-form";
    }
    @RequestMapping("/save")
    public String save(@RequestParam String username,
                       @RequestParam int age,
                       Model model) {
// 파라미터로 직접 받아서 사라지는 코드
//        String username = request.getParameter("username");
//        int age = Integer.parseInt(request.getParameter("age"));

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

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

    @RequestMapping
    public String members(Model model) {
        List<Member> members = memberRepository.findAll();
        model.addAttribute("members", members);
        return "members";
    }
}

RequestMapping을 보면 이게 메서드 단위라서 컨트롤러 클래스를 하나의 클래스로 통합할 수 있습니다. 그냥 save, list, form 클래스를 만들지 않고 memberController를 만들고 하나의 컨트롤러에 메서드를 다 넣을 수 있습니다.

근데 잘 보면 /spring/mvc/v2가 중복입니다. ReuqestMapping을 클래스에 달고 중복된 부분을 달면 제거 가능합니다.

 

- 실용적인 방법

항상 모델 뷰를 만드는게 불편합니다 예전에 v4로 바꾸면서 viewName만 반환하게 했습니다. 실무에서는 지금부터 설명하는 방식을 주로 사용합니다.

 

1. 모델 뷰

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

먼저 단순하게 string을 반환하게 합니다. 이러면 끝납니다. @기반 컨트롤러는 모델 엔 뷰를 반환해도 되고 str을 반환해도 됩니다. 예전에 controller 인터를 impl로 구현한게 아니라서 굉장히 유연하게 되어있습니다. 그러면 그냥 얘가 viewName으로 알고 진행이 됩니다.

 

2. req, resp

@PostMapping("/save")
    public String save(@RequestParam String username,
                       @RequestParam int age,
                       Model model) {
// 파라미터로 직접 받아서 사라지는 코드
//        String username = request.getParameter("username");
//        int age = Integer.parseInt(request.getParameter("age"));

@RequestParam을 쓰면 req, resp에서 받은 쿼리 파라미터를 req를 사용해서 쓰는게 아니라 컨트롤러 메서드(이게 process들임 비즈니스 로직이 들어있는 것)가 직접 파라미터를 받을 수 있습니다. = 앞의 키로 맞춰서 하면 됩니다. 파라미터로 직접 받아서 req로 부터 get하는 코드가 사라집니다.

 

3. 모델

@GetMapping
public String members(Model model) {
    List<Member> members = memberRepository.findAll();
    model.addAttribute("members", members);
    return "members";
}

모델도 받습니다. 디스패쳐에서 넣어준 모델일 것입니다. (이전에 프론트에서 모델을 new Map으로 만들어서 process에 넣어줬습니다. 그리고 프론트가 자기가 넣어준 모델이니 레퍼런스는 자신한테 있으니 그것을 모델 뷰의 render 메서드의 매개로 넣어줬었습니다.)

 

★ 지금까지 했던 어떤 코드보다도 깔끔합니다. 근데 단점이 있습니다. 요청의 get, post를 구분하지 않았습니다. @RequestMapping은 get으로 요청해도 되고 post로 와도 됩니다. 목록을 보는 건 조회니깐 get이 좋고 저장은 등록하는 것이니 post로 하는게 좋습니다. 따라서 GetMapping, PostMapping이 필요합니다.

postman으로 해보면 get도 되고 post도 됩니다. 이게 스프링에서 안 막은 것입니다. get으로 오든 post로 오든 나는 그냥 신경 안 쓰고 무조건 요청을 받을 거야라고 한 것입니다. 이런 것을 위해서 api 명세서를 작성해서 요청을 어떻게 할지 프론트와 규칙을 정하고 post로 와야하는데 get으로 오는 것은 백에서 다 막아줘야하는 것입니다.

 

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

 

> newForm을 getmapping으로 바꾸고 post로 호출해보면 405가 뜨면서 허락되지 않은 메서드라고 합니다. 이렇게 제약을 거는게 좋은 설계입니다. 단순 조회는 get, 변경 등록은 post로 해야합니다.

Comments