bytebuddy实现原理分析 &源码分析 (三)- advice 详解

advice详解

  • 八、advice
    • 8.1 AsmVisitorWrapper
      • 8.1.1 ForDeclareFields
        • 8.1.1.1 Entry
        • 8.1.1.2 DispatchingVisitor
        • 8.1.1.3 ForDeclaredField 的内部实现
        • 8.1.4 看一个子类的实现
      • 8.1.2 ForDeclaredMethods
      • 8.1.3 Compound
    • 8.2 AsmVisitorWrapper 的实现类
    • 8.3 Advice :最核心的类
      • 8.3.1 ArgumentHandler
        • 8.3.1.1 ForAdvice
          • 1 Default 类
          • 2 ForMethodEnter
          • 2 ForMethodEnter
        • 8.3.1.2 ForInstrumentedMethod
          • 1 Default
          • 2 Simple
          • 3 Copying
        • 8.3.1.3 Factory
      • 8.3.2 MethodSizeHandler
      • 8.3.3 StackMapFrameHandler
      • 8.3.4 ExceptionHandler
      • 8.3.5 PostProcessor
      • 8.3.6 Delegator
      • 8.3.7 OffsetMapping
        • 8.3.7.1 Target 类
        • 8.3.7.2 其他 OffsetMapping的实现类
      • 8.4.1 Dispatcher
        • 8.4.1.3 SuppressionHandler
          • 1 Bound
          • 2. Suppressing
        • 8.4.1.4 RelocationHandler
        • 8.4.1.2 Unresolved
        • 8.4.1.1 Resolved
          • 1 Bound
          • 2 ForMethodEnter
          • 3 ForMethodExit
          • 4 AbstractBase
      • 8.3.9 AdviceVisitor
    • 8.3 注解详解
      • 8.3.2 demo

bytebuddy实现原理分析 &源码分析(一)
bytebuddy实现原理分析 &源码分析 (二)
bytebuddy实现原理分析 &源码分析 (三)
bytebuddy实现原理分析 &源码分析 (四)

八、advice

增强,切面编程。
asm入门
asm手册
大致了解了asm,就可以知道Visitor是asm进行读写解析.class字节码文件的核心api。切面的原理就是匹配所有的方法,在方法前后加入额外的方法,AdviceAdapter类提供了onMethodEnteronMethodExit两个方法,实现就可以做到简单的切面。

bytebuddy 的增强就是基于 asm的core API然后提供了一些列注解,方法用户使用。
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第1张图片

8.1 AsmVisitorWrapper

就是对visitor的一个封装。
visitor以分为 ClassReaderClassWriter

首先 提供了一个字段 flags:代表了标志。
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第2张图片

核心方法

  • int mergeWriter(int flags); 合并写标记 flags|foo
  • int mergeReader(int flags); 合并读标记
  • ClassVisitor wrap(TypeDescription instrumentedType,
    ClassVisitor classVisitor,
    Implementation.Context implementationContext,
    TypePool typePool,
    FieldList fields,
    MethodList methods,
    int writerFlags,
    intreaderFlags);
    把一个DynamicType定义的类,使用ClassVisitor生成字节码。
    instrumentedType ,被修改的类型;implementationContext,就是定义了改动的字节码实现;TypePool 缓存池;MethodList 方法列表;

8.1.1 ForDeclareFields

bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第3张图片

8.1.1.1 Entry

  • matcher 用来命中的对象
  • FieldVisitorWrapper 的 封装了要被修改filed对应的Vistor。

这个类的作用是报存 matcher和visitor的对应关系。使用matcher命中被修改的区域,visitor来应用修改逻辑。

        protected static class Entry implements ElementMatcher<FieldDescription.InDefinedShape>, FieldVisitorWrapper {

            /**
             * The matcher to identify fields to be wrapped.
             */
            private final ElementMatcher<? super FieldDescription.InDefinedShape> matcher;

            /**
             * The field visitor wrapper to be applied if the given matcher is matched.
             */
            private final List<? extends FieldVisitorWrapper> fieldVisitorWrappers;

            /**
             * Creates a new entry.
             *
             * @param matcher              The matcher to identify fields to be wrapped.
             * @param fieldVisitorWrappers The field visitor wrapper to be applied if the given matcher is matched.
             */
            protected Entry(ElementMatcher<? super FieldDescription.InDefinedShape> matcher, List<? extends FieldVisitorWrapper> fieldVisitorWrappers) {
                this.matcher = matcher;
                this.fieldVisitorWrappers = fieldVisitorWrappers;
            }

            /**
             * {@inheritDoc}
             */
            public boolean matches(FieldDescription.InDefinedShape target) {
                return target != null && matcher.matches(target);
            }

            /**
             * {@inheritDoc}
             */
            public FieldVisitor wrap(TypeDescription instrumentedType, FieldDescription.InDefinedShape fieldDescription, FieldVisitor fieldVisitor) {
                for (FieldVisitorWrapper fieldVisitorWrapper : fieldVisitorWrappers) {
                    fieldVisitor = fieldVisitorWrapper.wrap(instrumentedType, fieldDescription, fieldVisitor);
                }
                return fieldVisitor;
            }
        }

