ProceedingJoinPoint.proceed() fails with ClassCastException when run on new thread

1.7k views Asked by At

I'm setting up and @Around aspect to run a method on a background thread, it looks like this

@Aspect
public class ThreadAspect {    
    @Around("call(@Background void *(..))")
    public void runInBackground(final ProceedingJoinPoint jp) throws Throwable {
        new Thread(new JPRunner(jp)).start();
    }

    private static class JPRunner implements Runnable {
        ...
        @Override
        public void run() {
            try {
                jp.proceed();
            } catch (Throwable e) {
                Log.e("TEST", "ThreadAspect", e);
            }
        }
    }
}

I applied the @Background annotation on a method that takes a String but it fails with a ClassCastException on the line with jp.proceed()

E/TEST    (20943): java.lang.ClassCastException: java.lang.String cannot be cast to org.aspectj.lang.JoinPoint

Funnily enough, if I did not use a thread, the call seems to make it through just fine. How can I make it run on a thread?

If it matters, I'm using aspectj on android with this plugin.

EDIT: this is the failing code

// Background.java
package com.github.larvyde.ex.aspect;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Background {}

// MainActivity.java
package com.github.larvyde.ex.aspect;

import android.os.Bundle;
import android.util.Log;

public class MainActivity extends android.support.v7.app.AppCompatActivity {
    @Override
    public void onCreate(Bundle saved) {
        super.onCreate(saved);

        Log.v("TEST", "calling runInBackground");
        runInBackground("run #1");
        Log.v("TEST", "calling runInBackground again");
        runInBackground("run #2");
    }

    @Background
    public void runInBackground(String str) {
        Log.v("TEST", str);
    }
}

// ThreadAspect.java
package com.github.larvyde.ex.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import android.util.Log;

@Aspect
public class ThreadAspect {
    @Around("call(@Background void *(..))")
    public void runInBackground(ProceedingJoinPoint jp) throws Throwable {
        new Thread(new JPRunner(jp)).start();
    }

    private static class JPRunner implements Runnable {
        private final ProceedingJoinPoint jp;

        public JPRunner(ProceedingJoinPoint jp) {
            this.jp = jp;
        }

        @Override
        public void run() {
            try {
                jp.proceed();
            } catch (Throwable e) {
                Log.e("TEST", "ThreadAspect", e);
            }
        }
    }
}

the logs

$ adb logcat | egrep 'TEST|AndroidRuntime'
V/TEST    (21315): calling runInBackground
V/TEST    (21315): calling runInBackground again
E/TEST    (21315): ThreadAspect
E/TEST    (21315): java.lang.ClassCastException: java.lang.String cannot be cast to org.aspectj.lang.JoinPoint
E/TEST    (21315):  at com.github.larvyde.ex.aspect.MainActivity$AjcClosure1.run(MainActivity.java:1)
E/TEST    (21315):  at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:149)
E/TEST    (21315):  at com.github.larvyde.ex.aspect.ThreadAspect$JPRunner.run(ThreadAspect.java:25)
E/TEST    (21315):  at java.lang.Thread.run(Thread.java:818)
E/TEST    (21315): ThreadAspect
E/TEST    (21315): java.lang.ClassCastException: java.lang.String cannot be cast to org.aspectj.lang.JoinPoint
E/TEST    (21315):  at com.github.larvyde.ex.aspect.MainActivity$AjcClosure3.run(MainActivity.java:1)
E/TEST    (21315):  at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:149)
E/TEST    (21315):  at com.github.larvyde.ex.aspect.ThreadAspect$JPRunner.run(ThreadAspect.java:25)
E/TEST    (21315):  at java.lang.Thread.run(Thread.java:818)
1

There are 1 answers

4
kriegaex On

For me this works nicely, maybe your actual code is different from the one you posted here or you omitted som important information. Look at my stand-alone example:

Marker annotation:

package de.scrum_master.app;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Background {}

Driver application:

As you can see, one method is marked by the annotation and another one is not.

package de.scrum_master.app;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class Application {
    static final DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    public static void foreground() {
        System.out.println(
            dateFormat.format(Calendar.getInstance().getTime()) +
            " - synchronous call"
        );
    }

    @Background
    public static void background() {
        System.out.println(
            dateFormat.format(Calendar.getInstance().getTime()) +
            " - asynchronous call"
        );
    }

    public static void main(String[] args) {
        foreground();
        background();
        foreground();
        background();
    }
}

Console output without AspectJ:

18:21:09 - synchronous call
18:21:09 - asynchronous call
18:21:09 - synchronous call
18:21:09 - asynchronous call

As you can see, the methods are logged in the order in which they are called and all with the same timestamp.

Aspect running marked methods asynchronously in their own threads:

This is pretty much your aspect code, but I inserted a waiting time of 2 seconds in order to demonstrate the @Background effect.

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class ThreadAspect {
    @Around("call(@de.scrum_master.app.Background void *(..))")
    public void runInBackground(final ProceedingJoinPoint jp) throws Throwable {
        new Thread(new JPRunner(jp)).start();
    }

    private static class JPRunner implements Runnable {
        ProceedingJoinPoint jp;
        JPRunner(ProceedingJoinPoint jp) { this.jp = jp; }

        @Override public void run() {
            try { Thread.sleep(2000); jp.proceed(); }
            catch (Throwable e) { e.printStackTrace(); }
        }
    }
}

Console output with AspectJ:

18:23:21 - synchronous call
18:23:21 - synchronous call
18:23:23 - asynchronous call
18:23:23 - asynchronous call

As you can see here, both asynchronous calls (backgound tasks) are printed 2 seconds after the synchronous ones.