⚠️ Эта статья является первой подписанной статьей сообщества Nuggets, и ее перепечатка без разрешения запрещена.
Автор: Брат Сяофу
Блог:bugstack.cn
Осаждайте, делитесь, растите и позвольте себе и другим получить что-то! 😄
1. Телефонный звонок с поздней ночи!
咋滴,你那上线的系统是裸奔呢?
Поздно вечером в выходные мне вдруг позвонил начальник ☎. "Посмотрите на WeChat, посмотрите на WeChat, мы не знаем почему проблемы с системой, мы должны дать обратную связь от пользователей, чтобы знать!!!" Я встал посреди ночи, включил компьютер и подключился к VPN, зевнул и открыл затуманенные глаза, Проверяю системный журнал, оказывается система зависла, быстро перезагружаемся и восстанавливаемся!
Хотя перезапуск восстановил систему, он также сбросил искаженное выражение лица босса. Но как система зависла?Потому что нет системы мониторинга,и я не знаю вызвано ли это излишним трафиком или программной проблемой,по логам мы можем лишь приблизительно оценить некоторые好像的标签
Сообщить боссу. Но боссне глупый, общаться взад и вперед, пусть все условия работы системы отслеживаются.
Волоча сонную голову руками, он некоторое время не мог придумать хорошего способа,Жестко ли выполнять трудоемкие вычисления для каждого метода?. После этого информация собирается в унифицированном виде и отображается на странице мониторинга.Страница мониторинга используетecharts, не говоря уже о том, что если он отображается таким образом, он может быть действительно красивым и простым в использовании.
- Но такое жесткое кодирование не проблема, это не беспокоит кодеров, которые двигают кирпичи в нашем отделе! Кроме того, они должны смотреть на меня свысока за это.Какой архитектор, чтобы следить за системой, надо ее хардкодить, тупица! ! !
- Я не могу уснуть, думая об этом, я должен найти информацию и сообщить об этом боссу завтра!
На самом деле, стабильная работа онлайн-системы зависит от ее работоспособности, которая включает комплексное значение различных показателей, таких как объем вызовов, доступность, продолжительность воздействия и производительность сервера. И когда в системе возникает нештатная проблема, вся ссылка на выполнение бизнес-метода может быть захвачена и выведена: входные параметры, выходные параметры, нештатная информация и т. д. в это время. Конечно, он также включает некоторые индикаторы производительности JVM, Redis и Mysql для быстрого обнаружения и решения проблем.
Так как же это сделать?На самом деле, есть еще много методов, таких как;
- Самый простой и грубый — это хардкодить его в методе, чтобы собирать время выполнения и входные и выходные параметры и информацию об исключении. Однако стоимость такого кодирования слишком велика, а после жесткого кодирования требуется большое количество регрессионных тестов, что может нести определенные риски для системы.На случай, если кто-то дрожит и копипастить неправильно!
- Вы можете выбрать метод аспекта, чтобы создать набор унифицированных компонентов мониторинга, что относительно лучше. Но это также требует жесткого кодирования, такого как написание аннотаций, и стоимость обслуживания не является низкой.
- На самом деле, на рынке существует полный набор решений для мониторинга вторжений для такого мониторинга, например, Google Dapper, Zipkin и т. д. могут удовлетворить требования системы мониторинга. технология, использующая расширение байт-кода.Информация о работе системы анализируется и отслеживается состояние операции.
Хорошо, тогда в этой статье вы попробуете несколько разных способов мониторинга реализации рабочего состояния системы.
2. Подготовка
Эта статья будет основана наAOP
, каркас байт-кода (ASM
,Javassist
,Byte-Buddy
) для реализации различных кодов реализации мониторинга соответственно. Вся структура проекта выглядит следующим образом:
MonitorDesign
├── cn-bugstack-middleware-aop
├── cn-bugstack-middleware-asm
├── cn-bugstack-middleware-bytebuddy
├── cn-bugstack-middleware-javassist
├── cn-bugstack-middleware-test
└── pom.xml
- Адрес источника:GitHub.com/заместитель комиссара/…
- Краткое введение: aop, asm, bytebuddy и javassist — это четыре разные реализации. test — это простой тестовый проект, основанный на SpringBoot.
- Используемые технологии: SpringBoot, asm, byte-buddy, javassist
cn-bugstack-middleware-test
@RestController
public class UserController {
private Logger logger = LoggerFactory.getLogger(UserController.class);
/**
* 测试:http://localhost:8081/api/queryUserInfo?userId=aaa
*/
@RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET)
public UserInfo queryUserInfo(@RequestParam String userId) {
logger.info("查询用户信息,userId:{}", userId);
return new UserInfo("虫虫:" + userId, 19, "天津市东丽区万科赏溪苑14-0000");
}
}
- Следующие различные реализации кода мониторинга будут реализованы путем мониторинга
UserController#queryUserInfo
Информация о выполнении метода в основном используется для просмотра того, как работают различные технологии.
3. Используйте АОП в качестве аспектного мониторинга
1. Инженерное сооружение
cn-bugstack-middleware-aop
└── src
├── main
│ └── java
│ ├── cn.bugstack.middleware.monitor
│ │ ├── annotation
│ │ │ └── DoMonitor.java
│ │ ├── config
│ │ │ └── MonitorAutoConfigure.java
│ │ └── DoJoinPoint.java
│ └── resources
│ └── META-INF
│ └── spring.factories
└── test
└── java
└── cn.bugstack.middleware.monitor.test
└── ApiTest.java
Основываясь на системе мониторинга, реализованной AOP, вышеуказанные проекты базовой логики не сложны.Основной момент заключается в понимании и применении аспектов, и некоторые элементы конфигурации необходимо разработать в соответствии с реализацией в SpringBoot.
- DoMonitor — это пользовательская аннотация. Его функция заключается в добавлении этой аннотации и настройке необходимой информации в интерфейсе мониторинга метода, который необходимо использовать.
- MonitorAutoConfigure в конфигурации может использовать yml-файл SpringBoot и может обрабатывать операции инициализации некоторых bean-компонентов.
- DoJoinPoint — это основная часть всего промежуточного ПО, которое отвечает за перехват и логическую обработку всех методов, добавляющих пользовательские аннотации.
2. Определите аннотации мониторинга
cn.bugstack.middleware.monitor.annotation.DoMonitor
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoMonitor {
String key() default "";
String desc() default "";
}
- @Retention(RetentionPolicy.RUNTIME),
Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
- @Retention — это аннотация аннотации, также известная как мета-аннотация. В этой аннотации есть входной параметр
RetentionPolicy.RUNTIME
В комментариях есть описание:Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
Фактически это означает, что эта аннотация добавляется, и ее информация будет доведена до исполняющей среды JVM.При вызове метода вы можете получить информацию об аннотации через рефлексию. Кроме того, RetentionPolicy имеет два свойства.SOURCE
,CLASS
, на самом деле эти три перечисления формально соответствуют порядку загрузки и выполнения кода Java, исходный файл Java -> файл .class -> байт-код памяти. И последний диапазон больше, чем первый, поэтому обычно вам нужно использовать только RetentionPolicy.RUNTIME. - @Target также является мета-аннотацией для отметки, ее имя аннотации является ее значением,Цель, то есть следует ли размещать нашу пользовательскую аннотацию DoWhiteList в классе, интерфейсе или методе.В JDK1.8 ElementType предоставляет в общей сложности 10 целевых перечислений: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE, вы можете ссылаться на свою собственную область аннотаций, чтобы установить
- Настраиваемая аннотация @DoMonitor предоставляет описание ключа и описания мониторинга, которое в основном записывает конфигурацию уникальных значений вашего метода мониторинга и текстовое описание метода мониторинга.
3. Определите перехват аспекта
cn.bugstack.middleware.monitor.DoJoinPoint
@Aspect
public class DoJoinPoint {
@Pointcut("@annotation(cn.bugstack.middleware.monitor.annotation.DoMonitor)")
public void aopPoint() {
}
@Around("aopPoint() && @annotation(doMonitor)")
public Object doRouter(ProceedingJoinPoint jp, DoMonitor doMonitor) throws Throwable {
long start = System.currentTimeMillis();
Method method = getMethod(jp);
try {
return jp.proceed();
} finally {
System.out.println("监控 - Begin By AOP");
System.out.println("监控索引:" + doMonitor.key());
System.out.println("监控描述:" + doMonitor.desc());
System.out.println("方法名称:" + method.getName());
System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("监控 - End\r\n");
}
}
private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
Signature sig = jp.getSignature();
MethodSignature methodSignature = (MethodSignature) sig;
return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
}
}
- Используйте аннотацию @Aspect для определения класса аспекта. Это очень распространенный способ определения аспектов.
-
@Pointcut("@annotation(cn.bugstack.middleware.monitor.annotation.DoMonitor)")
, который определяет точку отсечки. Есть много способов найти точки в Pointcut, включая указание имен методов, выражения фильтрации диапазона, и теперь мы используем пользовательские аннотации. Как правило, при разработке промежуточного программного обеспечения чаще используется метод пользовательских аннотаций, поскольку его можно более гибко применять к различным бизнес-системам. -
@Around("aopPoint() && @annotation(doMonitor)")
, что можно понимать как действие переплетения для улучшения метода. Эффект этой аннотации заключается в том, что при вызове метода с пользовательской аннотацией @DoMonitor вы сначала вводите этот метод улучшения pointcut. Затем в это время вы можете выполнить некоторые операции с методом, например, нам нужно выполнить мониторинг метода и распечатать журнал. - Наконец в
doRouter
Получить тело метода и выполнить методjp.proceed();
использоватьtry finally
Упакуйте его и распечатайте соответствующую информацию о мониторинге. Получение этой информации мониторинга, наконец, может быть отправлено на сервер с помощью асинхронных сообщений, а затем сервер обработает данные мониторинга и отобразит их на странице мониторинга.
4. Инициализируйте класс аспекта
cn.bugstack.middleware.monitor.config.MonitorAutoConfigure
@Configuration
public class MonitorAutoConfigure {
@Bean
@ConditionalOnMissingBean
public DoJoinPoint point(){
return new DoJoinPoint();
}
}
- @Configuration можно рассматривать как аннотацию компонента, которую можно загрузить для создания файла компонента при запуске SpringBoot.Поскольку аннотация @Configuration имеет аннотацию @Component
- MonitorAutoConfigure может обрабатывать пользовательскую информацию о конфигурации в yml, а также может использоваться для инициализации объектов Bean.Например, здесь мы создаем объект аспекта DoJoinPoint.
5. Запустите тест
5.1 Знакомство с конфигурацией POM
<!-- 监控方式:AOP -->
<dependency>
<groupId>cn.bugstack.middleware</groupId>
<artifactId>cn-bugstack-middleware-aop</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
5.2 Метод настройки регистрации мониторинга
@DoMonitor(key = "cn.bugstack.middleware.UserController.queryUserInfo", desc = "查询用户信息")
@RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET)
public UserInfo queryUserInfo(@RequestParam String userId) {
logger.info("查询用户信息,userId:{}", userId);
return new UserInfo("虫虫:" + userId, 19, "天津市东丽区万科赏溪苑14-0000");
}
- После внедрения собственных разработанных компонентов через POM вы можете получать информацию мониторинга с помощью пользовательских аннотаций и методов перехвата.
5.3 Результаты испытаний
2021-07-04 23:21:10.710 INFO 19376 --- [nio-8081-exec-1] c.b.m.test.interfaces.UserController : 查询用户信息,userId:aaa
监控 - Begin By AOP
监控索引:cn.bugstack.middleware.UserController.queryUserInfo
监控描述:查询用户信息
方法名称:queryUserInfo
方法耗时:6ms
监控 - End
- Откройте URL-адрес на веб-странице, запустив программу SpringBoot:
http://localhost:8081/api/queryUserInfo?userId=aaa
, вы можете видеть, что информация мониторинга может быть выведена на консоль. - Этот метод конфигурации с помощью пользовательских аннотаций может решить определенную работу по жесткому кодированию, но если к методу добавляется большое количество аннотаций, он также требует определенного объема разработки.
Далее мы представим использование инструментов байт-кода для ненавязчивого мониторинга системы.Существуют три часто используемых компонента для инструментовки байт-кода, в том числе: ASM, Javassit и Byte-Buddy.Далее мы представим, как они используются.
4. АСМ
ASM — это среда обработки байт-кода Java. Его можно использовать для динамического создания классов или для улучшения функциональности существующих классов. ASM может напрямую генерировать бинарные файлы классов или динамически изменять поведение классов перед их загрузкой в виртуальную машину Java. Классы Java хранятся в строго отформатированных файлах .class, метаданных которых достаточно для разрешения всех элементов класса: имени класса, методов, свойств и байт-кода Java (инструкций). После того, как ASM прочитает информацию из файлов классов, он может изменить поведение класса, проанализировать информацию о классе и даже создать новые классы в соответствии с требованиями пользователя.
1. Сначала пройдите тест
cn.bugstack.middleware.monitor.test.ApiTest
private static byte[] generate() {
ClassWriter classWriter = new ClassWriter(0);
// 定义对象头;版本号、修饰符、全类名、签名、父类、实现的接口
classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, "cn/bugstack/demo/asm/AsmHelloWorld", null, "java/lang/Object", null);
// 添加方法;修饰符、方法名、描述符、签名、异常
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
// 执行指令;获取静态属性
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
// 加载常量 load constant
methodVisitor.visitLdcInsn("Hello World ASM!");
// 调用方法
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
// 返回
methodVisitor.visitInsn(Opcodes.RETURN);
// 设置操作数栈的深度和局部变量的大小
methodVisitor.visitMaxs(2, 1);
// 方法结束
methodVisitor.visitEnd();
// 类完成
classWriter.visitEnd();
// 生成字节数组
return classWriter.toByteArray();
}
-
Вышеупомянутый код HelloWorld написан на основе ASM Весь процесс включает в себя: определение класса для создания ClassWriter, установка версии, модификатора, полного имени класса, подписи, родительского класса и реализованного интерфейса, который на самом деле является этим предложением;
public class HelloWorld
-
Дескриптор типа:
Тип Java дескриптор типа boolean Z char C byte B short S int I float F long J double D Object Ljava/lang/Object; int[] [I Object[][] [[Ljava/lang/Object; -
Дескриптор метода:
Объявления методов в исходных файлах дескриптор метода void m(int i, float f) (IF)V int m(Object o) (Ljava/lang/Object;)I int[] m(int i, String s) (ILjava/lang/String;)[I Object m(int[] i) ([I)Ljava/lang/Object; -
Выполнить инструкцию, получить статические свойства. В основном для получения System.out
-
Загрузить постоянную загрузку, вывести наш HelloWorld
methodVisitor.visitLdcInsn("Hello World");
-
Наконец, вызовите метод вывода и установите возврат null, а в конце установите глубину стека операндов и размер локальных переменных.
-
Это выводит
HelloWorld
Разве это не интересно, хотя вы можете подумать, что это слишком сложно кодировать и трудно понять. Однако вы можете установить подключаемый модуль ASM ASM Bytecode Outline в IDEA, который упрощает просмотр того, как общий код обрабатывается при использовании ASM. -
Кроме того, результаты тестирования приведенного выше кода в основном предназначены для создания файла класса и вывода
Hello World ASM!
результат.
2. Мониторинг проектной инженерной структуры
cn-bugstack-middleware-asm
└── src
├── main
│ ├── java
│ │ └── cn.bugstack.middleware.monitor
│ │ ├── config
│ │ │ ├── MethodInfo.java
│ │ │ └── ProfilingFilter.java
│ │ ├── probe
│ │ │ ├── ProfilingAspect.java
│ │ │ ├── ProfilingClassAdapter.java
│ │ │ ├── ProfilingMethodVisitor.java
│ │ │ └── ProfilingTransformer.java
│ │ └── PreMain.java
│ └── resources
│ └── META_INF
│ └── MANIFEST.MF
└── test
└── java
└── cn.bugstack.middleware.monitor.test
└── ApiTest.java
Вышеупомянутая инженерная структура использует структуру ASM для улучшения системного метода, что эквивалентно заполнению информации мониторинга до и после жестко запрограммированного метода записи через структуру. Однако этот процесс передается Javaagent#premain при запуске программы Java.
- MethodInfo — это определение метода, в основном описывающее имя класса, имя метода, описание, входные параметры и информацию о выходных параметрах.
- ProfilingFilter — это информация о конфигурации для мониторинга, в основном для фильтрации некоторых методов, не требующих операций расширения байт-кода, таких как main, hashCode, javax/и т. д.
- ProfilingAspect, ProfilingClassAdapter, ProfilingMethodVisitor, ProfilingTransformer — эти четыре класса в основном являются классами, которые выполняют операции инструментирования байт-кода и выводят результаты мониторинга.
- PreMain предоставляет запись Javaagent, JVM сначала пытается вызвать метод premain в классе агента.
- MANIFEST.MF — это информация о конфигурации, в основном для поиска Premain-Class
Premain-Class: cn.bugstack.middleware.monitor.PreMain
3. Запись класса мониторинга
cn.bugstack.middleware.monitor.PreMain
public class PreMain {
//JVM 首先尝试在代理类上调用以下方法
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ProfilingTransformer());
}
//如果代理类没有实现上面的方法,那么 JVM 将尝试调用该方法
public static void premain(String agentArgs) {
}
}
- Это фиксированный класс метода входа технологии Javaagent, и путь к этому классу необходимо настроить в MANIFEST.MF.
4. Обработка методом байт-кода
cn.bugstack.middleware.monitor.probe.ProfilingTransformer
public class ProfilingTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
try {
if (ProfilingFilter.isNotNeedInject(className)) {
return classfileBuffer;
}
return getBytes(loader, className, classfileBuffer);
} catch (Throwable e) {
System.out.println(e.getMessage());
}
return classfileBuffer;
}
private byte[] getBytes(ClassLoader loader, String className, byte[] classfileBuffer) {
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new ProfilingClassAdapter(cw, className);
cr.accept(cv, ClassReader.EXPAND_FRAMES);
return cw.toByteArray();
}
}
- Используйте базовые классы ASM ClassReader, ClassWriter и ClassVisitor для обработки входящих загрузчиков классов, имен классов, байт-кодов и т. д. и отвечайте за операции по улучшению байт-кода.
- Здесь в основном про классы работы ASM, ClassReader, ClassWriter, ClassVisitor и статьи о программировании байт-кода:Серия статей ASM, Javassist, Byte-bu
5. Разбор метода байт-кода
cn.bugstack.middleware.monitor.probe.ProfilingMethodVisitor
public class ProfilingMethodVisitor extends AdviceAdapter {
private List<String> parameterTypeList = new ArrayList<>();
private int parameterTypeCount = 0; // 参数个数
private int startTimeIdentifier; // 启动时间标记
private int parameterIdentifier; // 入参内容标记
private int methodId = -1; // 方法全局唯一标记
private int currentLocal = 0; // 当前局部变量值
private final boolean isStaticMethod; // true;静态方法,false;非静态方法
private final String className;
protected ProfilingMethodVisitor(int access, String methodName, String desc, MethodVisitor mv, String className, String fullClassName, String simpleClassName) {
super(ASM5, mv, access, methodName, desc);
this.className = className;
// 判断是否为静态方法,非静态方法中局部变量第一个值是this,静态方法是第一个入参参数
isStaticMethod = 0 != (access & ACC_STATIC);
//(String var1,Object var2,String var3,int var4,long var5,int[] var6,Object[][] var7,Req var8)=="(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;IJ[I[[Ljava/lang/Object;Lorg/itstack/test/Req;)V"
Matcher matcher = Pattern.compile("(L.*?;|\\[{0,2}L.*?;|[ZCBSIFJD]|\\[{0,2}[ZCBSIFJD]{1})").matcher(desc.substring(0, desc.lastIndexOf(')') + 1));
while (matcher.find()) {
parameterTypeList.add(matcher.group(1));
}
parameterTypeCount = parameterTypeList.size();
methodId = ProfilingAspect.generateMethodId(new MethodInfo(fullClassName, simpleClassName, methodName, desc, parameterTypeList, desc.substring(desc.lastIndexOf(')') + 1)));
}
//... 一些字节码插桩操作
}
- Когда программа начнет загружаться, каждый метод каждого класса будет отслеживаться. Здесь можно получить имя класса, имя метода, описание входных и выходных параметров метода и т.д.
- Во избежание потери производительности путем передачи параметров (информации о методе) каждый раз при последующем мониторинге и обработке, как правило, для каждого метода здесь создается глобальное антидублирование.
id
,сквозь этоid
Вы можете найти соответствующий метод. - Кроме того, входные и выходные параметры метода, которые можно увидеть здесь, описаны как заданный фрагмент кода,
(II)Ljava/lang/String;
, чтобы мы могли потом парсить параметры, нам нужно дизассемблировать эту строку.
6. Запустите тест
6.1 Настройка параметров ВМ Javaagent
-javaagent:E:\itstack\git\github.com\MonitorDesign\cn-bugstack-middleware-asm\target\cn-bugstack-middleware-asm.jar
- Среда выполнения IDEA настроена на
VM options
, адрес пакета jar настраивается в соответствии с собственным путем.
6.2 Результаты испытаний
监控 - Begin By ASM
方法:cn.bugstack.middleware.test.interfaces.UserController$$EnhancerBySpringCGLIB$$8f5a18ca.queryUserInfo
入参:null 入参类型:["Ljava/lang/String;"] 入数[值]:["aaa"]
出参:Lcn/bugstack/middleware/test/interfaces/dto/UserInfo; 出参[值]:{"address":"天津市东丽区万科赏溪苑14-0000","age":19,"code":"0000","info":"success","name":"虫虫:aaa"}
耗时:54(s)
监控 - End
- По результатам запуска теста мы видим, что после использования ASM-мониторинга нет необходимости жестко кодировать или использовать АОП для работы в коде. В то же время можно отслеживать более полную информацию о выполнении метода, включая тип входного параметра, значение входного параметра и информацию о выходном параметре, а также значение выходного параметра.
- Но вы можете обнаружить, что ASM довольно сложен в работе, особенно в очень сложной логике кодирования, вы можете столкнуться с различными проблемами, поэтому далее мы представим некоторые компоненты, разработанные на основе ASM, эти компоненты также могут выполнять ту же функцию.
5. Джавасист
Javassist — это библиотека с открытым исходным кодом для анализа, редактирования и создания байт-кода Java. Он был создан Сигеру Чиба из отдела математики и компьютерных наук Токийского технологического института. Он присоединился к проекту JBoss Application Server с открытым исходным кодом, чтобы реализовать динамическую структуру «AOP» для JBoss с использованием Javassist для управления байт-кодами.
1. Сначала пройдите тест
cn.bugstack.middleware.monitor.test.ApiTest
public class ApiTest {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("cn.bugstack.middleware.javassist.MathUtil");
// 属性字段
CtField ctField = new CtField(CtClass.doubleType, "π", ctClass);
ctField.setModifiers(Modifier.PRIVATE + Modifier.STATIC + Modifier.FINAL);
ctClass.addField(ctField, "3.14");
// 方法:求圆面积
CtMethod calculateCircularArea = new CtMethod(CtClass.doubleType, "calculateCircularArea", new CtClass[]{CtClass.doubleType}, ctClass);
calculateCircularArea.setModifiers(Modifier.PUBLIC);
calculateCircularArea.setBody("{return π * $1 * $1;}");
ctClass.addMethod(calculateCircularArea);
// 方法;两数之和
CtMethod sumOfTwoNumbers = new CtMethod(pool.get(Double.class.getName()), "sumOfTwoNumbers", new CtClass[]{CtClass.doubleType, CtClass.doubleType}, ctClass);
sumOfTwoNumbers.setModifiers(Modifier.PUBLIC);
sumOfTwoNumbers.setBody("{return Double.valueOf($1 + $2);}");
ctClass.addMethod(sumOfTwoNumbers);
// 输出类的内容
ctClass.writeFile();
// 测试调用
Class clazz = ctClass.toClass();
Object obj = clazz.newInstance();
Method method_calculateCircularArea = clazz.getDeclaredMethod("calculateCircularArea", double.class);
Object obj_01 = method_calculateCircularArea.invoke(obj, 1.23);
System.out.println("圆面积:" + obj_01);
Method method_sumOfTwoNumbers = clazz.getDeclaredMethod("sumOfTwoNumbers", double.class, double.class);
Object obj_02 = method_sumOfTwoNumbers.invoke(obj, 1, 2);
System.out.println("两数和:" + obj_02);
}
}
- Это процесс нахождения области круга и абстрактных классов и методов, сгенерированных Javassist, и запуска результатов.Вы можете видеть, что Javassist в основном использует такие методы, как ClassPool, CtClass, CtField и CtMethod.
- Результаты теста в основном включают создание класса по указанному пути.
cn.bugstack.middleware.javassist.MathUtil
, а также вывести результаты в консоль.
сгенерированный класс
public class MathUtil {
private static final double π = 3.14D;
public double calculateCircularArea(double var1) {
return 3.14D * var1 * var1;
}
public Double sumOfTwoNumbers(double var1, double var3) {
return var1 + var3;
}
public MathUtil() {
}
}
Результаты теста
圆面积:4.750506
两数和:3.0
Process finished with exit code 0
2. Мониторинг проектной инженерной структуры
cn-bugstack-middleware-javassist
└── src
├── main
│ ├── java
│ │ └── cn.bugstack.middleware.monitor
│ │ ├── config
│ │ │ └── MethodDescription.java
│ │ ├── probe
│ │ │ ├── Monitor.java
│ │ │ └── MyMonitorTransformer.java
│ │ └── PreMain.java
│ └── resources
│ └── META_INF
│ └── MANIFEST.MF
└── test
└── java
└── cn.bugstack.middleware.monitor.test
└── ApiTest.java
- Весь фреймворк мониторинга, реализованный с помощью javassist, очень похож по структуре на ASM, но большая часть работы по работе с байт-кодами передается фреймворку javassist, поэтому общая структура кода выглядит проще.
3. Инструментарий метода мониторинга
cn.bugstack.middleware.monitor.probe.MyMonitorTransformer
public class MyMonitorTransformer implements ClassFileTransformer {
private static final Set<String> classNameSet = new HashSet<>();
static {
classNameSet.add("cn.bugstack.middleware.test.interfaces.UserController");
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
try {
String currentClassName = className.replaceAll("/", ".");
if (!classNameSet.contains(currentClassName)) { // 提升classNameSet中含有的类
return null;
}
// 获取类
CtClass ctClass = ClassPool.getDefault().get(currentClassName);
String clazzName = ctClass.getName();
// 获取方法
CtMethod ctMethod = ctClass.getDeclaredMethod("queryUserInfo");
String methodName = ctMethod.getName();
// 方法信息:methodInfo.getDescriptor();
MethodInfo methodInfo = ctMethod.getMethodInfo();
// 方法:入参信息
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
CtClass[] parameterTypes = ctMethod.getParameterTypes();
boolean isStatic = (methodInfo.getAccessFlags() & AccessFlag.STATIC) != 0; // 判断是否为静态方法
int parameterSize = isStatic ? attr.tableLength() : attr.tableLength() - 1; // 静态类型取值
List<String> parameterNameList = new ArrayList<>(parameterSize); // 入参名称
List<String> parameterTypeList = new ArrayList<>(parameterSize); // 入参类型
StringBuilder parameters = new StringBuilder(); // 参数组装;$1、$2...,$$可以获取全部,但是不能放到数组初始化
for (int i = 0; i < parameterSize; i++) {
parameterNameList.add(attr.variableName(i + (isStatic ? 0 : 1))); // 静态类型去掉第一个this参数
parameterTypeList.add(parameterTypes[i].getName());
if (i + 1 == parameterSize) {
parameters.append("$").append(i + 1);
} else {
parameters.append("$").append(i + 1).append(",");
}
}
// 方法:出参信息
CtClass returnType = ctMethod.getReturnType();
String returnTypeName = returnType.getName();
// 方法:生成方法唯一标识ID
int idx = Monitor.generateMethodId(clazzName, methodName, parameterNameList, parameterTypeList, returnTypeName);
// 定义属性
ctMethod.addLocalVariable("startNanos", CtClass.longType);
ctMethod.addLocalVariable("parameterValues", ClassPool.getDefault().get(Object[].class.getName()));
// 方法前加强
ctMethod.insertBefore("{ startNanos = System.nanoTime(); parameterValues = new Object[]{" + parameters.toString() + "}; }");
// 方法后加强
ctMethod.insertAfter("{ cn.bugstack.middleware.monitor.probe.Monitor.point(" + idx + ", startNanos, parameterValues, $_);}", false); // 如果返回类型非对象类型,$_ 需要进行类型转换
// 方法;添加TryCatch
ctMethod.addCatch("{ cn.bugstack.middleware.monitor.probe.Monitor.point(" + idx + ", $e); throw $e; }", ClassPool.getDefault().get("java.lang.Exception")); // 添加异常捕获
return ctClass.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- По сравнению с реализацией ASM общий метод мониторинга аналогичен, поэтому здесь показаны только различия.
- Благодаря работе Javassist, в основном для достижения
ClassFileTransformer
Метод преобразования интерфейса, в котором байт-код получается и соответственно обрабатывается. - Процесс обработки включает в себя: получение класса, получение метода, получение информации о входных параметрах, получение информации о выходных параметрах, создание уникального идентификатора для метода, а затем запуск передних и задних операций расширения метода. добавьте код мониторинга в блок методов.
- Наконец вернуть информацию о байт-коде
return ctClass.toBytecode();
Теперь ваш вновь добавленный байт-код может быть загружен и обработан программой.
4. Запустите тест
4.1 Настройка параметров ВМ Javaagent
-javaagent:E:\itstack\git\github.com\MonitorDesign\cn-bugstack-middleware-javassist\target\cn-bugstack-middleware-javassist.jar
- Среда выполнения IDEA настроена на
VM options
, адрес пакета jar настраивается в соответствии с собственным путем.
4.2 Результаты испытаний
监控 - Begin By Javassist
方法:cn.bugstack.middleware.test.interfaces.UserController$$EnhancerBySpringCGLIB$$8f5a18ca.queryUserInfo
入参:null 入参类型:["Ljava/lang/String;"] 入数[值]:["aaa"]
出参:Lcn/bugstack/middleware/test/interfaces/dto/UserInfo; 出参[值]:{"address":"天津市东丽区万科赏溪苑14-0000","age":19,"code":"0000","info":"success","name":"虫虫:aaa"}
耗时:46(s)
监控 - End
- Судя по результатам тестирования, это то же самое, что и инструментарий ASM с байт-кодом, который может отслеживать информацию о выполнении системы. Но такая структура упростит процесс разработки и упростит его контроль.
6. Байт-приятель
В октябре 2015 года Byte Buddy был удостоен награды Duke's Choice Award от Oracle. Награда присуждается Byte Buddy за «огромные инновации в технологии Java». Для нас большая честь получить эту награду, и мы благодарим всех пользователей, которые помогли Byte Buddy добиться успеха, и всех остальных. Мы действительно это ценим!
Byte Buddy
это библиотека генерации кода и обработки дляJava
Создание и модификация среды выполнения приложенийJava
class без помощи компилятора. КромеJava
В дополнение к утилитам генерации кода, которые поставляются с библиотекой классов,Byte Buddy
Также разрешено создавать произвольные классы, и они не ограничиваются реализацией интерфейсов для создания прокси-серверов времени выполнения. также,Byte Buddy
Предоставляет удобный API, который можно использоватьJava
Прокси или изменение классов вручную в процессе сборки.
- Не нужно понимать инструкции байт-кода, вы можете легко манипулировать байт-кодом, управлять классами и методами с помощью простого API.
- Поддерживается Java 11, библиотека легковесна и зависит только от API посетителя библиотеки парсера байт-кода Java ASM, который сам по себе не требует никаких дополнительных зависимостей.
- По сравнению с динамическим прокси JDK, cglib и Javassist Byte Buddy имеет определенные преимущества в производительности.
1. Сначала пройдите тест
cn.bugstack.middleware.monitor.test.ApiTest
public class ApiTest {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
String helloWorld = new ByteBuddy()
.subclass(Object.class)
.method(named("toString"))
.intercept(FixedValue.value("Hello World!"))
.make()
.load(ApiTest.class.getClassLoader())
.getLoaded()
.newInstance()
.toString();
System.out.println(helloWorld);
}
}
- Это кейс «Hello World!», сгенерированный с использованием синтаксиса ByteBuddy, и результатом его работы является одна строка,
Hello World!
, основная функция всего блока кода заключается вmethod(named("toString"))
,оказатьсяtoStringметод, а затем путем перехватаintercept
, который устанавливает возвращаемое значение этого метода.FixedValue.value("Hello World!")
. На самом деле, основной способ добраться сюда — черезByte-buddy
и, наконец, загрузите, инициализируйте и вызовите выходные данные.
Результаты теста
Hello World!
Process finished with exit code 0
2. Мониторинг проектной инженерной структуры
cn-bugstack-middleware-bytebuddy
└── src
├── main
│ ├── java
│ │ └── cn.bugstack.middleware.monitor
│ │ ├── MonitorMethod
│ │ └── PreMain.java
│ └── resources
│ └── META_INF
│ └── MANIFEST.MF
└── test
└── java
└── cn.bugstack.middleware.monitor.test
└── ApiTest.java
- Это мой личный любимый фреймворк из-за его простоты в эксплуатации, который может использовать операции с расширенным байт-кодом, как обычный бизнес-код. Из текущей структуры проекта видно, что количество классов кода становится все меньше и меньше.
3. Инструментарий метода мониторинга
cn.bugstack.middleware.monitor.MonitorMethod
public class MonitorMethod {
@RuntimeType
public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable, @AllArguments Object[] args) throws Exception {
long start = System.currentTimeMillis();
Object resObj = null;
try {
resObj = callable.call();
return resObj;
} finally {
System.out.println("监控 - Begin By Byte-buddy");
System.out.println("方法名称:" + method.getName());
System.out.println("入参个数:" + method.getParameterCount());
for (int i = 0; i < method.getParameterCount(); i++) {
System.out.println("入参 Idx:" + (i + 1) + " 类型:" + method.getParameterTypes()[i].getTypeName() + " 内容:" + args[i]);
}
System.out.println("出参类型:" + method.getReturnType().getName());
System.out.println("出参结果:" + resObj);
System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("监控 - End\r\n");
}
}
}
-
@Origin
, который используется для перехвата исходного метода, чтобы можно было получить соответствующую информацию в методе. - Информация в этой части относительно полная, в частности получено количество и тип параметров, так что вывод цикла может быть выполнен при последующей обработке параметров.
Общие аннотации
В дополнение к вышеупомянутым аннотациям, используемым для получения информации о выполнении метода, Byte Buddy также предоставляет множество других аннотаций. следующее;
аннотация | инструкция |
---|---|
@Argument | привязать один параметр |
@AllArguments | привязать массив всех параметров |
@This | Текущий перехваченный, динамически сгенерированный объект |
@Super | Объект родительского класса текущего перехваченного, динамически сгенерированного объекта |
@Origin | Параметры, которые могут быть привязаны к следующим типам: Метод Исходный вызываемый метод Конструктор Исходный вызываемый конструктор Класс Текущий динамически созданный класс MethodHandle MethodType String Возвращаемое значение toString() динамического класса int Модификатор динамического метод |
@DefaultCall | вызовите метод по умолчанию вместо суперметода |
@SuperCall | Метод, используемый для вызова версии родительского класса |
@Super | Внедрить объект супертипа, который может быть интерфейсом, для вызова любого его метода. |
@RuntimeType | Может использоваться для возвращаемых значений и параметров, чтобы предложить ByteBuddy отключить строгую проверку типов. |
@Empty | Значение по умолчанию для типа введенного параметра |
@StubValue | Введите значение-заглушку. Для методов, которые возвращают ссылки и void, вставьте null; для методов, которые возвращают примитивные типы, вставьте 0 |
@FieldValue | Введите значение поля перехваченного объекта |
@Morph | Аналогичен @SuperCall, но позволяет указывать параметры вызова |
Общий API ядра
-
ByteBuddy
- Начальный класс для потокового API
- Обеспечить подклассы/переопределение/перебазирование для перезаписи байт-кода
- Все операции полагаются на DynamicType.Builder для создания неизменяемых объектов.
-
ElementMatchers(ElementMatcher)
- Предоставляет набор служебных классов для сопоставления элементов (named/any/nameEndsWith и т. д.)
- ElementMatcher (предоставляет способ сопоставления типов, методов, полей, аннотаций, аналогичный Predicate)
- Junction выполняет и/или операции над несколькими ElementMatchers
-
DynamicType
(Динамическая типизация, начало всех манипуляций с байт-кодом, очень интересно)
- Выгружен (динамически созданный байт-код еще не загружен в виртуальную машину, для загрузки требуется загрузчик классов)
- Загружено (после загрузки в jvm анализируется представление класса)
- По умолчанию (реализация DynamicType по умолчанию, выполнение соответствующих фактических операций)
-
`Implementation
(для обеспечения реализации динамических методов)
- FixedValue (вызов метода возвращает фиксированное значение)
- MethodDelegation (делегирование вызова метода, поддерживает два метода: вызов статического метода класса, вызов метода метода экземпляра объекта)
-
Builder
(Для создания DynamicType соответствующие интерфейсы и реализация будут подробно объяснены позже)
- MethodDefinition
- FieldDefinition
- AbstractBase
4. Настройте метод входа
cn.bugstack.middleware.monitor.PreMain
public class PreMain {
//JVM 首先尝试在代理类上调用以下方法
public static void premain(String agentArgs, Instrumentation inst) {
AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
return builder
.method(ElementMatchers.named("queryUserInfo")) // 拦截任意方法
.intercept(MethodDelegation.to(MonitorMethod.class)); // 委托
};
new AgentBuilder
.Default()
.type(ElementMatchers.nameStartsWith(agentArgs)) // 指定需要拦截的类 "cn.bugstack.demo.test"
.transform(transformer)
.installOn(inst);
}
//如果代理类没有实现上面的方法,那么 JVM 将尝试调用该方法
public static void premain(String agentArgs) {
}
}
- Метод premain в основном делегирует использование реализованного MonitorMethod, а также устанавливает в метод метод перехвата, который также может перейти в путь к классам и так далее.
5. Запустите тест
5.1 Настройка параметров ВМ Javaagent
-javaagent:E:\itstack\git\github.com\MonitorDesign\cn-bugstack-middleware-bytebuddy\target\cn-bugstack-middleware-bytebuddy.jar
- Среда выполнения IDEA настроена на
VM options
, адрес пакета jar настраивается в соответствии с собственным путем.
5.2 Результаты испытаний
监控 - Begin By Byte-buddy
方法名称:queryUserInfo
入参个数:1
入参 Idx:1 类型:java.lang.String 内容:aaa
出参类型:cn.bugstack.middleware.test.interfaces.dto.UserInfo
出参结果:cn.bugstack.middleware.test.interfaces.dto.@214b199c
方法耗时:1ms
监控 - End
- Byte-buddy является самым простым и удобным для работы среди нескольких фреймворков байт-кода во всем нашем процессе тестирования, а также очень легко расширять информацию. Весь процесс так же прост, как и использование АОП, но удовлетворяет потребности в ненавязчивом мониторинге.
- Поэтому при использовании фреймворка байт-кода вы можете рассмотреть возможность использования Byte-buddy, очень полезного фреймворка байт-кода.
7. Резюме
- Применение программирования байт-кода ASM очень широко, но в обычное время его можно не увидеть, потому что оно используется в сочетании с другими фреймворками в качестве вспомогательных услуг. Есть еще много подобных технологий, таких как javassit, Cglib, jacoco и т. д.
- Javassist часто используется в некоторых компонентах для полносвязного мониторинга.Он может использовать кодирование для работы с расширениями байт-кода или может обрабатываться как ASM.
- Byte-buddy — это очень удобный фреймворк, который в настоящее время используется все шире и шире, а сложность обучения для начала работы — самая низкая среди нескольких фреймворков. Помимо введения в использование кейса в этой главе, вы также можете посетить официальный веб-сайт:
https://bytebuddy.net
, чтобы узнать больше оByte Buddy
Содержание. - Весь исходный код для этой главы был загружен на GitHub:GitHub.com/заместитель комиссара/…