DEV&OPS/Java

FreeMarker 설정과 사용법

ALEPH.GEM 2026. 7. 6. 11:16
728x90

jdk11, gradle 6, springframework 5 기준으로 작성되었습니다.

 

FreeMarker

자바 기반 서버측 템플릿 엔진으로 뷰 레이어(프레젠테이션 레이어) 개발에 사용됩니다.

서버에서 데이터를 처리하고 freemarker 템플릿으로 뷰를 생성하여 최종 HTML을 브라우저에 전송합니다.

미리 정의된 템플릿고 동적으로 생성된 데이터를 결합시켜주는 소프트웨어입니다.

데이터 처리용 비즈니스 로직과 화면 표시용 프레젠테이션 로직을 분리하는데 사용합니다.

자바 코드에서는 데이터를 준비하고 freemarker 템플릿 파일(FTL)에서는 그 데이터를 사용자에게 보여주는 방식으로 정의합니다.

 

1. 디렉티브(Directives): 템플릿이 수행해야 할 작업을 지정

예) <# ... > 

2. 보간(Interpolations): 데이터를 출력

예) ${product.name}

3. 주석(Comments): freeMarker가 무시해줌

예) <#-- ... -->

 

 

Gradle 의존성 추가

dependencies {
    // FreeMarker 코어 라이브러리
    implementation 'org.freemarker:freemarker:2.3.31' 
    
    // Spring 5 환경에서 FreeMarker 연동
    implementation 'org.springframework:spring-webmvc:5.3.30' // Spring 5.x 버전 사용
    implementation 'org.springframework:spring-context-support:5.3.30' 

    // 필요하다면 Servlet API 
    compileOnly 'javax.servlet:javax.servlet-api:4.0.1' 

}

 

 

Spring 설정

Java Config 설정 파일에 FreeMarker View Resolver 등록합니다.

@Configuration 클래스에 빈을 정의합니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;

@Configuration
public class FreeMarkerConfig {

    //템플릿 엔진 자체를 설정합니다.
    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        // 템플릿 파일이 위치한 경로를 지정합니다.
        // 일반적인 WAR/JAR 프로젝트에서는 /WEB-INF/views/ 또는 /templates/ 경로를 사용합니다.
        configurer.setTemplateLoaderPath("/WEB-INF/views/"); 
        configurer.setDefaultEncoding("UTF-8");
        return configurer;
    }

    //Spring MVC가 FreeMarker 템플릿을 View로 인식하게 합니다.
    @Bean
    public FreeMarkerViewResolver freemarkerViewResolver() {
        FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
        resolver.setCache(true); // 개발 시에는 false로 설정하여 캐시를 끄는 것이 좋습니다.
        resolver.setPrefix(""); // 템플릿 파일명 앞뒤에 붙일 접두사/접미사 설정
        resolver.setSuffix(".ftl"); // FreeMarker 템플릿의 확장자
        resolver.setContentType("text/html; charset=UTF-8");
        return resolver;
    }
}

 

일반적으로 템플릿 파일은 /WEB-INF/views/ 경로에 .ftl 확장자로 저장합니다.

 

 

Spring MVC Controller 에서 데이터 준비

@Controller를 사용해서 데이터를 Model에 담아서 반환합니다.

package com.example.app.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Arrays;
import java.util.List;

// Product 클래스
class Product {
    private String name;
    private int price;

    public Product(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }
}

@Controller
public class HelloController {

    @GetMapping("/hello-ftl")
    public String showPage(Model model) {
        // Model에 데이터를 추가합니다.
        model.addAttribute("username", "테스터");
        
        List<Product> products = Arrays.asList(
            new Product("모니터", 30000),
            new Product("마우스", 2500)
        );
        model.addAttribute("itemList", products);

        // 반환값으로 지정된 String값(hello)을 이름으로  ViewResolver가 
        // 설정된 경로(/WEB-INF/views/)에서 .ftl 확장자를 붙여 파일을 찾습니다.
        // 예) /WEB-INF/views/hello.ftl
        return "hello"; 
    }
}

 

FreeMarker 템플릿으로 데이터 표시

/WEB-INF/views/hello.ftl 을 생성해서 데이터를 표시해 줍니다.

<!DOCTYPE html>
<html>
<head>
    <title>Spring + FreeMarker</title>
