참고자료

https://develop-writing.tistory.com/105

 

스프링 포맷터(Formatter), 포맷터를 지원하는 ConversionService

스프링 포맷터란(Formatter)?? 웹 애플리케이션에서 객체를 문자로, 문자를 객체로 변환하는 예 화면에 숫자를 출력해야 하는데, Integer => String 출력 시점에 숫자 1000 문자 "1,000" 이렇게 1000 단위에

develop-writing.tistory.com

 

스프링 포맷터(Formatter)

- 웹 애플리케이션에서 객체를 문자로, 문자를 객체로 변환하는 예

ex)

숫자 1000 -> "1,000" => 쉼표를 넣어서 출력

"1,000" -> 1000

날짜 객체"2021-01-01 10:50:11" 출력 or 반대

 

Formatter ProtoType

Locale : 날짜 숫자의 표현 방법은 Locale 현지화 정보가 사용됨.

// 객체를 문자로 변경
public interface Printer<T> {
	String print(T object, Locale locale);
}

// 문자를 객체로 변경
public interface Parser<T> {
	T parse(String text, Locale locale) throws ParseException;
}

public interface Formatter<T> extends Printer<T>, Parser<T> {
}

 

Converter vs Formatter

Converter는 범용(객체 <-> 객체)

Formatter는 문자에 특화(객체 -> 문자, 문자 -> 객체) + 현지화(Locale)

 

ex) 숫자 1000을 문자 "1,000" Format 적용(반대도 처리)

package hello.typeconverter.formatter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.format.Formatter;

import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

@Slf4j
public class MyNumberFormatter implements Formatter<Number> {
	@Override
    public Number parse(String text, Locale locale) throws ParseException {
    	log.info("text={}, locale={}", text, locale);
        NumberFormat format = NumberFormat.getInstance(locale);
        return format.parse(format);
    }
    
    @Override
    public String print(Number object object, Locale locale) {
    	log.info("object={}, locale={}", object, locale);
        return NumberFormat.getInstance(locale).foramt(object);
    }
}

 

// 테스트 검증
public class MyNumberFormatterTest {
    MyNumberFormatter formatter = new MyNumberFormatter();
    @Test
    void parse() throws ParseException {
        Number result = formatter.parse("1,000", Locale.KOREA);
        assertThat(result).isEqualTo(1000L); //Long 타입 주의
    }
    @Test
    void print() {
        String result = formatter.print(1000, Locale.KOREA);
        assertThat(result).isEqualTo("1,000");
    }

}

 

컨버전 서비스(ConversionService)

- 포맷터를 지원

- 컨버전 서비스에 포맷처를 추가할 수 있음 -> 어댑터 패턴을 사용

 

* 어댑터 패턴 - 디자인 패턴의 일종, 호환되지 않는 인터페이스들을 연결하는 디자인 패턴

 

우선순위: 컨버터 > 포맷터

 

public class FormattingConversionServiceTest {
    @Test
    void formattingConversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        //컨버터 등록
        conversionService.addConverter(new StringToIpPortConverter());
        conversionService.addConverter(new IpPortToStringConverter());
        //포맷터 등록
        conversionService.addFormatter(new MyNumberFormatter());
        //컨버터 사용
        IpPort ipPort = conversionService.convert("127.0.0.1:8080",
                IpPort.class);
        assertThat(ipPort).isEqualTo(new IpPort("127.0.0.1", 8080));
        //포맷터 사용
        assertThat(conversionService.convert(1000,String.class)).isEqualTo("1,000");
        assertThat(conversionService.convert("1,000",
                Long.class)).isEqualTo(1000L);
    }
}

 

DefaultFormattingConversionService 상속 관계

FormattingConversionService는 ConversionService관련 기능을 상속받기 때문에 결과적으로 컨버터도 포맷터도 모두 등록할 수 있습니다. 그리고 사용할 때는 ConversionService가 제공하는 convert를 사용하면 됩니다.

추가로 스프링 부트는 DefaultFormattingConversionService를 상속 받은 WebConversionService를 내부에서 사용합니다.

 

* formatter를 spring boot에 적용

