RE-Heat 개발자 일지

스프링 MVC 2편 - [1] 타임리프 - 기본기능(상편) 본문

백엔드/스프링

스프링 MVC 2편 - [1] 타임리프 - 기본기능(상편)

RE-Heat 2023. 7. 8. 19:35

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

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

인프런 김영한님의 스프링 MVC 2편 강의를 토대로 정리한 내용입니다.

 

[1] 프로젝트 생성

Gradle 세팅

롬복세팅

 

[2] 타임리프 소개

타임리프 특징

  • 서버 사이드 HTML 렌더링(SSR) : 타임리프는 백엔드 서버에서 HTML을 동적으로 렌더링하는 용도로 사용된다.
  • 내추럴 템플릿 :
    • 순수 HTML을 최대한 유지하려는 특성이 있어 웹 브라우저에서 파일을 직접 열어도 내용을 확인할 수 있다. 아울러 서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과도 확인할 수 있어 협업에 유리하다.
    • 반면 JSP는 파일 자체를 웹브라우저에서 열면 정상적인 HTML 결과를 확인할 수 없다.
  • 스프링 통합 지원 : 스프링과 자연스럽게 통합돼 스프링의 다양한 기능을 활용할 수 있다.

기본 표현식

• 간단한 표현:
    ◦ 변수 표현식: ${...}
    ◦ 선택 변수 표현식: *{...} 
    ◦ 메시지 표현식: #{...} 
    ◦ 링크 URL 표현식: @{...} 
    ◦ 조각 표현식: ~{...}
• 리터럴
    ◦ 텍스트: 'one text', 'Another one!',… 
    ◦ 숫자: 0, 34, 3.0, 12.3,…
    ◦ 불린: true, false 
    ◦ 널: null
    ◦ 리터럴 토큰: one, sometext, main,…
• 문자 연산:
    ◦ 문자 합치기: +
    ◦ 리터럴 대체: |The name is ${name}|
• 산술 연산:
    ◦ Binary operators: +, -, *, /, % 
    ◦ Minus sign (unary operator): -
• 불린 연산:
    ◦ Binary operators: and, or
    ◦ Boolean negation (unary operator): !, not
• 비교와 동등:
    ◦ 비교: >, <, >=, <= (gt, lt, ge, le) 
    ◦ 동등 연산: ==, != (eq, ne)
• 조건 연산:
    ◦ If-then: (if) ? (then)
    ◦ If-then-else: (if) ? (then) : (else) 
    ◦ Default: (value) ?: (defaultvalue)
• 특별한 토큰:

    ◦ No-Operation: 

 

[3] 텍스트 - text, utext

① HTML 태그의 속성에 기능을 정의해 동작한다.

    형식 th:text

    예) <span th:text=${data}">

② HTML 컨텐츠 영역 안에 직접 데이터 출력 

    형식 [[...]]

    예) [[${data}]]

 

HTML 엔티티

  • 개념 : HTML엔 예약된 몇몇 문자가 있으며(HTML 예약어) 이를 HTML 코드에서 사용하면 웹 브라우저는 충돌을 방지하기 위해 평소와는 다른 의미로 해석한다. 이를 위해 별도로 만든 문자셋을 엔티티라 부른다.
  • 대표적인 엔티티 예시

출처 : https://www.w3schools.com/html/html_entities.asp (w3schools.com)

 

Escape vs UnEscape

웹브라우저는 <, > 등을 부등호가 아닌 HTML 태그의 시작으로 인식한다. 따라서 HTML에서 사용하는 특수문자를 HTML 엔티티(&lt, &gt)로 변경해야 하고 이를 이스케이프(Escape)라 한다. 타임리프가 제공하는 th:text, [[...]]은 이스케이프가 기본값이다.

 

물론 때때로 Escape를 원하지 않을 때가 있다. 예를 들어 <b>태그를 이용해 Spring! 을 만들고 싶으면 어떻게 할까?

이럴 땐 th:text 대신 th:utext, [[...]] 대신 [(...)]을 쓰면 된다.

 

BasicController : text-unescaped

@GetMapping("text-unescaped")
public String textUnescaped(Model model){
    model.addAttribute("data", "Hello <b>Spring!</b>");
    return "basic/text-unescaped";
}

실행화면 

 

참고]

