Monday, 12 April 2010

Generate your class and proxy it too

Sometimes, you need to create a Java class from Clojure so that it can be called from a Java-based framework. At the same time, you want to use the class in your Clojure environment without needing to add a compile step to your workflow. To accomplish this, you can combine both gen-class and proxy.

In this article, I will show how to implement a servlet that can be both compiled into a Java class for inclusion in a WAR file and used via proxy within a development environment.

What about reify and deftype?

In Clojure 1.2, which is still in development, reify is preferred over proxy in most cases, and deftype can create classes both dynamically and AOT—all with better performance. Unfortunately, neither of these can extend other Java classes; they can only implement Java interfaces. As a result, neither of these can be used to subclass HttpServlet.

Implementing HelloServlet

Implementing a servlet is fairly simple. Using gen-class subclass javax.servlet.http.HttpServlet and implement any of the doXXX methods you would like your servlet to support. The following example shows the obligatory Hello servlet:

(ns #^{:doc "The servlet that says hello."
       :author "Daniel Solano Gómez"}
  example.HelloServlet
  (:gen-class :extends javax.servlet.http.HttpServlet
              :main false)
  (:import javax.servlet.http.HttpServletResponse))

(defn -doGet [_ _ response]
  (let [out (.getWriter response)]
    (.println out "Hello, world!"))
  (doto response
    (.setContentType "text/plain")
    (.setStatus HttpServletResponse/SC_OK)))

Assuming you have the proper class path set up, you can (compile 'example.HelloServlet). Now you have a servlet that you can include in a standard web application. However, unlike normal Java servlets, you can modify your Clojure servlet at run-time using an attached REPL. For example, since the -doGet function is called by the AOT-generated class, you should be able to redefine it without regenerating the class file. However, you will not be able to add or remove class methods, such as -doPut or -init, without recompiling. This may be an acceptable, but let’s take this a step further and add proxy…

Adding proxy to HelloServlet

At first, it may seem relatively simple to create a proxy for HelloServlet. For example, you could simply add a function that returns a proxy instance as follows:

(defn servlet []
  (proxy [javax.servlet.http.HttpServlet] []
    (doGet [request response] (-doGet this request response)))))

This works; you are able to get the proxy by calling servlet. However, whenever you compile your namespace, you will find example/HelloServlet/proxy$javax/servlet/http/HttpServlet$0.class among your generated class files. Even trying to use (when-not *compile-files* …) does not avoid this. If you want to keep your generated classes free from the proxy class, you will need to move this function to a new namespace. For example:

(ns #^{:doc "Dynamic proxy generator for example.HelloServlet"
       :author "Daniel Solano Gómez"}
  example.HelloServlet.proxy
  (:use example.HelloServlet))

(defn servlet []
  (proxy [javax.servlet.http.HttpServlet] []
    (doGet [request response] (-doGet this request response))))

Now, from Clojure, you can use the servlet function from the new namespace to add an instance of the servlet to your container. Furthermore, when you AOT compile, the proxy class will not be generated.

Using the proxy with Jetty 7

The following is a somewhat minimal example of how to do this with Jetty 7:

(ns #^{:doc "An example program that uses Jetty 7 to serve HelloServlet"
       :author "Daniel Solano Gómez"}
  jetty-hello-demo
  (:import org.eclipse.jetty.server.Server
           org.eclipse.jetty.servlet.ServletHolder
           org.eclipse.jetty.webapp.WebAppContext)
  (:require [example.HelloServlet.proxy :as hello-servlet]))

(defn- hello-servlet-holder []            
  (doto (ServletHolder.)                  
    (.setServlet (hello-servlet/servlet))))

(defn- create-webapp []
  (doto (WebAppContext.)
    (.setResourceBase "./web")
    (.setContextPath "/")
    (.setParentLoaderPriority true)              
    (.addServlet (hello-servlet-holder)
                 "/hello")))                                                          

(defn- create-server []
  (doto (Server. 8081)
    (.setStopAtShutdown true)
    (.setHandler (create-webapp))))

(def jetty-server (create-server))
(.start jetty-server)

Finally, a note about init

What if your servlet needs to use the init and destory life cycle methods? You may be tempted to do the following to generate your class:

(ns #^{:doc "The servlet that says hello." 
       :author "Daniel Solano Gómez"}
  example.HelloServlet 
  (:gen-class :extends javax.servlet.http.HttpServlet
              :main false)
  (:import javax.servlet.http.HttpServletResponse))

(defn -init [this]
  (.log this "Initialising…"))

(defn -doGet [this request response]
  (let [out (.getWriter response)]
    (.println out "Hello, world!"))
  (doto response
    (.setContentType "text/plain")
    (.setStatus HttpServletResponse/SC_OK)))

(defn -destroy [this]
  (.log this "Destructing…"))

And for your proxy, you would do:

(ns #^{:doc "Dynamic proxy generator for example.HelloServlet"
       :author "Daniel Solano Gómez"}
  example.HelloServlet.proxy
  (:use example.HelloServlet))

(defn servlet []
  (proxy [javax.servlet.http.HttpServlet] []
    (init [] (-init this)) 
    (doGet [request response] (-doGet this request response))
    (destroy [] (-destroy this))))

However, this does not work. The problem is that the nullary form of init is merely a convenience method that is called by the unary GenericServlet.init(ServletConfig). As explained by Mark Triggs in Tricky uses of Clojure gen-class and AOT compilation and Meikel Brandmeyer in proxy – gen-class little brother, using gen-class and proxy with methods that have multiple arities can be tricky.

As a result, you need to implement both the nullary and unary forms of init in order for your servlet to work. As explained in the documentation for GenericServlet, when you override init(ServletConfig), you must call super.init(config).

For gen-class you need to expose the superclass’s init function so that it can be called from your init function, as follows:

(ns #^{:doc "The servlet that says hello."
       :author "Daniel Solano Gómez"}
  example.HelloServlet
  (:gen-class :extends javax.servlet.http.HttpServlet
              :main false
              :exposes-methods {init initSuper})
  (:import javax.servlet.http.HttpServletResponse))

(defn -init [this]
  ([this config]            
   (. this initSuper config))
  ([this]                      
   (.log this "Initialising…")))

For proxy, you will need to use proxy-super as follows:

(ns #^{:doc "Dynamic proxy generator for example.HelloServlet"
       :author "Daniel Solano Gómez"}
  example.HelloServlet.proxy
  (:use example.HelloServlet))

(defn servlet []
  (proxy [javax.servlet.http.HttpServlet] []
    (init [config]
          (proxy-super init config)
          (-init this))
    (doGet [request response] (-doGet this request response))
    (destroy [] (-destroy this))))

Note that the proxy only implements the unary form of init. I am not entirely sure why, but it seems that if proxy provides both the nullary and unary forms of init, the nullary form is never called.

Conclusion

I hope that I have shown how you can successfully use both gen-class and proxy together to provide both a dynamic Clojure-driven development environment and a means of creating code that will be used within existing Java frameworks. The example I provided used a servlet, but the same techniques can be applied to other frameworks such as Spring or EJB.

TrackBacks

Comments