8.1.1.2 DispatchingVisitor

继承ClassVisitor方法

  protected class DispatchingVisitor extends ClassVisitor {

            /**
             * The instrumented type.
             */
            private final TypeDescription instrumentedType;

            /**
             * A mapping of fields by their name and descriptor key-combination.
             */
            private final Map<String, FieldDescription.InDefinedShape> fields;

            /**
             * Creates a new dispatching visitor.
             */
            protected DispatchingVisitor(ClassVisitor classVisitor, TypeDescription instrumentedType, Map<String, FieldDescription.InDefinedShape> fields) {
                super(OpenedClassReader.ASM_API, classVisitor);
                this.instrumentedType = instrumentedType;
                this.fields = fields;
            }

            @Override
            public FieldVisitor visitField(int modifiers, String internalName, String descriptor, String signature, Object defaultValue) {
                FieldVisitor fieldVisitor = super.visitField(modifiers, internalName, descriptor, signature, defaultValue);
                FieldDescription.InDefinedShape fieldDescription = fields.get(internalName + descriptor);
                if (fieldVisitor != null && fieldDescription != null) {
                    for (Entry entry : entries) {
                        if (entry.matches(fieldDescription)) {
                            fieldVisitor = entry.wrap(instrumentedType, fieldDescription, fieldVisitor);
                        }
                    }
                }
                return fieldVisitor;
            }
        }
  • instrumentedType ,是被修改的类。
  • fields, 一个map结构,一个fields。
  • visitField,使用entry.wrap最终将fieldVisitor 委托给FieldVisitorWrapper接口。所以FieldVisitorWrapper的子类只要实现FieldVisitorWrapperwrap方法,在其中定义具体的修改逻辑。
  public interface FieldVisitorWrapper {

            /**
             * Wraps a field visitor.
             *
             * @param instrumentedType The instrumented type.
             * @param fieldDescription The field that is currently being defined.
             * @param fieldVisitor     The original field visitor that defines the given field.
             * @return The wrapped field visitor.
             */
            FieldVisitor wrap(TypeDescription instrumentedType, FieldDescription.InDefinedShape fieldDescription, FieldVisitor fieldVisitor);
        }

8.1.1.3 ForDeclaredField 的内部实现

