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
No trackbacks, yet.
http://www.deepbluelambda.org/programming/clojure/generate-your-class-and-proxy-it-too.trackback.
Note that trackbacks are subject to moderation.
Comments
-
On Sunday, 14 Nov 2010 17:09, Denis Fuenzalida wrote the following:
Thanks a lot for this post. I was looking if somebody else had already done some basic Servlets in Clojure and your post was very helpful!
Follow me on Twitter
Clone me on GitHub