Comments 23
На дворе 2018 вроде, а значит AOP все еще зло.
@Pointcut(«execution(* com.example.demoAspects.MyService.check())»)
легко ломаются, и попробуйте разберитесь, что конкретно у вас сломается в сложном проекте с хотя бы сотнями pointcut (молчу уж про тысячи).
ЗЫ: И не думаю, что в будущем небезопасное программирование когда-нибудь выиграет общий тренд у безопасного, причина-то банальна: хороших программистов, могущих сознательно не стрелять себе в ноги — значительно меньше, чем программистов, иногда стреляющих, если есть такая возможность.
Вы попробуйте в чужой код (скажем, вендорский, без исходников) внедрить логирование, чтобы отловить ошибки, если там его нет. Или мониторинг со сбором метрик добавить.
Я пробовал. И по большому счету, ничего лучше чем AOP (только не в таком «штатном» виде, как тут описано, а скорее как вот тут).
Проблемы? Сломается? Но иногда в ситуациях вроде чужого кода это чуть ли не единственно возможный способ внести изменения в чужой код. Точнее, не единственный, потому что есть похожие инструменты типа byteman, но это по большому счету тот же AOP, только в профиль, с другим синтаксисом описания аспектов и их внедрения.
Согласен - потому что связь текстовой строкой сделана и компилятор не отследит порыв связи. Пока не будет так сделано:
@Pointcut(@execution(MyService.check))
Был 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 более наглядный. А кода примерно столько же, что аннтоация, что строчка вызова.
@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) — почему-то нет. В чём проблема, как думаете?
Например, у меня есть контроллер:
@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?
Пытался реализовать по аналогии - с классами MyAspect, MyService и DemoAspectsApplicationTests , с указанным в статье pom.xml . Не работало. Копировал код из примеров - всё равно не работало. Пытался понять, что же не так, пока не догадался почитать комменты и после этого создать конфиг-класс класс @Configuration с @EnableAspectJAutoProxy и указать этот класс в @SpringBootTest - после этого тест успешно заработал.
Альтернатива - раннер со @SpringBootApplication, с которым нужная настройка AspectJ добавляется автоматически.
Но для того, кто впервые пробует писать код со Spring AOP, это не очевидно, и хорошо бы в статью добавить.
Аспектно-ориентированное программирование, Spring AOP