Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AOP classes don't get the annotations of the superclass #201

Open
gissuebot opened this issue Jul 7, 2014 · 17 comments
Open

AOP classes don't get the annotations of the superclass #201

gissuebot opened this issue Jul 7, 2014 · 17 comments

Comments

@gissuebot
Copy link

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

@gissuebot
Copy link
Author

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);
       }
}

@gissuebot
Copy link
Author

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

@gissuebot
Copy link
Author

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.

@gissuebot
Copy link
Author

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...

@gissuebot
Copy link
Author

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

@gissuebot
Copy link
Author

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.

@gissuebot
Copy link
Author

From limpbizkit on June 08, 2008 16:28:43

See also issue 101 .

@gissuebot
Copy link
Author

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.

@gissuebot
Copy link
Author

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.

@gissuebot
Copy link
Author

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

@gissuebot
Copy link
Author

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.

@gissuebot
Copy link
Author

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.

@gissuebot
Copy link
Author

From cgruber@google.com on July 16, 2012 11:13:27

(No comment was entered for this change.)

Status: Acknowledged
Labels: Component-AOP

@gissuebot
Copy link
Author

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!

@Supagoat
Copy link

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.

@ashleyfrieze
Copy link

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.

@tsgautier
Copy link

tsgautier commented Apr 15, 2020

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;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants