2015-02-02 21 views
6

Ich versuche zu testen, ob @Async Annotation des Frühlings funktioniert wie erwartet auf meinem Projekt. Aber das tut es nicht.Federtest @async Methode

Ich habe diesen Test:

@RunWith(SpringJUnit4ClassRunner.class) 
    @ContextConfiguration(classes = GlobalConfiguration.class) 
    public class ActivityMessageListenerTest { 

    @Autowired 
    private ActivityMessageListener activityMessageListener; 

    private Long USER_ID = 1l; 
    private Long COMPANY_ID = 2l; 
    private Date DATE = new Date(10000000); 
    private String CLASSNAME = "className"; 
    private Long CLASSPK = 14l; 
    private Integer TYPE = 22; 
    private String EXTRA_DATA = "extra"; 
    private Long RECIVED_USER_ID = 99l; 

    @Before 
    public void setup() throws Exception { 
    } 

    @Test 
    public void testDoReceiveWithException() throws Exception { 
     System.out.println("Current thread " +  Thread.currentThread().getName()); 
     Map<String, Object> values = new HashMap(); 
     values.put(ActivityMessageListener.PARAM_USER_ID, USER_ID); 
     values.put(ActivityMessageListener.PARAM_COMPANY_ID, COMPANY_ID); 
     values.put(ActivityMessageListener.PARAM_CREATE_DATE, DATE); 
     values.put(ActivityMessageListener.PARAM_CLASS_NAME, CLASSNAME); 
     values.put(ActivityMessageListener.PARAM_CLASS_PK, CLASSPK); 
     values.put(ActivityMessageListener.PARAM_TYPE, TYPE); 
     values.put(ActivityMessageListener.PARAM_EXTRA_DATA, EXTRA_DATA); 
     values.put(ActivityMessageListener.PARAM_RECEIVED_USER_ID, RECIVED_USER_ID); 

     Message message = new Message(); 
     message.setValues(values); 
     MessageBusUtil.sendMessage(MKTDestinationNames.ACTIVITY_REGISTRY,  message); 

    } 
} 

Wie Sie ich drucke den Namen des aktuellen Threads sehen. Die Klasse der @Async Methode enthält, ist:

public class ActivityMessageListener extends BaseMessageListener { 

    public static final String PARAM_USER_ID    = "userId"; 
    public static final String PARAM_COMPANY_ID    = "companyId"; 
    public static final String PARAM_CREATE_DATE   = "createDate"; 
    public static final String PARAM_CLASS_NAME    = "className"; 
    public static final String PARAM_CLASS_PK    = "classPK"; 
    public static final String PARAM_TYPE     = "type"; 
    public static final String PARAM_EXTRA_DATA    = "extraData"; 
    public static final String PARAM_RECEIVED_USER_ID  = "receiverUserId"; 

    public ActivityMessageListener() { 
     MessageBusUtil.addQueue(MKTDestinationNames.ACTIVITY_REGISTRY, this); 
    } 

    @Override 
    @Async(value = "activityExecutor") 
    public void doReceive(Message message) throws Exception { 

     System.out.println("Current " + Thread.currentThread().getName()); 

     if (1> 0) 
      throw new RuntimeException("lalal"); 
     Map<String, Object> parameters = message.getValues(); 
     Long userId      = (Long)parameters.get(ActivityMessageListener.PARAM_USER_ID); 
     Long companyId     = (Long)parameters.get(ActivityMessageListener.PARAM_COMPANY_ID); 
     Date createDate     = (Date)parameters.get(ActivityMessageListener.PARAM_CREATE_DATE); 
     String className    = (String)parameters.get(ActivityMessageListener.PARAM_CLASS_NAME); 
     Long classPK     = (Long)parameters.get(ActivityMessageListener.PARAM_CLASS_PK); 
     Integer type     = (Integer)parameters.get(ActivityMessageListener.PARAM_TYPE); 
     String extraData    = (String)parameters.get(ActivityMessageListener.PARAM_EXTRA_DATA); 
     Long receiverUserId    = (Long)parameters.get(ActivityMessageListener.PARAM_RECEIVED_USER_ID); 
     ActivityLocalServiceUtil.addActivity(userId, companyId, createDate, className, classPK, type, extraData, receiverUserId); 
    } 

} 