import hello.typeconverter.converter.IntegerToStringConverter;
import hello.typeconverter.converter.IpPortToStringConverter;
import hello.typeconverter.converter.StringToIntegerConverter;
import hello.typeconverter.converter.StringToIpPortConverter;
import hello.typeconverter.formatter.MyNumberFormatter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
	@Override
    public void addFormatters(FormatterRegistry registry) {
    	registry.addConverter(new StringToIpPortConverter());
        registry.addConverter(new IpPortToStringConverter());
        
        registry.addFormatter(new MyNumberFormatter());
    }
}

// ConvertController
@Slf4j
@Controller
public class ConvertController {
	@GetMapping("/converter-view")
    public String converterView(Model model) {
    	model.addAttribute("number", 10000);
        model.addAttribute("ipPort", new IpPort("127.0.0.1", 8080));
        return "convert-view";
    }
}

 

 

'spring boot' 카테고리의 다른 글

스프링 타입 컨버터 - Converter  (1) 2023.11.23
타임리프 정리-2  (0) 2023.10.27
타임리프(Thymeleaf) 정리  (2) 2023.10.19

참고자료

https://develop-writing.tistory.com/103

 

스프링 타입 컨버터 - Converter

스프링 타입 컨버터란? 스프링을 사용해 애플리케이션을 개발할 때 문자를 숫자로 변환하거나, 반대로 숫자를 문자로 변환해야 하는 것처럼 타입을 변환할 때 사용하는 도구입니다. 스프링 MVC

develop-writing.tistory.com

* Converter Prototype

package org.springframework.core.convert.converter

public interface Converter<S, T> {
	T covert(S source);
}

 

스프링은 확장 가능한 컨버터 인터페이스를 제공

스프링에 추가적인 타입 변환이 필요하면 이 컨버터 인터페이스를 구현해서 등록

 

package hello.typeconverter.converter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;

// 문자를 숫자로 변환
@Slf4j
public class StringToIntegerConverter implements Converter<String,Integer> {
    @Override
    public Integer convert(String source) {
        log.info("convert source={}", source);
        return Integer.valueOf(source);
    }
}

// 숫자를 문자로 변환
@Slf4j
public class IntegerToStringConverter implements Converter<Integer, String> {
    @Override
    public String convert(Integer source) {
        log.info("convert source={}", source);
        return String.valueOf(source);
    }
}

 

// Test를 통해 적용
@Test
void stringToInteger(){
    StringToIntegerConverter converter = new StringToIntegerConverter();
    Integer result = converter.convert("10");
    assertThat(result).isEqualTo(10);
}

@Test
void integerToString() {
    IntegerToStringConverter converter = new IntegerToStringConverter();
    String result = converter.convert(10);
    assertThat(result).isEqualTo("10");
}

 

사용자 정의 타입 컨버터

 

예제) http://localhost:8080 에서 IpPort 객체로 변환하는 컨버터

package hello.typeconverter.type;

import lombok.EqualsAndHashCode;
import lombok.Getter;

@Getter
@EqualsAndHashCode
public class IpPort {
	private String ip;
    private int port;
    public IpPort(String ip, int port) {
    	this.ip = ip;
        this.port = port;
    }
}

 

package hello.typeconverter.converter;

import hello.typeconverter.type.IpPort;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;

@Slf4j
public class StringToIpPortConverter implements Converter<String, IpPort> {
	@Override
    public IpPort convert(String source) {
    	log.info("convert source={}", source);
        String [] split = source.split(":");
        String ip = split[0];
        int port = Integer.parseInt(split[1]);
        return new IpPort(ip, port);
    }
}

 

// 테스트를 통해 검증
@Test
void stringToIpPort() {
    StringToIpPortConverter converter = new StringToIpPortConverter();
    String source = "127.0.0.1:8080";
    IpPort result = converter.convert(source);
    assertThat(result).isEqualTo(new IpPort("127.0.0.1", 8080));
}
@Test
void ipPortToString() {
    IpPortToStringConverter converter = new IpPortToStringConverter();
    IpPort source = new IpPort("127.0.0.1", 8080);
    String result = converter.convert(source);
    assertThat(result).isEqualTo("127.0.0.1:8080");
}

 

 

'spring boot' 카테고리의 다른 글

