I have recently added an RSS feed to the site and thought I'm going to explain how it can be done. This is not a rocket science though it'll be good to discuss how to fit it into modern Spring Boot app using Java Configuration. In the process maybe one can also learn about creating custom views and some inner workings of Spring MVC.

RSS feed

An RSS feed is an XML document containing elements adhering to specs. The simplest one may look like this:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Channel title</title>
    <link>http://someurl/feed/</link>
    <description>Some description</description>
    <pubDate>Mon, 13 Jul 2015 12:51:08 GMT</pubDate>
    <item>
      <title>Item title</title>
      <link>http://someurl/</link>
      <description>Description</description>
      <pubDate>Mon, 13 Jul 2015 12:51:08 GMT</pubDate>
    </item>
    <item>
      <!-- there can be more items like the one above -->
    </item>
  </channel>
</rss>

As you see in the RSS world there is a <channel> with multiple <item> tags inside it. Both <title> and <link> are required for <channel>, whereas <item> requires either <title> or <description>. Having <pubDate> set is nice for clients as they may choose to check it and not to update their local copy of the channel if it's the same as what they've seen before. There are other possible elements, maybe you might want to read the specs, but above is sufficient for a feed to work.

Domain model

Say I have a domain object Document in the system that I want to appear in the feed:

public class Document {

    private String id;
    private String title;
    private String description;
    private String contents;
    private Date datePublished;

    // getters, setters

}

Nothing special here, standard stuff, this can be an @Entity and be fetched from a database as explained in other posts on this site. It's also obvious that it should map to RSS <item> quite well.

What is a View in Spring MVC?

It is defined by this interface:

public interface View {
    String getContentType();
    void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}

As you can see it's something that roughly takes a model Map and HttpServletRequest as an input and uses HttpServletResponse to render something fancy out of it.

There are of course many implementations of a View that come with Spring. Some of them render a view from JSP pages (JstlView), some serialize the model to JSON (MappingJackson2JsonView), whatever your client needs.

Knowing all of the above we could build an RSS feed using views provided with Spring in a same way as we serve HTML, i.e. we can build a feed from JSP page that will be served with application/rss+xml content type. Or even implement own View that will render the feed directly, it's just text after all. But why re-invent a wheel as we can have something dedicated to the task. It also would allow us to abstract out the creation process in case we'd like to switch RSS versions or move to Atom feeds entirely.

Enter ROME library

There's a couple of libraries to choose from, however Spring provides integration with ROME, which narrows down the choice. This is not a problem though as ROME supporting both RSS and Atom feeds in different versions seems to have what it takes.

So, as usual we need to add a dependency to pom.xml:

<dependency>
    <groupId>com.rometools</groupId>
    <artifactId>rome</artifactId>
    <version>1.5.0</version>
</dependency>

Whole strategy of using ROME gets down to creating a View containing our feed that will be served by a Controller method in a response to a request.

Creating a View for RSS feed

Spring provides AbstractRssFeedView abstract class which is an implementation of View that is using ROME underneath to create a RSS feed. It has simple interface, exposes some methods to be overridden:

public abstract class AbstractRssFeedView extends AbstractFeedView<Channel> {

    // I've skipped parts for clarity, left only those that are meant to be overridden

    public AbstractRssFeedView() {
        setContentType("application/rss+xml");
    }

    protected Channel newFeed() {
        return new Channel("rss_2.0");
    }

    protected abstract List<Item> buildFeedItems(Map<String, Object> model,
                                                 HttpServletRequest request,
                                                 HttpServletResponse response)
            throws Exception;

}

All we need to do is to extend it to create a Channel and a List containing Item objects and leave the rest to Spring and ROME.

What I have actually done looks more or less like this:

@Component("documentRssFeedView")
public class DocumentRssFeedView extends AbstractRssFeedView {

    private final DocumentService documentService;

    @Value("${application.base-url}")
    private String baseUrl;

    @Autowired
    public DocumentRssFeedView(DocumentService documentService) {
        this.documentService = documentService;
    }

    @Override
    protected Channel newFeed() {
        Channel channel = new Channel("rss_2.0");
        channel.setLink(baseUrl + "/feed/");
        channel.setTitle(CHANNEL_TITLE);
        channel.setDescription(CHANNEL_DESCRIPTION);
        documentService.getOneMostRecent().ifPresent(d -> channel.setPubDate(d.getDatePublished()));
        return channel;
    }

    @Override
    protected List<Item> buildFeedItems(Map<String, Object> model,
                                        HttpServletRequest httpServletRequest,
                                        HttpServletResponse httpServletResponse) throws Exception {
        return documentService.getRecent(NUMBER_OF_ITEMS).stream()
                .map(this::createItem)
                .collect(Collectors.toList());
    }

