드디어 스프링!
@RestController와 @Controller의 차이

controller 패키지에 BoardController와 UserController 클래스를 만들어준다

[ @Controller ]
return할 때 리턴 '파일'을 찾아주기 때문에 리턴되는 값이 데이터가 아니라 파일명이어야한다. 즉, @Controller는 포워드해주는 것! ex) 리턴에다가 hello를 적으면 hello라는 파일을 찾아준다
포워드 (내부요청) : 웹 애플리케이션에서 클라이언트의 요청을 다른 리소스(서블릿, JSP, HTML 등)로 전달하는 기능
[ @RestController ]
return 시 '문자를 그대로 응답'한다 ex) return 에다가 hello를 적으면 hello를 글자 그대로 출력


return 값 joinForm 이 그대로 출력되는 것 확인!
파일을 찾고 싶으면 @Controller
데이터를 리턴하고 싶으면 @RestController
/joinForm 폼 생성 (500과 404 에러)

회원 가입하는 폼 줘! = GET 요청. 때문에 @GetMapping

"/joinForm" URL에 대한 요청을 처리하기 위한 리소스(서블릿 또는 JSP)가 아직 구현되지 않았다면, 디스패처는 해당 요청을 처리할 수 있는 리소스를 찾지 못하게 되어 status 500 오류가 발생. 400 > 클라이언트 잘못 500 > 서버 잘못
만약, 우린 지금 템플릿 엔진을 mustache를 쓰고 있어서
파일명을 join.mustache가 아니라 join.html로 되어있으면 못찾음. 500 에러
view파일은 [templates] 폴더 (+Mustache)
view 파일을 [templates] 폴더에 둬야 찾을 수 있다! templates는 디폴트 경로로 변경해도 작동은 하나... 하지 마라! 이건 스프링(프레임)이잖아.
templates(=WEB-INF)에 파일을 넣은 덕분에,
디스패처를 통해서만 자원을 찾을 수 있게 강제할 수 있음 > 자원명으론 절대 못 찾음


우린 지금 탬플릿 엔진을 mustache를 사용하고 있다. 그럼 일단, Mustache부터 Install 하자!

mustache는 템플릿 엔진이니까 JSP처럼 html이랑 자바랑 섞기 편하게 해주는 것 회원가입 페이지는 자바 코드 안에서 구현하기 힘들기 때문에 따로 mustache로 빼서 만들어준다.
new > file > 확장지 이름을 직접 .mustache로 하면 mustache 생성 됨
뷰 리졸버 (View Resolver)
머스태치 Install


{id}

이렇게하면 joinForm URL 뒤에 뭘 넣어도 해당 메소드가 때려짐 /joinForm/199 > 해당 메소드 출력 이게 바로 디스패처 서블릿의 마법 중 하나 ^^
yml(야물)로 한글 깨지는 걸 해결하자

웹 페이지를 열었는데 한글이 깨진다. 해결하자!
[ 중요 파일 server.xml, context.xml, web.xml ]

스프링은 내장 톰캣을 쓰고 있어서 해당 파일을 볼 수 없지만, 원래 톰캣을 설치하면 저런 파일 3개가 나온다. (* 만약 회사에서 스프링을 사용하지 않으면, 관련 설정은 해당 파일에서 직접 잡아줘야 함)
1. server.xml
서버 초기화 설정을 server.xml에서 다 함 이 파일에서 포트, 프로토콜을 바꿀 수 있고, 동시접속 스레드 설정도 할 수 있다.
2. context.xml
DB 연결 관련 설정 담당
3. web.xml
문지기가 보는 문서
[ application.propeties ]
스프링은 application.propeties에서 설정한다.


이게 다 server.xml 에서 설정할 수 있는 파일들

* server.servlet.encoding.force=true utf-8로 강제성이 부여됨 안에서 갑자기 euc-kr로 바꿔도 force로 설정하면 무조건 utf-8로 바꿔줌 * server.servlet.encoding.enabled=true 이거는 활성화시키는 것
한글 설정 끝! 근데 이거… 가독성이 별로 안좋다. 다른거 쓰자 ......우리는 yml를 쓸 것이다!
[ yml ]



propeties랑은 문법이 다르다. encoding 밑에 charset, force, enabled를 적으면 저렇게 나온다
둘이 적용되는 기능은 똑같다.
우리는 가독성 좋은 yml(야물)을 쓸거니까 applicatin.properties 파일 삭제 ㄱㄱ
json보다 yml이 더 경량이지만, 통신의 짱은 아직 json. yml은 설정 파일로 많이 사용.

이제 한글이 잘나온다~~~
회원가입 페이지 만들기 (joinForm.mustache)
join은 insert니까 POST로 받는다.
[ button = submit ]

submit 타입은 form 타입을 한방에 슝 전송해주는 것이다. ★ 버튼을 form 안에 넣어야만 submit이 됨. submit은 form의 액션을 발동시키는 것!!! type = button이면… 그냥 just 버튼이다. 액션이 발동하지 않는다. 무조건 submit!!! 근데 form태그 안에 있으면 디폴트가 submit이라 생략할 수 있다

