1. SiteMesh란?

SiteMesh는 Java 기반 웹 애플리케이션에서 공통 레이아웃을 적용하기 위한 필터 기반 데코레이터

프레임워크.


2. 데코레이터 패턴이란?

기존 객체의 기능을 변경하지 않고, 동적으로 기능을 추가하거나 확장할 수 있게 하는 패턴


3. SiteMesh의 주요 개념

개념 설명
Decorator 페이지에 입힐 공통 레이아웃 (보통 layout.jsp)
Content 실제 사용자 컨텐츠 (board.jsp, main.jsp 등)
Filter 웹 요청 시 페이지를 가로채서 레이아웃을 적용
Tag <sitemesh:write property="title"/> 등으로 컨텐츠 위치 지정

4. SiteMesh의 동작 방식

- 동작방식

   요청: /board/list.jsp

  1. 사용자는 /board/list.jsp를 요청함
  2. SiteMesh Filter가 요청을 가로채서 내부적으로 렌더링 시작
  3. list.jsp의 내용(본문)을 먼저 생성함
    → 여기서 title, head, body 부분을 추출
  4. layout.jsp(데코레이터)에 그 내용들을 끼워 넣음:
    • <sitemesh:write property="title"/> → list.jsp의 <title>로 채움
    • <sitemesh:write property="body"/> → list.jsp의 본문 내용으로 채움
  5. 완성된 페이지를 사용자에게 전달

- Spring MVC의 ViewResolver와는 별개로 동작함

- 설정 파일 (decorators.xml) 또는 Java Config를 통해 URL 패턴 매핑 가능


5. SiteMesh의 장점

장점 설명
공통 레이아웃 관리 용이 header, footer 중복 제거
유지보수 편리 전체 UI 변경 시 layout.jsp만 수정하면 됨
디자인 일관성 모든 페이지가 동일한 틀을 가짐
적용 간단 복잡한 설정 없이도 빠르게 도입 가능
Spring MVC와 호환 가능 filter 기반이라 MVC 설정과 충돌 없음

6. SiteMesh 3.x vs 2.x 설정 방식 비교

항목 SiteMesh 2.x (XML 설정) SiteMesh 3.x (Java 설정)
설정 파일 decorators.xml Java 클래스 (SiteMeshFilter 설정)
레이아웃 매핑 방식 XML 기반 URL 패턴 코드 기반 URL 패턴
유연성/동적 처리 제한적 (고정된 설정) 매우 유연 (조건별 설정 가능)
커스터마이징 복잡하고 제한적 필터 체인 및 커스텀 파서 등 쉬움
초기화 방식 web.xml 필터 등록 FilterRegistrationBean 또는 Java Config
적용 대상 JSP 중심 HTML5, Thymeleaf 등도 OK

 

- 2.x는 전통적인 XML 설정 기반

- 3.x는 Java 기반 설정과 더 유연한 확장성 지원

 

SiteMesh 2.x 예제 (XML 설정 기반)

