2015-02-02 19 views
6

Sto provando a verificare se l'annotazione @Async di Spring funziona come previsto sul mio progetto. Ma non è così.test di primavera metodo @sync

ho questa prova:

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

    } 
} 

Come potete vedere sto stampare il nome del thread corrente. La classe che contiene il metodo @Async è:

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

} 

Qui sto stampare il nome del thread corrente all'interno del metodo @Async, e il nome è lo stesso di prima, principale. Quindi non funziona.

Il GlobalConfiguration è:

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

E dentro uno dei pacchetti specificati ha il fagiolo activityExecutor:

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

Quello che sto facendo di sbagliato?

+0

Per mia curiosità, 'MessageBusUtil.addQueue' ha fatto ciò che ho assunto? –

risposta

3

Ingannevole.

Il comportamento asincrono viene aggiunto tramite il proxy.

Spring fornisce un proxy che avvolge l'oggetto reale ed esegue l'invocazione effettiva in un thread separato.

Sembra qualcosa di simile (tranne la maggior parte di questo è fatto in modo dinamico con CGLIB o JDK deleghe e gestori di primavera)

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

Ora, in un mondo in primavera, si avrebbe un riferimento all'oggetto proxy , non allo ActivityMessageListener. Questo è

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

restituirebbe un riferimento allo ProxyListener. Quindi, tramite il polimorfismo, invocando doReceive si passerebbe al metodo overriden Proxy#doReceive che invocerebbe ActivityMessageListener#doReceive tramite delega e si otterrebbe il comportamento asincrono.

Tuttavia, sei in un mondo di mezza primavera.

Qui

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

il riferimento this è effettivamente riferisce al reale ActivityMessageListener, non al proxy. Così, quando, presumibilmente, si invia il messaggio sul bus qui

MessageBusUtil.sendMessage(MKTDestinationNames.ACTIVITY_REGISTRY,  message); 

si sta inviando all'oggetto reale, che non ha il comportamento asincrono proxy.

La soluzione completa di Spring sarebbe quella di avere il MessabeBus (e/o la sua coda) be bean Spring in cui è possibile iniettare completamente i bean process (proxy, inizializzati, inizializzati).


In realtà, dal momento che i proxy CGLIB sono in realtà solo sottoclassi di tue tipi, in modo che il ProxyListener sopra sarebbe in realtà si è anche aggiungere al bus in quanto il costruttore super sarebbe invocata. Sembrerebbe che solo uno MessageListener possa registrarsi con una chiave, come MKTDestinationNames.ACTIVITY_REGISTRY. Se questo non è il caso, dovresti mostrare più di quel codice per spiegazioni.


nel test, se lo fai

activityMessageListener.doReceive(message); 

si dovrebbe vedere che il comportamento asincrono in quanto activityMessageListener dovrebbe contenere un riferimento al proxy.