Guice: AOP classes don't get the annotations of the superclass

Created on 7 Jul 2014  路  17Comments  路  Source: google/guice

_From anthony.muller on May 30, 2008 05:41:03_

@ME :
I'm quite suprised... I annoted a method @MyAnnotation, in order to
intercept calls of this method. My problem is when I instanciate an object
which has a such annotation using guice. when i search the annoted method
through reflection, and i call isAnnotationPresent(MyAnnotation.class),
this method returns false!

@jesse@swank.ca: Under the hood, Guice creates a subclass of TotoImpl
in order to perform method interception.

For the most part, this is completely transparent to
the developer, but there's a few consequences. getClass()
doesn't return TotoImpl.class. This constraints how your
equals() method must be implemented.

I also expect Serializable won't interoperate with
method interception.

The workaround in this case is to use TotoImpl.class
rather than toto.getClass() to introspect on your
type.

_Original issue: http://code.google.com/p/google-guice/issues/detail?id=201_

Component-AOP Priority-Medium bug imported

Most helpful comment

Yeah I'm also a little shocked that this has gone on for 6 years now... I'm trying to add AOP to intercept the methods in a Dropwizard service and this totally kills that idea.

All 17 comments

_From anthony.muller on May 30, 2008 02:41:16_

package guice;

import static com.google.inject.matcher.Matchers.annotatedWith;
import static com.google.inject.matcher.Matchers.any;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.ImplementedBy;
import com.google.inject.Inject;
import com.google.inject.Injector;

public class AnnotationTest {

       @Target(ElementType.METHOD)
       @Retention(RetentionPolicy.RUNTIME)
       @interface MyAnno {

       }

       @ImplementedBy(TotoImpl.class)
       interface Toto {
               String getBidule();

               void setBidule(String bidule);
       }

       class TotoImpl implements Toto {

               private String bidule;

               @Inject
               public TotoImpl() { }

               public String getBidule() {
                       return bidule;
               }

               @MyAnno
               public void setBidule(String bidule) {
                       this.bidule = bidule;
               }
       }

       static class MyInterceptor implements MethodInterceptor {

               public Object invoke(MethodInvocation mi) throws Throwable {
                       return mi.proceed();
               }

       }

       static class MyModule extends AbstractModule {
               public void configure() {
                       // Comment or uncomment this line to see the problem:
                       // When this binding is uncomment, bellow main method
                       // will print 'false' otherwise 'true'
                       bindInterceptor(any(), annotatedWith(MyAnno.class), new
MyInterceptor());
               }
       }

       public static void main(String[] args) throws Throwable {
               Injector injector = Guice.createInjector(new MyModule());
               Toto toto = injector.getInstance(Toto.class);
               Method setter = toto.getClass().getMethod("setBidule",
                               new Class[] { String.class });
               boolean isPresent = setter.isAnnotationPresent(MyAnno.class);
               System.out.println("Present : " + isPresent);
       }
}

_From limpbizkit on June 03, 2008 02:49:41_

I'd like to know more -- is there a particular use case, tool or library that requires this?

Summary: AOP classes don't get the annotations of the superclass

_From robbie.vanbrabant on June 03, 2008 10:13:19_

As I noted on the mailing list, there's a workaround:

You could use an @Inherited-enabled annotation at the class level, and sneak the
class in like that:

  @Retention(RUNTIME)
  @Target(TYPE)
  @Inherited
  public @interface TheType {
    Class<?> value();
  }

  @TheType(TotoImpl.class)
  static class TotoImpl implements Toto {
  ...
  }

    Method setter =
              toto.getClass().getAnnotation(TheType.class).value()
                    .getMethod("setBidule", new Class[] { String.class });
    boolean isPresent = setter.isAnnotationPresent(MyAnno.class);
    System.out.println("Present : " + isPresent);

I'm also curious to see what the use case is though.

_From anthony.muller on June 03, 2008 12:26:08_

My Use Case is quite simple, I add some annotations in my business classes like
@Property, @GenerateEvent, @Children, @Parent, ... That allows to add extra
information  to make usage easier, to perform some tests to print out reports about
object model structure...

_From limpbizkit on June 03, 2008 17:19:54_

Chris - I'm totally ignorant of cglib's API, so I'll ask you. Would fixing this require a whole lot of code?

Cc: chris.nokleberg

_From chris.nokleberg on June 03, 2008 17:49:45_