submit을 생략한 모습 submit 시켜!! = 액션 발동시켜!

자, 이제 회원가입 페이지 (/joinForm) 를 만들었다

그런데 회원가입을 했더니 /join 페이지가 없어서 오류가... 만들어주자… join 페이지는 mustache로 만들 필요 없다. (디자인 x니까) (302 -> 200) 서블렛에 만들어주고, join을 리다이렉트 시키자. 컨트롤러인 UserController 파일에 만들러 가자
[ join ]
joinForm에서 작성한 회원가입 정보를 서버로 전송할 때 (=insert) 사용.
회원가입 정보를 처리하고, 회원가입이 완료되면 주로 메인 페이지로 리다이렉션 시킨다.
UserController 컨트롤러 +유효성 검사, +에러 페이지 구현

디스패처 서블릿이 어노테이션을 보고 리플렉션을 해서 join() 메서드를 실행한다. 특정 어노테이션이 join() 메서드에 적용되어 있다면, 디스패처 서블릿은 해당 어노테이션을 인식하고 리플렉션을 통해 join() 메서드를 실행함 이를 통해 디스패처 서블릿은 어노테이션과 리플렉션을 조합하여 동적으로 메서드를 호출 이 디스패처 서블릿을 호출해주는건 톰캣 (만드는 것도 톰캣) 톰캣이 요청(Request)과 응답(Response) 객체를 전달하니 디스패처 서블릿 또한 요청(Request)과 응답(Response) 객체를 들고 있겠지? 그러니까

이렇게 넣어줄 수 있다! HttpServletRequest를 사용할 수 있다!!!
자세히 알아보기 (컨트롤러 파싱 / request를 매개 변수로 받는 이유)

이 예시를 보면 컨트롤러에서 join메소드가 (HttpServletRequest request) 를 매개변수로 받고 있다. 이 말은 즉, 디스패처 서블릿에서

이런 식으로 디스패처 서블릿은 컨트롤러의 join 메서드를 호출할 때, HttpServletRequest 객체를 전달한다.

정확히는 이런 식으로... 아규먼츠를 파라미터로 전달해주듯이?
그래서!! 컨트롤러에서 HttpServletRequest 객체를 매개변수로 받았기에 해당 객체를 통해 클라이언트의 요청에 대한 다양한 정보를 파싱(parsing)할 수 있다. (클라이언트가 전송한 데이터를 파싱하거나, 세션에 저장된 정보를 확인하거나, 요청의 헤더 정보를 읽어올 수 있다.)
HttpServletRequest 객체를 매개변수로 선언하여 요청 정보를 받아올 수 있지만, HttpServletResponse 객체는 명시적으로 선언하지 않아도 된다.



회원 가입해서 날리니까, 파싱되어서 잘 들어오는 것 확인!

그런데, 이렇게 해도 받아옵니다! HttpServletRequest를 파싱된 상태로(?) 매개변수로 받는 것

유효성 검사
유효성 검사 2줄로 쉽게 끝내는 방법이 있는데 그건 나중에... 지금은 일단 수기로 유효성 검사할 걸 작성하자

username이 10글자 이상이면 유효성 검사에 걸려서 error 페이지가 뜨게 만든다. 는 유효성 검사를 작성. 그럼 이제 error 페이지 구현하러 가자
[ 유효성 검사는 프런트랑 백엔드 둘 다 해야한다 ]

required하면(프런트 유효성 검사 required)

값이 없으면 안들어가게 막아놓음 (프런트가 하는 유효성 검사!)


required maxlength="4" 라고 하면 4글자 이상은 안 넘어가게 프런트에서 유효성검사를 해준 것
[ 백은 무조건 유효성 검사를 해줘야 한다 ]

백에서 한 유효성 검사. 아이디가 10 이상이면 에러 페이지 리턴
정상적인 접근으로는 이 4글자를 넘어갈 수 없지만 비정상적인 방법으로 프런트에서 한 유효성 검사를 파괴할 수 있다. 4글자가 아니라 25글자로 와아아악 하고 보내버릴 수가 있음 ㅠㅠ 이렇게 프런트에서 파괴될 수 있으니 무조건 백에서 걸러줘야한다 그래서 유효성 검사를 2개 하는 것. 프런트가 유효성 검사를 안한다? UX가 좀 불편하겠죠. 하지만 백이 유효성 검사를 안하면? 망함.

이제 유효성검사를 통과하니 자연스럽게 main으로 간다! 근데 main페이지가 없어서 안 뜬다. 404 이제 main페이지를 만들자. 만약 폴더명 안에 main페이지를 만들면

이런 식으로 작성하면 된다.
에러 페이지 구현

[ error.mustache 코드 ]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>에러 : 잘못된 요청을 하셨어요</h1> </body> </html>

유효성 검사를 일부러 틀렸더니 내가 만든 에러 페이지로 들어왔다. ......근데 정상적으로 만들어도 똑같은 창이 뜨면서 뻑난다.

