Какую-то повторяющуюся работу, если ты можешь передать ее программе, ты никогда не сделаешь сам, это дух программиста.
0 написано впереди
Эта статья — последняя в этой серии, в этой статье я научу вас использовать ASM, чтобы делать что-то полезное в реальной разработке. В том числе, как изменить toString, как упоминалось ранее, выполнить некоторую десенсибилизацию.
1 Instrumentation
ПредыдущийASM байт-кодаНаучите вас изменить Bytecode? Я считаю, что студенты, которые видели, что у него уже есть определенное впечатление от того, как изменить байт-код, но есть проблема. В предыдущем разделе мы использовали его в память, чтение .Class Файл. Это не влияет на класс, который мы используем в фактическом JVM. Это действительно относительно сложная проблема для решения, по крайней мере, до JDK1.5, когда Java.lang.instrument родился в JDK1.5. Он освобождает функцию прибора Java от нативного кода, чтобы он мог решить проблему в пути кода Java. Java.lang.instrument - это реализация версии Java, представленная на вершине JVM TI. Основная функция, предоставленная приборостроением, является изменение поведения классов в JVM. Существует два способа применить применение приборов в Java SE6, Premain (командную строку) и AgentMain (Runtime).
1.1 premain
Мы знаем, что программы Java должны быть запущены через основной метод, и Premain означает, что Premain будет работать до главных началов. Сначала напишите класс Java, а затем включите один из следующих двух методов:
public static void premain(String agentArgs, Instrumentation inst);
public static void premain(String agentArgs);
Когда два вышеупомянутых существуют одновременно, 1 имеет более высокий приоритет, чем 2. Этот метод имеет два параметра:
- agentArgs: это параметр, передаваемый в основной функции.Строковый массив параметров, переданных здесь, должен быть проанализирован самостоятельно.
- Инструментарий: это наше ядро, интерфейс, определенный в пакете инструментов, а также основная часть этого пакета, которая концентрирует почти все функциональные методы, такие как преобразования и операции, определяемые классом, и так далее.
Затем реализуем интерфейс ClassFileTransformer.ClassFileTransform используется для преобразования класса. Его преобразование интерфейса является ключом к преобразованию класса, а его четвертый входной параметр также является ключом к нашей последующей модификации байт-кода:
public class ClassTransformerImpl implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("类的名字为:" + className);
return classfileBuffer;
}
}
В приведенном выше преобразовании мы распечатаны имена всех классов, Вернувшись в наш премайн наш метод следующим образом:
public class PerfMonAgent {
static private Instrumentation inst = null;
public static void premain(String agentArgs, Instrumentation _inst) {
System.out.println("PerfMonAgent.premain() was called.");
// Initialize the static variables we use to track information.
inst = _inst;
// Set up the class-file transformer.
ClassFileTransformer trans = new ClassTransformerImpl();
System.out.println("Adding a PerfMonXformer instance to the JVM.");
//将我们自定义的类转换器传入进去
inst.addTransformer(trans);
}
}
Мы можем изменить вышеуказанный метод premain следующим образом:
public class PerfMonAgent {
static private Instrumentation inst = null;
public static void premain(String agentArgs, Instrumentation _inst) {
System.out.println("PerfMonAgent.premain() was called.");
// Initialize the static variables we use to track information.
inst = _inst;
// Set up the class-file transformer.
ClassFileTransformer trans = new ClassTransformerImpl();
System.out.println("Adding a PerfMonXformer instance to the JVM.");
//将我们自定义的类转换器传入进去
inst.addTransformer(trans);
}
}
Аспект кода был определен. Далее вам нужно его упаковать.Если вы не используете Maven, то вам нужно добавить «Premain-Class» в атрибут манифеста, чтобы указать класс Java с написанным в нем premain. Если вы используете maven, вы можете использовать
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>instrument.PerfMonAgent</Premain-Class>
//这个是用来引入第三方包,需要在这里引入 <Boot-Class-Path>/Users/lizhao/.m2/repository/org/ow2/asm/asm/5.0.4/asm-5.0.4.jar</Boot-Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
Наконец, вы готовы к использованию, вы можете написать класс с основным методом по желанию:
java -javaagent:jar 文件的位置 [= 传入 premain 的参数 ]
Если это компилятор идей, вы можете указать его в конфигурации vm.
Затем запустите основной метод, он выведет имя вашего класса.1.2 agentmain
Premain — это прокси-метод, предоставляемый Java SE5, который преподнес разработчикам множество сюрпризов, но некоторые должны остаться без изменений, потому что в командной строке необходимо указать jar-прокси, а прокси-класс должен запускаться перед основным методом. Поэтому разработчикам необходимо подтвердить логику обработки и содержание параметров агента перед применением, что в некоторых случаях сложно. Например, в обычной производственной среде функция агента обычно не включена.В конце концов, java SE6 предоставляет нам agentmain для динамического изменения без установки агента. В документации по JavaSE6 разработчики могут не увидеть четкого введения в документации, относящейся к пакету java.lang.instrument, не говоря уже о конкретном примере применения agnetmain. Однако в новых функциях Java SE 6 есть незаметное место, раскрывающее использование agentmain. Это API-интерфейс Attach, предоставляемый в Java SE 6.
Attach API — это не стандартный API Java, а набор расширений API, предоставляемых Sun, который используется для «присоединения» (присоединения) программы агентского инструмента к целевой JVM. С его помощью разработчики могут легко контролировать JVM и запускать дополнительный агент. Я не буду здесь представлять, как работает attach api, одним словом, полагаться на attach api во всем процессе все равно обременительно, заинтересованные студенты могут прочитать его самостоятельно: https://www.ibm.com/developerworks/cn/java/j-lo-jse61/
1.3 Резюме
С нашим инструментом после того, как мы нашли источник нашего класса, полагаясь на знания, мы можем произвольно модифицировать код байта.
2. Десенсибилизировать toString вручную
2.1 Дизайн
Прежде всего, нам нужно разработать то, что мы собираемся делать дальше, чтобы у нас была итоговая сумма, чтобы мы не могли паниковать.
2.1.1 Цели
Измените байт-код toString, чтобы функция toString(), которая ранее печатала открытый текст, могла быть десенсибилизирована для наших пользовательских нужд.
2.1.2 Настройка
Я планирую настроить десенсибилизацию по аннотации, @ Desfiled отметьте поле, @ Desenstized, чтобы отметить десенсибированный класс, наследию базового штрафа.
2.2 Прежде чем начать
Прежде чем начать, дайте понять, что вы должны убедиться, что инструменты готовы.
- Плагин asm скачан?
- Был ли представлен пакет asm maven?
- Следили ли за моим официальным аккаунтом? Если все сделано, мы можем сделать следующее, мы сначала определяем наши аннотации:
@java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Inherited
public @interface DesFiled {
/**
* 加密类型
* @return
*/
public Class<? extends BaseDesFilter> value() default BaseDesFilter.class;
}
@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public @interface Desensitized {
}
Существует также наш интерфейс фильтра десенсибилизации, а его класс реализации используется для десенсибилизации поля мобильного телефона, который на самом деле преобразование:
public interface BaseDesFilter <T>{
default T desc(T needDesc){
return needDesc;
};
}
public class MobileDesFilter implements BaseDesFilter {
//不同类型转换
@Override
public Object desc(Object needDesc) {
if(needDesc instanceof Long ){
needDesc = String.valueOf(needDesc);
}
if (needDesc instanceof String){
return DesensitizationUtil.mobileDesensitiza((String) needDesc);
}
//如果这个时候是枚举类,todo
return needDesc;
}
}
Тогда мы пишем класс для десенсибилизации:
@Desensitized
public class StreamDemo1 {
@DesFiled(MobileDesFilter.class)
private String name;
private String idCard;
@DesFiled(AddressDesFilter.class)
private List<String> mm;
@Override
public String toString() {
return "StreamDemo1{" +
"name='" + name + '\'' +
", idCard='" + idCard + '\'' +
", mm=" + mm +
'}';
}
}
В это время ваш asm-плагин может показать свою мощь, (не только здесь, если будете разрабатывать asm-связанный в будущем, используйте плагин, чтобы увидеть его исходный код, а потом сравнить), здесь мы генерируем версию asm-кода через asm-плагин. В это время можно сохранить скриншот, а затем вручную модифицировать метод toString:
@Override
public String toString() {
return "StreamDemo1{" +
"name='" + DesFilterMap.getByClassName("MobileDesFilter").desc(name) + '\'' +
", idCard='" + idCard + '\'' +
", mm=" + mm +
'}';
}
Сгенерированный с помощью плагина, здесь мы можем узнать, что нам нужно добавить в ASM, если мы хотим добавить метод десенсибилизации.
Мы видим, что между добавлениями двух графиков есть некоторые различия (здесь следует объяснить, что компилятор оптимизирует знак + в добавление StringBuilder)Что нам нужно сделать, так это заменить красную рамку на втором изображении на красную рамку на первой картинке. Проще говоря, первое изображение сначала получает ссылку this, а затем получает поле. Второе изображение — сначала получить ссылку на метод десенсибилизации, а затем передать this.name для десенсибилизации.
Теперь мы знаем, что нам нужно сделать, на данный момент нам вообще не нужно смотреть на следующие детали, мы можем попробовать сами и посмотреть, как этого добиться.
2.2 Стартовые руки
Сначала определите преобразователь класса:
public class PerfMonXformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] transformed = null;
System.out.println("Transforming " + className);
ClassReader reader = new ClassReader(classfileBuffer);
//自动计算栈帧
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//选择支持Java8的asm5
ClassVisitor classVisitor = new DesClassVistor(Opcodes.ASM5,classWriter);
reader.accept(classVisitor,ClassReader.SKIP_DEBUG);
return classWriter.toByteArray();
}
}
В преобразователе классов мы использовали наши знания ASM из предыдущего раздела, а затем настраиваем ClassVisitor под названием DesClassVistor для обработки класса доступа, а затем генерируем массив байтов через наш classWriter:
public class DesClassVistor extends ClassVisitor implements Opcodes{
private static final String classAnnotationType = "L"+ Desensitized.class.getName().replaceAll("\\.","/")+";";
/**
* 用来标志是否进行脱敏
*/
private boolean des;
private String className;
private Map<String, FiledInfo> filedMap = new HashMap<>();
public DesClassVistor(int i) {
super(i);
}
public DesClassVistor(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public void visit(int jdkVersion, int acc, String className, String generic, String superClass, String[] superInterface) {
this.className = className;
super.visit(jdkVersion, acc, className, generic, superClass, superInterface);
}
/**
*
* @param type 注解类型
* @param seeing 可见性
* @return
*/
@Override
public AnnotationVisitor visitAnnotation(String type, boolean seeing) {
if (classAnnotationType.equals(type)){
this.des = true;
}
return super.visitAnnotation(type, seeing);
}
/**
*
* @param acc 访问权限
* @param name 字段名字
* @param type 类型
* @param generic 泛型
* @param defaultValue 默认值
* @return
*/
@Override
public FieldVisitor visitField(int acc, String name, String type, String generic, Object defaultValue) {
FieldVisitor fv = super.visitField(acc, name, type, generic, defaultValue);
if (des == false || acc >= ACC_STATIC){
return fv;
}
FiledInfo filedInfo = new FiledInfo(acc, name, type, generic, defaultValue);
filedMap.put(name, filedInfo);
FieldVisitor testFieldVisitor = new DesFieldVisitor(filedInfo,fv);
return testFieldVisitor;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (this.des == false || !"toString".equals(name)){
return mv;
}
MethodVisitor testMethodVistor = new DesMethodVistor(mv, filedMap);
return testMethodVistor;
}
}
Здесь переписаны еще три важных метода:
- visitAnnotation: используется для определения наличия аннотации @Desensitized, если да, установите des=true, чтобы указать, что аннотация включена.
- visitField: используется для преобразования поля в asm в свой собственный настраиваемый FieldInfo и занесения его в карту, что удобно для последующей обработки, и передачи поля в пользовательский DesFieldVisitor для обработки поданного
- visitMethod: используется для помещения метода toString в asm в пользовательский DesMethodVistor для обработки метода toString.
Существует следующий код для обработки файла:
public class DesFieldVisitor extends FieldVisitor {
private static final String desFieldAnnotationType = "L"+ DesFiled.class.getName().replaceAll("\\.","/")+";";
private FiledInfo info;
public DesFieldVisitor(int i) {
super(i);
}
public DesFieldVisitor(int i, FieldVisitor fieldVisitor) {
super(i, fieldVisitor);
}
public DesFieldVisitor(FiledInfo filedInfo, org.objectweb.asm.FieldVisitor fv) {
super(Opcodes.ASM5, fv);
info = filedInfo;
}
@Override
public AnnotationVisitor visitAnnotation(String s, boolean b) {
AnnotationVisitor av = super.visitAnnotation(s, b);
if (!desFieldAnnotationType.equals(s)){
return av;
}
info.setDes(true);
AnnotationVisitor avAdapter = new DesTypeAnnotationAdapter(Opcodes.ASM5, av, this.info);
return avAdapter;
}
}
Переписывая VisitanNotation, определяется получение аннотации DESFILED и аннотации.
public class DesMethodVistor extends MethodVisitor implements Opcodes{
Map<String, FiledInfo> filedMap;
public DesMethodVistor(int i) {
super(i);
}
public DesMethodVistor(int i, MethodVisitor methodVisitor) {
super(i, methodVisitor);
}
public DesMethodVistor(MethodVisitor mv, Map<String, FiledInfo> filedMap) {
super(ASM5, mv);
this.filedMap = filedMap;
}
@Override
public void visitVarInsn(int opcode, int var) {
if (!(opcode == Opcodes.ALOAD && var == 0)){
super.visitVarInsn(opcode, var);
}
}
/**
* 添加过滤逻辑
* @param opcode
* @param owner
* @param name
* @param desc
*/
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
FiledInfo filedInfo = filedMap.get(name);
if (filedInfo.isNotDes()){
super.visitVarInsn(ALOAD, 0);
super.visitFieldInsn(opcode, owner, name, desc);
return;
}
mv.visitLdcInsn(filedInfo.getFilterClass().getName());
mv.visitMethodInsn(INVOKESTATIC, ASMUtil.getASMOwnerByClass(DesFilterMap.class), "getByClassName", "(Ljava/lang/String;)Lasm/filter/BaseDesFilter;", false);
super.visitVarInsn(ALOAD, 0);
super.visitFieldInsn(opcode, owner, name, desc);
mv.visitMethodInsn(INVOKEINTERFACE, ASMUtil.getASMOwnerByClass(BaseDesFilter.class), "desc", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
mv.visitMethodInsn(INVOKESTATIC, ASMUtil.getASMOwnerByClass(String.class), "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", true);
}
}
Десенсибилизированный байт-код преобразуется путем переопределения метода visitFieldInsn. Конкретный код может относиться к моемуasm-log, настройте параметры виртуальной машины в StreamDemo и выполните основной метод. Обратитесь к моему коду:
@Desensitized
public class StreamDemo1 {
@DesFiled(MobileDesFilter.class)
private String name;
private String idCard;
@DesFiled(AddressDesFilter.class)
private List<String> mm;
@Override
public String toString() {
return "StreamDemo1{" +
"name='" + name + '\'' +
", idCard='" + idCard + '\'' +
", mm=" + mm +
'}';
}
public static void main(String[] args) throws Exception {
StreamDemo1 streamDemo1 = new StreamDemo1();
streamDemo1.setName("18428368642");
streamDemo1.setIdCard("22321321321");
streamDemo1.setMm(Arrays.asList("北京是朝阳区打撒所大所大","北京是朝阳区打撒所大所大"));
System.out.println(streamDemo1);
}
}
Аннотации записываются в классе и переменных класса. Один из них — это класс десенсибилизации, который использует номер мобильного телефона, а другой — класс десенсибилизации, который использует адрес. Когда основной метод выполняется, вывод выглядит следующим образом:
StreamDemo1{name='184****8642', idCard='22321321321', mm=[北京是朝阳区打*****, 北京是朝阳区打*****]}
Это избавит вас от необходимости тратить свое драгоценное время на многократное обращение к каждому классу и модификацию toString, что на самом деле слишком неэффективно.Как программисту, вам нужно иметь свой собственный хакерский дух.Вы не должны делать это сами. .
2.3 Размышление после окончания
Использование байт-кода в качестве инструмента действительно многому научило. По крайней мере, в будущем это будет очень полезно для понимания байт-кода и некоторых Java для обработки синтаксического сахара, но этот инструмент не очень общий. Вам нужно настроить агент или вы используете прикрепить api, что довольно проблематично для бизнес-конфигурации. Таким образом, мы можем использовать другие технологии для завершения наших инструментов, такие как процессоры аннотаций для изменения абстрактного синтаксического дерева, которое менее навязчиво для бизнеса, такого как Lombok.
В то же время роль ASM заключается не только в том, чтобы соответствовать инструменту, вы можете посмотреть исходный код аспекта cglib или посмотреть исходный код fastjson, вы можете изменить его байт-код на новые другие классы в соответствии с классами. уже загружен в jvm, здесь может быть прокси-класс или совершенно новый класс.
Наконец
В силу своего ограниченного уровня, особенно при описании этого относительно непопулярного знания, я не могу его очень хорошо абстрагировать.Надеюсь, всем понятно и понятно.В то же время я также надеюсь, что после прочтения вы сможете сделать небольшой инструмент про asm самостоятельно, что может занять время, чтобы воспроизвести метод, и это также может быть унифицированное управление транзакциями.
Изначально я планировал сразу же написать туториал по модификации синтаксического дерева, хотел научить вас вручную создавать ломбок (необходимый артефакт java), но обнаружил, что статьи с относительно редкими знаниями действительно сложны для понимания , а модифицировать синтаксическое дерево скорее чем байткод.Это немного сложнее, документов меньше, да и работаю я последнее время, поэтому пишу только до утра после выхода с работы. чувствую, что я могу абстрагировать более сложные точки знаний в простые, поэтому я решил пока не писать их. Если вам интересен принцип Ломбока или вам интересно, как реализовать свой собственный Ломбок, вы можете обратиться к моемуslothlog github(Кстати, попросите поставить звездочку) Здесь много мест, отмеченных комментариями. Если вам что-то непонятно, вы можете подписаться на мой публичный аккаунт и добавить меня в WeChat для личного общения.
Если вы считаете, что эта статья полезна для вас, или вы хотите получить статьи из последующих глав заранее, или если у вас есть какие-либо вопросы и вы хотите предоставить бесплатный VIP-сервис 1 на 1, вы можете подписаться на мою официальную учетную запись, и вы можете получить сотни G новейшего обучения java бесплатно Информационные видео, а так же свежие материалы интервью, ваше внимание и пересылка - самая большая поддержка для меня, O(∩_∩)O: