Gili
2008/11/25 Gili <gili.t...@gmail.com>Johan Haleby brought up an interesting approach to proxing classes
without the limitations introduced by ASM/CGLIB such as:
- requiring no-op constructors
- preventing the use of final or private methods
- losing annotations on the original class
I would love to get your opinion on it because frankly if we can get
rid of these limitations I would be *that* much happier :) Take a
look: http://groups.google.com/group/mockito/browse_thread/thread/2a85cace251eb92c
FWIW, I wouldn't call this a proxy per se - this is bytecode manipulation
which requires access to the original bytes (it uses javassist to do this)
and only works on non-interface classes... but you can easily fall back
to using standard proxies for interfaces - downside is you end up with
two approaches to maintain
however I would be wary of calling this a 'silver' bullet because there are
several limitations wrt. bytecode manipulation (security notwithstanding)
- for example, I don't know if javassist works with custom classloaders as
used in EJB containers (and OSGi, etc.) where the original class bytes
may not be available, or where the original class may already be in-use
elsewhere in the JVM
just warning we may be jumping from known issues into the unknown ;)Gili
--
Cheers, Stuart
Hi Stuart,
It is my understanding that all of the difficulties come from proxing
concrete classes, not interfaces (the latter can be proxied quite
easily using the core API).
I think it's well worth using two
different implementations for interfaces and concrete classes so long
as the differences can be abstracted away from end-users and it really
overcomes all the limitations I and you listed (i.e. EJB containers).
Let's ask Johan, perhaps he's already investigated EJB space.
The main thing I like about Johan's approach is that it maintains full
backwards compatibility with the original class. There is no magic.
You are invoking the original constructors and everything works as
expected.
Using Sun's hidden API to construct objects without invoking their
constructors is part of the magic I'd like to do away with.
PS: Sun is beta-testing changes to ClassLoader in Java7 so now would
be a really good time to request enhancements in this space if you've
got any.
Gili
> of concept at the the issue which is tracking this feature request:http://sourceforge.net/tracker/index.php?func=detail&aid=2070600&grou...
On Nov 25, 5:27 am, Esko Luontola <esko.luont...@gmail.com> wrote:
> On Nov 25, 10:29 am, Gili <gili.tzab...@gmail.com> wrote:
>
> > - requiring no-op constructors
>
> I assume that you are referring to the fact a CGLIB proxy calls the
> constructor of its superclass, which means that the class being
> proxied (unless only interfaces are proxied) must have an accessible
> constructor, possibly even a default constructor.
>
> I need to overcome this limitation of requiring an accessible default
> constructor for one project of mine, so I'll be modifying CGLIB so
> that it ignores the superclass's constructors. I have posted a proof
>
> Would this feature be useful for Guice?
There are two cases:
1) A is shared between different webapps. I am not particularly
interested in this use-case but I'm sure you can come up with
workarounds for it too.
2) A is used exclusively by one webapp, but someone else might load it
before Guice. We can solve this quite easily by proxying it eagerly
before anyone else without instantiating any instances.
My only concern is what happens if another framework sits on the same
webapp and also has a custom ClassLoader. Is there such a thing as
ClassLoaders cooperating?
Gili
There are two cases:
Stuart McCulloch wrote:
> my main concern is how you'd handle the situation where someone else is
> using class (A) but Guice wants to "proxy" it by rewriting - the new
> definition
> (A') would need to be loaded into another classloader, but that means that
> class A and class A' are incompatible, and people who expect A wouldn't be
> able to use A'
1) A is shared between different webapps. I am not particularly
interested in this use-case but I'm sure you can come up with
workarounds for it too.
2) A is used exclusively by one webapp, but someone else might load it
before Guice. We can solve this quite easily by proxying it eagerly
before anyone else without instantiating any instances.
My only concern is what happens if another framework sits on the same
webapp and also has a custom ClassLoader. Is there such a thing as
ClassLoaders cooperating?
Gili
Alternatively you could register the Guice classloader eagerly and
ensure that any types you refer to beyond that point would go through it
(by setting it as the Thread context CL for example). Any class injected
by Guice would automatically use the right CL. The only problem would
come from any classes loaded before the registration of the Guice CL.
Though, I suspect you should be able to do this very early on in most cases.
> however, two classes of the same name defined by two different loaders
> are distinct and incompatible (!A.equals(A')) which is the problem I have
> with redefining the class at runtime - as Johan says, it has to go into a
> separate classloader, unless you use an instrumentation agent
I believe instrumentation agent wouldn't work in the context of a web
container since you don't want to affect classes outside your webapp.
> there are also visibility limitations - if I have non-public classes A and B
> in the same package "foo" then they can only see each other if they're
> loaded by the same classloader (delegation doesn't help here)
Agreed, but I assume that if you're going to inject one class in a
package you're likely going to use injection for the rest of them too
(or load them from classes that *have* been injected).
Gili
Alternatively you could register the Guice classloader eagerly and
Stuart McCulloch wrote:
> it's the eager proxying that I'm worried about - could get hairy
>
> for example: how do I tell Guice about the class that should be proxied
> without actually loading that class (as Guice works primarily on types)
ensure that any types you refer to beyond that point would go through it
(by setting it as the Thread context CL for example). Any class injected
by Guice would automatically use the right CL. The only problem would
come from any classes loaded before the registration of the Guice CL.
Though, I suspect you should be able to do this very early on in most cases.
> however, two classes of the same name defined by two different loadersI believe instrumentation agent wouldn't work in the context of a web
> are distinct and incompatible (!A.equals(A')) which is the problem I have
> with redefining the class at runtime - as Johan says, it has to go into a
> separate classloader, unless you use an instrumentation agent
container since you don't want to affect classes outside your webapp.
> there are also visibility limitations - if I have non-public classes A and BAgreed, but I assume that if you're going to inject one class in a
> in the same package "foo" then they can only see each other if they're
> loaded by the same classloader (delegation doesn't help here)
package you're likely going to use injection for the rest of them too
(or load them from classes that *have* been injected).
Gili
I guess I agree with you for the most part. Thing is, for all this talk
about how "it's all fixable" no one actually done so...
I don't recall hearing of a single project that tried to improve the situation.
I might sound silly saying this... but it really ticks me off. I hate using APIs
that force no-op constructors and inappropriate method accessibility on
me. CGLIB acts more like a framework than a library in the sense that it
forces its bad design on you :( Anyway, end of rant. I'll be happy when
I can avoid CGLIB altogether in Guice.
Perhaps I'm mistaken but mabey aspectj would be able to help you here?
AJDT works in the Eclipse environment and thus should work in OSGi as
well
and afaik it would be possible to do delegation without proxying
the classes using this approach? I think Spring uses aspectj to inject
beans to instances that are not part of the Spring life-cycle (you use
the @Configurable annotation). I guess it would be possible to start
transactions and all kinds of stuff this way as well?
I didn't mean that they used aspectj for proxying but rather suggesting that aspectj could be used instead of proxying to achieve the same results.
We discussed earlier that e.g. guice-warp couldn't support final or private methods for starting transacation because it used CGLib, but using a delegating approach instead of using a proxy could solve the problem (which you seem to imply as well if I'm not misstaken). But whether this is applicable to Guice is another question.
I think it would be very cool if Guice could support something similar as Springs AOP support for injection. Actually I'd like to create a spike for this if I get some time over, I've been needing this kind of stuff myself.
/Johan