7

@AsyncSpring のアノテーションがプロジェクトで期待どおりに機能しているかどうかをテストしようとしています。しかし、そうではありません。

私はこのテストを持っています:

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

    }
}

ご覧のとおり、現在のスレッドの名前を出力しています。@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);
    }

}

ここでは、メソッド内の現在のスレッドの名前を出力しています@Async。名前は前と同じ main です。だからうまくいかない。

GlobalConfiguration は次のとおりです。

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

そして、指定されたパッケージの 1 つに activityExecutor Bean があります。

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

私が間違っていることは何ですか?

4

1 に答える 1

3

トリッキー。

プロキシによって非同期動作が追加されます。

Spring は、実際のオブジェクトをラップし、別のスレッドで実際の呼び出しを実行するプロキシを提供します。

これは次のようになります (ただし、ほとんどは CGLIB または JDK プロキシと Spring ハンドラーを使用して動的に行われます)。

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

さて、Spring の世界では、. への参照proxyではなく、オブジェクトへの参照がありますActivityMessageListener。あれは

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

への参照を返しProxyListenerます。次に、ポリモーフィズムを通じて、呼び出しdoReceiveはオーバーライドされたProxy#doReceiveメソッドに移動し、ActivityMessageListener#doReceive委任によって呼び出され、非同期動作が得られます。

しかし、あなたは半春の世界にいます。

ここ

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

参照thisは、実際ActivityMessageListenerにはプロキシではなく、実際の を参照しています。おそらく、ここのバスでメッセージを送信するとき

MessageBusUtil.sendMessage(MKTDestinationNames.ACTIVITY_REGISTRY,      message);

プロキシの非同期動作を持たない実際のオブジェクトに送信しています。

完全な Spring ソリューションは、MessabeBus (および/またはそのキュー) を Spring Bean にして、完全に処理された (プロキシされ、自動配線され、初期化された) Bean を注入できるようにすることです。


実際には、CGLIB プロキシは実際にはタイプのサブクラスにすぎないため、コンストラクターが呼び出されるため、ProxyListener上記は実際にはバスにも追加されます。のように、自分自身をキーで登録できるのsuperは 1 つだけのように思えます。そうでない場合は、説明のためにそのコードをさらに表示する必要があります。MessageListenerMKTDestinationNames.ACTIVITY_REGISTRY


あなたのテストでは、もしそうなら

activityMessageListener.doReceive(message);

activityMessageListenerプロキシへの参照を保持する必要があるため、非同期動作が表示されるはずです。

于 2015-02-02T22:45:39.780 に答える