관심사의 분리, MVC 패턴 - 실습

BackEnd

우선 기존 코드 전체를 불러온 다음 하나씩 뜯어서 설명해볼 생각이다 

주석을 잘 따라가면 쉽게 이해할 수 있다

package com.fastcampus.ch2;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;

//년월일을 입력하면 요일을 알려주는 프로그램
@Controller
public class YoilTellerMVC {//http://localhost:8080/getYoil?year=2021&month=10&day=1
    // public static void main(String[] args) {
//    @RequestMapping("/getYoil") // 기존맵핑주소
    @RequestMapping("/getYoilMVC") // 맵핑주소를 MVC로 바꿔준다 맵핑은 중복되면 충돌다니까 중복되지 않게 해야한다
//    public void main(HttpServletRequest request, HttpServletResponse response) throws IOException { // 톰캣에게 요청하는 객체와 받을 객체를 선언해주는 것
//    public void main(String year, String month, String day,  HttpServletResponse response) throws IOException { // MVC패턴 분리를 위해 각각 선언해서 request로 통으로 받지않고 개별로 받음
    public void main(int year, int month, int day,  HttpServletResponse response) throws IOException { // 하단에 Integer.parse를 쓰지않고 바로 int로 받기 위한 코드
        //1. 입력
//        String year = request.getParameter("year");
//        String month = request.getParameter("month");
//        String day = request.getParameter("day");


        //2. 작업
        //원래는 변환해줘야 하지만 상단에서 바로 int로 받았음으로 주석처리해도 괜찮다
//        int yyyy = Integer.parseInt(year);
//        int mm = Integer.parseInt(month);
//        int dd = Integer.parseInt(day);


        Calendar cal= Calendar.getInstance();
//        cal.set(yyyy, mm-1, dd -1); 기존 방식
        cal.set(year, month-1, day -1); // 위에서 직접 선언해줬으니까 원래 이름 그대로 적어줘야한다

        int dayOfweek = cal.get(Calendar.DAY_OF_WEEK); //1:일요일 2:월요일 ...
        char yoil= "일월화수목금토".charAt(dayOfweek);

        //3. 출력

        // 기존방식은 주석처리 하고 별도로 분리하여 사용함

//        response.setContentType("text/html"); //어떤 형태로 받을 것인지 작성 텍스트형태로 받음
//        response.setCharacterEncoding("utf-8"); // 한글로 깨지지 않고 잘 나오기 위해 작성
//        PrintWriter out = response.getWriter();// response객체에서 브라우저로의 출력 스트링을 얻는다.
//        out.println("<html>");
//        out.println("<head>");
//        out.println("</head>");
//        out.println("<body>");
//        out.println(year + "년" + month + "월" + day + "일은 " );
//        out.println(yoil+ "요일입니다.");
//        out.println("</body>");
//        out.println("</html>");
    }
}

 

기존에는 request로 통으로 받았는데 

위에 작성된 코드를 보면 해당 코드를 주석처리하고 int year, int month, int day라고 작성된 코드를 쓰고 있다

 

여기에서는 기존방식에서 MVC패턴으로 분리하기 위함인데

 

2가지 차이가 있다

 

첫번째로는 request로 한번에 받던 것에서 개별적으로 나눠서 받는다는 것

두번째로는 아래 주석처리된 Integer.parseInt로 String에서 int로 변환해서 사용하던 과정을 없애고 바로 int로 사용하는 것

 

이 2가지 차이가 가장 큰 차이점이라고 할 수 있겠다

 

하단으로 내려오면 원래는 int로 변환했기때문에 yyyy, mm , dd 라고 작성해서 사용했는데

이것 또한 이제 필요없으니 주석처리해주고 그대로 year, month, day로 사용해준다

 

이로써 3개중에 벌써 2개를 분리했는데 

 

첫째로는 입력이다 기존에 입력받던 3줄을 주석처리하고 바로 받았고

 

두번째로는 작업이다 변환해주는 3줄을 주석처리하고 바로 int로 받았다

 

이제 마지막으로 출력인데 출력은 여기서 하지 않고 따로 분리하기 위해 전체 주석처리해줬다

 

인텔리제이 기준으로 views라는 폴더안에 기본적으로 home.jsp라는 파일이 있는데

그걸 그대로 복사해서 yoil.jsp와 에러가 났을 때 보여주기위해서 yoilError.jsp를 생성해주자

 

