Zero to RMI with Groovy and Spring
In a recent post, I leveraged Spring to write a simple client-server app using 50 lines of Java.
I've moved it to Groovy. I've included the code below, but here are some fast facts:
- I used
groovyc
to compile the Groovy toclass
files, then used thejava
command. - I could only reduce the code by an
import
or two. However, I did save 220+ characters (notice how clean the Groovy is). Groovy gurus might be able to improve on this. - Note that the Spring config files have more namespaces for Groovy support. Discovering this was the hardest part of the exercise.
Here is the contract between the client and server. This would be in a
common
package:
interface EasyServer {
Message exchangeMessage(String user)
}
Here is a simple
Message
class. Of course, for RMI, being Serializable
is vital:
import java.io.Serializable
class Message implements Serializable {
private final String message
Message(String message) { this.message = message }
String getMessage() { return message }
}
So far, so good. Now the server implementation:
import org.springframework.context.support.ClassPathXmlApplicationContext
class EasyServerImpl implements EasyServer {
Message exchangeMessage(String user) {
return new Message(user + ", Spring with Groovy rocks!")
}
static void main(String[] args) {
def context =
new ClassPathXmlApplicationContext("server_config.xml")
context.getBean("easyServer")
println("server ready")
}
}
It defines an easy
exchangeMessage
method and a starting main method. Spring takes care of the rest, as shown in the server_config.xml
file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang-2.0.xsd">
<lang:groovy id="easyServer" script-source="classpath:EasyServerImpl.groovy"/>
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- does not necessarily have to be the same name as the bean to be exported -->
<property name="serviceName" value="easyServerService"/>
<property name="service" ref="easyServer"/>
<property name="serviceInterface" value="EasyServer"/>
<!-- defaults to 1099 -->
<property name="registryPort" value="1199"/>
</bean>
</beans>
For running and compiling the server, simply ensure that these are in the CLASSPATH:
SPRING_HOME/dist/spring.jar
SPRING_HOME/lib/jakarta-commons/commons-logging.jar
Then it is up and away:
$ java EasyServerImpl
[ Spring magic dust snipped ]
server ready
The client is even simpler:
import org.springframework.context.support.ClassPathXmlApplicationContext
class EasyClient {
private EasyServer easyServer
void setEasyServer(EasyServer easyServer) { this.easyServer = easyServer }
Message exchangeMessage(String user) {
return easyServer.exchangeMessage(user)
}
static void main(String[] args) {
def context =
new ClassPathXmlApplicationContext("client_config.xml")
def easyClient = context.getBean("easyClient")
String output =
easyClient.exchangeMessage("CtJ Reader").getMessage()
println( output )
}
}
The client-side configuration XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang-2.0.xsd">
<lang:groovy id="easyClient" script-source="classpath:EasyClient.groovy">
<lang:property name="easyServer" ref="easyServer"/>
</lang:groovy>
<bean id="easyServer" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://localhost:1199/easyServerService"/>
<property name="serviceInterface" value="EasyServer"/>
</bean>
</beans>
Using the same
CLASSPATH
, but with a separate window, we establish contact like so:
$ java EasyClient
[ Spring noise snipped ]
CtJ Reader, Spring with Groovy rocks!
Cool stuff...
4 comments:
There's a couple of useful Groovy techniques you can use to make the code much shorter, hence more readable.
First, Groovy supports "bean-scripting" declarations. Declaring an unscoped class member (say 'message') is the equivalent in Java of declaring a private member 'member' and public accessors 'getMember' and 'setMember'. Outside of the class, 'obj.message' invokes the appropriate accessor.
Second, Groovy automatically imports all the classes in java.io, java.util, and java.net, so you don't need these import statements.
So your Message class can collapse to 4 lines:
class Message implements Serializable {
final String message
Message(String arg ) { message = arg }
}
(The 'final' keyword results in 'getMessage' but not 'setMessage' being defined.)
This 'bean scripting' facility gets increasingly important as the number of properties in a class grows: it eliminates a lot of boilerplate.
If you remove the final constraint, it could be more tiny:
class Message implements Serializable {
String message
}
An so, create message objects using:
Message message = new Message(message:'Hi, I am a message')
At "exchangeMessage" server method, you could use GStrings and autoreturn and dynamic type for parameter:
Message exchangeMessage(user) {
new Message("$user, Spring with Groovy rocks!")
}
Nice post. Kind Regards
Thanks for the comments, Randy and Marcos.... good stuff.
I did try leaving off the getter but something was strange. I almost mentioned it in a draft but removed it for brevity. I knew you Groovites would spot it :-)
Big +1 on auto import of the common java packages.... good to know
Groovy allows for code to be written as scripts. To leverage this, I would remove EasyServerImpl.main(), rename EasyServer* to EasyService*, and create RunEasyServer.groovy whose contents would look like this:
new org.springframework.context.support.ClassPathXmlApplicationContext('server_config.xml')
println('server ready')
Then I simply type the following in the terminal:
groovy RunEasyServer.groovy
Post a Comment