Posts Apache Tiles 적용
Post
Cancel

Apache Tiles 적용

«Tistory 블로그에서 작성했던 글»

지난 프로젝트에서 적용했던 Apache Tiles 에 대해 정리해본다.

진행했던 프로젝트의 화면 구성이 헤더, 좌측 메뉴, footer, 그리고 본문.

이런식으로 구성이 되어 있다보니 반복되는 부분들이 많아서 

Apache Tiles를 적용해서 Layout 관리를 했다.

대충 그림으로 보자면 아래와 같다.

Header

Menu

Contents 

Footer 

라이브러리 추가


우선 Tiles를 사용하기 위해 Dependency를 추가해준다.

pom.xml


1
2
3
4
5
6
7
8
9
10
<dependencies> 
    .......... 
    <!-- Apache Tiles --> 
    <dependency> 
        <groupId>org.apache.tiles</groupId> 
        <artifactId>tiles-extras</artifactId> 
        <version>3.0.8</version> 
    </dependency> 
    .......... 
</dependencies>

gradle.build


1
2
3
4
5
dependencies { 
               .......... 
               compile group: 'org.apache.tiles', name: 'tiles-extras', version: '3.0.8' 
               .......... 
}

Dependency 추가가 완료 되었다면, Bean 등록을 진행한다.

XML 설정과 JAVA 설정 둘 다 정리해둔다.

1. XML Configuration


servlet-context.xml


1
2
3
4
5
6
7
8
9
10
11
12
<!-- Tiles Configuration --> 
<beans:bean id="tilesViewResolver" class="org.springframework.web.servlet.view.tiles3.TilesViewResolver"> 
    <beans:property name="order" value="1" /> 
</beans:bean> 

<beans:bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> 
    <beans:property name="definitions"> 
        <beans:list> 
            <beans:value>/WEB-INF/tiles/tiles.xml</beans:value> 
        </beans:list> 
    </beans:property> 
</beans:bean>

Spring에서 Tiles에 대한 TilesViewResolverTilesConfigurer 클래스를 제공해주고 있어서 이것을 사용했다.

주의할 점은 TilesViewResolver Bean 하위에 order property 항목이 있는데,  별도로 선언되어 있는 ViewResolver(아마 InternalResourceViewResolver) 보다 낮게 설정해야 한다.

TilesViewResolver가 먼저 로드 된 후 ViewResolver가 로딩 되어야 한다.

TileConfigurer 의 list에는 Tiles의 설정파일 경로를 넣어준다.

tiles.xml


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" "http://tiles.apache.org/dtds/tiles-config\_3\_0.dtd"> 

<tiles-definitions> 
    <definition name="sampleTiles" template="/WEB-INF/tiles/layouts/layout.jsp"> 
        <put-attribute name="header" value="/WEB-INF/tiles/layouts/header.jsp"/> 
        <put-attribute name="menu" value="/WEB-INF/tiles/layouts/menu.jsp"/> 
        <put-attribute name="footer" value="/WEB-INF/tiles/layouts/footer.jsp"/> 
    </definition> 
    
    <!-- 로그인 화면 --> 
    <definition name="/chaedae/login/*" template="/WEB-INF/tiles/layouts/empty.jsp"> 
        <put-attribute name="body" value="/WEB-INF/views/chaedae/login/.jsp" /> 
    </definition> 
    
    <!-- 메인 화면 --> 
    <definition name="/chaedae/*/*" extends="sampleTiles"> 
        <put-attribute name="body" value="/WEB-INF/views/chaedae/{1}/.jsp" /> 
    </definition> 
</tiles-definitions>

최상단 sampleTiles를 선언하면서 헤더와 메뉴 푸터를 분리 해준 뒤 메인 화면쪽에서 상속받아 사용하도록 처리했다.