Escape가 Default값인 이유 : 유저들은 글을 작성할 때 온갖 특수문자를 사용하며 이 탓에 자칫 잘못하면 HTML이 정상 렌더링되지 않는 문제가 발생한다. 따라서 타임리프에선 Escape가 기본이며 개발자도 꼭 필요할 때만 UnEscape를 사용해야 한다.

 

[4] 변수 - SpringEL

타임리프에서 변수를 사용할 때는 변수 표현식 ${...}을 사용한다.

그리고 변수 표현식에는 스프링 EL이라는 스프링이 제공하는 표현식을 사용할 수 있다.

    =>[2] 타임리프 소개 중 특징인 스프링 통합지원과 연관

 

BasicController: variable

@GetMapping("/variable")
public String variable(Model model){
    User userA = new User("userA", 10);
    User userB = new User("userB", 20);

    List<User> list = new ArrayList<>();
    list.add(userA);
    list.add(userB);

    Map<String, User> map = new HashMap<>();
    map.put("userA", userA);
    map.put("userB", userB);

    model.addAttribute("user", userA);
    model.addAttribute("users", list);
    model.addAttribute("userMap", map);

    return "basic/variable";
}

@Data
static class User{
    private String username;
    private int age;

    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

① variable() Object, List, Map 관련 SpringEL 다양한 표현식 접근 위해 세팅

② User class도 추가

 

SpringEL의 다양한 표현식 

  • Object
    user.username : user의 username을 프로퍼티접근 == user.getUsername()
    user['username'] : 위와 같음 user.getUsername()
    user.getUsername() : user의 getUsername()을직접호출
  • List
    users[0].username : List에서 첫 번째 회원을 찾고 username 프로퍼티 접근 == list.get(0).getUsername()
    users[0]['username'] : 위와 같음
    users[0].getUsername() : List에서 첫 번째 회원을 찾고 메서드 직접 호출 
  • Map
    userMap['userA'].username : Map에서 userA를 찾고, username 프로퍼티 접근 == map.get("userA").getUsername()
    userMap['userA']['username'] : 위와 같음
    userMap['userA'].getUsername() : Map에서 userA를 찾고 메서드 직접호출

지역변수 선언

th:with를 사용하면 지역 변수를 선언해 사용할 수 있다. 당연히 지역 변수는 선언한 태그 안(지역 스코프)에서만 사용할 수 있다.

 

실행화면 + variable.html

 

[5] 기본 객체들

타임리프가 제공하는 기본 객체

${#locale} : locale 헤더 정보 (언어·지역 설정)

${sesson} : HttpSession 객체 (http 세션 - 사용자 식별, 로그인 유지)

    => 브라우저 종료 전까지 로그인 상태 유지

${param} : HttpServletRequest 객체 (http 요청 메세지)

 

BasicController : basic-objects

@GetMapping("/basic-objects")
public String basicObjects(HttpSession session){
    session.setAttribute("sessionData", "Hello Session");
    return "basic/basic-objects";
}

 

기본 객체

주의] 스프링부트 3.0 버전 이후 ${#request}, ${#response}, ${#session}, ${#ServletContext}는 지원하지 않는다. 위 html의 코드는 스프링부트 3.0 이하 버전 

 

여러 객체를 지원하지 않아서 생긴 문제점은

HTTP 요청 파라미터 접근 ${param} , HTTP 세션 접근 ${session} , 스프링 빈 접근 @ 등 편의 객체를 제공해 해결.

 

BasicController : helloBean

@Component("helloBean")
static class HelloBean{
    public String hello(String data){
        return "Hello " + data;
    }
}

 

편의 객체

① URL에 paramData를 넣어주면 ${param}으로 쉽게 꺼내쓸 수 있다.

실제 URL

② session은 session.setAttribute()로 저장한 값 가져 옴.

③ spring bean은 @Component를 달아 스프링 빈으로 등록한 HelloBean의 return값 가져옴.

   => hello('Spring!') => return "Hello " + data(Spring!) => Hello Spring!

 

[6] 유틸리티 객체와 날짜

타임리프는 문자·숫자·날짜·URI 등을 편리하게 다루는 다양한 유틸리티 객체도 제공한다.

(필요할 때 매뉴얼에서 찾아 사용하는 게 좋다)

 

■ 타임리프 유틸리티 객체

#message : 메시지, 국제화처리
#uris : URI 이스케이프 지원
#dates : java.util.Date 서식 지원
#calendars : java.util.Calendar 서식 지원
#temporals : 자바8 날짜 서식지원
#numbers : 숫자 서식지원
#strings : 문자 관련편의기능
#objects : 객체 관련기능제공
#bools : boolean 관련기능 제공
#arrays : 배열 관련 기능제공
#lists , #sets , #maps : 컬렉션 관련 기능제공
#ids : 아이디처리 관련 기능제공

 

타임리프 유틸리티 객체

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#expression-utility-objects

유틸리티 객체 예시

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#appendix-b-expressionutility-objects

LocalDataTime 사용 예시

BasicController : date

@GetMapping("/date")
public String date(Model model){
    model.addAttribute("localDateTime", LocalDateTime.now());
    return "basic/date";
}

실제결과 - date.html

 

[7] URL 링크

타임리프에서 URL을 생성할 땐 @{...} 문법을 사용한다.

 

BasicController : link

@GetMapping("/link")
public String link(Model model){
    model.addAttribute("param1", "data1");
    model.addAttribute("param2", "data2");
    return "basic/link";
}

실행결과 - link.html

① 단순한 URL 

    @{/hello} => http://localhost:8081/hello

② 쿼리 파라미터 :

    @{/hello(param1=${param1}, param2=${param2})} => http://localhost:8081/hello?param1=data1&param2=data2

    ()안에 있는 부분은 쿼리 파라미터로 대체된다.

③ 경로 변수

    @{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})} => /hello/data1?param2=data2

    URL 경로상에 변수({param1}, {param2}}가 있으면 ()부분은 경로 변수로 처리된다.

