Эта статья продолжает предыдущуюИспользуйте удаленный мониторинг Java-программ на уровне метода Metrics.Говоря о. В приведенном выше примере мы просто реализовали функцию, но если ее превратить в библиотеку и использовать для нескольких проектов, будет хуже. Поэтому я сделал некоторые оптимизации в этой библиотеке.
1 недостаток
- Мертвые точки @RestController, @Controller и @Service не являются гибкими. Однако некоторые сервисы в нашем проекте также используют свои собственные пользовательские аннотации, такие как @XXController и @XXService (конкретные названия здесь писать не будем). В нашей бизнес-логике он принадлежит слою контроллера и слою службы. Если это предыдущий метод кодирования, методы в этих классах не могут отслеживаться. Поэтому я надеюсь, что аспект мониторинга можно настроить.
- Имя пакета прописано насмерть.Если я сменю бизнес и использую другое имя пакета, я не могу его контролировать. Поэтому я надеюсь, что имя пакета также можно настроить.
2 Реализация настраиваемых аспектов
2.1 Желаемая поза использования
Я надеюсь, что проекты, использующие эту библиотеку, смогут решить, какие классы они хотят отслеживать следующими способами.
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
// 这是我们自定义的Controller注解
import com.sinafenqi.cashloan.annotations.XXXControler;
@SpringBootApplication
//通过在Application上加一个注解,配置切点
@MonitorEnable({Controller.class, Service.class, XXXControler.class})
public class MonitorApplication {
public static void main(String[] args) {
SpringApplication.run(MonitorApplication.class, args);
}
}
Я хотел бы иметь возможность использовать аннотацию в приложении (например, MonitorEnable), а затем указать в нем pointcut (или даже пользовательскую аннотацию). Таким образом, мы можем произвольно выбирать код бизнес-уровня, который хотим отслеживать. Итак, давайте посмотрим, как это сделать дальше.
2.2 Получение конфигурационных точек из пользовательских аннотаций
2.2.1 Аннотации, используемые в пользовательской конфигурации
Первая пользовательская аннотацияMonitorEnable
и определитьvalue
Массив классов для аннотации. Здесь мы ограничиваем типы pointcut для простоты. Впоследствии, если вам нужно расширить функцию, отпустите ее.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MonitorEnable {
Class<? extends Annotation>[] value();
}
Это позволяет другим проектам использовать эту аннотацию в классе. Далее мы получаем значение Value в аннотации.
2.2.2 Получение срезов конфигурации из аннотаций
MonitorEnable
Роль заключается в предоставлении данных конфигурации. Итак, если мы хотим получить информацию, содержащуюся в нем, нам нужноMonitorEnable
плюс@Import
Аннотируем и указываем для него класс конфигурации, здесь мы указываемMonitorConfig
для класса конфигурации.
измененныйMonitorEnable
Файл аннотации:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MonitorConfig.class)
public @interface MonitorEnable {
Class<? extends Annotation>[] value();
}
хочу быть вMonitorConfig
Получено из класса конфигурацииMonitorEnable
Информация о конфигурации в , должна быть реализованаImportAware
интерфейс, так что Spring загружаетсяMetaData
перезвонит, когдаsetImportMetadata
метод. Мы можем получить содержимое в аннотации здесь.
@Configuration
@Log
public class MonitorConfig implements ImportAware{
// MonitorProperty类中包装了监控属性。用来存储配置的切点
public MonitorProperty monitorProperty = new MonitorProperty();
// 原来的内容不变,这里省略,详情请参考上一篇文章
// 这里把MonitorProperty装载到Spring容器。以供其他人使用
@Bean
public MonitorProperty monitorProperty() {
return monitorProperty;
}
// 这里获取配置的切点,并设置到monitorProperty中
@Override
public void setImportMetadata(AnnotationMetadata annotationMetadata) {
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(MonitorEnable.class.getName(), false);
AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(attributes);
Class<? extends Annotation>[] aopClasses = (Class<? extends Annotation>[]) annotationAttributes.getClassArray("value");
if (aopClasses == null || aopClasses.length == 0) {
throw new RuntimeException("monitor cannot get aop annotation classes. nothing to monitor. Please use MonitorEnable annotation on your application.");
}
monitorProperty.setAopAnnotationClasses(aopClasses);
}
}
Класс MonitorProperty:
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MonitorProperty {
private Class<? extends Annotation>[] aopAnnotationClasses;
}
Таким образом, мы можем получить его при запуске программыMonitorEnable
Значение, настроенное пользователем и сохраненное вMonitorProperty
середина.
2.3 Подготовьте таймер к методу мониторинга в соответствии с точкой отсечки
Теперь, когда pointcut настроен,为监控方法准备Timer
Этот шаг также следует изменить соответствующим образом. Этот шаг относительно прост.MethodMonitorCenter
Код повышения класса выглядит следующим образом: отMonitorProperty
Получите pointcut в , и замените ранее написанную логику:
@Log
public class MethodMonitorCenter implements ApplicationContextAware {
// 将MonitorProperty注入进来
@Autowired
private MonitorProperty monitorProperty;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, Object> monitorBeans = new HashMap<>();
// 这里从monitorProperty中获取切点
Class<? extends Annotation>[] classes = monitorProperty.getAopAnnotationClasses();
if (classes == null || classes.length == 0) {
return;
}
for (Class<? extends Annotation> aClass : classes) {
// 这里使用获取的切点获取要监控的类,下面的筛选逻辑与之前相同,省略
monitorBeans.putAll(applicationContext.getBeansWithAnnotation(aClass));
}
// 之后和以前一摸一样,这里省略。
}
}
2.4 Создание касательных плоскостей на основе касательных точек
Читатели, не знакомые со Spring AOP, могут поискать в Интернете. Есть много статей об этом. Я не буду повторять это.
Наши общие положения использования Spring AOP жестко запрограммированы. Так называемый жестко закодированный способ относится к аннотациям Java (метод, который я использовал в своей последней статье) и методам конфигурации XML. Теперь наш pointcut настроен. Тогда это не может быть достигнуто путем жесткого кодирования. Однако и динамические прокси Java, и AspectJ должны знать целевой класс прокси. Явно не подходит для нашего сценария. Но я считаю, что жесткое кодирование можно сделать, мягкое кодирование определенно можно сделать, но это может быть более проблематично. Итак, я пролистал исходный код Spring. Нашел способ. В этой статье не хочу включать исходный код и принципы, только реализацию.
Предпосылка, удалить предыдущую статьюMetricsMonitorAOP
class, потому что мы больше не можем использовать жестко закодированный способ.
2.4.1 Подготовка к созданию класса огранки
пользовательский классMonitorAdvice
выполнитьMethodInterceptor
интерфейс, из нихinvoke
Метод эквивалентен методу обертывания среза.
@Log
public class MonitorAdvice implements MethodInterceptor {
MetricRegistry metricRegistry;
public MonitorAdvice(MetricRegistry metricRegistry) {
this.metricRegistry = metricRegistry;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String methodName = invocation.getMethod().toString();
log.info("monitor invoke. method: " + methodName);
boolean contains = metricRegistry.getNames().contains(methodName);
if (!contains) {
return invocation.proceed();
}
log.info("monitor start method = [" + methodName + "]");
Timer timer = metricRegistry.timer(methodName);
Timer.Context context = timer.time();
try {
return invocation.proceed();
} finally {
context.stop();
}
}
}
2.4.2 Использование программного кода для создания срезов из точек касания
MonitorConfig
Добавьте код в класс, см. пояснения в комментариях:
@Configuration
@Log
// 让MonitorConfig实现BeanFactoryPostProcessor接口,
// 在其postProcessBeanFactory方法中我们可以软代码向Spring装载Bean
public class MonitorConfig implements ImportAware, BeanFactoryPostProcessor {
// 该类中其他代码保留不变,省略
// 这里将上面自定义的MonitorAdvice类装载到Spring中
@Bean
public MonitorAdvice monitorAdvice(MetricRegistry metricRegistry) {
return new MonitorAdvice(metricRegistry);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
DefaultListableBeanFactory factory = (DefaultListableBeanFactory) beanFactory;
MonitorAdvice monitorAdvice = (MonitorAdvice) factory.getBean("monitorAdvice");
// 获取配置的切点
Class<? extends Annotation>[] classes = monitorProperty.getAopAnnotationClasses();
if (classes == null || classes.length == 0) {
return;
}
for (Class<? extends Annotation> aClass : classes) {
// 软代码根据切点创建Pointcut
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(aClass);
// 软代码创建Advisor(硬编码的方式也是转化成这个东西)
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(DefaultPointcutAdvisor.class.getName())
.addPropertyValue("pointcut", pointcut)
.addPropertyValue("advice", monitorAdvice)
.getBeanDefinition();
// 然后将Advisor装载到Spring
factory.registerBeanDefinition("monitorAdvisor" + aClass.getName(), beanDefinition);
}
}
}
Таким образом, функция аспекта, которая ранее была жестко закодирована, может быть реализована с помощью программного кода.
3 Настраиваемые имена пакетов
Это намного проще, чем предыдущая оптимизация.
3.1 Желаемая поза использования
Я надеюсь, что пользователи смогут настроить имя пакета одним из следующих двух способов:
пройти черезapplication.properties
конфигурация файла, например, вapplication.properties
Добавьте в файл следующий код:
monitor.property.basePackages=com.xxx,com.yyy
или черезMonitorEnable
Аннотации настроены следующим образом:
@MonitorEnable(value = {/*这里是配置的切点们,省略*/}, basePackages = {"com.xxx","com.yyy"})
Для достижения имени пакета, разработанного с помощью мониторинга.
3.2 Реализовать настраиваемое имя пакета
Здесь только черезapplication.properties
Решение реализовано в виде файла конфигурации. Реализация конфигурации через аннотации есть только по-разному.Если интересно, можете сразу перейти к исходникам.
3.2.1 MonitorProperty добавляет свойство имени пакета
Мы можем заметить, что несколько имен пакетов можно настроить в 3.1, затем вMonitorProperty
добавить атрибутыbasePackages
:
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MonitorProperty {
private Class<? extends Annotation>[] aopAnnotationClasses;
private String[] basePackages;
}
3.2.2 Присвоение значения MonitorProperty
Есть много методов для этого шага, мы используемConfigurationProperties
Аннотация присваивает ему:
@Configuration
@Log
public class MonitorConfig implements ImportAware, BeanFactoryPostProcessor {
@Bean
// 在装载MonitorProperty的地方加上ConfigurationProperties注解,为其赋值。
@ConfigurationProperties("monitor.property")
public MonitorProperty monitorProperty() {
return monitorProperty;
}
}
3.2.3 Логика изменения фильтра
Имя пакета, настроенное пользователем, было получено, затем мы используем имя пакета, настроенное пользователем, чтобы переписать исходную логику фильтрации на основе имени пакета:
@Log
public class MethodMonitorCenter implements ApplicationContextAware {
@Autowired
private MonitorProperty monitorProperty;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, Object> monitorBeans = new HashMap<>();
// 获取要监控的Bean过程省略
monitorBeans.values().stream()
.map(obj -> obj.getClass().getName())
.map(this::trimString)
.map(this::getClass)
.filter(Objects::nonNull)
.filter(this::isInPackages) // 这里根据包名过滤
.forEach(this::getClzMethods);
}
private boolean isInPackages(Class<?> clazz) {
// 根据用户配置的包名过滤想要监控的类
String[] basePackages = monitorProperty.getBasePackages();
if (basePackages == null || basePackages.length == 0) {
return true;
}
return Stream.of(basePackages).anyMatch(basePackage -> clazz.getName().startsWith(basePackage));
}
// 其他代码不变,省略
}
Это позволяет пользователям настраивать собственные имена пакетов.
4 Эффект оптимизации
Пользователь добавляет в свой класс Application, добавляяMonitorEnable
аннотацию, а затем вы можете настроить pointcut конфигурации:
@MonitorEnable({RestController.class, Controller.class, Service.class, XXControler.class})
затем черезapplication.properties
конфигурация в файлеmonitor.property.basePackages
properties настройте имя пакета, который вы хотите отслеживать:
monitor.property.basePackages=com.xxx,com.yyy
затем пройти/monitor/metrics
Этот интерфейс Restful получает данные метода мониторинга.
5 Резюме
Среди двух пунктов этой оптимизации более сложно использовать метод программного кода для создания аспекта, а соответствующая литература относительно невелика. Если у меня будет время, я напишу отдельную статью, чтобы объяснить исходный код и принципы.
Наконец, приглашаю обратить внимание на мой личный публичный номер. Задавайте вопросы, общайтесь, что угодно.