이 모든 과정은 출력을 분리하기 위한 과정임을 기억하자

 

우선 yoil.jsp를 열어서 home.jsp에 적혀있던 것들을 지우고 

 

이렇게 작성해주자

 

주석에 써둔 것 처럼 이렇게 미리 적어두는 것을 쉽게 EL이라고 부르는데 

Expression Language라고 한다

 

이렇게 자리를 만들어 두면 모델 객체에 값이 해당 코드 EL에 들어가서 클라이언트에게 전달되는 원리이다

 

이번에는 yoilError.jsp로 가서

에러가 났을 경우 어떤 내용을 클라이언트에게 보여줄지 이렇게 작성을 해주면 된다

이렇게 해주는 이유는 500에러가 터지지 않기 위함이다

404에러는 흔히 클라이언트가 URL을 잘못 입력했을 경우 발생하지만

 

올바른 URL을 입력했지만 서버에서 예외처리 에러처리를 확실하게 하지 않으면

500에러가 터질 수 있으니 이렇게 값이 제대로 들어오지 않을 경우를 생각해서 나름대로 견고하게 코드를 짜야한다

 

마치 듬직한 수비수 골키퍼랄까..?

 

이제 YoilTellerMVC파일을 다시 열어서

 

return을 어디서 할 지 정해줘야한다

 

코드를 보면 return을 "yoil"이라고 써줬다

주석으로 작성된 내용처럼 WEB-INF안에 views안에 yoil.jsp를 통해서 결과를 보여주겠다고 작성된 코드인데

 

경로는 늘 같으니까 앞에 yoil만 떼어내서 작성한 것이다

 

하지만 이렇게 쓰면 에러가 나온다 왜냐면 상단에서 main을 void로 써줬기 때문이다

 

상단에 코드도 조금 수정해줘야한다

 

이처럼 public void main -> public String main으로 바꿔줘야한다 

 

밑에서 return을 "yoil" 문자열로 작성했으니 위에도 String으로 바꿔줘야 에러가 생기지 않는다

 

여기서 String이 뭘까? 바로 View의 이름이 되는 것이다

 

여기서 몇가지를 조금 더 추가해줘야하는데

 

일단 유효성 검사를 해줘야한다는 것이다

 

 

이렇게 작성해준다 년, 월, 일이 존재하지 않으면 아까 만들어뒀던 yoilError파일에 작성된 h1태그 문장이 클라이언트에게 전달되도록 하는 것이다 

 

이렇게 작성해주면 에러가 발생하는데 인텔리제이 기준으로 커맨드 숫자1을 해주면 

 

이렇게 메서드를 Import 하라고 나오는데 잠시 뒤에 처리하도록 하고

하단에 주석처리된 Calendar 요일을 계산한 부분을 별도의 메서드로 만들어줘야한다

 

주석을 풀고 해당 코드를 전부 드래그 한다음 우클릭을 누르면

Refactor라는 것이 보이는데 클릭해보면 Extract Method라는 것이 나온다 클릭해주자

 

 

그러면 이렇게 처음보는 창이 나오는데 위에 (year, month, day) 옆에 내가원하는 이름을 적어주면 되는데

 

편의상 getYoil이라고 적어줬다

 

그러면 하단에

이렇게 자동으로 메서드가 생성된다 

 

여기서 

 

이 부분은 이대로 사용하면 안되서

해당 부분은 커맨드 X로 잘라내고 return으로 바꿔주고

 

상단에 붙여넣어주고 

 

여기서 private 쪽을 수정해야하는데

 

 

void를 지우고 char가 반환되게 바꿔주면 모든 에러가 사라진다

 

지금까지 했던 것이 복잡해보이지만 간단하게 설명하면 코드를 메서드로 변경한 것이다

 

이제 상단에 가서 !isValid도 메서드를 만들어주자

 

 

커맨드 숫자1을 눌러서 Create method를 눌러주면

 

하단에 이렇게 

 

메서드가 자동으로 생성된다

 

 

boolean타입에 에러가 있으므로 return타입을 true로 작성해서 에러를 없애준다

 

생성된 2개의 메서드가 전부 Private으로 되어있는 이유는 이 클래스안에서만 사용되게 하기 위해서이다

 

이제 저장하고 실행해볼텐데

 