④ 경로 변수 + 쿼리 파라미터

    @{/hello/{param1}(param1=${param1}, param2=${param2})} => /hello/data1?param2=data2

    경로 변수로 처리{parma1}하고 남은 나머지는 자동으로 쿼리파라미터로 붙는다.

 

 

[8] 리터럴

리터럴은 소스 코드상에 고정된 값을 말하는 용어다.

String a = "hello"
int a = 10 * 20

hello는 문자 리터럴, 10, 20은 숫자 리터럴

 

타임리프 리터럴 종류

  • 문자: 'hello'
  • 숫자: 10
  • 불리언: true , false
  • null: null

① 타임 리프에서 문자리터럴은 ''작은따옴표로 감싸야한다.

    <span th:text="'hello'">

② 하지만 공백 없이 쭉 이어진다면 하나의 의미 있는 토큰으로 인식해 작은따옴표를 생략할 수 있다.

    <span th:text="hello">

 

잘못된 예 

<span th:text="hello world!"></span> : hello world사이에 공백이 있어 하나의 토큰으로 인식되지 않음

<span th:text="'hello world!'"></span>처럼 작은따옴표로 감싸면 정상 동작한다.

 

BasicController : literal

@GetMapping("/literal")
public String literal(Model model){
    model.addAttribute("data", "Spring!");
    return "basic/literal";
}

 

실제 결과 - literal.html

 

[9] 연산

타임리프 연산은 JAVA와 흡사하다. 단, HTML 안에서 사용하기 때문에 HTML 엔티티를 사용하는 부분은 주의해야 한다.

 

BasicController : operation

@GetMapping("/operation")
public String operation(Model model){
    model.addAttribute("nullData", null);
    model.addAttribute("data", "Spring!");
    return "basic/operation";
}

 

실제 결과 - operation.html

 

 

  • 비교연산 : HTML 엔티티를 사용해야 하는 부분을 주의하자
    • ! (not), == (eq), != (neq, ne), > (gt), < (lt), >= (ge), <= (le)
  • 조건식 : 자바의 조건식과 유사
  • Elvis 연산자 : 조건식의 편의 버전
    예시 ] <span th:text="${data}?: '데이터가 없습니다.'"></span>
        data가 있으면 data값을 출력, 없으면 '데이터가 없습니다.' 출력
        true일 때 출력:false일 때 출력으로만 이루어진 느낌

No-Operation : _ 마치 타임리프가 실행되지 않는 것처럼 동작
<span th:text="${nullData}?: _">데이터가 없습니다.</span>
HTML의 내용 그대로 활용 가능. 다시 말해 th:text가 무효화돼 HTML 기본값이 출력된다고 보면 된다.