Я написал свой 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 (не важно).
@ThomasAndolf спасибо. Можете ли вы показать пример, пожалуйста?
Почему бы не использовать поддержку метрик из Spring Boot вместо того, чтобы пытаться писать свои собственные?




Я обычно использую аспекты для этого
@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?
я никогда не использовал это раньше, но мне кажется странным, что для каждого аннотированного метода вы возвращаете прокси-класс. Потому что я понимаю, что
Proxy.newProxyInstanceвозвращает класс. Некоторое время назад я написал временную аннотацию и использовал для этого Spring AOP, было намного проще использовать аспекты.