타임리프 스프링 통합 문서 1~5
Tutorial: Thymeleaf + Spring
Document version: 20181029 - 29 October 2018
Project version: 3.0.11.RELEASE
Project web site: https://www.thymeleaf.org
Preface
이 튜토리얼은 타임리프가 어떻게 스프링 프레임워크와 통합되는 지 설명한다, 특히 스프링 MVC와.
타임리프는 스프링 프레임워크의 3.x와 4.x 버전 모두 통합이 있다, 그것은 thymeleaf-spring3
와 thymeleaf-spring4
라는 서로 다른 라이브러리로 제공된다. 이 라이브러리들과 패키지들은 다른 .jar 파일들로 되어있고, classpath에 추가해야 한다, 애플리케이션에서 타임리프-스프링 통합을 사용하려면.
코드 예제와 이 튜토리얼의 예제 애플리케이션은 스프링 4.x와 그것에 해당하는 타임리프 통합에 기반하고 있다, 그러나 튜토리얼의 내용들은 또한 스프링 3.x에서도 유효하다. 만약 당신의 애플리케이션이 스프링 3.x를 사용한다면, 당신이 해야할 것은 코드 샘플의 org.thymeleaf.spring4
패키지를 org.thymeleaf.spring3
패키지로 수정하는 것이다.
1 Integrating Thymeleaf with Spring
타임리프는 스프링 통합을 제공한다, 당신이 스프링 MVC 애플리케이션에서 JSP의 완전한 기능을 갖춘 대체물로 사용할 수 있도록.
이러한 통합들은 당신에게 제공한다:
- Spring MVC의
@Controller
객체의 매핑 된 메서드를 JSP에서와 똑같이 Thymeleaf에서 관리하는 템플릿으로 전달한다.
- 템플릿에서 OGNL 대신에 스프링 표현 언어(Spring EL)을 사용할 수 있다.
- (역주: *OGNL: JSP에서 사용하는 문법)
- 당신의 템플릿으로 form을 만들자, bean과 완벽하게 통합되는, 프로퍼티 에디터의 사용을 포함하고, 변환 서비스와 에러 핸들링을 검증하는.
- 스프링에서 관리되는 메시지 파일으로 부터 국제화 메시지를 표시하자(
MessageSource
객체로 부터)
- 당신의 템플릿을 스프링의 리소스 resolution 메커니즘을 사용하여 resolve 하자.
- (역주: *resolve: View resolver를 뜻하는 것으로 보임 )
- (View resolver: String 값 등으로 사용할 View 객체를 결정해주는 것 → 어떻게? 매핑되어 있음)
이 튜토리얼을 완벽하게 이해하기 위해서는, 당신은 우선 표준 방언을 깊이 있게 묘사하는 "Using Thymeleaf" 튜토리얼을 먼저 읽을 필요가 있다.
2 The SpringStandard Dialect
쉽고 나은 통합을 얻기위해서, 타임리프는 방언을 제공한다, 스프링과 올바르게 작동하기에 필요한 모든 특정한 기능들을 구현하면서.
이 특정한 방언은 타임리프 표준 방언을 기본으로 하며, org.thymeleaf.standard.StandardDialect
를 상속받은 org.thymeleaf.spring4.dialect.SpringStandardDialect
클래스에서 구현된다.
모든 기능은 표준 방언에서 이미 표현되지만 -따라서 상속된- , 스프링 표준 방언은 아래와 같은 특별한 기능들을 도입하였다.
- 다양한 표현언어로 스프링 표현언어(Spring EL 또는 SpEL)를 사용하라, OGNL 대신에. 결과적으로, 모든
${...}
그리고{...}
표현들은 Spring EL 엔진에 의해 평가될 것이다. Spring EL Compiler에 대한 지원은 Spring 4.2.4+에서 사용가능하다.
- 다음의 Spring EL 구문을 사용하면 Application Context에 있는 어떤 bean에도 접근가능하다 :
${@myBean.doSomething()}
- form processing을 위한 새로운 attribute를 제공한다,
th:field
,th:errors
그리고th:errorclass
, 게다가th:object
의 새로운 구현, form command 선택에서 사용될 수 있는.
- 표현 객체와 메서드
#themes.code(...)
, 이것은 JSP 커스텀 태그의spring:theme
와 동등하다.
- 표현 객체와 메서드
#mvc.uri(...)
, 이것은 JSP 커스텀 함수spring:mvcUrl(...)
와 동등하다.(오직 스프링 4.1+에서만)
대부분의 경우 설정의 일부로 일반 TemplateEngine
객체에서 이 방언을 사용하면 안됩니다. 당신이 매우 특정한 스프링 통합이 필요하다고 할지라도, 당신은 대신에 새로운 템플릿 엔진 클래스의 인스턴스를 생성하여, 모든 요구되는 설정 스텝들이 자동적으로 수행되게 해야한다: org.thymeleaf.spring4.SpringTemplateEngine
.
예제 bean 설정은 다음과 같다:
@Bean
public SpringResourceTemplateResolver templateResolver(){
// SpringResourceTemplateResolver 자동으로 스프링의 리소스 Resolution 인프라와
// 통합되므로, 강력하게 추천한다.
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
// HTML이 기본값이지만, 명확성을 위해 여기에 추가하였음.
templateResolver.setTemplateMode(TemplateMode.HTML);
// 템플릿 캐시는 기본값으로 true 이다. 만약, 수정시 자동으로
// 업데이트되기를 원하면 false로 세팅하라.
templateResolver.setCacheable(true);
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine(){
// SpringTemplateEngine는 자동적으로 SpringStandardDialect를 적용하여
// 스프링의 MessageSource 메시지 resolution 메커니즘을 가능하게 한다.
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
// 스프링 4.2.4+에서 SpringEl 컴파일러를 활성화 시키면,
// 대부분의 시나리오에서 실행이 빨라진다, 그러나 한 템플릿의 표현식이
// 여러 데이터 유형에서 재사용되는 특정 경우와 호환되지 않을 수 있으므로,
// 이 플래그는 더 안전한 이전 버전과의 호환성을 위해
// 기본적으로 false 값을 가진다.
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
또는, 스프링의 XML-기반 설정을 사용한다:
<!-- SpringResourceTemplateResolver 자동으로 스프링의 리소스 Resolution 인프라와 -->
<!-- 통합되므로, 강력하게 추천한다. -->
<bean id="templateResolver"class="org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/" />
<property name="suffix" value=".html" />
<!-- HTML이 기본값이지만, 명확성을 위해 여기에 추가하였음. -->
<property name="templateMode" value="HTML" />
<!-- 템플릿 캐시는 기본값으로 true 이다. 만약, 수정시 자동으로 -->
<!-- 업데이트되기를 원하면 false로 세팅하라. -->
<property name="cacheable" value="true" /></bean>
<!-- SpringTemplateEngine는 자동적으로 SpringStandardDialect를 적용하여 -->
<!-- 스프링의 MessageSource 메시지 resolution 메커니즘을 가능하게 한다. -->
<bean id="templateEngine"class="org.thymeleaf.spring4.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
<!-- 스프링 4.2.4+에서 SpringEl 컴파일러를 활성화 시키면, -->
<!-- 대부분의 시나리오에서 실행이 빨라진다, 그러나 한 템플릿의 표현식이 -->
<!-- 여러 데이터 유형에서 재사용되는 특정 경우와 호환되지 않을 수 있으므로, -->
<!-- 이 플래그는 더 안전한 이전 버전과의 호환성을 위해 -->
<!-- 기본적으로 false 값을 가진다. -->
<property name="enableSpringELCompiler" value="true" /></bean>
3 Views and View Resolvers
3.1 Spring MVC의 뷰와 뷰 리졸버
SpringMVC에는 다음 두개의 인터페이스가 있다, 템플레이팅 시스템의 핵심을 따르는.
org.springframework.web.servlet.View
org.springframework.web.servlet.ViewResolver
애플리케이션의 모델 페이지들을 보고, 동작을 수정하거나 미리 정의할 수 있다, 동작을 빈으로 정의함으로써. 뷰들은 실제 html 인터페이스를 렌더링할 책임을 가지며, 일반적으로 타임리프와 같은 템플릿 엔진을 실행하여.
뷰리졸버들은 뷰 객체를 얻을 책임을 가지는 객체이다, 특정한 연산과 로케일로. 전형적으로, 컨트롤러가 뷰리졸버에게 특정한 이름(컨트롤러에서 리턴한 String) 값을 사용하여 ,애플리케이션의 모든 뷰 리졸버가 순서가 있는 체인으로 뷰 객체를 찾을때까지 실행되어, 뷰 객체가 찾아지고, 컨트롤이 패스되어 HTML의 렌더링이 이루어진다.
과거의 스프링 MVC의 JSP와 JSTL 뷰리졸버의 전형적인 설정은 다음과 같다:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsps/" />
<property name="suffix" value=".jsp" />
<property name="order" value="2" />
<property name="viewNames" value="*jsp" />
</bean>
프로퍼티들을 간단히 살펴보면 어떻게 설정되는 지 알 수 있다:
viewClass
는 뷰 인스턴스의 클래스를 설정한다. 이것은 JSP 리졸버에게 필요하며, 그러나 타임리프로 작업할 때는 전혀 필요로 하지 않는다.
prefix
와suffix
타임리프의 템플릿리졸버 객체의 같은 이름을 가지는 속성들과 비슷한 방식으로 동작한다.
order
는 체인의 뷰리졸버가 질의되는 순서를 설정한다.
viewNames
은 뷰의 이름을 정의(또는 와일드카드*)한다, 뷰리졸버에 의해 결정되는.
3.2 타임리프의 뷰와 뷰 리졸버
타임리프는 위에서 언급한 두개의 인터페이스에 대한 구현체를 제공한다:
org.thymeleaf.spring4.view.ThymeleafView
org.thymeleaf.spring4.view.ThymeleafViewResolver
이 두개의 클래스들은 타임리프 템플릿 엔진을 컨트롤러 실행의 결과로 처리하는 책임을 가진다.
타임리프 뷰리졸버의 설정은 JSP의 설정과 매우 유사하다:
@Bean
public ThymeleafViewResolver viewResolver(){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
// 'order'와 'viewNames"은 옵션이다
viewResolver.setOrder(1);
viewResolver.setViewNames(new String[] {".html", ".xhtml"});
return viewResolver;
}
또는 xml에서:
<bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine" />
<!-- 'order'와 'viewNames"은 옵션이다 -->
<property name="order" value="1" />
<property name="viewNames" value="*.html,*.xhtml" /></bean>
templateEngine
패러미터는 당연히, 우리가 이전 챕터에서 정의한 SpringtemplateEngine
객체이다. 다른 두개 (order and viewNames)는 각각 옵션이며, 이전에 봤던 JSP 뷰리졸버와 같은 의미를 지닌다.
prefix와 suffix 패러미터를 가지지 않는 다, 왜냐하면 템플릿 리졸버에서 명시되었기 때문이다.( 차례로 템플릿 엔진으로 전달된다.)
그리고 View 빈을 정의하길 원하고, 정적인 변수를 추가하길 원한다면? 쉽게, 그것을 위한 프로토타입 빈을 추가하면 된다:
@Bean
@Scope("prototype")
public ThymeleafView mainView() {
ThymeleafView view = new ThymeleafView("main"); // templateName = 'main'
view.setStaticVariables(
Collections.singletonMap("footer", "The ACME Fruit Company"));
return view;
}
이것을 하기위해, 당신은 이 view 빈을 bean 이름으로 선택하여 실행시킬 수 있다.(위의 예제에서 mainView)
4 Spring Thyme Seed Starter Manager
가이드의 이 챕터와 미래 챕터의 예제 소스코드는 Spring Thyme Seed Starter Manager GitHub repository에서 찾을 수 있다.
4.1 The Concept
타임리프에는 백리향의 거대한 팬들이다, 매년 봄에 리는 우리의 씨앗을 준비한다, 스타팅 키트와 좋은 토양, 우리가 가장좋아하는 씨앗들, 스페인의 태양과 인내있는 기다림으로 우리의 새로운 식물을 기르게 한다.
그러나 이번 년도에 우리는 라벨을 시작 컨테이터에 붙여 각각의 컨테이너에 어떤 씨앗들이 자라는지 알게하는데 지쳤다. 그래서 스프링 MVC와 타임리프를 사용한 애플리케이션을 준비하여 우리의 스타터 목록을 작성하는 데 준비하도록 한다: 스프링 백리향 씨앗스타터 매니저.
비슷한 방식으로 좋은 백리향 가상 잡화 애플리케이션을 타임리프 튜토리얼로 개발하였다, STSM은 우리에게 Spring MVC의 템플릿 엔진으로의 타임리프의 통합의 대부분의 중요한 점들을 예시할 수 있게 해준다.
4.2 Business Layer
우리는 우리의 애플리케이션의 매우 단순한 비지니스 레이어를 필요로 한다. 첫째로, 우리의 모델 엔티티를 바라보자:
매우 단순한 서비스 클래스들의 커플은 요구되는 비지니스 메소드를 제공한다. 다음과 같이:
@Service
public class SeedStarterService {
@Autowired
private SeedStarterRepository seedstarterRepository;
public List<SeedStarter> findAll() {
return this.seedstarterRepository.findAll();
}
public void add(final SeedStarter seedStarter) {
this.seedstarterRepository.add(seedStarter);
}
}
그리고:
@Service
public class VarietyService {
@Autowired
private VarietyRepository varietyRepository;
public List<Variety> findAll() {
return this.varietyRepository.findAll();
}
public Variety findById(final Integer id) {
return this.varietyRepository.findById(id);
}
}
4.3 Spring MVC configuration
다음으로 우리는 애플리케이션의 Spring MVC 설정을 세팅한다. 리소스 핸들링이나 애노테이션 스캐닝과 같은 표준 스프링 MVC 아티팩트 뿐만 아니라, 템플릿 엔진과 뷰리졸버 인스턴스의 생성과 같은 것도 세팅에 포함된다.
@Configuration
@EnableWebMvc
@ComponentScan
public class SpringWebConfig
extends WebMvcConfigurerAdapter implements ApplicationContextAware {
private ApplicationContext applicationContext;
public SpringWebConfig() {
super();
}
public void setApplicationContext(final ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
/* ******************************************************************* */
/* 일반적인 설정 아티팩트 */
/* 정적 리소스, i18n 메시지, 포맷 변환 서비스 */
/* ******************************************************************* */
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
registry.addResourceHandler("/images/**").addResourceLocations("/images/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
}
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("Messages");
return messageSource;
}
@Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
registry.addFormatter(varietyFormatter());
registry.addFormatter(dateFormatter());
}
@Bean
public VarietyFormatter varietyFormatter() {
return new VarietyFormatter();
}
@Bean
public DateFormatter dateFormatter() {
return new DateFormatter();
}
/* **************************************************************** */
/* 타임리프 특화 아티팩트 */
/* 템플릿리졸버 <- 템플릿엔진 <- 뷰리졸버 */
/* **************************************************************** */
@Bean
public SpringResourceTemplateResolver templateResolver(){
// SpringResourceTemplateResolver는 자동으로 스프링의 리소스 찾기(?) 인프라와
// 통합된다, 강력하게 추천된다.
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
// HTML은 기본값이지만, 여기서는 명확성을 위해 추가하였음
templateResolver.setTemplateMode(TemplateMode.HTML);
// 템플릿 캐시는 디폴트 값이 true이지만, 수정시 템플릿이 자동으로
// 업데이트되기를 원한다면 false로 세팅하시오.
templateResolver.setCacheable(true);
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine(){
// SpringTemplateEngine은 자동으로 SpringStandardDialect를 적용하여
// 스프링의 메시지소스 메시지 해결 메커니즘을 가능하게 한다.
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
// 4.2.4+ SpringEL 컴파일러를 실행할 수 있게 하는 것은 대부분의
// 시나리오에서 속도를 증가시키지만, 하나의 템플릿에서 다른 데이터 타입을
// 넘어서 재사용하는 것과 같은 특정한 경우에 호환이 되지 않을 수 있다.
// 그런 경우를 대비하여 false가 기본 값으로 설정되어 있다,
// 더 안전한 이전 버전과의 호환성을 위해.
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
@Bean
public ThymeleafViewResolver viewResolver(){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
return viewResolver;
}
}
4.4 The Controller
물론, 우리는 또한 애플리케이션의 컨트롤러가 필요하다. STSM이 시드 스타터의 리스트와 새로운 것을 추가하는 폼을 가지는 오직 하나의 웹페이지를 포함함으로서, 우리는 서버 인터랙션을 위한 하나의 컨트롤러 클래스를 작성해야 한다.
@Controller
public class SeedStarterMngController {
@Autowired
private VarietyService varietyService;
@Autowired
private SeedStarterService seedStarterService;
...
}
지금 컨트롤러 클래스에 무엇을 추가하는 것을 살펴보자.
Model Attributes
첫째로 우리는 페이지에서 우리가 필요로 하는 ModelAttribute들을 추가하였다. ( 이렇게 하면 model에 추가됨)
@ModelAttribute("allTypes")
public List<Type> populateTypes() {
return Arrays.asList(Type.ALL);
}
@ModelAttribute("allFeatures")
public List<Feature> populateFeatures() {
return Arrays.asList(Feature.ALL);
}
@ModelAttribute("allVarieties")
public List<Variety> populateVarieties() {
return this.varietyService.findAll();
}
@ModelAttribute("allSeedStarters")
public List<SeedStarter> populateSeedStarters() {
return this.seedStarterService.findAll();
}
Mapped methods
그리고 지금 컨트롤러 파트에서 가장 중요한 것은, mapped 메소드들이다. 하나는 form page를 소개할때, 다른 하나는 새로운 SeedStarter 객체를 추가하는 처리를 위한.
@RequestMapping({"/","/seedstartermng"})
public String showSeedstarters(final SeedStarter seedStarter) {
seedStarter.setDatePlanted(Calendar.getInstance().getTime());
return "seedstartermng";
}
@RequestMapping(value="/seedstartermng", params={"save"})
public String saveSeedstarter(
final SeedStarter seedStarter, final BindingResult bindingResult, final ModelMap model) {
if (bindingResult.hasErrors()) {
return "seedstartermng";
}
this.seedStarterService.add(seedStarter);
model.clear();
return "redirect:/seedstartermng";
}
4.5 Configuring a Conversion Service
Date와 View layer에 있는 다양한 객체의 쉬운 포맷팅을 위해서, 우리는 우리의 애플리케이션을 설정하였다, Spring ConversionService 객체는 생성되고 초기화되었다( 우리가 확장한 WebMvcConfigurerAdapter로) 우리가 필요로 할 몇개의 포매터 객체와 함께. 이것을 다시보면:
@Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
registry.addFormatter(varietyFormatter());
registry.addFormatter(dateFormatter());
}
@Bean
public VarietyFormatter varietyFormatter() {
return new VarietyFormatter();
}
@Bean
public DateFormatter dateFormatter() {
return new DateFormatter();
}
스프링 포매터들은 org.springframework.format.Formatter
의 구현체들이다. 스프링 변환 인프라 작업들을 좀더 살펴보면, spring.io의 문서를 살펴보라.
Messages.properties
의 date.format
메시지 키에 있는 형식 문자열에 따라 날자 형식을 지정하는 DateFormatter
를 살펴보자:
public class DateFormatter implements Formatter<Date> {
@Autowired
private MessageSource messageSource;
public DateFormatter() {
super();
}
public Date parse(final String text, final Locale locale) throws ParseException {
final SimpleDateFormat dateFormat = createDateFormat(locale);
return dateFormat.parse(text);
}
public String print(final Date object, final Locale locale) {
final SimpleDateFormat dateFormat = createDateFormat(locale);
return dateFormat.format(object);
}
private SimpleDateFormat createDateFormat(final Locale locale) {
final String format = this.messageSource.getMessage("date.format", null, locale);
final SimpleDateFormat dateFormat = new SimpleDateFormat(format);
dateFormat.setLenient(false);
return dateFormat;
}
}
VarietyFormatter는 자동으로 Variety 엔티티를 우리가 원하는 방식의 폼으로 변경해준다.( 기본적으로, 그들의 id 필드 값으로):
public class VarietyFormatter implements Formatter<Variety> {
@Autowired
private VarietyService varietyService;
public VarietyFormatter() {
super();
}
public Variety parse(final String text, final Locale locale) throws ParseException {
final Integer varietyId = Integer.valueOf(text);
return this.varietyService.findById(varietyId);
}
public String print(final Variety object, final Locale locale) {
return (object != null ? object.getId().toString() : "");
}
}
이런 포매터들이 어떻게 우리의 데이터가 나중에 표시되는지 나중에 배울 것이다.
5 Listing Seed Starter Data
/WEB-INF/templates/seedstartermng.html
페이지가 보여주는 첫번째 것은 현재 저장된 시드 스타터의 리스트이다. 이를 위해, 우리는 일부 외부화된 메시지와 model 속성들의 표현 평가가 필요하다. 다음과 같이:
<div class="seedstarterlist" th:unless="${#lists.isEmpty(allSeedStarters)}">
<h2 th:text="#{title.list}">List of Seed Starters</h2>
<table>
<thead>
<tr>
<th th:text="#{seedstarter.datePlanted}">Date Planted</th>
<th th:text="#{seedstarter.covered}">Covered</th>
<th th:text="#{seedstarter.type}">Type</th>
<th th:text="#{seedstarter.features}">Features</th>
<th th:text="#{seedstarter.rows}">Rows</th>
</tr>
</thead>
<tbody>
<tr th:each="sb : ${allSeedStarters}">
<td th:text="${{sb.datePlanted}}">13/01/2011</td>
<td th:text="#{|bool.${sb.covered}|}">yes</td>
<td th:text="#{|seedstarter.type.${sb.type}|}">Wireframe</td>
<td th:text="${#strings.arrayJoin(
#messages.arrayMsg(
#strings.arrayPrepend(sb.features,'seedstarter.feature.')),
', ')}">Electric Heating, Turf</td>
<td>
<table>
<tbody>
<tr th:each="row,rowStat : ${sb.rows}">
<td th:text="${rowStat.count}">1</td>
<td th:text="${row.variety.name}">Thymus Thymi</td>
<td th:text="${row.seedsPerCell}">12</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
여기서 볼 것이 많다. 각각의 조각들을 분리하여 살펴보자.
첫째로, 만약 어떤 시드 스타터가 있다면 여기에 보여진다. 우리는 th:unless 속성과 #lists.isEmpty(...)
함수를 사용하여 기록한다.
<div class="seedstarterlist" th:unless="${#lists.isEmpty(allSeedStarters)}">
#lists
와 같은 모든 유틸리티 객체들은 Spring EL 표현식에서 사용가능하다, 표준 방언의 OGNL 표현식에서와 마찬가지로.
다음은 많은 국제화된(외부의) 텍스트들이다, 다음과 같은:
<h2 th:text="#{title.list}">List of Seed Starters</h2>
<table>
<thead>
<tr>
<th th:text="#{seedstarter.datePlanted}">Date Planted</th>
<th th:text="#{seedstarter.covered}">Covered</th>
<th th:text="#{seedstarter.type}">Type</th>
<th th:text="#{seedstarter.features}">Features</th>
<th th:text="#{seedstarter.rows}">Rows</th>
...
이러한 Spring MVC 애플리케이션은, 우리가 스프링 설정에서 이미 MessageSource 빈으로 정의하였다( MessageSource 객체들은 스프링 MVC의 외부의 텍스트를 다루는 표준 방법이다.) :
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("Messages");
return messageSource;
}
...그리고 basename
속성은 classpath에 Messages_es.properties 또는 Messages_en.properties와 같은 파일들이 있다는 것을 가르킨다. 스페인어 버전을 살펴보자:
title.list=Lista de semilleros
date.format=dd/MM/yyyy
bool.true=sí
bool.false=no
seedstarter.datePlanted=Fecha de plantación
seedstarter.covered=Cubierto
seedstarter.type=Tipo
seedstarter.features=Características
seedstarter.rows=Filas
seedstarter.type.WOOD=Madera
seedstarter.type.PLASTIC=Plástico
seedstarter.feature.SEEDSTARTER_SPECIFIC_SUBSTRATE=Sustrato específico para semilleros
seedstarter.feature.FERTILIZER=Fertilizante
seedstarter.feature.PH_CORRECTOR=Corrector de PH
테이블 리스트의 첫번째 칼럼은 시드 스타터가 준비된 날짜를 보여준다. 그러나 우리가 정의한 DateFormatter의 방식으로 보여줄 것이다. 이를 위해 이중 중괄호 구문을 사용할 수 있다(${{...}}), 이것은 Spring 변환 서비스를 자동으로 적용한다, 우리가 설정에서 등록했던 DateFormatter를 포함하여.
<td th:text="${{sb.datePlanted}}">13/01/2011</td>
다음은 시드 스타터 컨테이너가 커버되는지 아닌지를 보여준다, 불리언 변수의 값을 리터럴 대체 표현식으로 변환함으로서, 빈 속성의 국제화된 "yes" 또는 "no"로 변환하여:
<td th:text="#{|bool.${sb.covered}|}">yes</td>
지금 우리는 시드 스타터 컨테이너의 타입을 보여준다. 타입은 두개(WOO 와 PLASTIC)의 값을 가지는 자바 enum이고, 그것이 우리가 seedstarter.type.WOOD
and seedstarter.type.PLASTIC
라는 파일들을 Message에 두 속성을 정의해놓은 이유이다.
그러나 타입들의 국제화 이름을 얻으려면, 우리는 seedstarter.type
을 추가할 필요가 있다. 표현식을 통해 enum값에 접두어를 붙이면, 메시지 키로 사용할 수 있다.
<td th:text="#{|seedstarter.type.${sb.type}|}">Wireframe</td>
이 리스팅의 가장 어려운 부분은 속성 컬럼이다. 그것에서 우리는 컨테이너의 모든 속성들을 표시하려고 한다 - 속성 enum의 배열의 형태로 와서 -, 콤마에 의해 분리된다.“Electric Heating, Turf”.와 같이
Types에서 했던 것처럼 이런 enum 값들을 외부화시켜야 하기 때문에, 특히 어렵다. 플로우는 그래서:
- features 배열의 모든 원소에 해당하는 접두어를 붙인다.
- 스텝 1의 모든 키 값들에 해당하는 외부 메시지를 얻는다.
- 스텝 2에서 얻은 모든 메시지들을 콤마를(,) 구분자로 하여 합친다.
이것을 얻기 위해, 우리는 다음의 코드를 생성한다:
<td th:text="${#strings.arrayJoin(
#messages.arrayMsg(
#strings.arrayPrepend(sb.features,'seedstarter.feature.')),
', ')}">Electric Heating, Turf</td>
우리의 리스팅의 마지막 컬럼은 꽤 단순하다, 사실. 심지어 컨테이너의 각각의 행의 내용을 보여주기 위한 네스티드 테이블을 가지고 있다:
<td>
<table>
<tbody>
<tr th:each="row,rowStat : ${sb.rows}">
<td th:text="${rowStat.count}">1</td>
<td th:text="${row.variety.name}">Thymus Thymi</td>
<td th:text="${row.seedsPerCell}">12</td>
</tr>
</tbody>
</table>
</td>
https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#business-layer