I came up with a solution that:
- Allows creation of services that don't depend on any API
- Does not change the code of RemoteServiceServlet
And so the DelegatingFactoryBean [1], [2] was born.
It allows you to create POJO services and wrap GWT around them, having
it RPC method calls forward to your service. It is more a proof of
concept, both in design and implementation, the performance is mediocre
and I have not given any thought to the prototype/singleton
distinction.
The idea behind it is that the DelegatingFactoryBean creates via the
excellent CGLIB in runtime a class which extends the
GWTSpringController, wraps around an object (your service) and then
proxies any number of interfaces (important: your service interface
extending RemoteService). So, in the end, the DelegatingFactoryBean
creates GWTSpringController instances which:
- Extend the RemoteServiceServlet (per default)
- Implement your Service interface
- Wrap around an instance of your service
- Forward calls for the Service interface to your wrapped service
implementation
Things to do:
- Make thorough use of the CGLIB to invoke the wrapped service via
method calls instead of reflection
- Look into singleton/prototype generation
- Infer the implemented interfaces from the wrapped object instead of
supplying them
I am looking forward to your comments
G.
P.S: Please note that you will need a patched GWTSpringController [3],
the one in the 1.0 widget library is unaware of the application context
.
[1].
ackage gwtspring.serverimpl;
import java.lang.reflect.Method;
import java.util.List;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
public class DelegatingFactoryBean extends GWTSpringController
implements FactoryBean, InitializingBean {
private Object target;
private Enhancer enhancer;
private Class objectType;
private Class[] proxyInterfaces;
private Class[] getClasses(Object[] objects)
{
if (objects == null)
return null;
Class[] classes = new Class[objects.length];
for (int i = 0; i < objects.length; i++)
classes[i] = objects[i].getClass();
return classes;
}
private MethodInterceptor callback = new MethodInterceptor() {
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable
{
try
{
Method methodToInvoke =
target.getClass().getMethod(method.getName(), getClasses(args));
return methodToInvoke.invoke(target, args);
}
catch (NoSuchMethodException e)
{
return proxy.invokeSuper(obj, args);
}
}
};
public Object getObject() throws Exception
{
return enhancer.create();
}
public Class getObjectType()
{
return objectType;
}
public boolean isSingleton()
{
return true;
}
public void setTarget(Object parentObject)
{
this.target = parentObject;
}
public void afterPropertiesSet() throws Exception
{
enhancer = new Enhancer();
enhancer.setSuperclass(GWTSpringController.class);
enhancer.setInterfaces(proxyInterfaces);
enhancer.setCallbackType(MethodInterceptor.class);
objectType = enhancer.createClass();
enhancer.setCallback(callback);
}
public void setProxyInterfaces(List<String> proxyInterfaces) throws
Exception
{
this.proxyInterfaces = new Class[proxyInterfaces.size()];
for (int i = 0; i < proxyInterfaces.size(); i++)
{
String interfaceName = proxyInterfaces.get(i);
this.proxyInterfaces[i] = Class.forName(interfaceName);
}
}
}
[2]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/GWTSpring.rpc">TestController</prop>
</props>
</property>
</bean>
<bean id="TestServiceImpl"
class="gwtspring.serverimpl.TestServiceImpl" />
<bean id="GWTSpringController"
class="gwtspring.serverimpl.GWTSpringController" />
<bean id="PropagatingAdvice"
class="gwtspring.serverimpl.PropagatingAdvice">
<property name="wrappedObject" ref="TestServiceImpl"/>
</bean>
<bean id="TestController"
class="gwtspring.serverimpl.DelegatingFactoryBean">
<property name="target" ref="TestServiceImpl"/>
<property name="proxyInterfaces">
<list>
<value>gwtspring.server.TestService</value>
</list>
</property>
</bean>
</beans>
[3] public class GWTSpringController extends
org.gwtwidgets.server.rpc.GWTSpringController implements Controller,
ServletContextAware {
private ServletContext context;
public void setServletContext(ServletContext context)
{
this.context = context;
}
public ServletContext getServletContext()
{
return context;
}
}
My only concern is what you say about this solution's "mediocre"
performance.
To be honest, I'm a newbie with Spring and I haven't used CGLIB before,
but what I understand is that the overhead would be specially in
startup time (while initializing proxyInterfaces), which I consider
completely acceptable for a serious application.
And during runtime, the most time consuming operation would be defining
methodToInvoke. Wouldn't it be better to scan all interface's methods
in setProxyInterfaces and build some sort of cache to have them ready
to use? This would make startup even more expensive, but would improve
runtime performance.
Does it make sense?
Rodrigo.
Allow me to briefly explain why I consider this approach slow. Consider
this example where I have a base class C (= the GWTSpringController),
an Interface I (= your service interface), an class IImpl (your
service) which implements I. And now we need a run-time generated class
CnI which extends C (we want it to have all the abilities of the
GWTSpringController) and I (we also want it to be a service) but
forward all method invocations on I to IImpl:
Class C{
...
}
Interface I{
public Object m1(...);
public Object m2(...);
...
}
class IImpl implements I{
...
}
If you were to hand-code the CnI class you would do smth like:
Class CnI extends C implements I{
private I implementationOfI;
public Object m1(...){
return I.m1(...);
}
public Object m2(...){
return I.m2(...);
}
}
This leads to optimum performance (I suspect the VM will flatten out
the method wrappings anyway) and no CPU cycle is wasted. But look at
the solution I provided: _every_ method invocation goes through a
reflexive lookup. Even if we get around that and store the methods in a
hashmap, that is still a lookup in runtime compared to the hard-wired
invocation made in compile time. I am quite certain that there is a
solution using CGLIB, but quite like yourself I have not used CGLIB for
too long and do not yet know how to solve this.
I favour this approach particularily over having the GWT team open
their RemoteServiceServlet and letting people register their services
with it, because the RemoteServiceServlet will have to lookup the
methods via reflection - while the GWTSpringController can do it in
compile time.
I'm counting on your help here :-)
G.
I would just like to mention that even though a call using reflection
is generally slower than a direct method call, its performance is quite
respectable with recent VMs. For example,Hotspot already speeds up
reflective calls by bytecode generation internally[1].
Regards,
Ismael
[1] http://blogs.sun.com/sundararajan/entry/invokespecialdynamic
That's about the wrong side. The right side is, even like this, it's
fast enough. Considering that atop of a reflective method invocation
we'll still have HTTP traffic, RPC serialisation and the whole business
logic (DB access?) it is indeed negligible.
+ and much important, I forgot that the GWTSpringController (as
extending the RemoteServiceServlet) would access the implemented
interface via reflexion anyway! So, yes, there is no imminent
requirement for fully cglib-ing the controller - but I'd like to do it
just for the fun of it and the psychological impact of knowing that no
pulse of your GHz CPU is wasted :-)
G.
[1] import java.lang.reflect.Method;
public class PerfTest {
public int inc(int a) {
return a + 1;
}
public static void main(String... strings) throws Exception {
for (int warmup = 100; warmup >= 0; warmup--) {
PerfTest ttest = new PerfTest();
Object test = ttest;
long start = System.currentTimeMillis();
int a = 0;
Object args[] = new Object[] { a };
for (int i = 0; i < 100000; i++) {
Method method = test.getClass().getMethod("inc", new Class[] {
int.class });
args[0] = a;
a = (Integer) method.invoke(test, args);
}
long end = System.currentTimeMillis();
if (warmup == 0) {
System.out.println(a);
System.out.println("Reflexive invocation: " + (end - start));
}
start = System.currentTimeMillis();
a = 0;
for (int i = 0; i < 100000; i++) {
a = ttest.inc(a);
}
if (warmup == 0) {
System.out.println(a);
end = System.currentTimeMillis();
System.out.println("Direct invocation: " + (end - start));
}
}
}
}
I never said reflection was faster than direct calls. In fact, I said
it was slower, but with respectable performance. :)
Micro-benchmarks are dangerous. :) Anyway, if you want to make your
benchmark a little bit more realistic even if it's still a
microbenchmark that is not really useful for most scenarios, try this:
1) Change the inc(int) method to inc(String) and return a String. This
way the reflection code doesn't have to do boxing and unboxing.
2) Cache the method object in a map.
With these two changes, I actually get about the same performance for
direct and reflection based calls. But again, this is just a
microbenchmark and running short loops like that several times allows
an optimising VM like HotSpot (I am using the latest jdk6 snapshot) to
optimise it greatly.
> That's about the wrong side. The right side is, even like this, it's
> fast enough. Considering that atop of a reflective method invocation
> we'll still have HTTP traffic, RPC serialisation and the whole business
> logic (DB access?) it is indeed negligible.
>
> + and much important, I forgot that the GWTSpringController (as
> extending the RemoteServiceServlet) would access the implemented
> interface via reflexion anyway!
Exactly.
> So, yes, there is no imminent
> requirement for fully cglib-ing the controller - but I'd like to do it
> just for the fun of it and the psychological impact of knowing that no
> pulse of your GHz CPU is wasted :-)
That is a different story altogether. :)
Regards,
Ismael
I believe your idea is excellent! Could u polish DelegatingFactoryBean
and put it in GwtWidget next release? that would benefit a lot of
developer.
WangQi.
My question is, does the proposed Spring configuration
(http://groups.google.com/group/Google-Web-Toolkit/msg/e8a58aad7a8c867f)
need to be so complicated?
<beans>
<bean class="gwtspring.GWTServiceMapping">
<property name="mappings">
<props>
<prop key="/GWTSpring.rpc" value="TestServiceImpl"/>
</props>
</property>
</bean>
<bean id="TestServiceImpl"
class="gwtspring.serverimpl.TestServiceImpl" />
</beans>
I expect the argument against this idea is that the solution will lose
a lot of it's flexibility. I am just thinking that maybe simplifying
the config is worth the trade-off.
Flames welcome.
Rob
The configuration you propose seems very compact and I am not sure I
completely understand it. If I am correct, you propose that a
HandlerMapping instance intercepts the HTTPRequest, deserialises RPC
and invokes based on this the correct method of the TestServiceImpl
bean... interesting! It seems indeed to be the simplest possible
configuration.
I am however somewhat concerned over the bindings of these things:
Currently the RemoteServiceServlet expects a descendant of it self to
implement the service interface (it looks that up via reflection on
'this'). Thus, unless we want to be really creative, the outcome has
always to be a class that :
a) extends RemoteServiceServlet
b) implements an interface extending the RemoteService interface
c) and, since this whole discussion is about using POJOs, delegates the
interface methods to a (wrapped or cglib enhanced) instance of your
service.
Somewhere hidden in the mapper we'd need a RemoteServiceServlet
instance which we must 'merge' with the TestServiceImpl (we provide it
as a mapping in runtime thus the 'merging' takes place in runtime), so
again we can't avoid cglib-style class enhancing.
I will look into the conventions of handler mappings, but if you're
right, that will lead to a vastly improved configuration.
Regards
G.
George, I think you got the basic idea. The only thing I was trying
to infer was ease of configuration. The less steps I need to remember
when configuring a new service, the better.
Rob
Following your suggestions I implemented the GWTHandler which maps URLs
to RemoteService implementations. The novelty lies in that from now on
you do not have to extends either the RemoteServiceServlet nor the
GWTSpringController yourself - that is done by the GWTHandler through
the CGLIB transparently and in runtime so that you can write plain POJO
services.
The source code is here [1]
The source code with dependencies (spring, cglib, gwt) is here [2]
The usage is as simple [3] as suggested by Robert. Please note that I
consider this a functional draft with several remaining issues such as:
- Providing correct setup for the GWTSpringController part (servlet
context)
- Ensuring correct interface lookup via reflection (cases like method
visibility, overloading, interface inheritance etc)
- Have the GWTHandler implement only the service interfaces extending
RemoteService for security
- Implementing delegate's interfaces via bytecode instrumentation
rather than dynamic proxying
Also, unless you get there before me, I plan on writing a short example
article on how to use it.
Best wishes for the weekend ahead
G.
[1] http://g.georgovassilis.googlepages.com/GWTHandler.nolib.tar.gz
[2] http://g.georgovassilis.googlepages.com/GWTHandler.tar.gz
[3] Usage example:
<bean id="urlMapping"
class="org.gwtwidgets.server.rpc.GWTHandler">
<property name="mapping">
<map>
<entry key="/GWTSpring/add.rpc" value-ref="ServiceAdd"/>
<entry key="/GWTSpring/sub.rpc" value-ref="ServiceSub"/>
</map>
</property>
</bean>
<bean id="ServiceAdd" class="gwtspring.serverimpl.ServiceAddImpl"/>
<bean id="ServiceSub" class="gwtspring.serverimpl.ServiceSubImpl"/>
I try it and it works perfectly.
WangQi
A somewhat more configurable, better documented and more performant
version will be included is announced [1] for the next gwt-widget
library, I hope to be able to write a short introduction blog about it
though it's usage is indeed very simple.
Best regards
G.
Thanks for your contribution.
I wrote a short introduction & user manual for the GWTHandler included
with the GWT Server Library as well as a small demo application [1].
BR
G.
[1] http://g.georgovassilis.googlepages.com/usingthegwthandler
Many thanks,
François
-----Message d'origine-----
De : Google-We...@googlegroups.com
[mailto:Google-We...@googlegroups.com] De la part de georgeuoa
Envoyé : 25 septembre 2006 03:50
À : Google Web Toolkit
Objet : Re: Integrating Spring managed services without extending a base
class