It would require code to copy the annotations from the superclass to the subclass. It
would be a significant effort, and would have to be optional as it is not
backwards-compatible, which means the option would have to be propagated into the
Guice API somehow, which would be ugly. It is usually easier to just modify your
application code to look "one level up" when dealing with a proxy. It might be
helpful if Guice had a static method that could tell you if an object is from a proxy
class, or maybe it could just give you back the unproxied class regardless.

_From limpbizkit on June 08, 2008 16:28:43_

See also issue 101 .

_From botteaap on June 29, 2008 07:18:33_

When integrating to existing frameworks annotation inheritence on the subclass is
very useful. For example, the swing application framework uses @Action on methods
that you can bind to a JButton. To make it work with Guice you'll have to explicitly
pass in the class that contains the actions, in stead of "this" as Chris suggests.
This works, but it also implicates that you can't just enable some AOP aspect without
risking to break existing code.

_From gili.tzabari on November 24, 2008 08:19:07_

FYI, Jersey has a new hook that allows me to pass it the unproxied class, it then
examines that class for Jersey-specific annotations.

_From pavel.jbanov on November 24, 2008 09:28:00_

In my particular use-case I had a problem with Struts2 @SkipValidation action method
annotation when I used other interceptors... at the time I was convinced that
@SkipValidation wasn't set on the proxy class method. http://struts.apache.org/2.x/struts2-core/apidocs/org/apache/struts2/interceptor/validation/SkipValidation.html

_From roger.gonzalez on September 16, 2009 18:31:19_

I assume this issue also implies that parameter annotations are not copied to the proxy?

I tried intercepting some service methods that use annotations to define parameter
names for JSON-RPC clients, but the required annotations were all gone.  Looking "up
a level" doesn't seem like a viable option in this case.

_From joshgr on August 21, 2010 17:44:43_

I've been very frustrated by this shortcoming myself: my team is using JPA/Hibernate with Guice. To facilitate a "rich domain model" (ie, domain-logic directly in our entity-classes), I'm using a Hibernate Interceptor to augment Hibernate's Entity-instantiation, and constructing the entity instances with a Guice Injector.  This enables Guice AOP and injection in our Entity instances: we can annotate model-methods with @Transactional (a-la warp-persist), and we can @Inject dependencies into our Entities directly.  Sweet!

This works beautifully, EXCEPT that the standard JPA method annotations for lifecycle-event observers (@PrePersist, @PreUpdate, etc) aren't retained on the Guice-enhanced entity instances.  Surprise! This caused a subtle bug that only appeared when we added a guice MethodInterceptor to an existing entity class...  Suddenly, our entity-lifecycle callbacks stopped being invoked! Thank goodness for integration-tests.

I'm currently building a workaround involving a class-level EntityListener that will look up-the-tree to find and invoke persistence-event annotations, but this is very cumbersome and, more importantly, doesn't feel like it should be my concern: Guice is constructing my objects and providing "magic" AOP, but the abstraction is leaking here.

If it's too expensive to perform method-annotation-preservation by default, it seems appropriate that the framework should聽at least support it upon request, as a binding configuration..?

Annotation-based tools and functionality continue to become more pervasive; I anticipate the impact of this subtle failure may become more common and expensive for guice-enhanced developers.  Plz fix.

_From [email protected] on July 16, 2012 11:13:27_

(No comment was entered for this change.)

Status: Acknowledged
Labels: Component-AOP

_From leitao.otavio on February 16, 2014 09:50:33_

I'm facing the very same problem joshgr described and it's really frustrating. I just add a method annotation and Guice stops "coping" some (others) annotations to the proxy object. Please, fix it!

Yeah I'm also a little shocked that this has gone on for 6 years now... I'm trying to add AOP to intercept the methods in a Dropwizard service and this totally kills that idea.

I think I've fixed this issue by writing a custom method matcher - please see https://codingcraftsman.wordpress.com/2018/11/11/google-guice-aop-with-interface-annotations/ and feedback if it's a solution for you.

Building on @ashleyfrieze's solution, here's a bit of code that looks for the "real" method. Fortunately in our code we were able to substitute the proxied method for the real method in the specific scenario where this was failing for us:

private Method findRealMethod(Method proxiedMethod) {
    try {
        return proxiedMethod.getDeclaringClass().getSuperclass().getMethod(proxiedMethod.getName(), proxiedMethod.getParameterTypes());
    } catch (NoSuchMethodException e) {
        return proxiedMethod;
    }
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

afghl picture afghl  路  5Comments

prasanthgithub picture prasanthgithub  路  14Comments

nathanmerrill picture nathanmerrill  路  5Comments

kamenik picture kamenik  路  5Comments

jhm-ciberman picture jhm-ciberman  路  10Comments