<p dir="auto"><strong><a href="https://jira.spring.io/secure/ViewProfile.jspa?name=cbeams" rel="nofollow">Chris Beams</a></strong> opened <strong><a href="https://jira.spring.io/browse/SPR-3879?redirect=false" rel="nofollow">SPR-3879</a></strong> and commented</p>
<p dir="auto">Disclaimer: This may actually be an AspectJ bug.  I can't tell at this point.</p>
<p dir="auto">The problem behavior I'm seeing is a bit hard to describe in prose; the best bet is to take a look at the code, beans xml and attached log output.</p>
<p dir="auto">Essentially, it looks like AspectJ pointcut expressions aren't recognizing methods that override method declarations that take generic parameters.</p>
<p dir="auto">For example, if I have a class MySet that implements Set&lt;String&gt;, an AspectJ pointcut expression of "execution(* MySet.*(..))" will NOT properly match MySet.add(String str).  Again, this doesn't express very well in prose, so take a look at the code (I've made it as concise as possible) and see what you can make of it.  I do have a workaround, which is detailed at the bottom of the issue.</p>
<p dir="auto">If necessary, I'd be happy to upload a complete testcase.  If there are guidelines for testcase submission, please comment on this issue with a link to that documentation and I'll follow it.</p>
<p dir="auto">Consider the following interfaces and implementation:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="public interface Repository&lt;T&gt; extends Set&lt;T&gt; {}

public interface ProductRepository extends Repository&lt;Product&gt; {
        Set&lt;Product&gt; findByCategory(String category);
}

public class JdoProductRepositoryImpl extends AbstractSet&lt;Product&gt; implements ProductRepository {
    private final PersistenceManagerFactory pmf;

    public JdoProductRepositoryImpl(PersistenceManagerFactory pmf) {
        this.pmf = pmf;
    }

   @Override
   public boolean add(Product product) {
       PersistenceManager pm = pmf.getPersistenceManager();
       boolean isNew = !(JDOHelper.isPersistent(product) || JDOHelper.isDetached(product));
       pm.makePersistent(product);
       return isNew;
   }

    // clear(), size(), iterator(), findByCategory() methods omitted for brevity
}"><pre class="notranslate"><code class="notranslate">public interface Repository&lt;T&gt; extends Set&lt;T&gt; {}

public interface ProductRepository extends Repository&lt;Product&gt; {
        Set&lt;Product&gt; findByCategory(String category);
}

public class JdoProductRepositoryImpl extends AbstractSet&lt;Product&gt; implements ProductRepository {
    private final PersistenceManagerFactory pmf;

    public JdoProductRepositoryImpl(PersistenceManagerFactory pmf) {
        this.pmf = pmf;
    }

   @Override
   public boolean add(Product product) {
       PersistenceManager pm = pmf.getPersistenceManager();
       boolean isNew = !(JDOHelper.isPersistent(product) || JDOHelper.isDetached(product));
       pm.makePersistent(product);
       return isNew;
   }

