Spring MVC & Tiles 3: XML 없는 Java 코드로 동적 레이아웃 구현하기
Apache Tiles의 번거로운 XML 설정에서 벗어나, DefinitionsFactory와 WildcardHelper를 활용해 유연하고 타입 안전한 동적 레이아웃 시스템을 구축하는 방법을 소개합니다.
1. 왜 XML 대신 Java Config인가?
Spring MVC 프로젝트에서 Apache Tiles는 뷰 레이아웃을 구성하는 강력한 도구입니다. 하지만 전통적인 tiles-defs.xml 방식은 프로젝트 규모가 커질수록 관리가 어려워지고 비효율적일 수 있습니다. Java 기반 설정을 도입하면 다음과 같은 확실한 이점을 얻을 수 있습니다.
- 동적 생성의 유연성: 비즈니스 로직에 따라 뷰 정의를 런타임에 동적으로 생성하거나 변경할 수 있습니다.
- 타입 안전성(Type Safety): XML의 텍스트 기반 설정과 달리, 컴파일 시점에 경로 오류나 오타를 감지할 수 있습니다.
- 유지보수 용이성: 장황한 XML 태그 없이 필요한 로직만 간결하게 Java 메소드로 관리할 수 있습니다.
2. 핵심 구현: DefinitionsFactory와 WildcardHelper
이 구현의 핵심은 Tiles의 DefinitionsFactory 인터페이스를 직접 구현하여 런타임에 정의(Definition)를 제공하는 것입니다. 여기에 WildcardHelper를 결합하면 XML의 와일드카드 기능보다 더 강력한 패턴 매칭이 가능합니다.
2.1. 기본 레이아웃 정의
먼저 고정된 헤더/푸터와 동적으로 변하는 콘텐츠 영역을 정의하는 메소드를 작성합니다. 이는 XML의 <definition> 태그를 Java 코드로 옮긴 것과 같습니다.
2.2. 와일드카드 패턴 매칭 로직
컨트롤러가 반환한 뷰 이름(예: dddd/board)이 정의된 맵에 직접 없을 경우, WildcardHelper를 통해 패턴 매칭을 시도하는 로직이 필요합니다. 이를 통해 dddd/*와 같은 패턴을 미리 정의해두면, dddd/userList, dddd/admin 등 다양한 요청을 하나의 설정으로 처리할 수 있습니다.
3. [실전 예제] 전체 소스 코드 구현
아래 코드는 DefinitionsFactory를 상속받아 직접 구현한 설정 클래스입니다. 맵(Map)을 이용해 기본 정의를 관리하고, WildcardHelper를 이용해 동적 매칭을 수행합니다.
import org.apache.tiles.Definition;
import org.apache.tiles.definition.DefinitionsFactory;
import org.apache.tiles.request.Request;
import org.apache.tiles.wildcard.WildcardHelper;
// 기타 필요한 import 생략...
public class TilesDefinitionsConfig implements DefinitionsFactory {
private static final Map<String, Definition> tilesDefinitions = new HashMap<>();
private static final WildcardHelper wildcardHelper = new WildcardHelper();
// 생성자에서 기본 레이아웃 정의 초기화
public TilesDefinitionsConfig() {
// 1. 기본 레이아웃 (Base Definition)
addDefinition("base.layout", "기본 타이틀", "/WEB-INF/views/layout/defaultLayout.jsp");
// 2. 와일드카드 패턴 정의 (예: "site/*" 요청 처리)
// {1}은 실제 요청된 View 이름으로 치환됩니다.
addDefinition("site/*", "서브 페이지", "/WEB-INF/views/{1}.jsp");
}
/**
* Definition 추가 헬퍼 메소드
*/
private void addDefinition(String name, String title, String bodyAttribute) {
Map<String, Attribute> attributes = new HashMap<>();
attributes.put("title", new Attribute(title));
attributes.put("header", new Attribute("/WEB-INF/views/layout/header.jsp"));
attributes.put("body", new Attribute(bodyAttribute)); // 동적 콘텐츠 영역
attributes.put("footer", new Attribute("/WEB-INF/views/layout/footer.jsp"));
tilesDefinitions.put(name, new Definition(name, new Attribute("/WEB-INF/views/layout/mainLayout.jsp"), attributes));
}
/**
* 핵심 로직: 요청된 View 이름(name)에 해당하는 Definition 반환
*/
@Override
public Definition getDefinition(String name, Request tilesContext) {
// 1. 정확히 일치하는 정의가 있으면 반환
if (tilesDefinitions.containsKey(name)) {
return tilesDefinitions.get(name);
}
// 2. 일치하는 게 없으면 와일드카드 패턴 매칭 시도
for (Map.Entry<String, Definition> entry : tilesDefinitions.entrySet()) {
String key = entry.getKey();
int[] pattern = wildcardHelper.compilePattern(key);
// 매칭 성공 시 변수(vars)에 치환될 값들이 담김
List<String> vars = wildcardHelper.match(name, pattern);
if (vars != null) {
// 부모 Definition을 복제하여 새로운 Definition 생성
Definition parentDef = entry.getValue();
Definition newDef = new Definition(parentDef);
newDef.setName(name);
// 속성 내의 {1}, {2} 등을 실제 값으로 치환 (예: {1} -> board/list)
for (String attrName : parentDef.getAttributeAttributeMap().keySet()) {
Attribute attr = newDef.getAttribute(attrName);
if (attr != null && attr.getValue() instanceof String) {
String converted = wildcardHelper.convertParam((String) attr.getValue(), vars);
attr.setValue(converted);
}
}
return newDef;
}
}
return null; // 매칭 실패
}
}
4. Spring 설정 빈(Bean) 등록
작성한 TilesDefinitionsConfig를 Spring의 TilesConfigurer에 연결해주어야 합니다. 보통 WebMvcConfig 클래스에 아래와 같이 등록합니다.
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public TilesConfigurer tilesConfigurer() {
TilesConfigurer configurer = new TilesConfigurer();
// 앞서 만든 Java Config 클래스를 DefinitionsFactory로 지정
configurer.setDefinitionsFactoryClass(TilesDefinitionsConfig.class);
// 추가 설정 (CheckRefresh 등)
configurer.setCheckRefresh(true);
return configurer;
}
@Bean
public TilesViewResolver tilesViewResolver() {
TilesViewResolver resolver = new TilesViewResolver();
resolver.setOrder(1); // 우선순위 1등
return resolver;
}
}
🚀 결론: 이제 XML 파일을 열어보지 않고도 Java 코드 내에서 뷰의 논리적 구조를 관리할 수 있습니다. WildcardHelper를 활용하면 수백 개의 페이지도 단 몇 줄의 패턴 정의로 처리가 가능해집니다.
댓글
댓글 쓰기