Привет, я создаю библиотеку для обмена сообщениями между компонентами AWS, поэтому мне нужно легкое решение. Одна часть решения требует, чтобы я прослушивал, когда вызывается аннотированный метод, поэтому я подумал, что буду использовать pointcut и реализовывать совет о том, что делать, когда этот pointcut будет достигнут.
Скрипт gradle.build выглядит так:
buildscript {
ext {
// some company-specific config here
nexus = {
credentials {
username nexusBuildUserToken
password nexusBuildPassToken
}
url nexusRepoURL
}
}
repositories {
mavenCentral()
maven(nexus)
}
dependencies {
classpath("net.researchgate:gradle-release:$gradleReleasePluginVersion")
classpath("gradle.plugin.aspectj:gradle-aspectj:$gradleAspectJPluginVersion")
}
}
apply plugin: 'java'
// IDE
apply plugin: 'idea'
apply plugin: 'eclipse-wtp'
apply plugin: "aspectj.gradle"
jar {
enabled = true
}
// project artifact info
group = groupId
archivesBaseName = artifactId
repositories {
mavenCentral()
maven(nexus)
maven {
url "https://dl.bintray.com/findify/maven"
}
}
dependencies {
compile("org.aspectj:aspectjtools:$aspectjVersion")
compile("org.apache.commons:commons-lang3:${commonsLang3Version}")
compile("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${jacksonDataformatYamlVersion}")
compile("org.elasticmq:elasticmq-rest-sqs_2.11:0.14.1")
compile("com.amazonaws:aws-java-sdk-sqs:${awsMessagingVersion}")
compile("com.amazonaws:aws-java-sdk-sns:${awsMessagingVersion}")
compile("au.com.auspost:json-encryption:${jsonEncryptionVersion}")
compile("org.apache.commons:commons-lang3:${commonsLang3Version}")
compile("org.reflections:reflections:${reflectionsVersion}")
compile("redis.clients:jedis:${jedisVersion}")
compile("org.aspectj:aspectjweaver:$aspectjVersion")
compile("org.aspectj:aspectjrt:$aspectjVersion")
testCompile("junit:junit:${jUnitVersion}")
testCompile("org.mockito:mockito-core:${mockitoCoreVersion}")
testCompile("org.assertj:assertj-core:${assertjVersion}")
testCompile("ai.grakn:redis-mock:${embeddedRedisVersion}")
testCompile("org.slf4j:slf4j-simple:1.7.25")
testCompile("ch.qos.logback:logback-core:1.2.3")
testCompile(group: 'io.findify', name: 'sqsmock_2.11', version: '0.3.2')
}
Как видите, я включил все библиотеки для аспектов, чтобы убедиться, что я не упустил ничего, что мне нужно (не стесняйтесь говорить мне, что мне не нужно).
Класс, который я ожидаю получить, таков:
@Aspect
public class TopicSenderManager {
private ThreadPoolFactory threadPoolFactory;
private CorrelationService correlationService = new CorrelationService.Default();
private Map<String, TopicSenderProcessor> topicSenderProcessors = new HashMap<>();
private ExecutorService executor;
public TopicSenderManager(Map<String, TopicSenderProcessor> topicSenderProcessors) {
this.threadPoolFactory = new ThreadPoolFactory.Default();
this.topicSenderProcessors = topicSenderProcessors;
executor = threadPoolFactory.create(topicSenderProcessors.size(), "TopicSender");
}
@Around("@annotation(topicSender) && execution(* *(..))")
public Object sendMessageToTopic(ProceedingJoinPoint pjp, TopicSender topicSender) throws Throwable {
TopicSenderProcessor topicSenderProcessor = getProcessor(topicSender.topicAlias(), topicSender.eventType());
topicSenderProcessor.setCorrelationId(correlationService.read());
Object[] args = pjp.getArgs();
if (args == null || args.length != 1) {
throw new Exception("naughty, naughty");
} else {
topicSenderProcessor.setEventMessage((EventMessage) args[0]);
executor.execute(topicSenderProcessor);
}
return pjp.proceed();
}
public Map<String, TopicSenderProcessor> getTopicSenderProcessors() {
return topicSenderProcessors;
}
public TopicSenderProcessor getProcessor(String topic, String eventType) {
String senderKey = topic + "," + eventType;
return topicSenderProcessors.get(senderKey);
}
}
Я надеюсь, что это подберет каждое выполнение (вызов) метода, аннотированного с помощью @TopicSender
, и выполнит связанный процессор в потоке из пула потоков.
Я взглянул на декомпилированный класс TopicSenderManager, который показан ниже:
@Aspect
public class TopicSenderManager {
private ThreadPoolFactory threadPoolFactory = new Default();
private CorrelationService correlationService = new au.com.auspost.messaging.CorrelationService.Default();
private Map<String, TopicSenderProcessor> topicSenderProcessors = new HashMap();
private ExecutorService executor;
public TopicSenderManager(Map<String, TopicSenderProcessor> topicSenderProcessors) {
this.topicSenderProcessors = topicSenderProcessors;
this.executor = this.threadPoolFactory.create(topicSenderProcessors.size(), "TopicSender");
}
@Around("@annotation(topicSender) && execution(* *(..))")
public Object sendMessageToTopic(ProceedingJoinPoint pjp, TopicSender topicSender) throws Throwable {
TopicSenderProcessor topicSenderProcessor = this.getProcessor(topicSender.topicAlias(), topicSender.eventType());
topicSenderProcessor.setCorrelationId(ajc$inlineAccessFieldGet$au_com_auspost_messaging_send_topic_TopicSenderManager$au_com_auspost_messaging_send_topic_TopicSenderManager$correlationService(this).read());
Object[] args = pjp.getArgs();
if (args != null && args.length == 1) {
topicSenderProcessor.setEventMessage((EventMessage)args[0]);
ajc$inlineAccessFieldGet$au_com_auspost_messaging_send_topic_TopicSenderManager$au_com_auspost_messaging_send_topic_TopicSenderManager$executor(this).execute(topicSenderProcessor);
return pjp.proceed();
} else {
throw new Exception("naughty, naughty");
}
}
public Map<String, TopicSenderProcessor> getTopicSenderProcessors() {
return this.topicSenderProcessors;
}
public TopicSenderProcessor getProcessor(String topic, String eventType) {
String senderKey = topic + "," + eventType;
return (TopicSenderProcessor)this.topicSenderProcessors.get(senderKey);
}
public static TopicSenderManager aspectOf() {
if (ajc$perSingletonInstance == null) {
throw new NoAspectBoundException("au.com.auspost.messaging.send.topic.TopicSenderManager", ajc$initFailureCause);
} else {
return ajc$perSingletonInstance;
}
}
public static boolean hasAspect() {
return ajc$perSingletonInstance != null;
}
static {
try {
ajc$postClinit();
} catch (Throwable var1) {
ajc$initFailureCause = var1;
}
}
}
Я предполагаю, что это плетение, которое должно было произойти.
Следующее, что нужно сделать, это настроить (своего рода) модульный тест, который демонстрирует поведение, которое я хочу:
Во-первых, я определяю простой пример метода, который я хочу перехватить:
public class SenderT1E1 {
@TopicSender(topicAlias = "t1", eventType = "a.c.a.e1")
public void aSendingMethod(EventMessage<TestMessage> eventMessage) {
// do what you like before sending the message
}
}
Затем тест, который смотрит на это:
public class SenderE1T1Test {
static TopicSenderManager manager;
@BeforeClass
public static void setUp() throws Exception {
MockAmazonClient snsClient = new MockAmazonClient();
TopicSenderFactory senderFactory = new TopicSenderFactory();
PublishResult publishResult = new PublishResult().withMessageId("E1T1");
manager = senderFactory.make(snsClient.amazonSNS(publishResult), "aws-send-test.properties");
}
@Test
public void whenSenderIsCalledMessageIsSent() {
SenderT1E1 target = new SenderT1E1();
EventMessage message = new EventMessage<>();
message.setEventType("a.c.a.e1");
message.setPayload(new TestMessage());
target.aSendingMethod(message);
TopicSenderProcessor processor = manager.getProcessor("http://localhost:8001/topic/t1", "a.c.a.e1");
assertThat(manager.getTopicSenderProcessors().entrySet().size(), is(2));
manager.getTopicSenderProcessors().forEach((k,v) -> System.out.println(k + ", " + v));
assertThat(processor, is(notNullValue()));
// now, did it execute...
assertThat(processor.getEventMessage(), is(notNullValue()));
assertThat(processor.getLastPublishRequest(), is(notNullValue()));
assertThat(processor.getLastPublishResult(), is(notNullValue()));
}
}
В принципе, все до строки // now did it execute...
работает, но после нее все пусто. Таким образом, похоже, что pointcut никогда не достигается.
Значит, плетение работает неправильно? Pointcut и совет в спецификации @Around
неверны? есть ли какой-то триггер времени выполнения, о котором я забыл? Или что-то еще?
Я подумал, что должен сделать здесь заявление: причина, по которой я не использую Spring, заключается в том, что я обнаружил, что количество зависимостей приводит к действительно раздутой библиотеке, и я пытаюсь сделать ее как можно более аккуратной. Я хочу получить преимущества в скорости от aspectJ, и я бы предпочел переплетение во время компиляции, потому что это библиотека, которая будет использоваться в приложениях, к которым я прибегну Spring, если понадобится, но это действительно последнее средство.
SenderT1E1
. Но было бы намного проще настроить компилятор AspectJ, чтобы через-showWeaveInfo
показывалось, что он куда вплетает, хотя этот плагин совершенно недокументирован и я не знаю, как его настроить. Кстати, у меня никогда раньше не было Gradle, я использую AspectJ с Maven. Вы также можете проверить этот плагин. - person kriegaex   schedule 03.08.2018