Writing a Spring MVC macro in Clojure (part 2)

In a previous post, we wrote a Clojure program that implemented two very simple Spring MVC Controllers. The program contained 15 lines of (mainly boilerplate) code. We’ll reduce that to the following:

(ns com.vxcompany.clojuremvc.example
  (:use com.vxcompany.clojuremvc.mvcmacro))

(defmapping com.vxcompany.clojuremvc.MyMapping

    [:index {:info "Life's so much cooler with macros!"}])

    [:another {:info "It's really cooler!"}]))

In part 1 of this manual, we already made a first step. Now, let’s glue the pieces together.

I’m sorry, I lied

My first version of the MVC macro does NOT work. When trying, it went fine when I used the macro within the same jar. It also went fine, when I first expanded the macro manually (macroexpand ‘(defmapping …)). But when I compiled one project with an unexpanded (defmapping), and tried to use that mapping from another project: BAM! The JVM complained that it could not find a method postinit31.

The very short summary is, that in the very special case of (genclass), the macro is expanded at different times. (gensym) might evaluate to different values. And if it does, the JVM looks for postinit33, where it may have been generated as postinit29. So, we should not use (gensym) for generating method names! See also the more detailed explanation here.

To ensure unique method names, we’ll do something else. The post-init method for a mapping com.foo.MyMapping will be -MyMapping-postinit. The following code strips the fully qualified class name:

(defn strip
  "Takes the substring of a string, starting at the index returned by
   the application of (function string)."
  [string function]
  (.substring string (function string)))

;;; with the above function, this does it:
(strip (str name) #(inc (.lastIndexOf % ".")))

Making things more Clojurish

We can now take the macro we created in part 1 of the manual, and paste some code into the post-init method, so the class actually contains the mapping. For each mapping above (index.html and another.html), we’ll generate the necessary boilerplate code, and let Clojure paste the url, view name and model in it. We’ll do that in a moment.

First, let’s make things just a little bit nicer. In the original Clojure MVC controller, we used plain strings as keys for the model (“info”). Spring MVC indeed needs strings for the keys. However, in Clojure, we can also use keywords (:info). Can’t we use keywords in our own code, and let the macro convert the keyword into a string?

This function converts any value to a string. If the value is a keyword, the name of the keyword is returned. Otherwise, a default string conversion is done. We used (cond) instead of (if), so we can easily add more cases later. The last case, true, is an easy way to specify a default case.

(defn tostr
  "Replaces the value by a string, e.g. :a becomes \"a\"."
    (instance? clojure.lang.Keyword val) (name val)
    true (str val)))

The next function takes a map (in our case, the model) and does the above conversion to each key in a map. Idiomatic for functional languages, we made the function recursive:

(defn keys-to-strings
  "Takes a map and converts its keys into strings, e.g. {:a 1} becomes {\"a\" 1}"
  (loop [restmap a-map acc {}]
    (if (empty? restmap)
      (let [[key val] (first restmap)]
        (recur (rest restmap) (conj acc [(tostr key) val]))))))

The macro

With the above adjustments, the macro now becomes:

(defmacro defmapping
  "Defines a Spring MVC URL mapping. See example at
  [name & body]
  (let [this (gensym "this")
        shortname (strip (str name) #(inc (.lastIndexOf % ".")))]
       (defn ~(symbol (str "-" shortname "-postinit")) [~this]
         ~@(for [[url handler] body]
             `(.registerHandler ~this ~url
                 (proxy [AbstractController] []
                   (handleRequestInternal [request# response#]
                     (let [[view# model#] ~handler]
                       (ModelAndView. (tostr view#) (keys-to-strings model#))))))))
         :name ~name
         :post-init ~(str shortname "-postinit")

The for loop adds all the boilerplate code for each mapping in our definition. Note we have to splice ~@, because for returns a single list of statements. We need the separate statements instead of the list. Also note how we fill in the ~url, view# and model# in the template code.

With this macro, we can specify the mapping as we did at the top of this post.

Did we really need a macro?

There’s one question I didn’t answer. Did we really need a macro? Writing macros is more difficult than writing functions. The rule is: use functions if you can. Use macros only if you cannot do the same thing with functions only.

In this case, we clearly need a macro. There is code in the macro, we only want to evaluate AFTER the macro has been expanded. Take the (gen-class) specification. (gen-class) is evaluated at compile time! Functions are not evaluated at compile time! Putting (gen-class) within a function instead of a macro, would have caused very strange effects. Also, we’re passing parameters to the macro (e.g. a class name), that we don’t want to evaluate before calling the macro. Function parameters are always evaluated before the function is called.

So in this case it’s clear. But don’t forget to always ask yourself if you really need a macro, before writing one!


We showed how powerful macros are. The original 15 line UrlMapperHandler code, was reduced to a few lines only, that express the intention of the code much better. Of course, this took some effort. We had to write a macro. But we can reuse the macro each time we need it. And the actual code is much cleaner! In Java, doing the same thing would definitely have been more difficult!


Tags: , , , , , ,

One Response to “Writing a Spring MVC macro in Clojure (part 2)”

  1. Morgan Hoberg Says:

    Considerably, the report is really the freshest for this notable subject. I concur with your conclusions and may thirstily appear forward for your forthcoming updates.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: