Hacking with Handlebars in Java and Clojure: Part II
In Hacking with Handlebars in Java and Clojure: Part I, we took a look at how to extend the Handlebars.java library in Java and then in Clojure.
We used proxy
in Clojure to extend an abstract class. In that way, we implemented a Clojure version of a TemplateLoader
class in Handlebars.java.
In this post, I want to discuss some more ways in which this can be accomplished. We will look at some interesting ways to do Clojure-java interop.
Knowledge about the previous post or about Handlebars.java is not strictly necessary since the things we discuss can be applied to any scenario where you are trying to extend/use Java code in a Clojure project.
So let’s get started!
Recap
A brief recap on the problem we were trying to solve in Part I of this post. We wanted to store template files on the S3 bucket (which is not directly supported by Handlebars.java for some reason). Hence, we had to write our custom logic in the form of a TemplateLoader
class.
Without going into fine details, what we are trying to accomplish is either extend an Abstract Class from Handlebars.java or implement a TemplateSource
interface.
How to extend and use java library classes and interfaces in a Clojure project?
In the rest of the post, we will look at different ways this can be accomplished in Clojure.
1. Java in Clojure Project
One very simple solution on how to extend Java library in Clojure project. Just write Java code! That’s it.
You can write the necessary Java code and directly include it in your Clojure project. I will demonstrate quickly how this is possible with leiningen
.
In the earlier post, I wrote a HTTPTemplateLoader
java class to show how to write custom loaders in Java and later we implemented the same in Clojure in proxy
.
But let’s see how we can just drag and drop that Java implementation in a Clojure project.
Here’s what I did —
- Create a
java
dir in your Clojure project’ssrc
dir. - In
project.clj
, add the following config.
:java-source-paths ["src/java"]
- In the
java
dir, I added my Java code as follow:
java
└── com
└── github
└── jknack
└── handlebars
└── io
└── HTTPTemplateLoader.java
And that’s it!
Whenever you run any new repl or build using lein
, it will compile our Java code.
Now, we can directly use it in our Clojure code using the traditional Java interop:
=> (import com.github.jknack.handlebars.io.HTTPTemplateLoader)=> (def loader (HTTPTemplateLoader. "http://s3-url-prefix/"))
Reference:
2. Using reify
In the previous post, we used proxy
in order to extend an Abstract class URLTemplateLoader
in our Clojure code.
But there is another way we can implement a similar solution. By using reify
in Clojure.
reify
can be used to define implementation for a protocol or an interface.
The reify docs also say:
The result is better performance than proxy, both in construction and invocation. reify is preferable to proxy in all cases where its constraints are not prohibitive.
But reify
has certain constraints as well. We can only extend protocol or interfaces. So it is not possible for us to extend an Abstract or Base class using reify
. But instead, it can be used to implement Java interfaces.
Fortunately for us, Handlebars.java also contains TemplateLoader interface. Instead of extending any Abstract class, we can directly implement this interface using reify
for our custom TemplateLoader
.
As we saw in the previous post, we need to implement the resolve
method which gives us a custom logic on how to construct the template file-paths.
We are doing the same thing here by implementing the TemplateLoader
interface from Handlebars.java.
Then we can use the loader class instance returned by reify
in the same way, we used http-loader
loader instance returned by proxy.
A rule of thumb: Use reify
everywhere except where it is prohibitive.
References:
3. Using gen-class
There is a fundamental difference between method #1 and method #2 we discussed above.
reify
is purely a runtime implementation of the TemplateLoader
interface. But the Java implementation we did with HTTPTemplateLoader
gets compiled and we import the compiled class in our Clojure code for interop.
There are also a lot of constraints on extending interfaces using reify
. It is a good option when you can work within those constraints.
There is a way we can have the best of both worlds. We can have an implementation/extension of a Java interface or class written purely in Clojure. And it would get compiled and we can use it as a Java class at runtime in Clojure!
Essentially, we are writing the same class as the Java class we wrote before but instead in Clojure!
This is possible by using gen-class
. Let’s see how it works.
Suppose, we want to create a Java class that implements the TemplateLoader
interface.
Let’s go over the above code, bit by bit.
(ns clj-handlebars.http-loader
(:gen-class
:init init
:state state
:name com.github.jknack.handlebars.io.HTTPCljLoader
:implements [com.github.jknack.handlebars.io.TemplateLoader]
:constructors {[String] []}
:prefix "-")
(:import [com.github.jknack.handlebars.io URLTemplateSource]
[java.net URL]))
In this, we are adding some metadata to our ns declaration.
gen-class
: Tells that this namespace should be compiled and generate a class.init
: name of the constructor method. Yes, it’s the same as a Java constructor.state
: A variable in which you can track the state in your Java class instance.name
: The name of your Java class.constructors
: Constructor signatures. If you want overloaded constructors, you can do that using the multi-arity init function.prefix
: Prefix used to map the methods. All the overloaded methods in your class should be prefixed with this value. This is why in the above code, we have the method name-resolve
since we want to implement theresolve
method in ourTemplateLoader
interface.
In the rest of the code, we implement each required method from the TemplateLoader
interface (prepended with the prefix we specified in metadata).
In our constructor -init
method, it accepts one param which we set as the prefix
. The value returned by the -init
method is accessible inside the rest of the functions using the state
variable (or whatever you name it in metadata).
Each method you write needs the first param as this
which (as you might have guessed) is the same as Java’s this
. The state
can be accessed using this
.
But how to use this code in our regular Clojure code?
That requires that this code should be compiled to generate a Java class before we can import it into our regular Clojure code. Can we call it compiling this class ahead of time?
And that is essentially Ahead of Time Compilation concept in Clojure. It means compiling some things before we compile/run any of the regular stuff.
But we need to tell this to our build tool as well. We can do this in lein
by adding the following to our project.clj
:aot [clj-handlebars.http-loader]
Now, in your regular code, you can just import the Java class generated by the Clojure code.
=> (import com.github.jknack.handlebars.io.HTTPCljLoader)=> (def loader (HTTPCljLoader. "http://s3-url-prefix/"))
Whenever you run a lein
repl, this class will be compiled before (so it can be imported by our regular code). This also means that any changes in this Clojure file need a restart of the repl.
And that’s how you can generate java class extending/implementing any interface or class directly from Clojure.
References:
So these are some ways in which you can extend any Java library and use it in Clojure.
Hopefully, you learned something new from this post. I know I did ;)
Thank you!