Pull to refresh

Comments 23

На дворе 2018 вроде, а значит AOP все еще зло.

Так всё те же проблемы, которые были сразу высказаны: unknown side effects (или, говоря по-русски, хрен поймешь, сколько кода и в какой последовательности у тебя выполнится через все аспекты) и fragile pointcut (или, говоря по-русски, изменения в базовом коде изменят порядок и объем связанных с этим pointcut). Проще говоря, вот эти вот
@Pointcut(«execution(* com.example.demoAspects.MyService.check())»)

легко ломаются, и попробуйте разберитесь, что конкретно у вас сломается в сложном проекте с хотя бы сотнями pointcut (молчу уж про тысячи).
Что, ломаются, так это еще пока от не совершенства языка и реализации framework, а сама парадигма дает развитие, может через некоторое время все это и др., будет стандартом.
Это да. Но все эти годы я вижу тренд обратного направления, тренд на программистские инструменты, не позволяющие программисту нечаянно создать себе трудноразрешимых проблем (пусть и ценой разнообразных недостатков). AOP же совершенно в другую сторону, способов фатально отстрелить себе ногу тут сколько угодно, просто применив AOP там, где его применять не стоит.

ЗЫ: И не думаю, что в будущем небезопасное программирование когда-нибудь выиграет общий тренд у безопасного, причина-то банальна: хороших программистов, могущих сознательно не стрелять себе в ноги — значительно меньше, чем программистов, иногда стреляющих, если есть такая возможность.
Дело в том, что AOP — это всего-лишь инструмент. Бездумное применение инструмента — вот это и есть зло.

Вы попробуйте в чужой код (скажем, вендорский, без исходников) внедрить логирование, чтобы отловить ошибки, если там его нет. Или мониторинг со сбором метрик добавить.

Я пробовал. И по большому счету, ничего лучше чем AOP (только не в таком «штатном» виде, как тут описано, а скорее как вот тут).

Проблемы? Сломается? Но иногда в ситуациях вроде чужого кода это чуть ли не единственно возможный способ внести изменения в чужой код. Точнее, не единственный, потому что есть похожие инструменты типа byteman, но это по большому счету тот же AOP, только в профиль, с другим синтаксисом описания аспектов и их внедрения.

Согласен - потому что связь текстовой строкой сделана и компилятор не отследит порыв связи. Пока не будет так сделано:
@Pointcut(@execution(MyService.check))

Имел в виду, что язык не позволит делать трудно находимые ошибки, сейчас reflection, и аннотации, чуть ли не единственный способ расширить сам язык, отсюда и вероятность выстрелить себе в ногу ). Но желание сообщества иметь более совершенные способы разработки опережают предложение разработчика языка. Тут нужен пока разумеый компромисс
Могу подтвердить злобность AspectJ. Поначалу было вроде красиво и круто.
Был load time weaving. Потом после перехода на Java 8 полезли странные баги, которые трудно воспроизвести. Ну и время старта приложения не прилично росло — ему же надо весь код перелопатить. Перешли на обработку во время компиляции — тепепь хоть есть уверенность, что сервер не упадет от этих ошибок. Но все равно билды падают иногда в местах, где все синтактически правильно. Баг репорты заполнены, но поскольку оно падает может 1 раз из ста, никто их не пофиксил. И врядли пофиксит. Ну о том что оно скачет неизвестно куда в дебагере и в стектрейсы странные видят люди, которые не разбираются в том, что аспект делает, я уже молчу.

Не берусь судить, как используется АОП, но соглашусь, что сделать сложным к пониманию, отладки, проект с ним можно. Но это вопрос к архитектуре, но так как на проектах унас большая текучка, то и допускаю что много перегибов. Меня больше интересовал академический аспект АОП и его частная реализация, ну а пока мы заложники прогресса. Спасибо.

Декларативное описание, да еще не type-safe, да еще по имени класса/метода с сигнатурой и с wildcards… Прям как новая серия игр "Что? Где? Когда?". Тут простой рефакторинг все сразу поломает.
И главное, для чего? Есть же Proxy.newProxyInstance(), ByteBuddy и BeanPostProcessor для детерминированного инжекта.