    private Item createItem(Document document) {
        Item item = new Item();
        item.setLink(baseUrl + document.getId());
        item.setTitle(document.getTitle());
        item.setDescription(createDescription(document));
        item.setPubDate(document.getDatePublished());
        return item;
    }

    private Description createDescription(Document document) {
        Description description = new Description();
        description.setType(Content.HTML);
        description.setValue(document.getDescription());
        return description;
    }

}

This requires an explanation:

  • @Component("documentRssFeedView") - this makes a Spring component out of it, with specific name set. This will cause Spring Boot to 'notice it' and specific name will allow us to select this one view apart from others.
  • DocumentService is a service I made to deal with Document, has two relevant methods:

    public interface DocumentService {
        Collection<Document> getRecent(int count);
        Optional<Document> getOneMostRecent();
    }
    

    One returns a Collection of most recently published Document entities, the other optional most recent document (empty if there is none).

  • baseUrl property gets populated from application.properties and is used to construct URLs.
  • In newFeed() the Channel gets created, with link, title and description properties set, which are required for the feed to be valid. pubDate property is set to a publication date of a most recent Document.
  • In buildFeedItems() the Collection of Document entities is pulled from DocumentService, and is mapped to a List of Item objects, and mapping myself is quite straightforward here.
  • I'm not overriding the constructor, as it already does what I want - it sets the right content type this view will provide.

Looking at this class one may ask why all the parameters of buildFeedItems are ignored here. Well, I'm not really interested in reading request and modifying response here as the feed contents will be the same for each request, and the job to modify response belongs to parent classes.

As for the model I could have made it so the Collection of Document entities would have been given in a Map, presumably set by a Controller 'speaking' to DocumentService as it's supposed to be. However that would require some tedious code to check if it's actually there, and given the Object type for Map values also checking and casting the value to required type. Too much hassle I'd say for no obvious gain as this implementation is too concrete for that.

Plugging the View into the Controller

Most of the plumbing is done by Spring Boot, so to make use of this view a Controller method can be written like that:

@Controller
public class DocumentController {

    @RequestMapping(value = "/feed/", produces = "application/*")
    public String getFeed() {
        return "documentRssFeedView";
    }

}

This should be enough to map /feed/ request path to our DocumentRssFeedView and got it rendered. Enjoy.

However it would be nice to know why that works, and even nicer when the view fails to render to find out what's going on. So let me explain:

  • When Spring encounters a bean implementing View interface, exactly like the one we've created, it 'knows' it's one of available views rendering content of application/xml+rss type.
  • The request comes for /feed/ and we're returning documentRssFeedView as a view name.
  • This is where ViewResolver kicks in - based on the request (content types accepted by client, request path) and view name returned from the method it tries to figure which view out of available views will be used.
  • There is actually a chain of ViewResolver objects, they executed in sequence, and the first one that figures the view 'wins'.
  • One of view resolvers is BeanNameViewResolver, which knows "documentRssFeedView" is a view name. It looks for a bean with the same name implementing View. That's how it finds our DocumentRssFeedView, and that's exactly what we hope for to happen.

What may cause possible problems is that the chain of ViewResolver is setup and ordered by Spring Boot based on what's on the classpath. Moreover BeanNameViewResolver ends up somewhere at the end of the chain. Imagine the client makes a request to /feed/ with an usual header saying it accepts [text/html, application/xhtml+xml, image/webp, application/xml;q=0.9, */*;q=0.8]. This may lead to ambiguities. For example InternalViewResolver, which is earlier in the chain, may believe that documentRssFeedView refers to internal resource with content type text/html and it will try to read it. This will end up in client getting 404 NOT FOUND status.

This is prevented here by adding produces = "application/*" to @RequestMapping on the Controller method as it eliminates views producing content types other than that from being considered. Other way to make our view applicable would be to have the method on a path indicating content type, i.e. /feed.xml. It narrows down what Spring thinks can be accepted by the client, effectively accomplishing the same. The ultimate solution for this kind of problem would be to inject DocumentRssFeedView straight into the Controller and just return it from the method:

@Autowired
private DocumentRssFeedView view;

@RequestMapping(value = "/feed/")
public DocumentRssFeedView getFeed() {
    return view;
}

That would just bypass ViewResolver chain entirely, probably also saving some processing time.

Conclusion

Yup, it's nice and relatively easy to add RSS feed to a Spring Boot application. If you want to look at the code - the source code of Akanke can serve as an example of such integration.

One last tip maybe - RSS feeds are usually a nice candidate to be cached. They are relatively static, don't update so frequently, but may be accessed quite often by people's RSS readers. Why waste resources to build them each time. With upcoming Spring 1.3 it can be done with annotations alone.

This site uses cookies. By continuing to browse the site, you are agreeing to our use of cookies. Find out more in Privacy and Cookies Policy.