반응형

Spring REST


REST 에 대한 설명은 이전 포스팅 에서 정리했으니, 추가적인 설명은 하지 않고 스프링의 REST 에 대해 살펴보려 한다.

 

1. REST 서비스로 XML 발행하기


스프링에서 REST 서비스를 설계할 때는 두 가지 가능성을 고려해야 한다. 하나는 애플리케이션 데이터 자체를 REST 서비스로 발행하는 것이고, 다른 하나는 애플리케이션에서 쓸 데이터를 서드파티 REST 서비스에서 가져오는 것이다. 여기서는 전자의 경우를 먼저 설명하고 후자의 경우는 뒤에서 다시 설명할 것이다.

스프링 MVC 에서 애플리케이션 데이터를 REST 서비스로 발행하기 위한 주된 방법은 @RequestMapping 과 @PathVariable 두 어노테이션이다. 이들을 적절히 사용함으로써 스프링 MVC 핸들러 메소드가 자신이 조회한 데이터를 REST 서비스로 발행하도록 하는 것이다. 이 외에도 스프링에는 REST 서비스의 페이로드를 생성하는 다양한 매커니즘이 있다.

 

웹 애플리케이션에서 데이터를 REST 서비스로 발행하는 (웹 서비스의 기술 용어로 이를 '엔드포인트를 생성한다' 라고 표현한다) 작업은 3장의 스프링 MVC 와 밀접한 연관을 갖는다. 스프링 MVC 에서는 핸들러 메소드에 @RequestMapping 어노테이션을 붙임으로써 액세스 지점을 정의했는데, REST 서비스의 엔드포인트도 이와 같이 정의하는 것이 좋다.

 

MarshallingView 로 XML 만들기

다음은 스프링 MVC 컨트롤러의 핸들러 메소드에 REST 서비스의 엔드포인트를 정의한 코드이다.

@Controller
public class RestMemberController {
	
    private final MemberService memberService;
    
    // Constructor
    
    @RequestMapping("/members")
    public String getRestMembers(Model model) {
        Members members = memberService.findAll();
        model.addAttribute("members", members);
        return "membertemplate";
    }
}

핸들러 메소드인 getRestMembers() 에 @RequestMapping("/members") 를 붙였기 때문에 REST 서비스 엔드포인트는 "호스트명/[애플리케이션명]/members" URI 로 접근할 수 있고, 마지막 줄에서 membertemplate 논리 뷰로 실행의 흐름을 넘기고 있다. membertemplate 은 구성 클래스에서 다음과 같이 정의할 필요가 있다.

public class CourtRestConfiguration {
	
    @Bean
    public View membertemplate() {
        return new MarshallingView(jaxb2Marshaller());
    }
    
    @Bean
    public Marshaller jaxb2Marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setClassesToBeBound(Members.class, Member.class);
        return marshaller;
    }
    
    @Bean
    public ViewResolver viewResolver() {
        return new BeanNameViewResolver();
    }
}

membertemplate 뷰는 MarshallingView 형으로 정의한다. MarshallingView 는 마샬러를 이용해 응답을 렌더링하는 범용 클래스이다. 마샬링 (Marshalling) 이란, 간단히 말해 메모리에 있는 객체를 특정한 데이터 형식으로 변환하는 과정이다. 위 예제에서 마샬러는 Member, Members 객체를 XML 형식으로 바꾸는 일을 한다.

마샬러 역시 구성이 필요하다. 위에서는 Jaxb2Marshaller 를 사용했다. Jaxb2Marshaller 를 구성할 때는 classesToBeBound, contextPath 둘 중 하나의 프로퍼티를 설정한다. classesToBeBound 는 XML 로 변환할 대상 클래스를 의미한다. 다음과 같이 마샬링할 클래스에 @XmlRootElement 어노테이션을 붙여 설정할 수 있다.

@XmlRootElement
public class Member {
    
    ...
}
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Members {
    
    @XmlElement(name = "member")
    private List<Member> members = new ArrayList<>();
    
    ...
}

위의 Member 클래스에 붙인 @XmlRootElement 는 Jaxb2Marshaller 가 클래스 필드를 자동으로 감지해 XML 데이터로 바꾸도록 지시한다.

