Мой BeanPostProcessor не работает в Spring Boot

Я написал свой BeanPostProcessor так, чтобы все методы, помеченные моей аннотацией @Время, отображали в консоли время их выполнения.

Я использую Spring Boot.

Мой BeanPostProcessor выглядит так:

    import com.example.version2.annotation.Timing;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.stereotype.Component;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;

    @Component
    public class TimingBeanPostProcessor implements BeanPostProcessor {

        @Override
        public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
            Class type = bean.getClass();
            Method[] methods = type.getMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(Timing.class)) {
                    Object proxy = Proxy.newProxyInstance(type.getClassLoader(),type.getInterfaces(), new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            long before = System.nanoTime();
                            Object retVal = method.invoke(bean, args);
                            long after = System.nanoTime();
                            System.out.println("Method worked: " + (after - before) + " nano seconds");
                            return retVal;
                        }
                    });
                    return proxy;
                } else {
                    return bean;
                }
            }

             return bean;
        }

        @Override
        public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
            return bean;
        }

    }

Это моя аннотация @Время:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Timing {
}

Я объявляю эту аннотацию в одном из методов класса dao:

@Timing
public  List<Map<String, Object>> selectQuery() {
    String selectQuery = prop.getMYSQL_SELECT();
    return mysqlTemplate.queryForList(selectQuery);
}

При запуске приложения проблем нет, но при выполнении запроса в консоли ничего не вижу. Вроде сам BeanPostProcessor написал правильно. Не могу найти в чем ошибка.

Еще хотелось бы узнать, как я могу передать эту информацию о времени выполнения метода на фронтенд в json или какой-нибудь List (не важно).

я никогда не использовал это раньше, но мне кажется странным, что для каждого аннотированного метода вы возвращаете прокси-класс. Потому что я понимаю, что Proxy.newProxyInstance возвращает класс. Некоторое время назад я написал временную аннотацию и использовал для этого Spring AOP, было намного проще использовать аспекты.

Toerktumlare 18.06.2019 16:58

@ThomasAndolf спасибо. Можете ли вы показать пример, пожалуйста?

Kirill Sereda 18.06.2019 17:38

Почему бы не использовать поддержку метрик из Spring Boot вместо того, чтобы пытаться писать свои собственные?

M. Deinum 18.06.2019 18:00
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
3
1 480
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я обычно использую аспекты для этого

@Aspect
public class TimedAspect {

    @Around("@annotation(some.thing.Timed)")
    public Object timeSomething(ProceedingJoinPoint joinPoint) throws Throwable {
        final long before = System.nanoTime();
        final Object returnValue = joinPoint.proceed()
        final long after = System.nanoTime();
        System.out.println("Method worked: " + (after - before) + " nano seconds");
        return returnValue;       
    }
}

по времени

package some.thing;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed {


}

зависимость:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

общая информация о весеннем АОП

Весенний АОП

(я не пробовал, если этот код работает, скопируйте вставленные вещи из моего проекта)

Вы перебираете все методы, но возвращаете bean-компонент, если первый метод не имеет аннотации Timing:

for (Method method : methods) {
    if (method.isAnnotationPresent(Timing.class)) {
        Object proxy = ...
         return proxy;
     } else {
         return bean;
     }

Это означает, что вы создадите свой собственный прокси только в том случае, если первый метод, который вы найдете в аннотации.

Вы можете избавиться от предложения else и позволить вашему return bean после цикла for обрабатывать случай, когда ни один метод не имеет аннотации.

Ответ принят как подходящий

Во-первых, такой функционал уже есть. Spring Boot интегрируется с инфраструктурой Micrometer, которая допускает такой тип поведения (Spring Boot 1.x использует метрики dropwizard с дополнительной поддержкой бэкпорта микрометра, оба допускают этот декларативный стиль аннотаций).

Здесь является соответствующей главой документации микрометра.

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

Теперь слово о Spring AOP, предложенное другими ответами. Я (и это только мое мнение) считаю, что в данном случае использование bean-постпроцессоров имеет преимущество перед АОП. Во-первых, возможно, вы вообще не используете Spring AOP, только обычный Spring. Вторая причина выбрать этот стиль реализации — это производительность, АОП добавляет довольно много вызовов в стек. Очевидным преимуществом АОП является простота реализации.

Итак, предположим, вам нужен способ BPP:

Я думаю, прежде всего, вы должны проверить, что постпроцессор Bean "распознается" весной во время запуска приложения.

Чтобы проверить это, вы можете создать конструктор без аргументов в BPP и напечатать там что-то вроде «Привет из BPP» или использовать отладчик.

Теперь, что касается предлагаемой реализации: Вам нужно перебрать методы и создать прокси только один раз. Нет смысла создавать прокси через прокси через прокси.... Таким образом, представленный код неверен.

Я думаю, вам следует перебрать методы, подготовить список методов и запомнить этот набор, а затем создать прокси, который будет иметь метод invoke, который будет проверять, находится ли метод в наборе методов, и если да, то делать магию прокси, иначе просто делегировать вызов базовому компоненту.

Две вещи, которые вы должны иметь в виду, когда идете по этому пути:

  • Прокси не будет работать с реальными классами, только с интерфейсом. Если у вас есть класс и не работает интерфейс, вам нужно возиться с CGLIB

  • Другие постпроцессоры компонентов также могут обернуть ваш компонент каким-либо прокси, например, что, если вы измеряете метод, аннотированный @Transactional?

Другие вопросы по теме