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.
Trackbacks are closed for this story.
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!
-
On Thursday, 11 Jul 2013 19:12, Mayumi wrote the following:
This is one of the problems on 4clojure. Here is my soluoitn, for the sake of comparison:(fn [roman](apply str (let [roman (str roman) i "IXCM" v "VLD" x "XCM" ms {\1 [i] \2 [i i] \3 [i i i] \4 [i v] \5 [v] \6 [v i] \7 [v i i] \8 [v i i i] \9[i x]} powers (reverse (range (count roman)))] (mapcat (fn [power digit] (map #((vec %) power) (ms digit))) powers roman))))
Comments are closed for this story.