에러가 나오도록 작성된 boolean메서드가 true가 아닌 false;로 저장해두면 

디폴트가 거짓이므로 계속해서 에러가 나와야 성공이다

 

먼저 false로 저장하고 실행시켜서 정상적으로 출력되는지 확인해보자

 

브라우저를 열고

http://localhost:8080/getYoilMVC?year=2021&month=10&day=1

라고 작성해보면?!

 

짜잔!

 

알수없는 문자가 나온다 왜 이런게 나온 것일까?

 

이유는 인코딩에 문제가 있기 때문이다

 

실행되게 설정된 yoilError.jsp파일에 인코딩 정보를 넣어줘야한다


상단에 이렇게 contentType과 charset을 적어주고 저장 후 재실행해보자

 

이제 다시 실행해보면?!

 

이렇게 내가 작성한 에러발생 시 출력될 문장이 잘 나오는 것을 볼 수 있게 되었다

 

이 코드를 복사해서 yoil.jsp에도 넣어주자

 

그리고 false를 다시 true로 바꿔주자 

 

그리고 저장하고 다시 새로고침을 하면

 

뙇?!

한글은 잘 나오는데 이번에는 년월일 숫자가 또 출력되지 않는다

 

왜그럴까?

 

 

여기서 getYoil을 잘 받아서 getYoil을 잘보여줘야하는데

 

컨트롤러가 작업한 내용이 View에 전달이 안되었기 때문인데

 

기존 코드를 주석처리하고 이렇게 Model model을 작성해준다 

 

그리고 아래로 내려와서

 

 

이렇게 총 3단계로 다시한번 정리해서 적어주는데

 

첫번째로 유효성 검사를 통해서 에러가 났을 경우 에러문자를 뱉어주는 jsp파일을 연결해둔다

 

두번째로 요일을 계산해주고

 

마지막 세번째는 model.addAttribute를 통해서 년, 월, 일, 요일 순서로 모델에 담은 다음

 

yoil을 리턴해주면 된다

 

이제 저장하고 다시 새로고침해보자

 

이제 정상적으로 출력되는 것을 확인 할 수 있다 

 

모델에 담긴 값이 view로 잘 전달되서

 

yoil.jsp에서 준비했던 코드에 값이 들어오고

 

위에 작성된 양식에 맡게 year, month, day, yoil 순서로 잘 출력되는 것이다

 

그렇다면 

return "yoil"; //WEB-INF/views/yoil.jsp 를 이용해서 작업처리된 결과를 출력해라는 의미이고 반환타입은 문자열이므로 상단에 public void를 public String으로 변경해야함

해당 코드는 어떻게 yoil이라고만 적었는데 경로를 알아서 찾아가서 yoil.jsp에게 전달되는지 궁금해진다

 

그 이유를 지금부터 살펴보자

 

 

여기 views위에 보면 spring이라는 폴더가 있고 그 안에 설정파일이 2개가 있다

2개 모두 스프링 설정파일이다 

스프링폴더안에 appServlet안에 servlet-context.xml파일을 열어보면

 

이런 코드가 있다

 

이 부분이 view의 경로를 지정해주는 곳이다 

 

저기서 prefix는 접두사 suffix접미사인데 그냥 말이 어렵지

접두사는 return할 때 썻던 문자 앞에 붙는 것이고

접미사는 문자 뒤에 붙는다고 이해하면 된다

 

접두사가 /WEB-INF/views/ 니까 앞에 붙어서 폴더 경로를 찾아주고

 

그 폴더안에 yoil이라는 이름을 찾고 

 

접미사에서 .jsp를 붙여주니까 

 

폴더경로 + 내가 리턴한 이름 + .jsp  

 

이렇게 되는 것이다 

 

그 결과로 yoil이라고만 써줘도 /WEB-INF/views/yoil.jsp 라는 것이 완성되어지게 된다

 

이 설정은 내가 원하는데로 바꿀 수도 있다

 

불필요한 주석들을 지우고 코드정리를 해주고 동작 순서를 다시한번 처음부터 정리해보면

 

Spring MVC에 기본적인 컨트롤러는 이런식으로 작성된다

입력받을 매개변수를 이런식으로 선언하고 모델을 선언해서

작업을 이렇게 처리한다음에

모델에 작업 결과를 저장한 다음에

이 작업 결과를 보여줄 view를 반환해주면

 

