Весеннее тестирование @async методом
Я пытаюсь проверить, если @Async
аннотация Spring работает как положено на моем проекте. Но это не так.
У меня есть этот тест:
@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
метод, и имя такое же, как и раньше, основной. Так что это не работает.
Глобальная конфигурация:
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
@ComponentScan({
"com.shn.configurations",
...some packages...
})
public class GlobalConfiguration {...}
И внутри одного из указанных пакетов есть бин 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;
}
}
Что я делаю не так?
1 ответ
Tricky.
Асинхронное поведение добавляется через прокси.
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);
Теперь, в весеннем мире, у вас будет ссылка на 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 (и / или его очередь) были bean-компонентами Spring, в которые можно внедрить полностью обработанные (проксированные, автоматически подключенные, инициализированные) bean-компоненты.
В действительности, поскольку прокси-серверы CGLIB на самом деле являются просто подклассами ваших типов, поэтому ProxyListener
выше на самом деле также добавить себя в автобус, так как super
конструктор будет вызван. Казалось бы, что только один MessageListener
может зарегистрироваться с помощью ключа, как MKTDestinationNames.ACTIVITY_REGISTRY
, Если это не так, вам придется показать больше этого кода для объяснения.
В вашем тесте, если вы делаете
activityMessageListener.doReceive(message);
вы должны увидеть это асинхронное поведение, так как activityMessageListener
должен содержать ссылку на прокси.