Мой BeanPostProcessor не работает в Spring Boot - PullRequest
1 голос
/ 18 июня 2019

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

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

My 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;
        }

    }

Это моя аннотация @ Timing :

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 (не важно).

Ответы [ 3 ]

1 голос
/ 18 июня 2019

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

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

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

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

Итак, давайте предположим, что вы действительно хотите способ BPP:

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

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

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

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

При переходе по этому пути следует помнить две вещи:

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

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

1 голос
/ 18 июня 2019

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

@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>

общая информация о пружине AOP

Spring AOP

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

0 голосов
/ 18 июня 2019

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...