Порекомендуйте как тестировать такой код, чтобы быть спокойным, что аспект пророс куда нужно и не пророс куда не нужно?
Если правка в бизнес коде нечаянно ломает аспект, как это обнаружить и чья ответственность чинить поломанное?
@Around("callAtMyServiceSecurityAnnotation(user)")
АОП сковзная функциональность, которую вполне можно вызывать через статический вызов класса, внутри которого инстанс соаздается через ServiceLocator.
public static Security{
private static final Lazy<ISecurityService> lazySecurityService = new Lazy(Container::Resolve<ISecurityService>)

public void logEvent(anyparams){
lazy.value.logEvent(anyparams);
}
}

В общем случае это тоже не идеальное решение, непонятно в какое время будет проинстанциирован ISecurityService и когда уничтожен, но такой подход избавляет от ненужных параметров в конструкторе и в случае падения stacktrace более наглядный. А кода примерно столько же, что аннтоация, что строчка вызова.
Я попытался использовать AOP по Вашему примеру, но что-то не заходит в сам метод Before. Я описал проблему на stackoverflow, посмотрите, пожалуйста, и дайте совет, что не так.
Проверьте pom, на предмет spring-boot-starter-aop, spring-boot-starter-test
В тесте должен
@RunWith(SpringRunner.class)
@SpringBootTest
Как будто АОП не стартует
всё оказалось проще — не хватало @EnableAspectJAutoProxy
Чувствовал, что аоп не стартует
Тогда ещё один вопрос. Вот у меня два метода, на которые я хочу повесить аспект.

@ApiLogBefore(transferType = TransferType.REQUEST, httpMethod = HttpMethod.GET, path = "", param = "transactionId")
public ResponseEntity save(@RequestParam("transactionId") String transactionId) {

и

@ApiLogBefore(transferType = TransferType.REQUEST, httpMethod = HttpMethod.GET, path = "/id", param = "id")
public ResponseEntity get(@RequestParam("id") Long id, HttpServletRequest request) {

Метод, который должен перехватывать обе аннотации, выглядит так:

    @Before(value = "@annotation(before) && args(param,..)")
    public void before(ApiLogBefore before, String param) {


Но почему-то перехватывается только первый, а второй (где вторым аргументом HttpServletRequest) — почему-то нет. В чём проблема, как думаете?
Ошибку нашёл, просьба не беспокоиться :)
А есть ли возможность сетить значение из @Aspect-метода обратно в обрабатываемый метод?
Например, у меня есть контроллер:

    @ApiLogRequest(httpMethod = HttpMethod.POST, path = "/planet")
    @PostMapping
    public ResponseEntity<PlanetDto> save(@RequestBody PlanetDto dto) {
        Long requestId;
        return ResponseEntity.ok(service.save(dto));
    }

и метод, обрабатывающий запрос:

    @AfterReturning(value = "@annotation(after)", returning = "responseEntity")
    public void after(ResponseEntity responseEntity, ApiLogResponse after) throws JsonProcessingException {
        service.save(ApiLog.of(
                TransferType.RESPONSE.name(),
                after.httpMethod().name(),
                after.path(),
                new ObjectMapper().writeValueAsString(responseEntity)
        ));
    }

Можно ли засетить из метода after значение в Long requestId метода save?
Делаю сейчас в проекте логирование API вызовов через аннотации. Возник вопрос куда вешать аннотацию: на интерфейс или на реализацию?

Пытался реализовать по аналогии - с классами MyAspect, MyService и DemoAspectsApplicationTests , с указанным в статье pom.xml . Не работало. Копировал код из примеров - всё равно не работало. Пытался понять, что же не так, пока не догадался почитать комменты и после этого создать конфиг-класс класс @Configuration с @EnableAspectJAutoProxy и указать этот класс в @SpringBootTest - после этого тест успешно заработал.

Альтернатива - раннер со @SpringBootApplication, с которым нужная настройка AspectJ добавляется автоматически.

Но для того, кто впервые пробует писать код со Spring AOP, это не очевидно, и хорошо бы в статью добавить.

Sign up to leave a comment.

Articles