Announcement Announcement Module
Collapse
No announcement yet.
Using multiple aspects' annotations at the same time requires annotating interfaces? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Using multiple aspects' annotations at the same time requires annotating interfaces?

    I'm using @Transactional to annotate members of a class. I didn't bother to declare an interface for the class because its one of a kind. Spring generates GCLIB proxies. This all works fine.

    I have a need to add an additional annotation of my own to some methods on the same class.
    I used the DefaultAdvisorAutoProxyCreator in my configuration file, and declared an Advisor for my aspect/pointcut/advice. Here are the relevant fragments:

    configuration fragment:
    --
    <bean class="org.springframework.aop.framework.autoproxy .DefaultAdvisorAutoProxyCreator">
    <!-- there's really no need to create interfaces for one-time use classes -->
    <property name="proxyTargetClass" value="true"/>
    </bean>

    <!-- this installs the WritableInterceptor on the appropriate method pointcuts -->
    <bean class="com.netflix.rwsplit.WritableAdvisor"/>
    --

    WritableAdvisor:
    --
    public class WritableAdvisor
    implements PointcutAdvisor
    {
    private final static Pointcut pointcut = Pointcuts.union(
    new AnnotationMatchingPointcut(null, Transactional.class),
    new AnnotationMatchingPointcut(null, Writable.class));

    public Pointcut getPointcut()
    {
    return pointcut;
    }

    private final static Advice advice = new WritableInterceptor();

    public Advice getAdvice()
    {
    return advice;
    }

    public boolean isPerInstance()
    {
    return false;
    }
    }
    --

    However, my advice, WritableInterceptor (implements MethodInterceptor) is never installed.

    I created the pointcut expression above in a small junit test and tried it on my target class. It worked as expected, returning true if either @Transactional, or @Writable appear on a method or the class.

    I blew up the WritableAdvisor with nested classes that log calls to the ClassFilter and MethodMatcher returned by the Pointcut, and saw that while they are being called, they were only being called on MyClass$GCLIBEnhancer or something like that. To me this looks like @Transaction is getting its proxy wrapped around my class, and then that is processed again, but the Pointcut doesn't match what that proxy exposes. Does that sound right?

    Next, I tried creating an interface for my class, then declaring it as implementing that. No change, except that the logging reports the tests are being run against something like $Proxy8, and that also fails the pointcut tests.

    According to the docs, proxies for classes that are implementations of interfaces are created by creating new subclasses of the interfaces (http://static.springsource.org/sprin...e/aop-api.html, sections 7.5.3 et al). So I guessed that if I moved all my annotations from the concrete class to the interface, then when the Pointcut is evaluated on the proxy that was created for @Transactional, it would again pass the Pointcut tests.

    This does appear to be the case: after I moved my annotations from the concrete class to the interface, everything worked as I expected: both @Transactional and my @Writable annotation were having their effects. (I also removed proxyTargetClass="true".)

    However, I'm concerned about this, because according to http://static.springsource.org/sprin...ansaction.html, section 9.5.6, "The Spring team's recommendation is that you only annotate concrete classes with the @Transactional annotation, as opposed to annotating interfaces. You certainly can place the @Transactional annotation on an interface (or an interface method), but this will only work as you would expect it to if you are using interface-based proxies. The fact that annotations are not inherited means that if you are using class-based proxies then the transaction settings will not be recognised by the class-based proxying infrastructure and the object will not be wrapped in a transactional proxy (which would be decidedly bad). So please do take the Spring team's advice and only annotate concrete classes (and the methods of concrete classes) with the @Transactional annotation."

    I can't shake the feeling that I'm doing something wrong because I have seen other references to the ability to Order pieces of Advice from different aspects, yet no mention of any special considerations to make that work in light of the recommendation to only annotate concrete classes.

    Am I using the DefaultAdvisorAutoProxyCreator incorrectly (unfortunately the only example in the docs is for @Transactional, and that drags in a bunch of source attribute stuff I don't need)?

    Is my Pointcut definition wrong?

    Or is it really the case that this is the only way to have two independent aspects active on a single object: to make the object implement to an interface, and annotate the interface?

    Is this going to cause me grief per the recommendation above? I don't understand the comment about "annotations not being inherited," considering I used @Inherited on my annotation.

    Is this a bug in Spring? It seemed like perhaps the Pointcut should refer to the TargetSource class to evaluate, but there's no way to get it inside the ClassFilter or MethodMatcher within the Pointcut.

  • #2
    When posting code I suggest the [ code][/code ] tags.

    Regarding annotations and inheritance I suggest the java specification. Basically annotations on interfaces aren't inherited by implementing classes. In spring there is a hack which also detects annotations on interfaces but it isn't supported normally on the java virtual machine.

    The problem is that you are using 2 different proxy mechanism, which leads to proxying a proxy. I suggest instead of using the tx:namespace to register the TransactionAttributeSourceAdvisor which will then be used by your DefaultAdvisorAutoProxyCreator. Because everything is now proxyied in 1 go instead of 2 goes this should make it work.

    Comment


    • #3
      I did as you suggested, and now the Spring config (fragment) looks like this:
      --
      Code:
          <!-- use this to get various advisors installed -->
          <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
      
          <!-- this installs the WritableInterceptor on the appropriate method pointcuts -->
          <bean class="com.netflix.rwsplit.WritableAdvisor"/>
      
          <!--
           This is used *instead* of <tx:annotation-driven .../> because we need to have multiple advices on
           @Transactional methods, namely the @Writable annotation's advice so that we can control the routing
           data source, and we're getting that from the DefaultAdvisorAutoProxyCreator, and WritableAdvisor..
      
           See http://forum.springsource.org/showthread.php?p=258110#post258110 .
          -->
          <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
            <property name="transactionInterceptor"> <!--  ref="transactionInterceptor" -->
                <bean class="org.springframework.transaction.interceptor.TransactionInterceptor"> <!-- id="transactionInterceptor" -->
                    <property name="transactionManager" ref="transactionManager"/>
                    <property name="transactionAttributeSource">
                        <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
                    </property>
                </bean>
            </property>
          </bean>
      --
      Works like a charm. I was even able to get rid of the useless interface and just proxy the object again. Thank you very much for replying.

      In case it helps anybody else, here's what I'm doing:
      I'm building a web service that uses MySQL replication to scale; I have lots of reads, but very few writes. I need to route the writes to the MySQL master, and the reads to a farm of MySQL slaves fronted by a load balancer. I'm using my own @Writable annotation to indicate that a transaction needs to be routed to the master. The transactionManager above is hooked to a routing data source I implemented based on this article: http://blog.springsource.com/2007/01...ource-routing/ . But my routing data source's selector is set by the presence of my @Writable annotation (handled by a MethodInterceptor installed by my WritableAdvisor above), rather than manually as in that article.

      If I may impose on you a bit further, one thing in your reply puzzled me, and I'd like to understand it better. You mentioned that I am using two different proxy mechanisms. I had come to believe that regardless of how I specified the proxies (i.e, DefaultAdvisorAutoProxyCreator, aop:config/aop:aspect/aopointcut/aop:advice, bean post processors, etc) that underneath it all the same mechanisms were being employed (generate a jdk or cglib proxy), but that they just used more or less pre-existing bits and pieces (such as DefaultPointcutAdvisors and argument expression evaluators, etc). I expected this to result in a proxy chain or a proxy calling a proxy calling my object. If it wasn't, how else could you possibly handle multiple around-advices? I can see how a single proxy would do for all before- or after-advices, because a clever proxy could just call them from a list before and after delegating to the original target. But I don't see how that can work for multiple around-advices. Nor how there is more than one proxying mechanism, as opposed to just multiple ways to specify what or how to proxy. Could you elaborate on that?

      Comment


      • #4
        Thank you for your reply, it really helped.

        I tried what you suggested, and now my xml configuration (fragment) looks like this:
        Code:
            <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
        
            <bean class="com.netflix.rwsplit.WritableAdvisor"/>
        
            <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
              <property name="transactionInterceptor"> <!--  ref="transactionInterceptor" -->
                  <bean class="org.springframework.transaction.interceptor.TransactionInterceptor"> <!-- id="transactionInterceptor" -->
                      <property name="transactionManager" ref="transactionManager"/>
                      <property name="transactionAttributeSource">
                          <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
                      </property>
                  </bean>
              </property>
            </bean>
        
            <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                <property name="dataSource" ref="routingDataSource"/>
            </bean>
        Works like a charm. I do have a question about it though: in the examples like this in the docs, there are references to having to use commons attributes, but there's no reason given as to why. In my original version, I was avoiding this because it wasn't clear what the Attribute is for in the TransactionAttributeSourceAdvisor. I tried mine with and without the commons attributes bean, and it doesn't seem to make a difference. What is that stuff?

        In case it helps anybody else, here's what I'm doing:
        I use MySQL to scale out access to data that has many reads but very few writes. I've written a routing data source based on this article: http://blog.springsource.com/2007/01...ource-routing/ . However, instead of programmatically selecting the source, my @Writable annotation indicates that the MySQL master should be used instead of the slaves. The routing data source chooses between a read-only or read-write source. My around advice, a MethodInterceptor provided by the WritableAdvisor above, sets a ThreadLocal to indicate which to use, similar to the example in the article.

        If I may impose on you further, I didn't understand a comment you made in your answer, but I would like to. You said that I was having problems because I was using two different proxy mechanisms.

        From reading the docs, I had come to believe that regardless of how proxying was specified (e.g., tx:annotation-driven, DefaultAdvisorAutoProxyCreator, or aop:config/aop:aspect/aop:advice), that underneath it all, the same mechanisms are used (either a cglib or a jdk proxy). This is why I had specified things the way I had. Are there really multiple proxying mechanisms (not including AspectJ weaving)?

        Also, you made a reference to doing things with one proxy instead of two. For before and after advice, I can see how a single proxy could always be used; the proxy can run through lists of before and after advices around the call that delegates to the target object. However, for around advice, it seems like there's no choice but to have a delegate chain, leading to two proxies in this case. Is that not correct?

        Comment


        • #5
          Works like a charm. I do have a question about it though: in the examples like this in the docs, there are references to having to use commons attributes, but there's no reason given as to why. In my original version, I was avoiding this because it wasn't clear what the Attribute is for in the TransactionAttributeSourceAdvisor. I tried mine with and without the commons attributes bean, and it doesn't seem to make a difference. What is that stuff?
          Commons-attributes is an apache project which gives you 'annotations' on JDK 1.4. They are embedded in the javadocs of classes. So in your case you don't need that .

          If I may impose on you further, I didn't understand a comment you made in your answer, but I would like to. You said that I was having problems because I was using two different proxy mechanisms.
          Well maybe not 2 proxy mechanism but 2 proxying steps. First the proxy gets created by a the InfrastructureAdvisorAutoProxyCreator which already creates a proxy for the tx:namespace. Next your configured DefaultAdvisorAutorProxyCreated kicks in and it doesn't match anymore because of the proxy created. Basically it tries to proxy a proxy. (There must be an issue regarding this in JIRA somewhere but couldn't find it).

          Also, you made a reference to doing things with one proxy instead of two. For before and after advice, I can see how a single proxy could always be used; the proxy can run through lists of before and after advices around the call that delegates to the target object. However, for around advice, it seems like there's no choice but to have a delegate chain, leading to two proxies in this case. Is that not correct?
          Hmm not sure where I mentioned that (also after re-reading my post). Basically with Spring AOP everything is executed as an around advice and it simply checks what to execute when. Although you don't notice it from a user perspective .

          Could you register a JIRA issue for this, because I really think this is something that needs to be fixed. (There should be some detecting going on for the proxy, if it is, check the original definition/instance and add interceptors to the proxy).

          Edit: Took the liberty to register an issue for you. feel free to comment/add information.
          Last edited by Marten Deinum; Sep 7th, 2009, 02:24 AM.

          Comment


          • #6
            Thanks again. Looks like you beat me to it on the JIRA issue.

            Comment


            • #7
              Hi all,

              first of all, thanks for this thread since it solved a similiar problem for me
              But unfortunately I ran into another problem with this setup.
              It seems to me that because of using the DefaultAdvisorAutoProxyCreator as the only AutoProxyMechanism that now every class that is loaded is considered for annotations.

              I ran into a lot of trouble because my application slowed down extremly until it crashes with an out of memory exception. After doing a heap dump, I saw that I have 12 instances of AnnotationAttributeSource which together consume over 200MB of memory. The property consuming all the space is the attributeCache of class AbstractFallbackTransactionAttributeSource.

              Has anybody here ever seen this behavior or can explain to me why this happens?

              I understand that the InfrastructureAdvisorAutoProxyCreator that is used by the tx:annotation-driven namespace checks each bean if it is of ROLE_INFRASTRUCTURE and I guess that is why this problem does not happen then.

              So, the question is, how can I prevent that the DefaultAdvisorAutoProxyCreator does not cache every class that crosses its way?

              Greetz,
              Patrick

              Comment


              • #8
                So, I found out that the beans that are processed are actually all part of the container definition and for now there is nothing I can change about the numbers of beans that are in the container.

                Is there any way I can avoid every method of every class to be present in the annotation cache with a null-Object?

                Comment

                Working...
                X