Inflearn

스프링 MVC 2편 (1)

시롱시롱 2023. 6. 10. 18:47

타임리프 기본기능

  • 이미 앵간한건 아니까 가볍게 정리함
  • SSR을 위해 쓰는 것
  • 순수 HTML을 최대한 유지하는 특징이 있음
  • JSP를 포함한 다른 뷰 템플릿들은 JSP를 웹 브라우저에서 열어보면 JSP소스코드랑 HTML이 섞여 정상적으로 확인하기 힘들지만, Thymeleaf는 서버를 통하지 않고 웹 브라우저에서 열어도 HTML은 보이고 서버를 통하면 동적으로 결과를 렌더링할 수 있음
  • 순수 HTML을 유지하면서도 뷰 템플릿도 사용할 수 있는 타임리프의 특징을 네츄럴 템플릿이라 한다.
  • 텍스트
    • th:text=”${data}” 이렇게 사용
    • 컨텐츠 안에서 그냥 출력하려면 [[${data}]]이렇게 사용하면 됨
      • 컨텐츠 안이라 하면 그냥 <h1> 여기 </h1> 말하는 거
    • 이런 데이터에 html 태그를 추가하면 그대로 안나옴
      • < 나 > 이런 애들을 HTML엔티티라고함
      • 얘네는 &lt &gt로 바뀌게 됨
      • 그냥 문자에 넣고 그대로 html렌더링하면 태그로 인식하게 되니 이걸 다른 문자로(엔티티로)바꿔서 보여준다.
      • 이런 변경 행위를 escape라고 함
      • 뭐 th:text , [[${}]] 이렇게 써도 escape를 자동으로 지원함
    • 이런 escape를 사용하지 않으려면
      • th:utext
      • [(${})]
      • 이거 두개사용하면 됨
      • 뭐, 인젝션같은 위험 요소가 있으니 앵간해선 unescape는 안쓰는게 좋음
  • 변수
    • SpringEl표현식을 지원함
      • 뭐 user.username이나 user[’username’] user.getUsername() 이걸 다 지원해줌
      • 즉, 모델에 객체나 컬렉션을 넘겨도 다 사용이 가능함
      • list도 뭐 users[0].username이렇게 쓸 수 있음, 맵도 userMap[’userA’]이렇게 사용 하면 됨
    • th:with
      • 아래처럼 지역 변수를 선언할 수 있음
      • <div th:with="first=${users[0]}"> <p>처음 사람의 이름은 <span th:text="${first.username}"></span></p> </div>
  • 기본 객체들
    • request,response, session, servletContext같은걸 원래 기본적으로 따로 안 보내줘도 자체적으로 쓸 수 있었는데 부트 3.0부터 다 막힘
    • 3.0부터는 전부 addAtrribute로 추가해줘야 함
    • <span th:text="${param.paramData}"></span>
    • 이렇게 편의 객체로 param을 기본으로 제공함
    • session도 마찬가지
    • @빈이름.클래스 로 빈에 직접 접근도 가능함
  • 유틸리티 객체와 날짜
  • URL링크
    • 이렇게 다양하게 사용 할 수 있음
    • <li><a th:href="@{/hello}">basic url</a></li> <li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query param</a></li> <li><a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">path variable</a></li> <li><a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">path variable + query parameter</a></li>
    • 쿼리 파라미터 주려면 2번처럼 하면되고 서버에서 추가한 변수를 그대로 링크에 사용하려면 3번처럼 하면 됨
    • 4번같은경우 param1은 경로 변수로 사용했는데 2는 사용안하고 쿼리 변수처럼 놔두면 2는 쿼리 파라미터로 자동으로 들어감
  • 리터럴
    • 리터럴은 소스 코드상에 고정된 값을 말하는 용어이다.
    • 문자는 항상 작은 따옴표로 감싸야 함 ex) th:text=“’hello’” 이렇게
      • 근데 귀찮기에, 공백 없이 쭉 이어서 쓰면 작은 따옴표 생략해도 됨
    • 아래 참고( 리터럴 대체 문법 쓰면 깔끔하게 사용할 수 있긴 함 )
    • <li>'hello' + ' world!' = <span th:text="'hello' + ' world!'"></span></li> <li>'hello world!' = <span th:text="'hello world!'"></span></li> <li>'hello ' + ${data} = <span th:text="'hello ' + ${data}"></span></li> <li>리터럴 대체 |hello ${data}| = <span th:text="|hello ${data}|"></span></li>
  • 연산
    • 크게 다를건 없는데 HTML엔티티 사용하는 부분만 조심하면 됨
    • Elvis연산자 : th:text="${nullData}?: '데이터가 없습니다.'"
      • 이렇게 데이터가 없으면 뒤에꺼 출력
    • No Operation: <span th:text="${nullData}?: _">데이터가 없습니다.</span>
      • 이렇게 쓰면 thymeleaf태그 렌더링을 안하는 것
      • 타임리프 부분 렌더링 안하면 그냥 데이터가 없습니다.만 렌더링 된다
  • 속성 값 설정
    • th:*로 지정한 속성 * 을 대체함. 없다면 새로 만들고
      • 즉, name= “ 212” th:name=”123123123” 이면 th:name만 적용된다.
    • 뭐, 그냥 파일을 직접 열면 엔진을 안거치니 th 태그는 다 무시 된다.
    • attrappend, attrprepend, classappend등으로 여기저기 붙여줄 수 있음
    • checked란 속성만 존재하면 그냥 체크박스에서 체크가 되어 버림
    • 타임리프에선 th:checked값이 false면 checked속성을 제거해 버리는 것
  • 반복
    • th:each=”user:${users}” 형태로 사용
    • 여기서 반복에 두번째 파라미터를 설정하면 그걸로 반복의 상태를 확인할 수 있음
    • 지정하지 않으면 알아서 user+Stat = userStat으로 생성해서 사용할 수 있게 해 줌
      • count, size, even,odd 등등 제공
  • 조건부 평가
    • if, unless 지원
    • 조건이 충족하면 출력하고 그렇지 않으면 해당 태그를 삭제함
    • swith도 있음
  • 주석
    • HTML주석 : <!— ~ —!>
    • 타임리프 파서 주석 <!—/* ~ */—> : 얘도 다 주석처리해서 렌더링 과정에서 전부 삭제 시켜버림
    • 타임리프 프로토 타입 주석 : html주석형태라 파일로 열면 렌더링이 되지 않음, 근데 타임리프 렌더링을 거치면 주석이 해제되고 보임
  • 블록
    • 타임리프 자체 태그
    • 뭐, div 여러개를 반복문으로 쭉쭉 찍어내고 싶을 때 사용한다. each랑 짝꿍인 div느낌?
  • 자바스크립트 인라인
    • <script th:inline="javascript">
    • 이거 달아주면 됨 (알잖아)
    • 그냥 플레인스크립트에 모델 값을 사용하면 더블쿼터를 써야하던가 내추럴템플릿을 사용하지 못하던가.. 그렇다고함
    • 이걸 자바스크립트 인라인을 사용하게 되면
      • 문자같은 타입은 알아서 더블쿼터 달아주고
      • 자바스크립트 주석인 /* ~ */ 안의 값을 렌더링을 시켜줌
      • 또, 객체를 출력하면 객체를 json으로 바꿔서 렌더링 해 준다.
    • [# th:each="user, stat : ${users}"]
    • `var user[[${stat.count}]] = [[${user}]]; [/]`
    • 자바스크립트 내에서 each를 사용하려면 위에 처럼 하면 됨
  • 템플릿 조각
    • fragment를 말하는 것, 그니까 navbar, footer이런거
    • <div th:insert="~{template/fragment/footer :: copy}"></div>
    • 이렇게 사용, th:insert th:replace사용 가능
    • fragment등록 시 설정한 이름으로 해당 경로의 fragment들에 접근할 수 있음
    • 또, fragment에 파라미터를 설정할 수 있음
  • 템플릿 레이아웃
    • 저렇게 쓰면 title태그가 통째로 들어가는 것 base에선 받아온 title로 replace를 함
  • <head th:replace="template/layout/base :: common_header(~{::title},~{::link})"> <title>메인 타이틀</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}"> </head>

타임리프 스프링 통합과 폼

  • 타임리프는 스프링과 정말 자연스럽게 통합해서 사용할 수 있음
  • 입력 폼 처리
    • th:object로 model에서 받아온 객체를 폼에 달아주면
    • th:field를 “${}”로 안 써도 *{}로 접근할 수 있게 됨
      • 이렇게 되면 item의 필드에 바로 접근하게 되는 것
      • ${item.itemName} = *{itemName}
      • 그리고 id,name,value 모두 필드에 적은 이름으로 자동으로 만들어 준다.
        • 물론 밸류는 값이 있어야 그 값을 출력함
      • 또, 필드 이름을 잘못적으면 렌더링시점에 오류를 던짐 → 에러를 찾기 쉽다
  • enum에 string 필드 만들어서 생성자 만들어주면 enum에 필드를 끼워 넣을 수 있음
  • 체크박스-1
    • 체크박스를 선택하면 open=on이라는 값을 넘겨주고 이를 true로 변환한다
    • 근데, 만약 체크박스를 선택하지 않게되면 open이란 필드가 애초에 서버로 전송되지 않음
      • 만약, 체크되어 있던 품목을 해제하고 사용자가 제출하면 서버는 이게 변경된 지 모르니 값을 변경하지 못한다.
    • 이를 해결하기 위해 _open이란 기존 체크박스의 name에 _를 붙인 히든 input 을 추가 하고 value를 on으로 설정함
      • 만약 open의 값이 없으면 (전달되지않으면) 히든 인풋인 _open이 전송되고 이걸로 open=false로 설정한다.
      • 그리고, 체크되어 있는 경우 _open의 값은 전송되어도 무시하고 open의 값인 on을 보고 true로 설정한다.
  • 체크박스 -2
    • 이렇게 매번 설정하기에는 너무 귀찮고 힘들다. 타임리프가 해결 해줌 !
    • 체크박스에 그냥 th:field 추가해주면 타임리프가 알아서 체크박스인거 보고 알아서 히든 인풋까지 만들어서 렌더링 함
    • 심지어, 이미 필드 값이 true인 경우 checked까지 추가해서 렌더링 해준다.
  • 다중체크박스
    • 여러 곳에서 똑같은 attribute를 모델에 더하는 경우 일일이 다 addattribute하는 것 보단
    • @ModelAttribute를 만들어서 컨트롤러에 놔두면 , 해당 컨트롤러 내의 주소로 접근하는 경우 자동으로 모델에 해당 애트리뷰트들이 다 담기게 된다.
    • th:each로 반복해서 체크박스를 만들고 체크박스에 th:field를 박아넣으면 자동으로 그 이름 뒤에 1,2..이렇게 순서를 붙여서 id를 설정한다
    • 타임리프는 이렇게 동적으로 아이디가 생성되는 경우를 위해 #ids.prev, next를 지원한다. (이걸로 동적으로 생성되는 아이디를 사용할 수 있다.)
    • diabled를 추가하면 th:field와 th:value에 든 값을 비교해서 공통으로 있는 값에는 checked를 설정해주고 그렇지 않고 값에만 존재한다면 checked를 설정하지 않음
  • 라디오버튼
    • ENUM.values()하면 바로 배열로 뽑을 수 있다
    • 얘는 체크를 안하고 넘기면 그냥 null임 , 라디오 버튼은 한번이라도 뭘 선택하면 3개중에 하나는 항상 선택되어 있기에 굳이 히든필드 안만들고 처리. 뭐 아무것도 체크안하고 저장하면 null이고 나중에 다시 조회해도 null이니 개체 모두에 체크를 안한상태로 보여줌
    • ${T(com.example.domain. …).values()} 이런식으로 Spring EL문법으로 ENUM을 직접 접근할 수 있음 →근데 뭐 너무 복잡하고 위치 바뀌면 또 의존적으로 바뀌어야 하니 별로다 !
  • 셀렉트박스
    • 뭐 체크박스랑 비슷함

