AspectJ с Gradle Pointcut или Advice не работает - НЕ используя Spring или Android

Привет, я создаю библиотеку для обмена сообщениями между компонентами 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, если понадобится, но это действительно последнее средство.


person Michael Coxon    schedule 31.07.2018    source источник
comment
Это много кода, но в вашем скрипте Gradle отсутствуют версии зависимостей, поэтому он не собирается. Кстати, с какой целью вы декомпилировали аспект? Если вы хотите увидеть, произошло ли переплетение, скорее декомпилируйте целевые классы, такие как SenderT1E1. Но было бы намного проще настроить компилятор AspectJ, чтобы через -showWeaveInfo показывалось, что он куда вплетает, хотя этот плагин совершенно недокументирован и я не знаю, как его настроить. Кстати, у меня никогда раньше не было Gradle, я использую AspectJ с Maven. Вы также можете проверить этот плагин.   -  person kriegaex    schedule 03.08.2018
comment
(продолжение) И да, конечно, вам не нужен Spring при использовании AspectJ, они совершенно не связаны. Можете ли вы опубликовать MCVE без каких-либо зависимостей от вашего внутреннего Nexus и минимального кода, воспроизводящего проблему на GitHub? Тогда я посмотрю и помогу вам исправить это, если смогу. Я также нашел для вас это, возможно, это будет полезно.   -  person kriegaex    schedule 03.08.2018


Ответы (1)


Проблема была в чем-то другом. Причина, по которой Pointcut не запускался, заключалась в том, что я использовал переплетение во время компиляции, а переплетенный целевой класс не находился в пути к классам во время выполнения. Сплетенный целевой класс находился в src/test/java, а pointcut — в src/main/java, и когда приложение скомпилировано, src/test/java нигде не видно. Что мне было нужно, так это сплетение во время выполнения, чтобы тестовый класс можно было обнаружить и сплести, а затем заработал pointcut. К сожалению, переплетение во время выполнения является дорогостоящей операцией, поэтому не подходит для моих целей, поэтому я перешел к использованию шаблона прослушивателя. Это не так чисто с точки зрения разработчиков, но гораздо лучше с точки зрения производительности.

person Michael Coxon    schedule 09.08.2018