也是封装了上面提到的类

   class ForDeclaredFields extends AbstractBase {

        /**
         * The list of entries that describe matched fields in their application order.
         */
        private final List<Entry> entries;

        /**
         * Creates a new visitor wrapper for declared fields.
         */
        public ForDeclaredFields() {
            this(Collections.<Entry>emptyList());
        }

        /**
         * Creates a new visitor wrapper for declared fields.
         *
         * @param entries The list of entries that describe matched fields in their application order.
         */
        protected ForDeclaredFields(List<Entry> entries) {
            this.entries = entries;
        }

        /**
         * Defines a new field visitor wrapper to be applied if the given field matcher is matched. Previously defined
         * entries are applied before the given matcher is applied.
         *
         * @param matcher             The matcher to identify fields to be wrapped.
         * @param fieldVisitorWrapper The field visitor wrapper to be applied if the given matcher is matched.
         * @return A new ASM visitor wrapper that applied the given field visitor wrapper if the supplied matcher is matched.
         */
        public ForDeclaredFields field(ElementMatcher<? super FieldDescription.InDefinedShape> matcher, FieldVisitorWrapper... fieldVisitorWrapper) {
            return field(matcher, Arrays.asList(fieldVisitorWrapper));
        }

        /**
         * Defines a new field visitor wrapper to be applied if the given field matcher is matched. Previously defined
         * entries are applied before the given matcher is applied.
         *
         * @param matcher              The matcher to identify fields to be wrapped.
         * @param fieldVisitorWrappers The field visitor wrapper to be applied if the given matcher is matched.
         * @return A new ASM visitor wrapper that applied the given field visitor wrapper if the supplied matcher is matched.
         */
        public ForDeclaredFields field(ElementMatcher<? super FieldDescription.InDefinedShape> matcher, List<? extends FieldVisitorWrapper> fieldVisitorWrappers) {
            return new ForDeclaredFields(CompoundList.of(entries, new Entry(matcher, fieldVisitorWrappers)));
        }

        /**
         * {@inheritDoc}
         */
        public ClassVisitor wrap(TypeDescription instrumentedType,
                                 ClassVisitor classVisitor,
                                 Implementation.Context implementationContext,
                                 TypePool typePool,
                                 FieldList<FieldDescription.InDefinedShape> fields,
                                 MethodList<?> methods,
                                 int writerFlags,
                                 int readerFlags) {
            Map<String, FieldDescription.InDefinedShape> mapped = new HashMap<String, FieldDescription.InDefinedShape>();
            for (FieldDescription.InDefinedShape fieldDescription : fields) {
                mapped.put(fieldDescription.getInternalName() + fieldDescription.getDescriptor(), fieldDescription);
            }
            return new DispatchingVisitor(classVisitor, instrumentedType, mapped);
        }

8.1.4 看一个子类的实现

可以看到最终是attributeAppenderFactory.make产生一个FieldAttributeAppender来修改字节码。

   public FieldVisitor wrap(TypeDescription instrumentedType, FieldDescription.InDefinedShape fieldDescription, FieldVisitor fieldVisitor) {
            return new FieldAttributeVisitor(fieldVisitor,
                    fieldDescription,
                    attributeAppenderFactory.make(instrumentedType),
                    annotationValueFilterFactory.on(fieldDescription));
        }

前面说过FieldAttributeAppender的apply方法。就是负责应用实现,现在可以看一个具体的apply实现。这里就调到了具体的api逻辑

   public static void apply(AnnotationVisitor annotationVisitor, TypeDescription valueType, String name, Object value) {
            if (valueType.isArray()) { // The Android emulator reads annotation arrays as annotation types. Therefore, this check needs to come first.
                AnnotationVisitor arrayVisitor = annotationVisitor.visitArray(name);
                int length = Array.getLength(value);
                TypeDescription componentType = valueType.getComponentType();
                for (int index = 0; index < length; index++) {
                    apply(arrayVisitor, componentType, NO_NAME, Array.get(value, index));
                }
                arrayVisitor.visitEnd();
            } else if (valueType.isAnnotation()) {
                handle(annotationVisitor.visitAnnotation(name, valueType.getDescriptor()), (AnnotationDescription) value, AnnotationValueFilter.Default.APPEND_DEFAULTS);
            } else if (valueType.isEnum()) {
                annotationVisitor.visitEnum(name, valueType.getDescriptor(), ((EnumerationDescription) value).getValue());
            } else if (valueType.represents(Class.class)) {
                annotationVisitor.visit(name, Type.getType(((TypeDescription) value).getDescriptor()));
            } else {
                annotationVisitor.visit(name, value);
            }
        }

8.1.2 ForDeclaredMethods

bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第4张图片
ForDeclareFields的接口类似,但是稍微复杂一点,注意除了method方法以外还有构造器和invoke方法

8.1.3 Compound

复合内置一个AsmVisitorWrapper数组。
class Compound implements AsmVisitorWrapper {

    private final List asmVisitorWrappers;
    ...
    ...

}

8.2 AsmVisitorWrapper 的实现类

在这里插入图片描述

  • MemberRemoval 移出方法成员

  • MemberSubstitution 替换方法成员

  • ModifierAdjustment 修改修饰符权限

  • TypeReferenceAdjustment 引用类型的修改

  • MemberAttributeExtension 虽然不是子类,但是也是类似使用来扩展属性的值。

8.3 Advice :最核心的类

这个类的有11000行。实现类使用注解api,可以用来做切面。
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第5张图片
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第6张图片
官方描述
advice 包装器 copy模板方法里代码到在一个被matched方法的入口和结束。为了实现这一点,一个类被标注了@OnMethodEnter或者@OnMethodExit的静态方法,会被目标类调用。

被标注了@OnMethodEnter的方法可以使用@Argument(使对被标注的参数访问被替换为对被修改方法的访问,也就是可以把目标对象的变量反向传递给我们定义的切面逻辑)标注目标的参数。参数@This,代表这个整个被修改对象的指针。也可以为目标对象提供一个新的值(@Argument可以带参数)。 如果没有注解,那就意味者被标记为n。只有所有的方法和被标注的类的类型匹配,那么才算命中。

被标注了OnMethodExit的方法可以被标注@Argument@This,额外的可以标注@Return去接受原始方法的返回值。通过重新标记这个返回值,可以做到替代返回的值。如过一个方法返回了一个异常,那么参数会被设置为默认值,比如int =0,Integer=null。参数的类型必须和被修改方法的类型之一。@Thrown注解可以传递目标方法抛出的异常,如果正常结束,那么异常会被赋值为null。 这么做的化,我们有机会拦截异常。如果一个方法标注了@OnMethodEnter注解,那么这个方法的返回值,可以被标注了@Enter的参数访问到,当然类型必须一至。 如果这个参数被标注了read-only,那么这个返回值的super类型会被返回。如果方法的返回值是void,那么任何返回的值都会被 OnMethodExit丢弃掉。

如果被增强(advice)的方法逻辑抛出一个异常,那么目标方法就会被提前终止。如果方法被标注了@OnMethodEnter,抛出了异常,那么@OnMethodEnter注解就不会被调用。如果被增强的方法抛出了一个异常只有把OnMethodExit.onThrowable()属性设置为true,@OnMethodEnter才被调用。但是如果设置了false@Thrown注解就不会被调用。

Byte buddy 不会建设在inlined advice中的任何类型是可见的。需要用户来判断,是否能对访问到一个方法内的变量。如果不能会在运行时抛出IllegalAccessError异常。

advice可以被用来作为AsmVisitorWrapper,用来增强现有的修改的修改类型。也就是说advice可以作为一个Implementation,来增强目标类原有方法的逻辑。如果某个类不需要或者比较特殊,可以在implementation覆盖Advice#wrap(Implementation)逻辑,来自定义。

当把 advice 类作为一个 visitor wrapper时,本地和抽象方法会被跳过,不匹配。

注意:生成inlined methods是不可能触发断点的。但是原始的目标方法可以设置断点。这个特性允许在单元测试时无需改动源码就能调用增强逻辑。

未定义的组件(没有经过正式的声明与编译,直接修改字节码)可能会在运行时导致异常。

adviceAgentBuilder一起使用时。经常要注解在agent .jar文件中不要包含依赖具体库的代码(和目标应用发生冲突)。为了可以依赖库中找到 advice code,Byte buddy 提供了一个net.bytebuddy.agent.builder.AgentBuilder.Transformer.ForAdvice的implementation,可以注册agent的的文件寻找器(class file locators),这么做可以在运行时汇总所有的增强逻辑,和响应具体的用户使用。

8.3.1 ArgumentHandler

负责寻找定位 参数在字节码中的本地变量的偏移量。比如public String hello(int id),参数名"id"就放在本地变量中。

instrumented 表示被修改的目标方法,advice表示增强。

方法名
int argument(int offset) 定位instrumented 方法的偏移
int returned() 定位instrumented return value的offset
int thrown() 定位instrumented exception的offset
int exit() 定位exit advice 的 的偏移
int enter() 定位enter advice 方法的偏移
int named(String name) 根据name定位变量在本地变量 方法的偏移

8.3.1.1 ForAdvice

ArgumentHandler 的实现针对于遍历增强advice的方法。
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第7张图片

  • mapped 方法,解决advice中方法的偏移。

bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第8张图片

1 Default 类

bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第9张图片
可以看到保留有 对被修改类型和增强的引用。以及退出类型,可获得的本地变量关系。
主要方法的实现

				public int exit() {
                    return instrumentedMethod.getStackSize();
                }

                /**
                 * {@inheritDoc}
                 */
                public int named(String name) {
                    return instrumentedMethod.getStackSize()
                            + exitType.getStackSize().getSize()
                            + StackSize.of(namedTypes.headMap(name).values());
                }

                /**
                 * {@inheritDoc}
                 */
                public int enter() {
                    return instrumentedMethod.getStackSize()
                            + exitType.getStackSize().getSize()
                            + StackSize.of(namedTypes.values());
                }
2 ForMethodEnter

继承default的实现,专门针对进入方法的类型。可以看出return()和thrwown()方法不实现。

		protected static class ForMethodEnter extends Default {

                    /**
                     * Creates a new argument handler for an enter advice method.
                     *
                     * @param instrumentedMethod The instrumented method.
                     * @param adviceMethod       The advice method.
                     * @param exitType           The exit type or {@code void} if no exit type is defined.
                     * @param namedTypes         A mapping of all available local variables by their name to their type.
                     */
                    protected ForMethodEnter(MethodDescription instrumentedMethod,
                                             MethodDescription adviceMethod,
                                             TypeDefinition exitType,
                                             TreeMap<String, TypeDefinition> namedTypes) {
                        super(instrumentedMethod, adviceMethod, exitType, namedTypes);
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public int returned() {
                        throw new IllegalStateException("Cannot resolve the return value offset during enter advice");
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public int thrown() {
                        throw new IllegalStateException("Cannot resolve the thrown value offset during enter advice");
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public int mapped(int offset) {
                        return instrumentedMethod.getStackSize()
                                + exitType.getStackSize().getSize()
                                + StackSize.of(namedTypes.values())
                                - adviceMethod.getStackSize() + offset;
                    }
                }
2 ForMethodEnter

针对退出方法的处理

protected static class ForMethodExit extends Default {

                    /**
                     * The enter type or {@code void} if no enter type is defined.
                     */
                    private final TypeDefinition enterType;

                    /**
                     * The stack size of a possibly stored throwable.
                     */
                    private final StackSize throwableSize;

                    /**
                     * Creates a new argument handler for an exit advice method.
                     *
                     * @param instrumentedMethod The instrumented method.
                     * @param adviceMethod       The advice method.
                     * @param exitType           The exit type or {@code void} if no exit type is defined.
                     * @param namedTypes         A mapping of all available local variables by their name to their type.
                     * @param enterType          The enter type or {@code void} if no enter type is defined.
                     * @param throwableSize      The stack size of a possibly stored throwable.
                     */
                    protected ForMethodExit(MethodDescription instrumentedMethod,
                                            MethodDescription adviceMethod,
                                            TypeDefinition exitType,
                                            TreeMap<String, TypeDefinition> namedTypes,
                                            TypeDefinition enterType,
                                            StackSize throwableSize) {
                        super(instrumentedMethod, adviceMethod, exitType, namedTypes);
                        this.enterType = enterType;
                        this.throwableSize = throwableSize;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public int returned() {
                        return instrumentedMethod.getStackSize()
                                + exitType.getStackSize().getSize()
                                + StackSize.of(namedTypes.values())
                                + enterType.getStackSize().getSize();
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public int thrown() {
                        return instrumentedMethod.getStackSize()
                                + exitType.getStackSize().getSize()
                                + StackSize.of(namedTypes.values())
                                + enterType.getStackSize().getSize()
                                + instrumentedMethod.getReturnType().getStackSize().getSize();
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public int mapped(int offset) {
                        return instrumentedMethod.getStackSize()
                                + exitType.getStackSize().getSize()
                                + StackSize.of(namedTypes.values())
                                + enterType.getStackSize().getSize()
                                + instrumentedMethod.getReturnType().getStackSize().getSize()
                                + throwableSize.getSize()
                                - adviceMethod.getStackSize()
                                + offset;
                    }
                }

8.3.1.2 ForInstrumentedMethod

针对被修改方法参数的定位。

方法名
int variable(int index) 定位instrumented 方法的偏移
int prepare(MethodVisitor methodVisitor) 处理参数
ForAdvice bindEnter(MethodDescription adviceMethod) 绑定一个advice方法作为方法开始的处理
ForAdvice bindExit(MethodDescription adviceMethod, boolean skipThrowable) 绑定一个advice方法作为方法结束的处理
boolean isCopyingArguments() 在执行目标类的方法前,是否copy原始的方法参数
List getNamedTypes() 按照声明顺序返回被命名类的类型

bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第10张图片

1 Default

绑定了入口和退出的方法处理

  			public ForAdvice bindEnter(MethodDescription adviceMethod) {
                    return new ForAdvice.Default.ForMethodEnter(instrumentedMethod, adviceMethod, exitType, namedTypes);
                }

                /**
                 * {@inheritDoc}
                 */
                public ForAdvice bindExit(MethodDescription adviceMethod, boolean skipThrowable) {
                    return new ForAdvice.Default.ForMethodExit(instrumentedMethod,
                            adviceMethod,
                            exitType,
                            namedTypes,
                            enterType,
                            skipThrowable ? StackSize.ZERO : StackSize.SINGLE);
                }

                /**
                 * {@inheritDoc}
                 */
                public List<TypeDescription> getNamedTypes() {
                    List<TypeDescription> namedTypes = new ArrayList<TypeDescription>(this.namedTypes.size());
                    for (TypeDefinition typeDefinition : this.namedTypes.values()) {
                        namedTypes.add(typeDefinition.asErasure());
                    }
                    return namedTypes;
                }
2 Simple

简单的hanlder实现

3 Copying

其中prepare方法的实现。修改了目标方法的每一个参数。

 		public int prepare(MethodVisitor methodVisitor) {
                        StackSize stackSize;
                        if (!instrumentedMethod.isStatic()) {
                            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
                            methodVisitor.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize()
                                    + exitType.getStackSize().getSize()
                                    + StackSize.of(namedTypes.values())
                                    + enterType.getStackSize().getSize());
                            stackSize = StackSize.SINGLE;
                        } else {
                            stackSize = StackSize.ZERO;
                        }
                        for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) {
                            Type type = Type.getType(parameterDescription.getType().asErasure().getDescriptor());
                            methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ILOAD), parameterDescription.getOffset());
                            methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ISTORE), instrumentedMethod.getStackSize()
                                    + exitType.getStackSize().getSize()
                                    + StackSize.of(namedTypes.values())
                                    + enterType.getStackSize().getSize()
                                    + parameterDescription.getOffset());
                            stackSize = stackSize.maximum(parameterDescription.getType().getStackSize());
                        }
                        return stackSize.getSize();
                    }

8.3.1.3 Factory

定义了两个常量,

  • SIMPLE 对参数不处理。
  • COPYING 执行目标方法前,先copying一次参数。
   /**
             * A factory for creating a simple argument handler.
             */
            SIMPLE {
                @Override
                protected ForInstrumentedMethod resolve(MethodDescription instrumentedMethod,
                                                        TypeDefinition enterType,
                                                        TypeDefinition exitType,
                                                        Map<String, TypeDefinition> namedTypes) {
                    return new ForInstrumentedMethod.Default.Simple(instrumentedMethod,
                            exitType,
                            new TreeMap<String, TypeDefinition>(namedTypes),
                            enterType);
                }
            },

            /**
             * A factory for creating an argument handler that copies all arguments before executing the instrumented method.
             */
            COPYING {
                @Override
                protected ForInstrumentedMethod resolve(MethodDescription instrumentedMethod,
                                                        TypeDefinition enterType,
                                                        TypeDefinition exitType,
                                                        Map<String, TypeDefinition> namedTypes) {
                    return new ForInstrumentedMethod.Default.Copying(instrumentedMethod,
                            exitType,
                            new TreeMap<String, TypeDefinition>(namedTypes),
                            enterType);
                }
            };

8.3.2 MethodSizeHandler

类似ArumentHander,这个是用来处理方法的。用来计算目标方法的字节码大小。
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第11张图片

8.3.3 StackMapFrameHandler

处理栈帧映射的
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第12张图片

8.3.4 ExceptionHandler

处理异常的
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第13张图片

8.3.5 PostProcessor

在advice方法被调用后会被执行
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第14张图片

  • make 方法是负责创建一个PostProcessor的。

8.3.6 Delegator

将advice的方法的调用,委托给这个Delegator
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第15张图片

  • ForStaticInvocation 委托给一个静态方法
    可以看出apply方法使用methodVisitor 生成了一个静态方法,经advice的方法放了进去。
public void apply(MethodVisitor methodVisitor,
                              MethodDescription.InDefinedShape adviceMethod,
                              TypeDescription instrumentedType,
                              MethodDescription instrumentedMethod,
                              boolean exit) {
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
                        adviceMethod.getDeclaringType().getInternalName(),
                        adviceMethod.getInternalName(),
                        adviceMethod.getDescriptor(),
                        false);
            }
  • ForDynamicInvocation 使用动态方法生成
    apply的实现更复杂,单和上面的原理类似,这里就不贴了。