메시지 , 국제화

  • 상품 명이라고 적은 말을 상품 이름 으로 전부 변경할 때 일일이 파일에서 다 변경하는 것이 아니라, messages.properties라는 메세지 관리용 파일을 만들고 이를 이용해서 전체 파일을 관리하는 것
  • 국제화란 이런 메세지 파일을 나라별로 관리해서 request의 accept language헤더를 보고 나라에 맞게 메세지 파일을 사용하는 것
  • 스프링에선 MessageSource를 빈으로 등록해서 사용하면 되지만, 부트에선 이미 등록되어 있다.
  • 별도의 설정을 하지 않는다면, messages로 시작하는 이름의 설정파일을 자동으로 스캔한다.
  • messages_en,kr이렇게 설정파일을 리소스에 넣어주면 알아서 국제화 파일로 인식하고 언어에 맞게 사용한다.
  • MessageSource를 DI받아서 getMessage메소드를 호출해서 테스트 할 수 있음
  • getMessage(code, object[], default, locale)
    • 코드가 설정파일에 등록한 그 이름이고, 오브젝트 배열이 설정 파일에 {0}이렇게 등록한 그 배열에 등록함, 뭐 default는 없을 때 기본으로 넣을 메세지, 로케일은 지역..
  • 타임리프에선 #{}로 사용가능하다.
    • 저 안에 파라미터도 넣어서 사용할 수 있음
  • 국제화는 뭐 그냥 웹에서 언어 설정 바꾸고 새로고침해보면 바뀜.
  • 스프링은 MessageSource테스트할 때 Locale정보 넣어서 한 것 처럼 기본적으로 Accept-Language헤더 값을 사용한다.
  • 근데, 이 Locale 선택 방식을 변경하고 싶다면?
  • LocaleResolver
    • 기본적으로 부트는 Accept-language를 활용하는 AcceptHeaderLocaleResolver를 사용함
    • 뭐 이걸 변경해서 국가 선택기능을 사용할 수 있게 됨 뭐 쿠키나 세션에 해당 정보를 저장하게 끔 하던지 해서..