와일드 카드를 이용해서 경로 정보를 설정해줬고, (/chaedae// ==> /chaedae/main/Main.jsp)
로그인 화면은 별도의 레이아웃이 필요없는 관계로 빈화면을 넣었다.

2. JAVA Configuration


ServletConfig.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.chaedae.config;

import org.springframework.context.MessageSource; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.ComponentScan; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.context.support.ResourceBundleMessageSource; 
import org.springframework.web.servlet.config.annotation.EnableWebMvc; 
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 
import org.springframework.web.servlet.view.InternalResourceViewResolver; 
import org.springframework.web.servlet.view.JstlView; 
import org.springframework.web.servlet.view.tiles3.TilesConfigurer; 
import org.springframework.web.servlet.view.tiles3.TilesView; 
import org.springframework.web.servlet.view.tiles3.TilesViewResolver; 
import com.chaedae.tiles.config.TilesDefinitionsConfig; 

@Configuration 
@EnableWebMvc 
@ComponentScan(basePackages = {"com.chaedae"}) 
public class ServletConfig implements WebMvcConfigurer { 
    /** 
     * Apache-Tiles Configuration 
     * @return tilesViewResolver 
     */ 
    @Bean public TilesViewResolver viewResolver() { 
        TilesViewResolver viewResolver = new TilesViewResolver(); 
        viewResolver.setViewClass(TilesView.class); 
        viewResolver.setOrder(1); 
        
        return viewResolver; 
    } 
    
    /** 
     * Apache-Tiles Init 
     * @return tilesConfigurer 
     */ 
    @Bean 
    public TilesConfigurer getTilesConfigurer() { 
        TilesConfigurer tilesConfigurer = new TilesConfigurer(); 
        tilesConfigurer.setCheckRefresh(true); 
        tilesConfigurer.setDefinitionsFactoryClass(TilesDefinitionsConfig.class); 
        TilesDefinitionsConfig.addDefinitions(); 
        
        return tilesConfigurer; 
    } 
    
    /** 
     * ViewResolver Configuration 
     * @return viewResolver 
     */ 
    @Bean public InternalResourceViewResolver resolver() { 
        InternalResourceViewResolver resolver = new InternalResourceViewResolver(); 
        resolver.setViewClass(JstlView.class); 
        resolver.setPrefix("/WEB-INF/views/"); 
        resolver.setSuffix(".jsp"); 
        
        return resolver; 
    } 
}

XML 설정을 Java에 그대로 녹였다고 보면 된다.

tiles.xml 파일 대신 TilesDefinitionsConfig 클래스를 만들어서 Definitions를 지정해줬다.

TilesDefinitionsConfig.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.chaedae.tiles.config; 

import java.util.HashMap; 
import java.util.Map; 
import org.apache.commons.lang.StringUtils; 
import org.apache.tiles.Attribute; 
import org.apache.tiles.Definition; 
import org.apache.tiles.definition.DefinitionsFactory; 
import org.apache.tiles.request.Request; 

/** 
 * <h1>Apache-Tiles Definitions Config</h1> 
 * xml : tiles.xml 
 * @author ChaeDae 
 * 
 */ 
public class TilesDefinitionsConfig implements DefinitionsFactory { 
    private static final Map<String, Definition> TILES_DEFINITIONS = new HashMap<>(); 
    
    @SuppressWarnings("serial") 
    private static final Map<String, String> TILES_LAYOUTS = new HashMap<>() { 
        { 
            this.put("TILES_EMPTY_LAYOUT", "/WEB-INF/tiles/layouts/empty.jsp"); 
            this.put("TILES_LAYOUT", "/WEB-INF/tiles/layouts/layout.jsp"); 
        } 
    }; 
    
    private static String TILES_HEADER = "/WEB-INF/tiles/layouts/header.jsp"; 
    private static String TILES_MENU = "/WEB-INF/tiles/layouts/menu.jsp"; 
    private static String TILES_FOOTER = "/WEB-INF/tiles/layouts/footer.jsp"; 
    
    @Override public Definition getDefinition(String name, Request tilesContext) { 
        return TILES_DEFINITIONS.get(name); 
    } 
    
    /** 
     * Init 
     */ 
    public static void addDefinitions() { 
        // Login Layout 
        setDefinitions("/chaedae/login/Login", "/WEB-INF/views/chaedae/login/Login.jsp", "TILES_EMPTY_LAYOUT"); 
        // default Layout 
        setDefinitions("/chaedae/main/Main", "/WEB-INF/views/chaedae/main/Main.jsp", "TILES_LAYOUT"); 
    } 
        
    /** 
     * Set Definitions 
     * @param name definition name 
     * @param bodyName 
     * @param layouts layout name 
     */ 
    public static void setDefinitions(String name, String bodyName, String layout) { 
        Map<String, Attribute> attributes = new HashMap<>(); 
        
        if (!StringUtils.equals(layout, "TILES_EMPTY_LAYOUT")) { 
            attributes.put("header", new Attribute(TILES_HEADER)); 
            attributes.put("menu", new Attribute(TILES_MENU)); 
            attributes.put("footer", new Attribute(TILES_FOOTER)); 
        } 
        attributes.put("body", new Attribute(bodyName)); 
        
        Attribute baseTemplate = new Attribute(TILES_LAYOUTS.get(layout)); 
        TILES_DEFINITIONS.put(name, new Definition(name, baseTemplate, attributes)); 
    } 
}

원래는 위의 XML 설정과 동일하게 와일드카드를 이용해서 매핑을 하려 했는데, 소스가 너무 복잡해질 것 같아서 간단하게 특정 페이지들을 매핑 하였다.

페이지가 몇 개 없고 직관적으로 관리하겠다 한다면 사용할 수야 있겠지만,… 나중을 생각하면 그냥 XML 로 설정하는게 더 나은 선택일 것 같다.

XML파일을 참조하는 자바 설정은 ServletConfig에서 Definitions관련 설정을 변경 해주면 된다.

ServletConfig.java (with tiles.xml)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.chaedae.config; 

import org.springframework.context.MessageSource; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.ComponentScan; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.context.support.ResourceBundleMessageSource; 
import org.springframework.web.servlet.config.annotation.EnableWebMvc; 
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 
import org.springframework.web.servlet.view.InternalResourceViewResolver; 
import org.springframework.web.servlet.view.JstlView; 
import org.springframework.web.servlet.view.tiles3.TilesConfigurer; 
import org.springframework.web.servlet.view.tiles3.TilesView; 
import org.springframework.web.servlet.view.tiles3.TilesViewResolver; 
import com.chaedae.tiles.config.TilesDefinitionsConfig; 

@Configuration 
@EnableWebMvc 
@ComponentScan(basePackages = {"com.chaedae"}) 
public class ServletConfig implements WebMvcConfigurer { 
    /** 
     * Apache-Tiles Configuration 
     * @return tilesViewResolver 
     */ 
    @Bean public TilesViewResolver viewResolver() { 
        TilesViewResolver viewResolver = new TilesViewResolver(); 
        viewResolver.setViewClass(TilesView.class); 
        viewResolver.setOrder(1); 
        
        return viewResolver;
    } 
    
    /** 
     * Apache-Tiles Init 
     * @return tilesConfigurer 
     */ 
    @Bean public TilesConfigurer getTilesConfigurer() { 
        TilesConfigurer tilesConfigurer = new TilesConfigurer(); 
        tilesConfigurer.setDefinitions(new String[]{ "/WEB-INF/tiles/layouts/tiles.xml" }); 
        tilesConfigurer.setCheckRefresh(true); 
        
        return tilesConfigurer; 
    } 
    
    /** 
     * ViewResolver Configuration 
     * @return viewResolver 
     */ 
    @Bean public InternalResourceViewResolver resolver() { 
        InternalResourceViewResolver resolver = new InternalResourceViewResolver(); 
        resolver.setViewClass(JstlView.class); 
        resolver.setPrefix("/WEB-INF/views/"); 
        resolver.setSuffix(".jsp"); 
        
        return resolver; 
    } 
}

화면 레이아웃 설정


empty.jsp


1
2
3
4
5
6
7
8
9
10
11
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 

<html> 
    <body> 
        <div> 
            <tiles:insertAttribute name="body" /> 
        </div> 
    <body> 
</html>

별다른 내용 없이 tiles 태그와 jstl 태그만 추가해주고  <tiles:insertAttribute> 태그로 메인영역을 잡아주었다.
(tiles.xml의 put-attribute태그의 name과 동일해야 한다.)

layout.jsp


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 

<html> 
    <head> 
        <style> 
            div { 
                  border: 1px solid; 
                  text-align: center; 
            } 
        </style> 
    </head> 
    
    <body> 
        <div id="top" style="height:100px;"> 
            <tiles:insertAttribute name="header" /> 
        </div> 
        
        <div id="body"> 
            <div id="lnb" style="position:absolute; height:300px; width: 350px"> 
                <tiles:insertAttribute name="menu" /> 
            </div> 
            
            <div id="contents" style="margin-left:351px; height:300px;"> 
                <tiles:insertAttribute name="body" /> 
            </div> 
        </div> 
        
        <div id="footer" style="height:100px;"> 
            <tiles:insertAttribute name="footer" /> 
        </div> 
    </body> 
</html>

여기서는 실제 구상했던 레이아웃대로 화면을 나누었다.

header.jsp


1
2
3
4
5
6
7
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 

<div> 
    <span style="text-align: center;">
        <h1>Header</h1>
    </span> 
</div>

1
2
3
4
5
6
7
8
9
10
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 

<div> 
    <ul> 
        <li><a href="#">Menu1</a></li> 
        <li><a href="#">Menu2</a></li> 
        <li><a href="#">Menu3</a></li> 
        <li><a href="#">Menu4</a></li> 
    </ul> 
</div>

footer.jsp

1
2
3
4
5
6
7
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 

<div> 
    <span style="text-align: center;">
        <h1>Footer</h1>
    </span> 
</div>

header, menu, footer에는 그냥 영역 표시만 해두었다.

테스트 화면 작성


Login.jsp


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 

<style> 
    table, td {
                margin: auto; 
                text-align: center; 
    } 
</style> 

<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> 

<script type="text/javascript"> 
    function fnLogin() { 
        $.ajax({ 
            type : "POST", 
            url : "/chaedae/login/loginProc.json", 
            data : $('#loginForm').serialize(), 
            success : function (response) { 
                if (response == "SUCCESS") { 
                    location.href = "/chaedae/main/main.cd"; 
                } else { 
                    alert(response); 
                } 
            }, 
            error : function (response) { 
                alert(response.responseText); 
            } 
        }); 
    } 
</script> 

<form id="loginForm"> 
    <table> 
        <tr> 
            <td> 
                <h1> Welcome to Chaedae World </h1> 
            </td> 
        </tr> 
        <tr> 
            <td> 
                <label for="userId">ID</label> 
                <input type="text" id="userId" name="userId" title="ID" /> 
            </td> 
        </tr> 
        <tr> 
            <td> 
                <label for="pwd">PW</label> 
                <input type="password" id="pwd" name="pwd" title="패스워드" /> 
            </td> 
        </tr> 
        <tr> 
            <td> 
                <button type="button" style="height: 50px; width:100px" onclick="fnLogin();">Login</button> 
            </td> 
        </tr> 
    </table> 
</form>

Main.jsp


1
2
3
4
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 

<span>메인 화면 입니다.</span>

로그인 화면과 메인화면도 별다른 내용은 없다.. 그저 확인용..

확인을 위한 화면 request를 받아줄 컨트롤러도 생성 해준다.

LoginController.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.chaedae.login; 

import javax.servlet.http.HttpServletResponse; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.stereotype.Controller; 
import org.springframework.web.bind.annotation.PostMapping; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.ResponseBody; 
import com.chaedae.model.User; 
import com.chaedae.service.UserService; 

@Controller 
public class LoginController { 
    @Autowired 
    private UserService userService; 
    
    @RequestMapping(value = "/chaedae/login/login.cd") 
    public String login() { 
        return "/chaedae/login/Login"; 
    } 
    
    @PostMapping(value = "/chaedae/login/loginProc.json") 
    @ResponseBody 
    public void loginProc(User vo, HttpServletResponse response) throws Exception { 
        String rtn = "FAILURE"; 
        
        if (vo != null && vo.getUserId() != null) { 
            User user = userService.selectByUserId(vo); 
            
            if (user != null && user.getPwd().equals(vo.getPwd())) { 
                rtn = "SUCCESS"; 
            } 
        } 
        
        response.getWriter().print(rtn);
    } 
}

MainController.java


1
2
3
4
5
6
7
8
9
10
11
12
package com.chaedae.main; 

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

@Controller 
public class MainController { 
    @GetMapping(value = "/chaedae/main/main.cd") 
    public String main() { 
        return "/chaedae/main/Main"; 
    } 
}

여기까지 완료 되었다면 프로젝트 구조는 대략 아래와 같아진다.

Setting Image1

이제 서버를 구동한 뒤 localhost:8080/chaedae/login/Login.cd 로 접속을 해서 확인을 해보자.

Setting Image2

대략 위처럼 아주 못생긴 로그인 화면이 나오고…

로그인을 해보면.. (제 프로젝트로 따라오셨다면 master/a1234 로 접속)

Setting Image3

더 못생긴 위와 같은 화면이 나온다… (다음 포스팅 때는 디자인도 신경을 좀 써야 할 듯..)


:: Git Repo ::
https://github.com/Chaedae/SpringMVC-tiles


This post is licensed under CC BY 4.0 by the author.

Java9 + Spring5 + Gradle + MyBatis 개발환경 만들기

Spring Boot + H2 Rest API

Comments powered by Disqus.