Open GoogleCodeExporter opened 8 years ago
Any chance that this could move up in priority? I've been thinking about adding
gwt-log
to our GWT projects, but the thought of filling the source code with cross
cutting
concerns gives me nightmares :P
Original comment by arthur.k...@gmail.com
on 13 Aug 2009 at 2:25
Original comment by aragos
on 1 Dec 2009 at 1:49
Here is a proposal for AOP:
Add a method in the GinBinder interface, to bind an interceptor giving class
matchers, method matchers and a key:
void bindInterceptor(Matcher<? super Class<?>> classMatcher,
Matcher<? super Method> methodMatcher, Key<? extends MethodInterceptor>
interceptorKey);
The interceptor key implies that the MethodInterceptor must be a GIN managed
instance. MethodInterceptor instances should not be interceptables to avoid
recursive
interceptions and StackOverflowExceptions.
GIN should generate a Proxy class for each intercepted class, and override the
intercepted methods to invoke the interceptors. The overriden methods must
invoke a
MethodInvoker instance. The MethodInvoker is an utility class to produce an
invocation chain with a unique MethodInvocation instance:
public class MethodInvoker {
private final Object target;
private final MethodInterceptor[] interceptors;
public MethodInvoker(Object target, MethodInterceptor... interceptors) { ... }
public <T> T invoke(Object... arguments) { ... }
}
MethodInvoker instances are placed as instance fields in the generated proxy.
The
proxy constructor must receive as arguments the interceptors and super
constructor
parameters, and initialize the invoker fields:
public class Foo {
Foo(String salutation) { ... }
public String sayHello(String name) { ... }
}
public class proxy_Foo extends Foo {
private final MethodInvoker sayHello_invoker;
public proxy_Foo(MethodInterceptor interceptor_1, MethodInterceptor interceptor_2,
String name) {
super(name);
sayHello_invoker = new MethodInvoker(interceptor_1, interceptor_2, new
MethodInterceptor() {
public Object invoke(MethodInvocation invocation) throws Throwable {
return Foo.super.sayHello((String) invocation.getArguments()[0]);
}
});
}
@Override
public String sayHello(String name) {
return sayHello_invoker.invoke(name);
}
}
This pattern could be optimized when there is only one interceptor intercepting
the
method. A CallProxyConstructorBinding, a specialized CreatorBinding, adds
instantiations for interceptable classes.
There is an issue with intercepted classes with zero arg constructors because
they
can't be instantiated with GWT.create() done that their interceptors are
required in
the constructor argument list. This will break the assumption that GIN is
invoking
generators for zero arg constructors.
Open Questions:
1) Should the ServiceAsync's be interceptable? Done the fact that ServiceAsync are
interfaces, they will be exclude by the contract of Guice to only intercept
instantiable implementations, but a main interest to bring AOP to GWT is to
manage
remote interaction. We could ignore the Guice contract here and produce
wrappers for
ServiceAsyncs:
interface FooAsync {
void sayHello(String name, AsyncCallback<String> callback);
}
class proxy_FooAsync implements FooAsync {
private final MethodInvoker sayHello_invoker;
public proxy_FooAsync(MethodInterceptor interceptor1, MethodInterceptor
interceptor2) {
final FooAsync wrapped = GWT.create(FooService.class);
sayHello_invoker = new MethodInvoker(interceptor1, interceptor2, new
MethodInterceptor() {
public Object invoke(MethodInvocation invocation) throws Throwable {
wrapped.sayHello((String) invocation.getArguments()[0], (AsyncCallback)
invocation.getArguments()[1]);
return null;
}
};
}
public void sayHello(String name, AsyncCallback callback) {
sayHello_invoker.invoke(name, callback);
}
}
2) Should java.lang.Method be instantiated? The java.lang.Method class could be
softly emulated to compile Matchers and MethodInvocation implementations
without
requiring instantiations. But for mostly AOP applications, a method reference
is
required. In our company project we want support for cache and logging, and it
can't
be possible without Method instances.
3) Interception of MethodInterceptor instances should be allowed? We could allow
interception for non bound MethodInterceptor, without raising StackOverflow
issues.
4) Interception of GinModules should be allowed? Given that GinModules are managed
as GIN instances they could be accidentally intercepted.
We have developed a proof of concept patch with the proposal exposed here
without
solve the open questions. I will soon commit the patch.
Original comment by andres.a...@gmail.com
on 11 Mar 2010 at 4:47
Thanks for looking into this!
My main concern with AOP is its heavy reliance on reflection at runtime. We
can't
offer any introspection on the code during application runtime and I think this
restriction will make most traditional AOP approaches useless. Providing
"Method"
instances for example won't work - there is no java class information left once
the
GWT compiler has done its work.
That doesn't mean that we can't use AOP, we probably just need another
approach. One
option that I could see working is to generate (custom) interceptor code at
compile
time, i.e. offer the possibility to run "interceptor generators". Gin would
then wire
the code produced by these interceptor generators into the object creation
process.
An example:
// Implemented by Gin. Note that there is no "getMethod" call.
class GinMethodInvocation {
Object[] getArguments() {...};
Object getThis() {...};
Object proceed() {...};
}
// Implementation generated by developer's generator.
interface GinMethodInterceptor {
Object invoke(GinMethodInvocation invocation);
}
interface InterceptorGenerator {
// Returns name of generated class implementing GinMethodInterceptor.
String generate(TreeLogger logger, GeneratorContext ctx, Method intercepted)
throws UnableToCompleteException;
}
void bindInterceptor(Matcher<? super Class<?>> classMatcher,
Matcher<? super Method> methodMatcher,
InterceptorGenerator generator);
For class Foo, Gin would generate the following:
class proxy_Foo extends Foo {
proxy_Foo(String p1) {
super(p1);
}
public String sayHello(String name) {
GinMethodInvocation invocation = new GinMethodInvocation(
GWT.create("GeneratedInterceptorOne.class"),
GWT.create("GeneratedInterceptorTwo.class"),
new GinMethodInterceptor() {
public Object invoke(GinMethodInvocation invocation) {
return proxy_Foo.super.sayHello(name);
}
});
return invocation.proceed();
}
}
================
Your questions are still valid - the "magic" GWT.create calls that Gin performs
don't
go well with proxies. I think I'd be fine with your special solution for async
services. Since it isn't generally applicable to all proxies we might have to
give
developers the choice: GWT.create magic or AOP but not both.
I also would say we don't allow interception on modules and interceptors - the
former
are not constructed by using dependency injection anyhow and the latter might
also be
created on the fly as in the example above.
Original comment by aragos
on 20 Mar 2010 at 12:06
Hi Peter. I understand that AOP needs heavy use of reflection, but I think
Guice
Matchers performs most of the reflective work. Could a MethodInfo class provide
the
reflective information?
Original comment by andres.a...@gmail.com
on 29 Mar 2010 at 12:56
We could provide a custom MethodInfo class that holds some static information
about a
method. There are two caveats:
We can't just return class objects for the parameters, enclosing class etc.
because
those won't be available at runtime. The AOP code will have to look very
different
from what it usually is like, not inspecting any java properties but potentially
doing a lot of string matching.
I'm also not sure how efficient (especially in terms of code-size) that would
be -
we'd have to have a lot of method information stored, depending on the number of
methods you'd be able to intercept.
I still think that running AOP on a generator model might be useful, most
importantly
it gives the GWT compiler the opportunity to optimize the AOP code across the
project
(which would be more in line with Gin's approach to replacing reflection). This
also
gives the AOP code the advantage of being able to use any reflection necessary
to
perform its function.
Original comment by aragos
on 30 Mar 2010 at 10:28
You're Right. My concern about generator solution, is how to inject the
interceptors
with GIN managed instances, i. e. Guice allows interceptor injection by mean of
requestInjection(interceptor).
Original comment by andres.a...@gmail.com
on 30 Mar 2010 at 10:41
Yeah, if you use a generator based solution you won't be able to do anything at
Runtime, but as aragos points out, a generator based approach would be more
efficient
and would allow the compiler to do all its code optimization magic.
Original comment by arthur.k...@gmail.com
on 31 Mar 2010 at 1:22
Well, you can still do stuff at runtime: The generator you write is allowed to
generate anything. It could for instance implement the approach with
MethodInfo. :)
Allowing developers to use generators will give them a lot more flexibility
though.
Original comment by aragos
on 31 Mar 2010 at 1:25
Well yeah, but the functionality would be set at compile time :P. But AOP
support in any form would be pretty
killer :)
Original comment by arthur.k...@gmail.com
on 31 Mar 2010 at 2:05
What do you think about managing the InterceptorGenerator as an internal GIN
instance,
to access NameGenerator, SourceWriterUtil, MemberCollector, etc.?
Original comment by andres.a...@gmail.com
on 5 Apr 2010 at 5:01
Sure, that wouldn't be a problem. We can create a nice small generator util API
for this.
Original comment by aragos
on 5 Apr 2010 at 6:10
What I can't figure is how to bind managed instances to generated interceptors.
Guice
solves interceptor injection by means of requestInjection(interceptor). How we
can
inject generated interceptors?
Original comment by andres.a...@gmail.com
on 5 Apr 2010 at 7:03
I suggest we create a gin-specific binding that takes the matchers and generator
class (instead of matchers and interceptor instance) as arguments. We then
process
all such bindings in the gin generator, invoking the interceptor generators and
calling their generated code from the code generated by gin.
Original comment by aragos
on 6 Apr 2010 at 12:49
I have some questions about your proposal:
1) Should bindInterceptor() bind InterceptorGenerator instances or classes?
I.e.:
a) binder.bindInterceptor(Matchers.any(), Matchers.any(), new
MyInterceptorGenerator())
b) binder.bindInterceptor(Matchers.any(), Matchers.any(),
MyInterceptorGenerator.class)
I like alternative 'a'.
2) Should interceptor instance be scoped as Eager Singleton, Singleton, or
Default? I
think Eager Singleton fits with Guice semantics.
3) Super source issues. bindInterceptor() requires sources for
java.lang.Method,
InterceptorGenerator, and InterceptorGenerator subclass. How can we deal with
that?
4) Interceptor injection. Suposse an interceptor instance requiring a bound
instance,
for example:
class MyGeneratedInterceptor implements GinInterceptor {
@Inject
Provider<EventBus> eventBusProvider;
public Object invoke(GinMethodInvocation invocation) {
InterceptionEvent.fire(eventBusProvider.get());
return invocation.proceed();
}
}
How can we solve Provider<EventBus> injection if MyGeneratedInterceptor
instanciation
is performed by GWT.create() ?
Original comment by andres.a...@gmail.com
on 6 Apr 2010 at 11:14
Is there any new on this topic?
Original comment by andres.a...@gmail.com
on 12 Apr 2010 at 4:13
Sorry for the delay, I'm quite busy with work these days. Here are some answers:
I have some questions about your proposal:
> 1) Should bindInterceptor() bind InterceptorGenerator instances or classes?
I.e.:
>
> a) binder.bindInterceptor(Matchers.any(), Matchers.any(), new
MyInterceptorGenerator())
>
> b) binder.bindInterceptor(Matchers.any(), Matchers.any(),
MyInterceptorGenerator.class)
>
> I like alternative 'a'.
This is really an implementation detail. However, if we use b) we can
instantiate the
class ourselves, using Guice to inject it with all kinds of useful utilities (if
requested).
> 2) Should interceptor instance be scoped as Eager Singleton, Singleton, or
Default?
I think Eager Singleton fits with Guice semantics.
Sounds fine to me.
> 3) Super source issues. bindInterceptor() requires sources for
java.lang.Method,
> InterceptorGenerator, and InterceptorGenerator subclass. How can we deal with
that?
We could either emulate Method with super-source or offer Gin-specific
matchers. The
emulation shouldn't be a problem since configure() is only run at compile time
when
we actually have all the necessary code available. Not sure which is more
efficient.
> 4) Interceptor injection. Suposse an interceptor instance requiring a bound
instance, for example:
>
> class MyGeneratedInterceptor implements GinInterceptor {
>
> @Inject
> Provider<EventBus> eventBusProvider;
>
> public Object invoke(GinMethodInvocation invocation) {
> InterceptionEvent.fire(eventBusProvider.get());
> return invocation.proceed();
> }
> }
>
> How can we solve Provider<EventBus> injection if MyGeneratedInterceptor
instanciation is performed by GWT.create() ?
This is indeed a problem and somewhat related to issue #95. Solving it will be
complicated - in this case we could introduce a mechanism by which the
generator lets
us know what the generated class requires but that's a bad hack. Let's solve it
properly in the other issue and then use the solution here.
Original comment by aragos
on 12 Apr 2010 at 9:13
I'm interesting in seeing andres' patch. Would it be possible to have SVN
branch for this AOP proposal?
Original comment by dusan.ma...@gmail.com
on 28 Apr 2010 at 5:49
Original issue reported on code.google.com by
aragos
on 26 May 2009 at 2:41