스프링 포맷터(Formatter)  (0) 2023.11.23
타임리프 정리-2  (0) 2023.10.27
타임리프(Thymeleaf) 정리  (2) 2023.10.19
// 스프링 시큐리티 의존성
implementation('org.springframework.boot:spring-boot-starter-oauth2-client')
    
@SpringBootApplication(exclude = SecurityAutoConfiguration.class) <= 이부분 추가

'spring boot > setting' 카테고리의 다른 글

.gitignore파일 제대로 동작하지 않는다면?  (0) 2023.11.13

.gitignore가 제대로 동작하지 않아서 ignore처리된 파일이 changed 파일에 계속 뜨는 경우

 


1.1 원인

.gitignore에 파일을 추가하기 전에 stage에 올라간 파일들이 캐시처리되어 기록이 남아있기 때문


1.2 해결

 

git rm -r --cached .
git add .
git commit -m "fixed untracked files"
git push origin 자신의 branch

 

👍 해결완료

'spring boot > setting' 카테고리의 다른 글

Spring Security 기본 로그인 화면 제거  (0) 2023.11.14

1. 유틸리티 

타임리프에서 자바8 날짜인 LocalDate, LocalDateTime, Instant를 사용하려면 추가 라이브러리 필요 -> 스프링 부트 타임리프를 사용하면 자동으로 추가(걱정 X)

 

사용예시

// basic/date.html
<span th:text="${#temporals.format(localDate, 'yyyy-MM-dd HH:mm:ss')}"></span>

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

2. URL 링크

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

// basic/link.html
<ul>
      <li><a th:href="@{/hello}">basic url</a></li> // 1
      <li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query param</a></li> // 2
      <li><a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">path variable</a></li> // 3
      <li><a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">path variable + query parameter</a></li> // 4
</ul>

1. 단순한 URL: /hello

2. 쿼리 파라미터: /hello?param1=data1&param2=data2

3. 경로 변수: /hello/data1/data2

4. 경로 변수 + 쿼리 파라미터: /hello/data1?param2=data2

 

3. 리터럴(Literals)

// 타임리프에서 문자 리터럴은 항상 '로 감싸야 한다.
<span th:text="'hello'">
// 하지만 너무 귀찮고~ => 공백 없이 쭉 이어진다면 하나의 의미있는 토큰으로 인지 -> 생략 가능
<span th:text="hello"> (o)
<span th:text="hello world"> (x)
<span th:text="'hello world'"> (o)
<span th:text="'hello' + ' world!'"> (o)
<span th:text="'hello ' + ${data}"> (o)
<span th:text="|hello ${data}|"> (o) // -> 리터럴 대체 문법 : 마치 템플릿을 사용하는 것처럼 편하쥬

// 추가 예정 // 

4. 연산

5. 속성 값 설정

6. 반복

7. 조건부 평가

8. 주석

9. 블록

10. 자바스크립트 인라인

11. 템플릿 조각

12. 템플릿 레이아웃

'spring boot' 카테고리의 다른 글

스프링 포맷터(Formatter)  (0) 2023.11.23
스프링 타입 컨버터 - Converter  (1) 2023.11.23
타임리프(Thymeleaf) 정리  (2) 2023.10.19

타임리프 특징

1. 서버 사이드 HTML 렌더링(SSR): 타임리프는 백엔드 서버에서 HTML을 동적으로 렌더링하는 용도로 사용된다.

2. 네츄럴 템플릿: 순수 HTML을 그대로 유지하면서 뷰 템플릿도 사용할 수 있는 타임리프의 특징을 네츄럴 템플릿

3. 스프링 통합 지원: 타임리프는 스프링과 자연스럽게 통합되고, 스프링의 다양한 기능을 편리하게 사용할 수 있게 지원한다.

 

타임리프 사용 선언

<html xmlns:th="http://thymeleaf.org">

 

타임리프의 텍스트 출력 - text, utext

HTML 태그의 속성에 기능을 정의해서 동작한다. HTML의 콘텐츠(content)에 데이터를 출력 => th:text

<span th:text-"${data}">

HTML 태그의 속성이 아니라 HTML 콘텐츠 영역안에서 직접 데이터를 출력하고 싶으면 다음과 같이 [[...]]를 사용하면 된다.

컨텐츠 안에서 직접 출력하기 = [[${data}]]

 