8.3.7 OffsetMapping

直接用定义在二进制的字节码文件的偏移量来代替传统的遍历访问。
这个map保存这样的映射关系。

bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第16张图片

  • 核心的方法
    Target resolve(TypeDescription instrumentedType,
    MethodDescription instrumentedMethod,
    Assigner assigner,
    ArgumentHandler argumentHandler,
    Sort sort);
    Target封装了 目标方法的offset信息

8.3.7.1 Target 类

bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第17张图片
核心的三个方法

  • StackManipulation resolveRead();
    一个StackManipulation 代表者一个adivce参数的读取,这就是处理读的指令。
  • StackManipulation resolveWrite();
    处理写的指令
  • StackManipulation resolveIncrement(int value);
    处理增加指令

首先它的实现类中。都各自有ReadOnlyReadWrite实现。
ReadOnly实现了读取目标类的字节码,并被StackManipulation包装。

比如ForDefaultValue的实现read: 直接返回类型对应的StackManipulation

 public StackManipulation resolveRead() {
                    return new StackManipulation.Compound(DefaultValue.of(typeDefinition), readAssignment);
                }
                。。。
                // 解析typeDefinition,返回不同类型对应的StackManipulation
   public static StackManipulation of(TypeDefinition typeDefinition) {
        if (typeDefinition.isPrimitive()) {
            if (typeDefinition.represents(long.class)) {
                return LONG;
            } else if (typeDefinition.represents(double.class)) {
                return DOUBLE;
            } else if (typeDefinition.represents(float.class)) {
                return FLOAT;
            } else if (typeDefinition.represents(void.class)) {
                return VOID;
            } else {
                return INTEGER;
            }
        } else {
            return REFERENCE;
        }
    }