error는 프레임워크에 디폴트로 설정이 있는게 있나보다… error 라는 이름의 파일이 있으면 무조건 error.mustache 파일로 들어가게 끔 설정되어 있다 그러니 이름을 error로 하지말고 바꾸자! 그럼 정상적으로 뜬다!


HTTPServletResponse는 매개변수로 안받아도 괜찮다

String username, String password, String email = HTTPServletRequest를 파싱한걸 매개 변수로 받음 HTTPServletResponse는 매개변수로 안받아도 괜찮다 HTTPServletRequest로부터 받은 바디 데이터를 사용하여 유효성 검사를 수행하다가 유효성 검사에 걸리면, return "error-404"와 같은 값을 리턴하여 디스패처로 이동한다 그럼 디스패처가 알아서 HTTPServletResponse에 응답을 담아 클라이언트에게 보내줄테니까 HTTPServletResponse를 매개변수로 명시적으로 적을 필요성은 없다.
유효성 검사를 통해 발생한 에러를 "error-404"와 같은 값으로 리턴하여 디스패처로 보내면, 디스패처는 이 에러를 처리하고 클라이언트에게 알릴 수 있다. 디스패처는 웹 애플리케이션에서 요청과 처리를 관리하는 중요한 역할을 한다.
BoardController (/main)
[ main 페이지가 뜨는 경우 ] 1. 회원가입이 완료되고 나서 메인 페이지로 가는 경우 2. 바로 메인 페이지로 가는 경우 그러니 BoardController에서 만들어주자

메인 창만 띄우는 용도라서 받는 데이터가 없다. 파싱, 유효성 검사도 필요 없다!

/main 페이지 만들어줬다. 이제 회원가입하면 메인 페이지 뜨죠?
[ 리다이렉션 해주기 ]

근데… UserController 클래스에 main을 찾는 코드가 있는데 (이거는..내가 직접 메인 페이지를 읽는거니까 이렇게 하지마라?)

BoardController에서 또 적을 필요가…? → 리다이렉션(302) 해서 재사용 해주자!

return "redirect:/main"; 이렇게!!! 헤더에 302 - location에 /main한거랑 같은 코드다! : 으로 파싱해서 '아하 리다이렉션이구나' 하겠지요
내가 만들어놓은 컨트롤러가 있으면 절대 뷰를 리턴하지 말고, 만들어놓은 컨트롤러를 리턴! 즉, 리다이렉션 해줘야한다. 컨트롤러를 때려라!
★ 무조건 리다이렉션해서 호출해줘야 함



회원가입 하고 메인 페이지가 뜬 걸 확인해보니 리다이렉션 됐죠?
전체 코드

[ controller - BoardController ]
package com.example.demo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class BoardController { @GetMapping("/main") public String main() { return "main"; } }
[ controller - UserController ]
package com.example.demo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; //@RestController @Controller public class UserController { @GetMapping("/joinForm") public String joinForm() { return "joinForm"; } @PostMapping("/join") public String join(String username, String password, String email) { // String username = request.getParameter("username"); // String password = request.getParameter("password"); // String email = request.getParameter("email"); System.out.println("username : " + username); System.out.println("password : " + password); System.out.println("email : " + email); if (username.length() > 10) { return "error-404"; } return "redirect:/main"; } }
[ resources - templates - error-404.mustache ]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>에러 : 잘못된 요청을 하셨어요</h1> </body> </html>
[ resources - templates - joinForm.mustache ]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>회원가입 페이지</h1> <hr> <form action="/join" method="post"> <input type="text" name="username" placeholder="username" required maxlength="10"> <input type="password" name="password" placeholder="password" required> <input type="email" name="email" placeholder="email" required> <button>회원가입</button> </form> </body> </html>
[ resources - templates - main.mustache ]
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h1>main page</h1> <hr> </body> </html>
[ resources - application.yml ]
server: servlet: encoding: charset: utf-8 force: true enabled: true
form 태그는 DELETE랑 PUT을 지원하지 않는다. > 이건 자바 스크립트에서 지원!
우리가 지금 할 수 있는건 GET과 POST
컨트롤러에서 유효성 검사를 제대로 안 한 상태로 DB에 넘겨버리면 DB에서 오류가 난다
레이어의 책임을 분명히 할 것!
DB가 이상하다고 DB 탓하지 말고 컨트롤러한테 가라!
public String main()과 같이 문자열을 반환하는 메서드를 작성하면,
반환된 문자열은 기본적으로 HTML 형식으로 처리된다.
view (=그림을 그리기 위한 파일) ex) jsp, mustache에서 로직 처리를 안하는게 좋다
(어쩔 수 없는 것만 하자)
ex) 011을 010으로 바꾸고 싶다면 view에서 바꾸지 말고,
DB에서 if문으로 바꿔버린 후, 010으로 바뀐 데이터만 VIEW 파일에 뿌려라
HTML 파일에 자바 코드를 많이 섞지 않고도 데이터의 변환과 필터링을 효율적으로 처리 가능하니, 비즈니스 로직은 주로 서버 사이드에서 처리하고, 뷰는 데이터를 표현하는 역할에 집중하라
Share article