</head>
<body>

    <h1>안녕하세요, ${username}님!</h1>    
    <h2>오늘의 추천 상품</h2>
    
    <ul>
    <#list itemList as item>
        <li>
            ${item.name} (${item.price?string["#,##0"]} 원)
        </li>
    <#else>
        <li>현재 추천 상품이 없습니다.</li>
    </#list>
    </ul>

</body>
</html>

 

 

import 디렉티브

.ftl에서 사용하는 <#import /> 구문으로 재사용 가능한 코드를 관리할 수 있습니다.

매크로나 함수같은 재사용 가능한 요소들을 템플릿으로 가져오는 역할을 합니다.

<#import />는 불러온 파일의 내용을 특정 네임스페이스(변수)에 담아줍니다.

그래서 현재 템플릿의 변수와 불러온 파일의 이름이 충돌하는 것을 방지해줍니다.

<#import 경로 as 네임스페이스변수이름>

 

사용 예제

1. 재사용할 매크로를 정의 합니다. 관례적으로 공통 파일은 이름 앞에 언더바를 붙입니다.

src/main/resources/templates/_layout.ftl

<#-- HTML 헤더 부분을 출력합니다. -->
<#macro pageHeader title="기본 제목">
    <!DOCTYPE html>
    <html>
    <head>
        <title>${title}</title>
        <meta charset="UTF-8">
        <style>
            .header { background-color: #f0f0f0; padding: 10px; border-bottom: 2px solid #ccc; }
        </style>
    </head>
    <body>
        <div class="header">
            <h1>${title}</h1>
        </div>
</#macro>

<#-- HTML 푸터 부분을 출력합니다. -->
<#macro pageFooter>
    <div class="footer" style="text-align: center; margin-top: 50px; padding: 10px; border-top: 1px solid #ddd;">
        <p>&copy; ${.now?string["yyyy"]} FreeMarker 예제</p>
    </div>
    </body>
    </html>
</#macro>

 

2. 페이지 템플릿에서 불러와서 사용합니다.

위에서 정의한 _layout.ftl 파일을 layout이라는 네임스페이스 이름으로 불러와서 사용합니다.

src/main/resources/templates/main.ftl

<#-- _layout.ftl 파일의 내용을 'layout' 변수에 할당합니다. -->
<#import "/_layout.ftl" as layout>

<#-- 불러온 매크로 사용: 'layout' 네임스페이스를 통해 매크로에 접근합니다. -->
<@layout.pageHeader title="메인 페이지: import 예시"/>

    <main style="padding: 20px;">
        <h2>환영합니다!</h2>
        <p>이 내용은 메인 페이지의 본문입니다.</p>        
        <#-- Controller에서 전달된 데이터가 있다면 여기서 출력 가능합니다. (예: ${userName}) -->
    </main>

<#-- 푸터 매크로 사용 -->
<@layout.pageFooter/>

 

3. 자바 소스에서 FreeMarker에 main.ftl 템플릿을 랜더링 하도록 요청하는 컨트롤러를 작성합니다.

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MainController {

    @GetMapping("/")
    public String showMainPage() {
        // "main" 템플릿 (main.ftl)을 렌더링하도록 지시합니다.
        return "main"; 
    }
}

 

 

assign 디렉티브

<#assign /> 은 템플릿 내에서 변수를 생성하고 값을 할당하는데 사용합니다.

템플릿 내에서 계산 결과, 문자열 조작, 자주 사용될 값을 다룹니다.

기본적으로 변수의 스코프(Scope)는 해당 템플릿 파일 내에서만 유효한 로컬 변수로 존재합니다.

global 또는 template 스코프를 명시적으로 지정해서 다른 템플릿에서도 접근 가능하게 할 수 있습니다.

또 아래 예제 처럼 여러 변수를 동시헤 할당 할 수있습니다.

<#assign var1 = value1, var2 = value2, var3 = value3>

 

사용 예제

1. 기본 변수 할당 및 출력

<#-- 문자열 할당 -->
<#assign appName = "FreeMarker Demo">

<#-- 숫자 계산 결과를 할당 -->
<#assign resultPrice = 5000 * 0.9>

<#-- 리스트 할당 -->
<#assign userList = ["안철수", "이재명", "윤석렬"]>

<!DOCTYPE html>
<html>
<head>
    <title>${appName}</title> <#-- 할당된 변수 사용 -->
</head>
<body>
    <h1>이름: ${appName}</h1>
    
    <p>가격은: <strong>${resultPrice}</strong>원 입니다.</p>
    
    <h2>사용자:</h2>
    <#list userList as user>
        <p>- ${user}</p>
    </#list>
    
    <#-- 기존 변수 값 변경 -->
    <#assign appName = "새로운 앱">
    <p>이름 변경 후: ${appName}</p>
</body>
</html>

 

2. 본문 내용을 할당해서 사용하는 예

<#-- HTML 구조를 변수에 할당 -->
<#assign welcomeBlock>
    <div style="border: 1px solid #009; padding: 15px;">
        <p>오늘 날짜: ${.now?date}</p>
    </div>
</#assign>

<h2>본문 시작</h2>

<#-- 할당된 변수를 필요한 위치에서 출력 -->
${welcomeBlock}

 

3. 네임스페이스와 스코프를 활용하는 예

<#-- 전역 (global) 변수 할당: 다른 템플릿에서도 ${.globals.VERSION}으로 접근 가능 -->
<#assign VERSION = "1.0.0" scope="global">

<#-- 템플릿 (template) 스코프 할당: 현재 처리 중인 모든 템플릿에서 접근 가능 -->
<#assign themeColor = "#3366ff" scope="template">

 

4. inline style 속성 동적 생성 예제

css 속성 값을 변수에 저장하고 이를 html 태그의 style 속성 내에 출력해서 css를 변경 할 수 있습니다.

서버에서 전달된 userLevel에 따라서 사용자 이름의 색을 다르게 표시하고 싶다고 가정한 예제입니다.

<#-- 조건에 따라 CSS 값을 변수에 할당 -->
<#if userLevel == "admin">
    <#assign highlightColor = "red">
    <#assign fontWeight = "bold">
<#elseif userLevel == "vip">
    <#assign highlightColor = "blue">
    <#assign fontWeight = "normal">
<#else>
    <#assign highlightColor = "black">
    <#assign fontWeight = "normal">
</#if>

<#-- HTML 태그의 style 속성에서 변수 사용 -->
<div>
    <h2>사용자 정보</h2>
    <p>
        이름: 
        <span style="color: ${highlightColor}; font-weight: ${fontWeight};">
            ${username}
        </span>
    </p>
</div>

 

<head> 색션 내의 <style> 태그 내부에서도 css 를 변경할 수 있습니다.

<#-- 서버에서 받은 테마 색상을 변수에 할당 -->
<#assign mainThemeColor = themeColor!"#333333"> <#-- themeColor가 null이면 "#333333" 사용 -->

<!DOCTYPE html>
<html>
<head>
    <title>CSS 예제</title>
    
    <#-- <style> 태그 내에서 변수 사용 -->
    <style>
        h1 {
            color: ${mainThemeColor};
            border-bottom: 2px solid ${mainThemeColor};
        }
    </style>
</head>
<body>
    <h1>안녕하세요!</h1>
    <span>이 h1 태그의 색상은 동적으로 설정되었습니다.</span>
</body>
</html>

 

5. CSS 클래스 이름을 할당해서 사용 예제

CSS를 다룰 때 일반적으로 권장되는 방법입니다.

스타일 자체는 .css 파일에 정의해 두고 <#assign>을 사용해서 상황에 맞는 css 클래스 이름을 할당해서 사용합니다.

먼저 css 파일에 스타일을 정해둡니다.

/* style.css */
.message-box {
    padding: 10px;
    border: 1px solid;
    margin: 10px 0;
}

.message-box.success {
    background-color: #e0f8e0;
    border-color: #34c734;
    color: #006400;
}

.message-box.error {
    background-color: #f8e0e0;
    border-color: #c73434;
    color: #640000;
}

그리고 나서 .ftl 파일에서 클래스 이름을 사용합니다.

<#-- 메시지 타입에 따라 적용할 css 클래스 이름을 변수에 할당 -->
<#if messageType == "success">
    <#assign statusClass = "success">
<#elseif messageType == "error">
    <#assign statusClass = "error">
<#else>
    <#assign statusClass = "">
</#if>

<#-- HTML 태그의 class 속성에 변수 사용 -->
<div class="message-box ${statusClass}">
    ${messageContent}
</div>

예를 들어 messageType이 error이라면 <div class="message-box error">로 렌더링되어 .css 파일에 정의된 빨간색 스타일이 적용됩니다.

 

 

 

 

 

 

728x90