HTML엔티티 - 웹 브라우저는 <를 HTML 태그의 시작으로 인식한다. 따라서 <를 태그의 시작이 아니라 문자로 표현할 수 있는 방법이 필요한데 이것을 HTML 엔티티라고 한다.

 

이스케이프(escape) - HTML에서 사용하는 특수 문자를 HTML엔티티로 변경하는 것.

th:text, [[...]]는 기본적으로 이스케이프를 제공

====> 이스케이프를 사용하지 않으려면

th:text -> th:utext

[[...]] -> [(...)]

 

th:inline="none": 타임리프는 [[...]]를 해석하기 때문에, 화면에 [[...]]글자를 보여줄 수 없다. 이 테그 안에서는 타임리프가 해석하지 말라는 옵션

ex)

<li><span th:inline="none">[[...]] = </span> [[${data}]]</li> -> 이스케이프된채로(=문자 그대로 출력된다.)

<li><span th:inline="none">[[...]] = </span> [(${data})]</li> -> 이스케이프되지 않아서 태그를 그대로 받아들여짐

 

변수 - 스프링에서는 SpringEL이라는 스프링이 제공하는 표현식을 사용

 

@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";
  }

 

 

<!DOCTYPE html>
  <html xmlns:th="http://www.thymeleaf.org">
  <head>
      <meta charset="UTF-8">
      <title>Title</title>
  </head>
<body>
<h1>SpringEL 표현식</h1> <ul>Object
      <li>${user.username} =
      <li>${user['username']} = <span th:text="${user['username']}"></span></li>
      <li>${user.getUsername()} = <span th:text="${user.getUsername()}"></span></
  li>
  </ul>
  <ul>List
      <li>${users[0].username}    = <span th:text="${users[0].username}"></
  span></li>
      <li>${users[0]['username']} = <span th:text="${users[0]['username']}"></
  span></li>
      <li>${users[0].getUsername()} = <span th:text="$
  {users[0].getUsername()}"></span></li>
  </ul>
  <ul>Map
      <li>${userMap['userA'].username} =  <span th:text="$
  {userMap['userA'].username}"></span></li>
      <li>${userMap['userA']['username']} = <span th:text="${userMap['userA']
  ['username']}"></span></li>
  <li>${userMap['userA'].getUsername()} = <span th:text="$
  {userMap['userA'].getUsername()}"></span></li>
  </ul>
  </body>
</html>

 

지역변수 선언

th:with 사용

ex)

<div th:with="first=${users[0]}">

  <p><span th:text="${first.username}"></span>

</div>

 

기본 객체들은 스프링 부트 3.0이상부터 model에 넣어서 관리한다.

@GetMapping("basic-objects")
public String basicObjects(Model model, HttpServletRequest request,
	HttpServletResponse response, HttpSession session) {
    session.setAttribute("sessionData", "Hello Session");
    model.addAttribute("request", request);
    model.addAttribute("response", response);
    model.addAttribute("servletContext", request.getServletContext());
    return "basic/basic-objects";
}

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

 

 

<!DOCTYPE html>
  <html xmlns:th="http://www.thymeleaf.org">
  <head>
      <meta charset="UTF-8">
      <title>Title</title>
  </head>
<body>
<h1>식 기본 객체 (Expression Basic Objects)</h1> <ul>
      <li>request = <span th:text="${request}"></span></li>
      <li>response = <span th:text="${response}"></span></li>
      <li>session = <span th:text="${session}"></span></li>
      <li>servletContext = <span th:text="${servletContext}"></span></li>
      <li>locale = <span th:text="${#locale}"></span></li>
</ul>
<h1>편의 객체</h1> <ul>
      <li>Request Parameter = <span th:text="${param.paramData}"></span></li>
      <li>session = <span th:text="${session.sessionData}"></span></li>
      <li>spring bean = <span th:text="${@helloBean.hello('Spring!')}"></span></
li> </ul>
  </body>
  </html>

'spring boot' 카테고리의 다른 글

스프링 포맷터(Formatter)  (0) 2023.11.23
스프링 타입 컨버터 - Converter  (1) 2023.11.23
타임리프 정리-2  (0) 2023.10.27

+ Recent posts