DispatcherServlet이 작업결과가 저장되어있는 모델을 yoil이라는 view에 전달을 한다

 

그러면 이 view는 데이터가 담겨있는 모델 객체에서 값을 읽어서 최종결과를 만들어서 보여주는 것이다

 

여기서 몇가지 변동이 있을 수 있는데 예를들어

여기서 String을 지우고 void로 바꿔주고

유효성 검사도 주석처리하고 하단에 return을 주석처리해서 view의 이름을 반환하지 않을 수 있다

그러면 view의 이름은 RequestMapping된 url에 의해서 결정된다 현재 맵핑은 getYoilMVC이다

 

그럼 getYoilMVC라는 jsp파일을 만들어보자

 

이렇게 생성해주고 

 

이렇게 작성해준다음 저장하고 다시 서버를 재실행하고

 

http://localhost:8080/getYoilMVC?year=2021&month=10&day=1

 

주소창에 해당 URL을 넣어서 정말 이름이 저렇게 바뀌었는지 확인해보자

보이는 것 처럼 정말 변경이 잘 되었다

 

이로써 여러가지 변형이 존재한다는 것을 알게 되었고

반환타입을 void로 주고 view이름을 반환하지않으면 맵핑주소 해석이 된다는 것을 알아두자

 

잘 사용하지는 않지만 이런것도 있다는 것을 알고있으면 될 것 같다 

 

다른 방법으로는 이렇게 모델과 뷰를 같이 사용할 수도 있다

void를 지우고 ModelAndView라고 작성해주고 

이렇게 mv라고 선언해주고 new ModelAndView();라고 적어주자

 

그리고 3번에서 기존에 코드를 지워주고 mv로 바꿔주고 addAttribute를 addObject로 전부 변경해준다

 

3번까지 작성되었다면 마지막 4번에서 mv.setViewName을 통해서 view이름을 지정하는데 yoil이라고 동일하게 써주고

모든 준비가 끝났으면 return mv로 전달하면 된다

 

저장하고 껏다 켠 후 다시 새로고침을 해보면

잘 출력됨을 알 수 있다

 

이 방법은 잘 사용하지 않는 방법이지만 이런식으로도 가능하다는 것을 알고 있으면 좋을 것 같다

 

 

 

마무리로 정리를 다시한번 해보면

기존처럼 Model을 선언하면 DispatcherServlet에서 Model을 만들게 되는데

방금 알아본 방법처럼 컨트롤러에서 모델과 뷰를 만들게 할 수 도 있다

 

위 이미지의 코드처럼

1번에서 모델과 뷰를 생성하고

2번에서 유효성검사를 하고 

3번에서 작업을하고

4번에서 작업한 결과를 mv에 담고

5번에서 결과를 보여줄 view이름을 yoil로 지정하고

6번에서 마무리로 반환하는 것이다

 

쉽게말해 디스패쳐가 모델을 만드느냐 아니면 컨트롤러에서 자체적으로 모델과 뷰를 만드느냐의 차이인데

 

굳이 따지자면 초반에 설명한 String으로 작성된 방식을 사용하지

후자처럼 모델과 뷰를 컨트롤러에서 만드는 방식은 잘 사용하지 않는다

 

컨트롤러 메서드의 반환타입별로 정리를 해보면

 

반환타입이 String인 경우에는 view이름을 반환해야한다

만약에 void로 반환타입을 하면 맵핑된 url의 끝 단어가 뷰 이름이 됨으로 밑에서 반환하면 안된다 주석처리해야한다

반환타입이 ModelAndView라면 위 이미지처럼 컨트롤러내에서 모델과 뷰를 생성하고

처리를 해주고 결과를 담아주고 view이름을 지정해주고 반환해주면 된다

 

이것으로 정리를 마치고 여러 예제를 통해서 더 공부해볼 생각이다

 

긴 글 따라오신분이 계시다면 고생하셨어요 물한잔 마십시다 우리!

 

'BackEnd' 카테고리의 다른 글

서블릿과 JSP(2) - 유효범위와 속성  (0) 2022.12.17
서블릿과 JSP  (0) 2022.12.16
관심사의 분리, MVC 패턴 - 이론  (0) 2022.12.15
Base64 64진법이란?  (0) 2022.12.15
텍스트 파일 vs 바이너리 파일  (0) 2022.12.15