比如ForDefaultValue的实现write: Removal是StackManipulation的子类,代表着删除

   public StackManipulation resolveWrite() {
                        return Removal.of(typeDefinition);
                    }

实现类

  • ForDefaultValue 对默认值的读写
  • ForVariable 对本地变量的处理
  • ForArray 对数组类型的处理
  • ForField 对Field的处理
  • ForStackManipulation 工具类,直接返回对某个类型的StackManipulation包装。

8.3.7.2 其他 OffsetMapping的实现类

都是对不同类型,offset的处理
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第18张图片

8.4.1 Dispatcher

单例用来指向不同advice的分发器
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第19张图片

  • isAlive() 判断dispatcher()的是否活着
  • TypeDefinition getAdviceType() 获取advice的类型

8.4.1.3 SuppressionHandler

superess是抑制的意思,这个类的作用拦截Suppress exception。在前后加入自定一的动做
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第20张图片

1 Bound

封装了拦截的动作。上面的bind函数就是返回这么一个对象,包含了对handler的额外处理

  • void onPrepare(MethodVisitor methodVisitor); 准备一个suppression handler是被调用
  • void onStart(MethodVisitor methodVisitor); 方法开始时被调用
  • onEnd 方法结束时被调用
  • onEndWithSkip 当跳过应该被除理的异常时,被调用。