decorators.xml
<decorators defaultdir="/WEB-INF/decorators">
    <decorator name="default" page="layout.jsp">
        <pattern>/*</pattern>
    </decorator>
</decorators>

 

web.xml
<filter>
    <filter-name>sitemesh</filter-name>
    <filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>sitemesh</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

 

SiteMesh 3.x 예제 (Java 설정 기반)

SiteMeshConfig.java
import org.sitemesh.builder.SiteMeshFilterBuilder;
import org.sitemesh.config.ConfigurableSiteMeshFilter;
import org.sitemesh.builder.SiteMeshFilterBuilder;
import org.sitemesh.webapp.WebAppContext;

import javax.servlet.annotation.WebFilter;
import javax.servlet.FilterConfig;

@WebFilter("/*")
public class SiteMeshConfig extends ConfigurableSiteMeshFilter {
    @Override
    protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
        builder
            .setMimeTypes("text/html")  // 필터링할 MIME 타입
            .addDecoratorPath("/*", "/WEB-INF/decorators/layout.jsp") // 데코레이터 지정
            .addExcludedPath("/static/*"); // 제외할 경로
    }
}

7. 정리

SiteMesh는 웹 페이지에 일관된 레이아웃을 자동으로 적용해 유지보수를 쉽게 만드는 필터 기반 템플릿 프레임워크다.

** SiteMesh 적용 프로젝트 | https://github.com/LuckyStrike1989/my-spring-framewrok-project

'BackEnd > Spring&SpringBoot' 카테고리의 다른 글

[Spring] RestfulA API HATEOAS 설정  (0) 2024.08.21
[Spring] Validation API 유효성 체크  (0) 2024.08.04

REST API Level3을 위한 HATEOAS 설정

 

Hateoas란?

HATEOAS(Hypermedia As The Engine of Application State)는 웹 API를 실제로 "RESTful"로 만드는 REST 애플리케이션 아키텍처의 제약 조건입니다. 기본적으로 요청에 대해 서버는 데이터만 클라이언트에 보냅니다. HATEOAS를 사용하면 응답에 데이터뿐만 아니라 해당 데이터와 관련된 가능한 작업도 링크 형식으로 포함됩니다.

 

Leonard Richardson이 제시한 REST 성숙도 모델

출처: https://grapeup.com/blog/how-to-build-hypermedia-api-with-spring-hateoas

 

- 레벨 0

API 구현은 HTTP 프로토콜을 사용하지만 전체 기능을 활용하지는 않습니다. 또한 리소스에 대한 고유 주소가 제공되지 않습니다.

method : POST / URI : /movie

 

- 레벨 1

리소스에 대한 고유 식별자가 있지만 리소스에 대한 각 작업자에는 고유한 URL이 있습니다

method : POST / URI : /movie/1/delete

 

- 레벨 2

동작을 설명하는 동사 대신 HTTP 메소드를 사용합니다. 예를 들어 레벨 1처럼 URI에 delete를 표기하여 작업자를 나타내지않고, 대신 delete 메소드를 사용합니다.

method : DELETE / URI : /movie/1

 

- 레벨 3

HATEOAS라는 용어가 도입됨. 간단히 리소스에 하이퍼미디어를 도입합니다. 이를 통해 가능한 작업에 대해 알려주는 응답에 링크를 배치할 수 있으므로 API를 통해 탐색할 수 있는 가능성이 추가됩니다.

method : DELETE / URI : /movie/1

 

 

Level2에서는 단순히 데이터 영역만을 표기하여 응답해줍니다. Level3의 Hypermedia Controls 부터는 데이터 영역 뿐만 아니라 링크 영역을 통해 자원에 호출 가능한 API 정보를 반영하여 표현합니다. 링크영역에서 반영 되는 개념이 바로 HATEOAS 입니다. 

{
    ----- 데이터 영역 -----
    "id": 1,
    "name": "Kenneth",
    "joinDate": "2024-08-17T12:58:26.575+00:00",
    ----- 데이터 영역 -----
    
    ----- 링크 영역 -----
    "_links": {
        "self": {
            "href": "http://localhost:8088/users/1"
        },
        "all-users": {
            "href": "http://localhost:8088/users"
        }
    }
    ----- 링크 영역 -----
}

 

HATEOAS 프로젝트 설정 및 구현

 

 

▶ pom.xml (build.gradle)

## Maven
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-hateoas</artifactId>  
</dependency>

## Gradle
implementation 'org.springframework.boot:spring-boot-starter-hateoas'

 

먼저 프로젝트에 Hateoas를 추가해 줍니다.

 

▶ UserController.java

@GetMapping("/users/{id}")
    public ResponseEntity<EntityModel<User>> retrieveUser(
           @Parameter(description = "사용자 ID", required = true, example = "1") @PathVariable int id
    ) {
        User user = userDaoService.findOne(id);
		
        // 사용자가 없을 경우, 예외를 발생시킨다.
        if( user == null ) {
            throw new UserNotFoundException(String.format("ID[%s] not found", id));
        }

        // 단일로 link를 만들때
        // EntityModel entityModel = EntityModel.of(user);
        // WebMvcLinkBuilder linTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
        // entityModel.add(linTo.withRel("all-users"));    // http://localhost:8080/users -> all-users

		// 다수로 link를 만들때
        return ResponseEntity.ok().body(
                EntityModel.of(user)
                        .add(linkTo(methodOn(this.getClass()).retrieveUser(id)).withSelfRel()) // http://localhost:8080/users/1 -> self
                        .add(linkTo(methodOn(this.getClass()).retrieveAllUsers()).withRel("all-users")) // http://localhost:8080/users -> all-users
        );
    }

 

EntityModel<T> 클래스를 이용하고, Static Method로 객체를 만들기 때문에 EntityModel.of()를 통해서 객체르 생성합니다.

add()메소드를 통해서 link를 추가할 수 있습니다. linkTo(methodOn(Controller.class).method(argument)) 이런 형식으로 추가하면 API의 URI가 매핑됩니다.

link에 대한 이름은 withSelfRel()과 withRel()이 있습니다.

withSelfRel() 메소드는 self로 지정되는데 호출되는 자기 자신에 대한 정보를 표현합니다.

withRel()는 withRel("명칭") 형태로 사용하며 매개변수로 지정된 값이 이름으로 표현됩니다.

 

▶ 실행결과

{
    "id": 1,
    "name": "Kenneth",
    "joinDate": "2024-08-20T14:37:49.307+00:00",
    "_links": {
        "self": {
            "href": "http://localhost:8088/users/1"
        },
        "all-users": {
            "href": "http://localhost:8088/users"
        }
    }
}

 

Hateoas를 적용하여 실행한 결과 입니다. 다음과 같이 _links에 호출 가능한  API 정보를 반영 되었습니다. 

이번 포스팅은 간단하게 Hateoas 적용 방법을 정리해 봤습니다.

해당 Full Source는 아래 Git 주소를 통해서 확인 가능합니다. 여기까지 지루한 글 읽어주셔서 감사합니다!

 

https://github.com/LuckyStrike1989/restful-web-service

 

 

'BackEnd > Spring&SpringBoot' 카테고리의 다른 글

[Spring] SiteMesh란?  (0) 2025.04.05
[Spring] Validation API 유효성 체크  (0) 2024.08.04

 Validation이란?

 

어떤 데이터의 값이 유효한지, 잘못된 내용이 있는지 확인하는 단계를 뜻합니다. 예를들어 휴대폰 번호의 포멧은 "010-1234-1234"이여야 하는데, 포멧과 맞지않는 값이 들어온다면 해당 값의 저장을 막을 수 있습니다.

(휴대 폰 번호 막고도 최소길이, 최대길이, 이전날짜, 이후날짜 등 다양하게 유효성 체크를 할 수 있습니다)

 

Validation  API 사용
의존성 추가

 

Validation API를 사용하기 위해서는 먼저 아래와 같이 라이브러리를 추가 해주어야 합니다.

## maven
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-validation</artifactId>  
    <version>3.0.2</version>  
</dependency>

## gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'

 

VO(DTO)에서 어노테이션을 통해 체크하고 @Valid 어노테이션을 통해 간단히 유효성 체크를 할 수 있습니다.

 

@Size 값이 min고 max사이에 해당하는지 확인
@NotNull Null을 허용하지 않음
@NotEmpty Null을 허용하지 않으면 공백 문자열을 허용하지 않음
@NotBlank Null을 허용하지 않으며 문자가 한 개 이상 폼하되어야함(공백제외)
@Past 과거 날짜인지 확인
@PastOrPresent 오늘이거나 과거 날짜인지 확인
@Future 미래 날짜인지 확인
@FutureOrPresent 오늘이거나 미래 날짜인지 확인
@Pattern 적용된 정규식 확
@Max 값이 최대값(Max)보다 큰지 확인
@Min 값이 최소값(Min)보다 작은지 확인
@AssertTure / False True / False 인지 확인
@Valid 해당 VO(DTO) 유효성 체크 실시

 

유효성 체크 예제 소스

 

VO(DTO)에 어노테이션을 통해 유효성 체크 할 부분을 명시해 줍니다.

@Data  
@AllArgsConstructor  
public class User {
    private Integer id;  
  
    @NotBlank(message="이름을 입력해주세요.")
    @Size(min = 2, message = "Name은 2글자 이상 입력해 주세요.")  
    private String name;  
  
    @Min(0, "나이는 0보다 커야 합니다.")
    private int age;
    
    @Email("이메일 형식이 잘못되었습니다.")
    private int email;
  
    @Past(message = "등록일은 미래 날짜를 입력하실 수 없습니다.")  
    private Date joinDate;
}

 

Controller에서 유효성이 필요한 부분에서 주로 @RequestBody 앞에 @Valid 어노테이션 입력해 줍니다.

@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
	User savedUser = userDaoService.save(user);

	URI location = ServletUriComponentsBuilder.fromCurrentRequest()
               		.path("/{id}")
                	.buildAndExpand(savedUser.getId())
                	.toUri();

    // 반환 값을 201 Created 준다. 인자로 location을 넘겨주면, 헤더에 Location이 추가된다.
    return ResponseEntity.created(location).build();
}

 

VO(DTO)에서 명시해 놓은 값에서 문제가 발생 했을 경우 예외처리를 통해 결과 값을 받아 보실 수 있습니다.

@ControllerAdvice 어노테이션을 통해 Controller에서 발생한 예외처리를 진행합니다.

@ControllerAdvice를 간단히 설명하면 Controller에서 @RequestMapping가 달린 메서드 실행 중 발생한 예외를 가로채는 exceptions interceptor라고 생각 하시면 됩니다.

또한 ResponseEntity에서 발생된 예외상황을 처리하기위해 ResponseEntityExceptionHandler 상속 받고

예외 응답 객체를 만들어 ResponseEntity에 담아 처리해주면 조금 더 상세하게 유효성 체크를 할 수 있습니다.

@ControllerAdvice
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), "Validation failed", ex.getBindingResult().toString());
        return new ResponseEntity(exceptionResponse, HttpStatus.BAD_REQUEST);
    }
    
}

Validation API 유효성 체크를 통한 실행 결과

   

이번 포스팅에는 SpringBoot에서 유효성 체크 하는 방법을 정리 해보았는데요

해당 Full Source는 아래 Git주소를 통해서 확인 부탁드립니다!!

여기까지 지루한 글 읽어주셔서 감사합니다ㅎㅎ

https://github.com/LuckyStrike1989/restful-web-service

'BackEnd > Spring&SpringBoot' 카테고리의 다른 글

[Spring] SiteMesh란?  (0) 2025.04.05
[Spring] RestfulA API HATEOAS 설정  (0) 2024.08.21

+ Recent posts