    // clear(), size(), iterator(), findByCategory() methods omitted for brevity
}
</code></pre></div>
<p dir="auto">beans.xml:</p>
<p dir="auto">&lt;beans&gt;<br>
&lt;bean<br>
id="pmf"<br>
class="org.springframework.orm.jdo.LocalPersistenceManagerFactoryBean"<br>
p:config-location="classpath:jpox.properties"/&gt;</p>
<p dir="auto">&lt;bean id="pmfProxy"<br>
class="org.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy"<br>
p:target-persistence-manager-factory-ref="pmf"<br>
p:allow-create="true"/&gt;</p>
<p dir="auto">&lt;bean id="productRepos" class="demo.product.JdoProductRepositoryImpl"&gt;<br>
&lt;constructor-arg ref="pmfProxy"/&gt;<br>
&lt;/bean&gt;</p>
<p dir="auto">&lt;bean id="txnManager"<br>
class="org.springframework.orm.jdo.JdoTransactionManager"<br>
p:persistence-manager-factory-ref="pmf"/&gt;</p>
<p dir="auto">&lt;tx:advice id="txnAdvice" transaction-manager="txnManager"&gt;<br>
tx:attributes<br>
&lt;tx:method name="clear" propagation="REQUIRED" read-only="true"/&gt;<br>
&lt;tx:method name="size" propagation="REQUIRED" read-only="true"/&gt;<br>
&lt;tx:method name="find*" propagation="REQUIRED" read-only="true"/&gt;<br>
&lt;tx:method name="add" propagation="REQUIRED" read-only="false"/&gt;<br>
&lt;/tx:attributes&gt;<br>
&lt;/tx:advice&gt;</p>
<p dir="auto">aop:config<br>
&lt;aop:advisor advice-ref="txnAdvice" pointcut="execution(* demo.product.Repository+.*(..))"/&gt;<br>
&lt;/aop:config&gt;<br>
&lt;/beans&gt;</p>
<p dir="auto">main method:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="public static void main(String... args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext(&quot;beans.xml&quot;);
    ProductRepository productRepos = (ProductRepository) ctx.getBean(&quot;productRepos&quot;);
    int size;

    productRepos.clear();
    size = productRepos.size();
    logger.info(&quot;size before add: &quot; + size);
    assert size == 0;

    Product product = new Product(&quot;foo&quot;);
    productRepos.add(product); // &lt;-- throws here with the stack trace below

    size = productRepos.size();
    logger.info(&quot;size after add: &quot; + size);
    assert size == 1;

    logger.info(&quot;products with category 'foo': &quot;
                + productRepos.findByCategory(&quot;foo&quot;).size());

    logger.info(&quot;products with category 'bar': &quot;
                + productRepos.findByCategory(&quot;bar&quot;).size());
}"><pre class="notranslate"><code class="notranslate">public static void main(String... args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    ProductRepository productRepos = (ProductRepository) ctx.getBean("productRepos");
    int size;

    productRepos.clear();
    size = productRepos.size();
    logger.info("size before add: " + size);
    assert size == 0;

    Product product = new Product("foo");
    productRepos.add(product); // &lt;-- throws here with the stack trace below

    size = productRepos.size();
    logger.info("size after add: " + size);
    assert size == 1;

    logger.info("products with category 'foo': "
                + productRepos.findByCategory("foo").size());

    logger.info("products with category 'bar': "
                + productRepos.findByCategory("bar").size());
}
</code></pre></div>
<p dir="auto">Stack trace:<br>
Exception in thread "main" org.jpox.jdo.exceptions.TransactionNotActiveException: Transaction is not active. You either need to define a transaction around this, or run your PersistenceManagerFactory with 'NontransactionalRead' and 'NontransactionalWrite' set to 'true'<br>
at org.jpox.AbstractPersistenceManager.assertWritable(AbstractPersistenceManager.java:1959)<br>
at org.jpox.AbstractPersistenceManager.makePersistent(AbstractPersistenceManager.java:607)<br>
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)<br>
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)<br>
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)<br>
at java.lang.reflect.Method.invoke(Method.java:585)<br>
at org.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy$TransactionAwareInvocationHandler.invoke(TransactionAwarePersistenceManagerFactoryProxy.java:212)<br>
at $Proxy2.makePersistent(Unknown Source)<br>
at demo.product.JdoProductRepositoryImpl.add(JdoProductRepositoryImpl.java:38)<br>
at demo.product.JdoProductRepositoryImpl.add(JdoProductRepositoryImpl.java:1)<br>
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)<br>
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)<br>
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)<br>
at java.lang.reflect.Method.invoke(Method.java:585)<br>
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:296)<br>
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:198)<br>
at $Proxy1.add(Unknown Source)<br>
at demo.app.Main.main(Main.java:33)</p>
<p dir="auto">So, as you'll see in the attached log output, a transaction is opened and closed just fine for the calls to clear() and to size(), but not for the call to add(product).  This appears to be because add(product) is not getting advised by AspectJ whatsoever.</p>
<p dir="auto">I have found a simple workaround (that makes this issue feel all the more like a bug to me):</p>
<p dir="auto">If I explicitly declare an add(Product) method in the ProductRepository interface as follows, the call to add(product) works just fine (i.e.: a transaction is opened and closed appropriately):</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="public interface ProductRepository extends Repository&lt;Product&gt; {
        void add(Product product);
        Set&lt;Product&gt; findByCategory(String category);
}"><pre class="notranslate"><code class="notranslate">public interface ProductRepository extends Repository&lt;Product&gt; {
        void add(Product product);
        Set&lt;Product&gt; findByCategory(String category);
}
</code></pre></div>
<p dir="auto">See the log.workaround.txt file for full output when this fix is in place.</p>
<p dir="auto">I've also attached my jpox.properties file for further clarity.</p>
<p dir="auto">Hopefully this is just an error on my part in understanding AspectJ pointcut syntax (or something else trivial).</p>
<p dir="auto">Again, I'd be happy to put together a more complete testcase if that helps, and/or answer any additional questions.</p>
<hr>
<p dir="auto"><strong>Affects:</strong> 2.0.6</p>
<p dir="auto"><strong>Attachments:</strong></p>
<ul dir="auto">
<li><a href="https://jira.spring.io/secure/attachment/12909/jpox.properties" rel="nofollow">jpox.properties</a> (<em>682 bytes</em>)</li>
<li><a href="https://jira.spring.io/secure/attachment/12907/log.txt" rel="nofollow">log.txt</a> (<em>9.08 kB</em>)</li>
<li><a href="https://jira.spring.io/secure/attachment/12908/log.workaround.txt" rel="nofollow">log.workaround.txt</a> (<em>18.78 kB</em>)</li>
</ul>
<p dir="auto"><strong>Issue Links:</strong></p>
<ul dir="auto">
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="398078567" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/8239" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/8239/hovercard" href="https://github.com/spring-projects/spring-framework/issues/8239">#8239</a> Spring AOP doesn't process generics correctly (<em><strong>"duplicates"</strong></em>)</li>
</ul>
<p dir="auto"><strong><a href="https://jira.spring.io/secure/ViewProfile.jspa?name=astefan" rel="nofollow">Andrei Stefan</a></strong> opened <strong><a href="https://jira.spring.io/browse/SPR-3556?redirect=false" rel="nofollow">SPR-3556</a></strong> and commented</p>
<p dir="auto">I made two tests:</p>
<ul dir="auto">
<li>one with AspectJ support;</li>
<li>one with Spring's AspectJ support.</li>
</ul>
<p dir="auto">The tests involve two interfaces: Base&lt;T&gt; and DerivedInterface&lt;T&gt; extends Base&lt;T&gt;.<br>
A Before pointcut matching all methods from Base and DerivedInterface is defined: <code class="notranslate">@Before</code>("execution(* example.Base+.*(..))")<br>
In AspectJ a test creating an instance of a class implementing DerivedInterface: DerivedInterface&lt;String&gt; obj = new DerivedString(); and then calling methods on both interfaces demonstrates advice being applied on both methods.<br>
The same test in Spring getting a bean instance from ApplicationContext like DerivedInterface&lt;String&gt; bean = (DerivedInterface&lt;String&gt;) context.getBean("myBean"); and calling methods on both interfaces, the advice is not applied to methods receiving as parameter the generic type &lt;T&gt;.</p>
<p dir="auto">Methods like method(&lt;T&gt;) don't get the advice applied, but method2() have the advice applied.</p>
<hr>
<p dir="auto"><strong>Affects:</strong> 2.0.5, 2.1 M3</p>
<p dir="auto"><strong>Attachments:</strong></p>
<ul dir="auto">
<li><a href="https://jira.spring.io/secure/attachment/12641/SpringAspectJ.zip" rel="nofollow">SpringAspectJ.zip</a> (<em>1.86 kB</em>)</li>
</ul>
<p dir="auto"><strong>Issue Links:</strong></p>
<ul dir="auto">
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="398079012" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/8309" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/8309/hovercard" href="https://github.com/spring-projects/spring-framework/issues/8309">#8309</a> Spring AOP <code class="notranslate">@Aspect</code> pointcut ignores generic type params and matches to many join points  (<em><strong>"is depended on by"</strong></em>)</li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="398080881" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/8559" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/8559/hovercard" href="https://github.com/spring-projects/spring-framework/issues/8559">#8559</a> AspectJ pointcut expressions fail to match generically parameterized methods (<em><strong>"is duplicated by"</strong></em>)</li>
</ul>