2. Suppressing

Suppressing是 SuppressingHandler的实现。
要知道 字节码中,if,switch,或者 try catch等会经常使用标签label,方便根据判断跳转(jump)到不同的代码逻辑。
可以看到这里就是使用几个lable,完成这样的工作

protected static class Bound implements SuppressionHandler.Bound {

                    /**
                     * The suppressed throwable type.
                     */
                    private final TypeDescription suppressedType;

                    /**
                     * The stack manipulation to apply within a suppression handler.
                     */
                    private final StackManipulation exceptionHandler;

                    /**
                     * A label indicating the start of the method.
                     */
                    private final Label startOfMethod;

                    /**
                     * A label indicating the end of the method.
                     */
                    private final Label endOfMethod;

                    /**
                     * Creates a new active, bound suppression handler.
                     *
                     * @param suppressedType   The suppressed throwable type.
                     * @param exceptionHandler The stack manipulation to apply within a suppression handler.
                     */
                    protected Bound(TypeDescription suppressedType, StackManipulation exceptionHandler) {
                        this.suppressedType = suppressedType;
                        this.exceptionHandler = exceptionHandler;
                        startOfMethod = new Label();
                        endOfMethod = new Label();
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void onPrepare(MethodVisitor methodVisitor) {
                        methodVisitor.visitTryCatchBlock(startOfMethod, endOfMethod, endOfMethod, suppressedType.getInternalName());
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void onStart(MethodVisitor methodVisitor) {
                        methodVisitor.visitLabel(startOfMethod);
                    }

        }

8.4.1.4 RelocationHandler

负责对instrumented method的控制流,链式的串联起来。
重定位的操作是在字节码中jump到不同代码逻辑,一般依赖字节码中存在的lable。但是最终写到字节码前,可把很多条件保留在代码逻辑里,具体生成是按照代码逻辑生成不同lable。这个RelocationHandler就是组合逻辑,在不同位置生成label
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第21张图片

8.4.1.2 Unresolved

是Dispatcher 的实现,表明没有解决的的dispacther。仅仅内置了一个Map getNamedTypes();保存所有被enter声明

  • Resolved.ForMethodEnter asMethodEnter(List> userFactories,
    ClassReader classReader,
    Unresolved methodExit,
    PostProcessor.Factory postProcessorFactory);
    转化为入口方法

  • Resolved.ForMethodExit asMethodExit(List> userFactories,
    ClassReader classReader,
    Unresolved methodEnter,
    PostProcessor.Factory postProcessorFactory);
    转化为出口方法

8.4.1.1 Resolved

bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第22张图片

1 Bound
 interface Bound {

            /** 准备advice的异常处理时
             * Prepares the advice method's exception handlers.
             */
            void prepare();

            /** 初始化advice的的`本地变量`
             * Initialized the advice's methods local variables.
             */
            void initialize();

            /** 应用变化时
             * Applies this dispatcher.
             */
            void apply();
        }
2 ForMethodEnter

入口方法的处理
Map getNamedTypes();就是处理类型的兼容。

3 ForMethodExit

退出方法的处理

4 AbstractBase

bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第23张图片
注意这里的类内变量

  • adviceMethod 对增强方法的引用
  • postProcessor advice 的后被调用的方法
  • 一个Mapping,保留对所有字节码定义的偏移量(offset),访问一个字节码时不用遍历,而是直接使用offset
  • suppressionHandler 对suppress异常的处理
  • relocationHandler, byte buddy 给原始的代码加了一堆label,所以控制跳转到不同label能实现代码逻辑的替换。

8.3.9 AdviceVisitor

给目标类添加advice的逻辑,
这些变量都是前面定义的。
bytebuddy实现原理分析 &源码分析 (三)- advice 详解_第24张图片

8.3 注解详解

注解
@OnMethodEnter 表示这个方法会在,进入目标方法时调用,这个注解声明的方法必须是static。当目标的方法是constructor构造器时,@This只能写field,不能读field,或者调用方法 skipOn() 跳过一些方法
prependLineNumber() 如果为true,会改目标方法的行号
inline() 标识方法应该被内联到目标的方法中去
suppress() 忽略某些异常
@OnMethodExit 表示这个方法会在,目标方法结束时调用,这个注解声明的方法必须是static。如果方法提前终止了,那么这个就不i呗调用 repeatOn() 标识目标方法是否被重复执行
onThrowable() 一般被织入的方法抛出了某些异常,可以有响应的handler处理
backupArguments() 备份所有被执行方法的类型,开始会影响效率
inline() 标识方法应该被内联到目标的方法中去
suppress() 忽略某些异常
@This 表示被注解的参数,应该是被修改对象的引用 optional() 决定被修改的类型是否需要被设置为null,如果目标方法类型是static或者在一个constructor
readOnly() 只读不能修改
typing() 类型转化,默认要求静态转化,就是转化不能改变类型。动态是可以改变
@Argument 被标注到目标类型的参数上,表示被标注的参数会被value()代表的索引拿到
readOnly() 只读
typing() 转换这个类型使用的方式,默认是静态转换(类型不会被改动),动态转换是void.class可以被改成其他类
optional() 备选值,如果索引不存在,会使用这个提供的值。默认是关闭的
@AllArguments 使用一个数组来包含目标方法的参数,目标的参数必须是一个数组。
readOnly() 只读
typing() 类型开关
@Return 标注在参数上,来引用目标方法的返回值 readOnly() 只读
typing() 类型转化,默认是静态转换
@Thrown 获取抛出的异常
readOnly() 只读
typing() 默认是动态转化,可以改变类型
@FieldValue 被注解的参数,可以引用,目标method内的定义的局部变量 String value() 局部变量的名称
declaringType() 被声明的类型
readOnly() 只读的
typing() 默认是静态转化
@Origin 使用一个String代表目标方法的 String value() default DEFAULT 默认值是""
@Enter 标注在参数上,指向被标注@OnMethodEnter的advice方法的返回值,
readOnly() 只读标记
typing() 转换
@Exit 标注在参数上,指向被标注@OnMethodExit的advice方法的返回值,
readOnly() 只读标记
typing() 转换
@Local 声明被注解的参数当做一个本地变量,被Byte Buddy,织入目标方法中。本地变量可以被@OnMethodEnter@link OnMethodExit读写。然而如果本地变量被exit advice 引用了,它必须也在enter 的advice所声明。就是用来交换变量的。 String value() name
@StubValue mock值,总是返回一个设定的值
@Unused 让被标记的参数,总是返回默认值,比如int 返回0, 其他类型返回null

8.3.2 demo

你可能感兴趣的:(bytebuddy)