더보기

예)

name = John → <name>john</name>

email = john@doe.com → <email>john@doe.com</email>

정리하자면, "http://{호스트명}/{애플리케이션명}/members.xml" 형식의 URL 로 접속하면 담당하는 핸들러가 Members 객체를 생성해 이를 membertemplate 논리 뷰로 넘긴다. 마지막 뷰에 정의한 내용에 따라 마샬러를 이용해 Members 객체를 XML 페이로드로 바꾼 후 REST 서비스를 요청한 클라이언트에게 돌려준다.

 

여기서 예시한 대로 REST 서비스의 엔드포인트를 잘 보면 ".xml" 이라는 확장자로 끝난다. 확장자를 다른 걸로 바꾸거나 아예 빼고 접속할 경우, 이 REST 서비스는 전혀 동작하지 않는다. 이런 로직은 뷰를 해석하는 스프링 MVC 와 직접 연관된 것으로 REST 서비스 자체와는 무관하다.

이 핸들러 메소드에 연결된 뷰는 기본적으로 XML 을 반환하므로 .xml 확장자를 쓸 경우에만 동작하고, 이런 식으로 동일한 핸들러 메소드는 엔드포인트에 따라 여러 뷰를 지원할 수 있다. 요청에 따라 어떤 뷰를 제공할 것인지는 스프링 MVC 의 Content Negotiation 과정에 따라 결정되며, 이 내용은 이전 포스팅 에 정리해 두었다.

 

@ResponseBody 로 XML 만들기

MarshallingView 로 XML 파일을 생성하는 건 결과 데이터를 보여주는 여러 방법 중 하나에 불과하다. 같은 데이터를 다양한 표현형으로 나타내기 위해서는 그때마다 뷰를 하나씩 추가해야 하기 때문에 상당히 불편할 것이다. 이럴 때 스프링 MVC 의 HttpMessageConverter 를 이용하면 유저가 요청한 표현형으로 객체를 손쉽게 변환할 수 있다.

@Controller
public class RestMemberController {
	
    @ReqeustMapping("/members")
    @ResponseBody
    public Members getRestMembers() { ... }
}

getRestMembers() 에 붙인 @ResponseBody 는 메소드 실행 결과를 응답의 본문으로 취급하겠다고 스프링 MVC 에게 밝히는 것이다. XML 형식으로 보려면 스프링 Jaxb2RootElementHttpMessageConverter 클래스가 마샬링을 수행한다. 이처럼 핸들러 메소드에 @ResponseBody 를 붙이면 뷰 이름을 전달하지 않고 Members 객체를 반환하는 것으로 XML 데이터를 만들 수 있다.

더보기

NOTE.

스프링 4부터는 일일이 메소드에 @ResponseBody 를 붙이지 않고 컨트롤러 클래스에 @Controller 대신 @RestController 를 붙임으로써 위와 같은 효과를 얻을 수 있다.

이와 같이 변경하고 나면 구성 클래스에서 기존에 작성했던 MarshallingView, Jaxb2Marshaller 등의 마샬러는 따로 설정을 하지 않아도 된다.

 

@PathVariable 로 결과 거르기

REST 서비스는 응답 페이로드의 양을 제한하거나 필터링할 의도로 요청 매개변수를 넣는 것이 일반적이다. 예를 들어, "http://{호스트명}/{어플리케이션명}/member/353" 으로 요청하면 353번 회원 정보, "http://{호스트명}/{어플리케이션명}/reservations/2010-07-07" 으로 요청하면 2010년 7월 7일 예약 내역만 추릴 수 있다.

스프링에서 REST 서비스를 구성할 때는, 다음과 같이 핸들러 메소드의 입력 매개변수에 @PathVariable 을 붙여 메소드 내부에서 사용할 수 있다.

@RestController
public class RestMemberController {
	
    @RequestMapping("/member/{memberId}")
    public Member getMember(@PathVariable("memberId") long memberId) { ... }
}

 