Hier I Innenseite des @Async Verfahrens der Namen des aktuellen Threads bin Druck, und der Name ist das gleiche wie zuvor, main. Es funktioniert also nicht.

Die GlobalConfiguration ist:

@Configuration 
@EnableAspectJAutoProxy 
@EnableTransactionManagement 
@ComponentScan({ 
     "com.shn.configurations", 
...some packages... 
}) 
public class GlobalConfiguration {...} 

Und in einem der angegebenen Pakete die activityExecutor Bohne hat:

@Configuration 
@EnableAsync(proxyTargetClass = true) 
public class ExecutorConfiguration { 

    @Bean 
    public ActivityMessageListener activityMessageListener() { 
     return new ActivityMessageListener(); 
    } 

    @Bean 
    public TaskExecutor activityExecutor() 
    { 
     ThreadPoolTaskExecutor threadPoolTaskExecutor = 
     new ThreadPoolTaskExecutor(); 
     threadPoolTaskExecutor.setCorePoolSize(10); 
     threadPoolTaskExecutor.setMaxPoolSize(10); 
     threadPoolTaskExecutor.setQueueCapacity(100); 

     return threadPoolTaskExecutor; 
    } 
} 

Was mache ich falsch?

+0

Für meine eigene Neugierde, hat 'MessageBusUtil.addQueue' getan, was ich annahm? –

Antwort

3

Tricky.

Asynchrones Verhalten wird durch Proxying hinzugefügt.

Spring bietet Ihnen einen Proxy, der das tatsächliche Objekt umschließt und den tatsächlichen Aufruf in einem separaten Thread ausführt.

Es sieht ungefähr so ​​aus (außer den meisten davon ist dynamisch mit CGLIB oder JDK-Proxies und Spring-Handler getan)

class ProxyListener extends ActivityMessageListener { 
    private ActivityMessageListener real; 
    public ProxyListener(ActivityMessageListener real) { 
     this.real = real; 
    } 
    TaskExecutor executor; // injected 
    @Override 
    public void doReceive(Message message) throws Exception { 
     executor.submit(() -> real.doReceive(message)); // in another thread 
    } 
} 

ActivityMessageListener real = new ActivityMessageListener(); 
ProxyListener proxy = new ProxyListener(real); 

, jetzt in einer Frühlings Welt, würden Sie einen Verweis auf das proxy bezwecken , nicht an die ActivityMessageListener. Das ist

ActivityMessageListener proxy = applicationContext.getBean(ActivityMessageListener.class); 

einen Verweis auf die ProxyListener zurückkehren würde. Dann würde der Aufruf von doReceive durch Polymorphie zur überschriebenen Proxy#doReceive-Methode gehen, die ActivityMessageListener#doReceive durch Delegierung aufrufen würde, und Sie würden Ihr asynchrones Verhalten erhalten.

Sie sind jedoch in einer halben Frühlingswelt.

Hier

public ActivityMessageListener() { 
    MessageBusUtil.addQueue(MKTDestinationNames.ACTIVITY_REGISTRY, this); 
} 

die Referenz this bezieht sich tatsächlich auf die reale ActivityMessageListener, nicht an den Proxy. Wenn also vermutlich Sie Ihre Nachricht auf dem Bus senden hier

MessageBusUtil.sendMessage(MKTDestinationNames.ACTIVITY_REGISTRY,  message); 

sind Sie es an das reale Objekt zu senden, die nicht den Proxy-asynchrones Verhalten hat.

Die vollständige Spring-Lösung wäre, dass der MessabeBus (und/oder seine Warteschlange) Spring Beans wäre, in die Sie die vollständig prozedurierten (auto- vired, initialisierten) Beans injizieren können.


In der Realität, da CGLIB Proxies wirklich nur Unterklassen Ihrer Typen sind, also eigentlich auch die ProxyListener oben würde sich, da die geltend gemacht werden würde super Konstruktor mit dem Bus hinzuzufügen. Es scheint jedoch, dass nur ein MessageListener sich mit einem Schlüssel registrieren kann, wie MKTDestinationNames.ACTIVITY_REGISTRY. Wenn dies nicht der Fall ist, müssten Sie mehr von diesem Code zur Erklärung zeigen.


In Ihrem Test, wenn Sie das tun

activityMessageListener.doReceive(message); 

Sie, dass die asynchrones Verhalten sehen sollte, da activityMessageListener einen Verweis auf den Proxy halten sollte.