{} 표기법 대신 와일드카드 문자(*) 로 REST 엔드포인트를 나타내는 방법도 있다. REST 를 설계하는 사람들은 대개 표현적인 URL 을 사용하거나, SEO (Search Engine Optimization) 기법을 응용해 조금이라도 검색 엔진에 친화적인 REST URL 을 선호한다. 다음은 와일드카드 표기법을 사용해 REST 서비스를 선언한 코드이다.

@RequestMapping("/member/*/{memberId}")
public Member getMember(@PathVariable("memberId") long memberId) { ... }

와일드카드를 추가해도 REST 서비스의 본연의 기능에는 어떤 영향도 없지만 /member/John+Smith/353, /member/Mary+Jones/353 같은 형식의 URL 로 요청할 수 있으면 유저 가독성이나 SEO 측면에서 효과를 기대할 수 있다.

 

REST 엔드포인트 핸들러 메소드에서는 다음과 같이 데이터를 바인딩할 수도 있다.

@InitBinder
public void initBinder(WebDataBinder binder) {
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, flase));
}

@RequestMapping("/reservations/{date}")
public void getReservation(@PathVariable("date" Date resDate) { ... }

이와 같이 정의하면, "http://{호스트명}/{어플리케이션명}/reservations/2010-07-07" 을 요청할 경우, getReservation() 메소드가 2010-07-07 값을 꺼내 resDate 변수에 할당하고 이 값을 이용해 REST 웹 서비스 페이로드를 필터링할 수 있다.

 

ResponseEntity 로 클라이언트에게 알리기

Member 인스턴스를 하나만 조회하는 엔드포인트의 결과는 올바른 회원 정보를 반환하든지, 아니면 무엇도 반환하지 않든지 둘 중 하나의 결과를 갖는다. 어느 쪽이든 요청한 클라이언트 입장에서는 정상 처리를 의미하는 HTTP 응답 코드 200 을 받게 될 것이다. 하지만 리소스가 없으면 없다는 사실을 있는 그대로 알리는 차원에서, "NOT FOUND" 에 해당하는 응답 코드 404 를 반환하는 것이 맞다.

@RequestMapping("/member/{memberId}")
public ResponseEntity<Member> getMember(@PathVariable("memberId") long memberId) {
    Member member = memberService.find(memberId);
    if (member == null) {
        return new ResponseEntity(HttpStatus.NOT_FOUND);
    } else {
    	return new ResponseEntity<>(member, HttpStatus.OK);
    }
}

스프링 MVC 에서 ResponseEntity 는 응답 결과의 본문을 HTTP 상태 코드와 함께 집어넣은 래퍼 클래스이다. 이제 getMember() 메소드는 요청에 따른 결과가 존재하면 응답 코드 200 과 결과를, 없으면 응답 코드 400 을 반환하게 된다.

 

2. REST 서비스로 JSON 발행하기


AJAX 를 고려한 스프링 애플리케이션의 REST 서비스는 브라우저의 처리 능력이 한정되기 때문에 대부분 JSON 형태의 페이로드를 발행하는 방식으로 설계한다. 브라우저에서 XML 페이로드를 발행하는 REST 서비스로부터 데이터를 받아 처리하는 건 불가능하진 않지만 별로 효율이 좋지 않다. 어느 브라우저든 자바스크립트 언어 해석기는 다 장착되어 있기 때문에 JSON 형식으로 페이로드를 받아 처리하는 방식이 여러모로 효율적이다.

표준 규격인 RSS/Atom Feed 와 달리 JSON 은 딱히 구조가 정해져 있는 형식이 아니기 때문에 JSON 의 페이로드 구조는 AJAX 설계팀과 논의해 결정하는 것이 일반적이다.

 

MappingJackson2JsonView 로 XML 만들기

@RequestMapping("/members")
public String getRestMembers(Model model) {
    ...
    return "jsonmembertemplate";
}

반환하는 뷰의 이름만 빼고 위에서 작성한 핸들러 메소드와 동일하다. 이 메소드가 반환한 jsonmembertemplate 이라는 뷰 이름은 MappingJackson2JsonView 뷰로 매핑되는데, 다음과 같이 구성 클래스에 설정할 수 있다.

public class CourtRestConfiguration {
    
    @Bean
    public View jsonmembertemplate() {
        MappingJackson2JsonView view = new MappingJackson2JsonView();
        view.setPrettyPrint(true);
        return view;
    }
}

MappingJackson2JsonView 뷰는 잭슨2 라이브러리를 이용해 객체를 JSON 으로 바꾸거나 그 반대 작업을 수행한다. 내부적으로는 이 라이브러리의 ObjectMapper 인스턴스가 그 일을 도맡는다.

 

위의 예시와 같은 코드에서, /members 에 대한 요청이나 /member.* 에 대한 요청 모두 JSON 이 생성된다. 이 때 다양한 페이로드를 제공하기 위해서 다음과 같이 메소드를 추가할 수 있다.

@RequestMapping(value = "/members", produces = MediaType.APPLICATION_JSON_VALUE)
public String getRestMembersJson(Model model) {
    ...
    return "jsonmembertemplate";
}

@RequestMapping(value = "/members", produces = MediaType.APPLICATION_JSON_VALUE)
public String getRestMembersXml(Model model) {
    ...
    return "xmlmembertemplate";
}

반환 뷰의 이름을 제외한 나머지 로직은 동일한 getMembersXml(), getMembersJson() 두 메소드가 준비되었다. 어느 메소드를 호출할 지는 @RequestMapping 의 produces 속성값으로 결정된다.

이런 식의 구현도 잘 동작은 하겠지만, 애플리케이션 개발 시 지원 뷰 타입마다 메소드를 일일이 중복해서 구현하는 것은 문제가 있다. Helper 메소드를 만들어 사용하면 중복을 줄일 수는 있지만, @RequestMapping 설정 내용은 메소드마다 다르기 때문에 유사한 코드가 여러 메소드에 도배되는 것을 막기는 힘들 것이다.

 

@ResponseBody 로 JSON 만들기

MappingJackson2JsonView 로 JSON 응답을 생성할 수 있지만 앞에서 언급했듯, 여러 뷰 타입을 지원할 경우 문제가 발생할 수 있다. 이럴 땐 스프링 MVC 의 HttpMessageConverter 를 이용해 유저가 요청한 표현형으로 객체를 변환하는 것이 좋다.

@Controller
public class RestMemberController {
    
    @RequestMapping("/members")
    @ResponseBody
    public Members getRestMembers() { ... }
}

앞서 설명한 것과 같이 @ResponseBody 는 메소드의 실행 결과를 응답의 본문으로 취급하겠다고 스프링 MVC 에 밝힌다. XML 을 발행하는 것과 마찬가지로 이와 같이 적용할 경우 Members 객체만 반환하면 되고, 구성 클래스에서도 마샬러를 따로 작성할 필요가 없다. (@Controller 를 @RestController 로 바꾸면 @ResponseBody 를 사용하지 않아도 되는 것도 같다)

 

이제 /members.xml 을 요청하면 XML 을 받고 /members.json 을 요청하면 JSON 을 받는다. 구성 클래스에서 명시적으로 이를 선언하지 않았음에도 어떻게 이게 가능할까? 스프링 MVC 가 클래스패스에 있는 것을 자동으로 감지해 요청에 따라 적절한 HttpMessageConverter 를 등록해 변환하기 때문이다. 이제 데이터를 어떤 뷰로 제공할 것인지에 대한 고민은 하지 않고, 데이터 객체를 그대로 전달하면 스프링이 알아서 적절한 뷰를 제공한다는 것을 확인했다.

 

3. 스프링으로 REST 서비스 액세스하기


스프링 애플리케이션에서 서드파티 REST 서비스의 페이로드를 사용하려면 어떻게 해야 할까?

서드파티 REST 서비스에 접근하기 위한 방법으로 RestTemplate 클래스를 이용하는 방법이 있다. 이 클래스는 다른 스프링의 *Template 류 클래스와 마찬가지로 장황한 작업을 기본 로직으로 단순화하자는 설계 사상을 따르고 있다. 덕분에 스프링 애플리케이션에서 REST 서비스를 호출하고 반환받은 페이로드를 사용하기 매우 간편하다.

 

스프링 RestTemplate 클래스는 애당초 REST 서비스를 호출할 의도로 설계된 클래스이므로 주요 메소드가 다음 표에서 볼 수 있듯 HTTP 요청 메소드와 같이 REST 의 기본 토대와 밀접하게 연관되어 있다.

RestTemplate Method Description
headForHeaders(String, Object...) HTTP HEAD 작업을 수행한다.
getForObject(String, Class, Object...) HTTP GET 작업을 한 뒤, 주어진 클래스 타입으로 결과를 반환한다.
getForEntity(String, Class, Object...) HTTP GET 작업을 한 뒤, ResponseEntity 를 반환한다.
postForLocation(String, Object, Object...) HTTP POST 작업을 한 뒤, location 헤더값을 반환한다.
postForObject(String, Object, Class, Object...) HTTP POST 작업을 한 뒤, 주어진 클래스 타입으로 결과를 반환한다.
postForEntity(String, Object, Class, Object...) HTTP POST 작업을 한 뒤, ResponseEntity 를 반환한다.
put(String, Object, Object...) HTTP PUT 작업을 수행한다.
delete(String, Object...) HTTP DELETE 작업을 수행한다.
optionsForAllow(String, Object...) HTTP OPTIONS 작업을 수행한다.
execute(String, HttpMethod, RequestCallback,
ResponseExtractor, Object...)
CONNECT 를 제외한 모든 HTTP 작업이 가능한 메소드이다.

위 표에서 볼 수 있듯, RestTemplate 의 메소드명은 HTTP 요청 메소드로 시작한다. execute 는 사용 빈도가 적은 TRACE 를 비롯, 어떤 HTTP 메소드라도 사용 가능한 범용 메소드이다. (단, execute 메소드가 내부에서 사용하는 HttpMethod Enum 에서 제외된 CONNECT 메소드는 지원하지 않는다)

더보기

NOTE.

REST 서비스에서 가장 많이 사용되는 HTTP 메소드는 단연 GET 이다. GET 은 안전하게 정보를 가져오지만 (즉, 데이터 변경이 이루어지지 않지만) PUT, POST, DELETE 등의 메소드는 원본 데이터를 수정하는 수단이므로 REST 서비스의 공급자가 이런 메소드까지 지원할 가능성은 희박하다. 일반적으로 서비스 공급자는 데이터 변경이 필요한 경우 REST 서비스의 대체 수단인 SOAP 프로토콜을 선호한다.

RestTemplate 클래스의 메소드를 대략적으로 살펴봤으니 이번엔 REST 서비스를 스프링 프레임워크에서 자바로 호출해보자. 다음은 서드파티 REST 서비스를 액세스해 그 결과를 표준 출력으로 출력하는 코드이다.

public static void main(String [] args) throws Exception {
    final String uri = "http://localhost:8080/court/members.json";
    RestTemplate restTemplate = new RestTemplate();
    
    String result = restTemplate.getForObject(uri, String.class);
    System.out.println(result);
}

먼저 RestTemplate 객체를 생성한 뒤, getForObject() 메소드를 호출한다. getForObject() 는 브라우저가 REST 서비스 페이로드를 가져오는 것처럼 HTTP GET 요청을 수행한다.

getForObject() 의 호출 결과로 받은 응답을 String 형 변수 result 에 할당한다. 앞서 이 REST 서비스를 브라우저에서 호출한 결과 화면에 표시됐던 내용을 String 형식으로 담는 것이다. 메소드에 전달한 첫 번째 매개변수는 브라우저에서 썼던 것과 동일한 URL 이고, 두 번째 매개변수는 해당 응답의 결과를 어떤 형태로 반환받을 것인지를 의미한다.

위의 코드를 실행하면 브라우저 화면에서 출력됐던 결과와 동일한 문자열이 콘솔 창에 출력될 것이다.

 

매개변수화한 URL 에서 데이터 가져오기

앞에서 URL 을 호출해 데이터를 가져오는 것은 수행했는데, 만약 URL 에 필수 매개변수를 넣어야 하는 경우는 어떻게 해야 할까? 다행히 RestTemplate 의 메소드는 URL 에 placeholder 를 넣고 나중에 이 곳을 실제 값으로 치환할 수 있다. placeholder 는 @RequestMapping 과 같이 {} 로 삽입할 위치를 표시한다.

public static void main(String [] args) throws Exception {
    final String uri = "http://localhost:8080/court/member/{memberId}";
    Map<String, String> params = Map.of("memberId", 1);
    RestTemplate restTemplate = new RestTemplate();
    
    String result = restTemplate.getForObject(uri, String.class, params);
    System.out.println(result);
}

 

데이터를 매핑된 객체로 가져오기

결과를 String 으로 반환받는 대신 Members/Member 클래스로 직접 매핑해서 사용할 수 있다. getForObject() 메소드의 두 번째 매개변수를 String.class 가 아닌, 매핑할 클래스로 지정하면 해당 클래스에 맞게 응답이 매핑된다.

public static void main(String [] args) throws Exception {
    final String uri = "http://localhost:8080/court/member/{memberId}";
    Map<String, String> params = Map.of("memberId", 1);
    RestTemplate restTemplate = new RestTemplate();
    
    Member result = restTemplate.getForObject(uri, Member.class, params);
    System.out.println(result);
}

 

4. RSS/Atom Feed 발행하기


RSS/아톰 피드는 정보 발행 시 널리 사용되는 수단으로, 보통 REST 서비스를 사용해 액세스한다. 즉, 일단 REST 서비스를 구축한 다음 REST/아톰 피드를 발행할 수 있다. 스프링의 기본적인 REST 지원 기능 외에 RSS/아톰 피드 전용 서드파티 라이브러리를 사용하면 편리하게 구현할 수 있다. 여기서는 오픈소스 자바 프레임워크인 ROME 을 사용했다.

 

우선, 어떤 정보를 RSS/아톰 피드로 발행할 지를 결정한다. 정보를 가져오는 방법은 여기서 다룰 내용이 아니므로 넘어가고, 발행할 정보를 선택한 다음 RSS/아톰 피드로 구조를 잡아야 하는데 여기서 바로 ROME 이 사용된다.

 

RSS/아톰 피드는 정보를 발행하는 데 여러 엘리먼트를 활용한 XML 페이로드일 뿐이다. RSS/아톰 피드는 두 형식 모두 공통적인 특징이 있다. 간단히 정리하면 다음과 같다.

  • 피드 내용을 서술하는 메타데이터 영역이 존재한다. (예: 아톰의 <author> 와 <title>, RSS 의 <description> 과 <pubDate>)
  • 순환 엘리먼트로 복수의 정보를 나타낼 수 있다. (예: 아톰의 <entry>, RSS 의 <item>)
    각 순환 엘리먼트는 자체 엘리먼트를 가지고 있어 더 자세한 정보를 나타낼 수 있다.
  • 버전이 다양하다. RSS 는 0.90, 0.01 넷스케이프, 0.91 유저랜드, 0.92, 0.93, 0.94, 1.0 을 거쳐 현재 2.0 이 최신 버전이고 아톰은 0.3 과 1.0 버전이 있다.
    ROME 을 사용하면 버전에 상관없이 자바 코드에서 기용한 정보를 바탕으로 피드의 메타데이터 영역, 순환 엘리먼트를 생성할 수 있다.

RSS/아톰 피드의 구조 및 ROME 의 역할을 간단히 봤으니, 스프링 MVC 컨트롤러에서 유저에게 피드를 표시하는 코드를 살펴보자.

@RestController
public class FeedController {
    
    @RequestMapping("/atomfeed")
    public String getAtomFeed(Model model) {
        List<TournamentContent> tournamentList = new ArrayList<>();
        
        ...
        
        model.addAttribute("feedContent", tournamentList);
        return "atomfeedtemplate";
    }
    
    @RequestMapping("/rssfeed")
    public String getRSSFeed(Model model) {
        List<TournamentContent> tournamentList = new ArrayList<>();
        
        ...
        
        model.addAttribute("feedContent", tournamentList);
        return "rssfeedtemplate";
    }
}

/atomfeed 을 getAtomFeed() 메소드에 /rssfeed 을 getRSSFeed() 메소드에 매핑했다. 이들 메소드엔 TournamentContent 객체 리스트가 tournamentList 변수로 선언되어 있다. 여기서 TournamentContent 객체는 일반 POJO 이다. 두 메소드는 반환 뷰에서 이 리스트에 접근할 수 있도록 Model 객체의 속성에 할당한 뒤 각각 논리 뷰를 반환한다. 논리 뷰는 스프링 구성 클래스에 다음과 같이 정의한다.

public class RestConfiguration {
	
    @Bean
    public AtomFeedView atomfeedtemplate() {
        return new AtomFeedView();
    }
    
    @Bean
    public RSSFeedView rssfeedtemplate() {
    	return new RSSFeedView();
    }
}

 

스프링은 ROME 기반으로 제작된 RSS/아톰 전용 뷰 클래스 AbstractAtomFeedView 와 AbstractRssFeedView 를 지원한다. 두 추상 클래스로 인해 RSS/아톰 형식을 세세하게 알지 못해도 뷰를 구현할 수 있다.

 

다음은 AbstractAtomFeedView 를 상속한 AtomFeedView 클래스로, 논리 뷰 atomfeedtemplate 을 구현한 코드이다.

public class AtomFeedView extends AbstractAtomFeedView {
	
    @Override
    protected void buildFeedMetadata(Map model, Feed feed, HttpServletRequest request) {
        feed.setId("tag:tennis.org");
        feed.setTitle("Grand Slam Tournaments");
        
        List<TournamentContent> tournamentList = 
            (List<TournamentContent>) model.get("feedContent");
        
        feed.setUpdated(tournamentList.stream().map(TournamentContent::getPublicationDate)
            .sorted().findFirst().orElse(null));
    }
    
    @Override
    protected List buildFeedEntries(Map model, HttpServletRequest request,
        HttpServletResponse response) throws Exception {
        List<TournamentContent> tournamentList = 
            (List<TournamentContent>) model.get("feedContent");
        
        return tournamentList.stream().map(this::toEntry).collect(Collectors.toList());
    }
    
    private Entry toEntry(TournamentContent content) { ... }
}

AbstractAtomFeedView 클래스를 상속한 뒤, 이 클래스로부터 물려받은 buildFeedMetadata(), buildFeedEntries() 두 메소드의 세부 로직을 위와 같이 작성한다.

 

다음은 AbstractRssFeedView 를 상속해 구현한 RssFeedView 클래스이다.

public class RSSFeedView extends AbstractRssFeedView {
	
    @Override
    protected void buildFeedMetadata(Map model, Channel feed, HttpServletRequest request) {
        feed.setTitle("World Soccer Tournaments");
        feed.setDescription("FIFA World Soccer Tournament Calendar");
        feed.setLink("tennis:org");
        
        List<TournamentContent> tournamentList = 
            (List<TournamentContent>) model.get("feedContent");
        
        feed.setLastBuildDate(tournamentList.stream()
            .map(TournamentContent::getPublicationDate)
            .sorted().findFirst().orElse(null));
    }
    
    @Override
    protected List<Item> buildFeedItems(Map model, HttpServletRequest request,
        HttpServletResponse response) throws Exception {
        List<TournamentContent> tournamentList = 
            (List<TournamentContent>) model.get("feedContent");
        
        return tournamentList.stream().map(this::toMap).collect(Collectors.toList());
    }
    
    private Item toItem(TournamentContent content) { ... }
}

AbstractRssFeedView 클래스를 상속한 뒤, 위와 마찬가지로 buildFeedMetadata(), buildFeedItems() 메소드의 로직을 작성한다.

 


#Reference.

 

스프링 5 레시피(4판)

이 책은 스프링 5에 새로 탑재된 기능 및 다양한 구성 옵션 등 업데이트된 프레임워크 전반을 실무에 유용한 해법을 제시하는 형식으로 다룹니다. IoC 컨테이너 같은 스프링 기초부터 스프링 AOP/A

www.hanbit.co.kr

 

+ Recent posts