【Android面试题】2023最新面试专题:Java反射类加载

2023-08-07  本文已影响0人  小城哇哇

9 动态代理的方法怎么初始化的?(字节跳动)

这道题想考察什么?

面试者对设计模式中的代理模式掌握情况,是否了解并能合理运用静态代理与动态代理,知道两者的区别;动态代理原理与其所涉及到的知识点

考察的知识点

  1. 代理模式
  2. 反射

考生应该如何回答

在Java动态代理中会根据代理的接口生成Proxy派生类作为代理类。此类中会生成一段静态代码块,在静态代码块中使用反射获取到被代理类的所有方法并赋值给成员属性,以供后续使用。

在执行方法时,会通过InvocationHandler 将此方法对应的被代理类方法(静态代码块获得的Method) 对象以及参数回调给使用者。

public final class Proxy$0 extends Proxy implements NDK {
    
    //在动态代理对象上调用ndk方法,通过InvocationHandler回调出去
    public final void ndk() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
   
    //静态代码块,加载就会找到对应的method,比如ndk方法
    static {
        try {
           //......
            m3 = Class.forName("com.xxx.NDK").getMethod("ndk");
           //......
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

10 CGLIB动态代理(字节跳动)

这道题想考察什么?

代理思想与CGLIB实现的动态代理的原理及其中涉及到的知识点。

考察的知识点

ASM、字节码增强技术

考生如何回答

这个问题可能会与JDK动态代理一起讨论,所以在回答此问题之前需要首先清楚代理模式-动态代理的特点以及JDK动态代理实现原理

CGLIB动态代理原理

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。

//cglib和jdk动态代理不同,不止只能代理接口
public class HelloService {
 
    public void sayHello() {
        System.out.println("HelloService:sayHello");
    }

}
 
public class CglibMethod implements MethodInterceptor{
    /**
     * sub:cglib生成的代理类的对象
     * method:被代理对象的方法
     * objects:方法入参
     * methodProxy: 代理方法
     */
    @Override
    public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
        System.out.println("======插入前置处理======");
        Object object = methodProxy.invokeSuper(sub, objects);
        System.out.println("======插入后置处理======");
        return object;
    } 
}

static void main(String[] args){
    Enhancer enhancer = new Enhancer();
    //设置要代理的类型
    enhancer.setSuperclass(HelloService.class);
    //设置代理对象调用方法回调,代理对象上调用方法回调:CglibMethod#intercept
    enhancer.setCallback(new CglibMethod());
    //创建代理对象
    HelloService proxy= (HelloService)enhancer.create();
    // 通过代理对象调用目标方法
    proxy.sayHello();
}

JDK代理只能代理接口,而CGLIB动态代理则没有此类强制性要求。CGLIB会生成继承被代理类的一个子类(代理类),并在这个类中对代理方法进行强化处理(前置处理、后置处理等)。在CGLIB底层,其实是借助了ASM这个非常强大的Java字节码生成框架。

创建代理对象的几个步骤:

生成的代理类Class文件反编译之后的Java代码如下:

public class HelloService$$EnhancerByCGLIB$$1f1876d extends HelloService implements Factory {
    private static final Method CGLIB$sayHello$0$Method;  //被代理方法
    private static final MethodProxy CGLIB$sayHello$0$Proxy; //代理方法
    static {
        CGLIB$STATICHOOK1();
    }
    static void CGLIB$STATICHOOK1() {
        //............
        Class var0 = Class.forName("com.enjoy.compile.HelloService$$EnhancerByCGLIB$$1f1876d");
        Class var1;
        // 反射获得被代理类的Method:sayHello
        CGLIB$sayHello$0$Method = ReflectUtils.findMethods(new String[]{"sayHello", "()V"}, (var1 = Class.forName("com.enjoy.compile.HelloService")).getDeclaredMethods())[0];
        // 创建 HelloService#sayHello的代理方法
        CGLIB$sayHello$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHello", "CGLIB$sayHello$0");
         // ...........
    }
    
    final void CGLIB$sayHello$0() {
        super.sayHello();
    }

    public final void sayHello() {
        // CglibMethod
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
        // 回调CglibMethod#intercept 方法
        if (var10000 != null) {
            var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy);
        } else {
            super.sayHello();
        }
    }
    //..............
}

通过CGLIB生成的代理类可以看出,实际上我们创建的代理对象,是由CGLIB生成的一个代理类的子类对象,而这个子类中也和父类一样实现了sayHello方法。而调用sayHello方法,则会回调我们设置的Callback:CglibMethod#intercept 方法。

@Override
    public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
        System.out.println("======插入前置处理======");
        Object object = methodProxy.invokeSuper(sub, objects);
        System.out.println("======插入后置处理======");
        return object;
} 

在回调中我们进行执方法的前置处理与后置处理,使用methodProxy.invokeSuper去调起被代理类中实现的sayHello方法,此时会调用到CGLIB生成代理类中的:

 final void CGLIB$sayHello$0() {
     //执行父类:HelloService#sayHello
     super.sayHello();
}

总结

1、CGLIB在运行时生成代理类HelloService$$EnhancerByCGLIB$$YYYY继承被代理类HelloService注意:final方法无法代理

2、代理类会为委托方法生成两个方法,一个是重写的sayHello方法,另一个是CGLIB$sayHello$0方法;

3、当执行代理对象的sayHello方法时,则将调用创建代理对象时Enhancer.setCallback设置的MethodInterceptor.intercept方法,在intercept方法中调用methodProxy.invokeSuper则回调到CGLIB$sayHello$0方法。


11 为什么IO是耗时操作?

这道题想考察什么?

Java中常用的IO流程,以及项目中使用的注意事项

考察的知识点

Java IO原理

考生应该如何回答

计算机硬件上使用DMA(Direct Memory Access,直接内存存取)来访问磁盘等IO,也就是请求发出后,CPU就不再管了,直到DMA处理器完成任务,再通过中断告诉CPU完成了。

所以,单独的一个IO时间,对CPU的占用是很少的,阻塞了就更不会占用CPU了,因为程序都不继续运行了,CPU时间交给其它线程和进程了。

虽然IO不会占用大量的CPU时间,但是"量变引起质变",所以面对大量IO的任务,有时候是需要算法来合并IO,或者通过cache来缓解IO压力的。

最后

关注哇哇,以上均可分享哦

上一篇下一篇

猜你喜欢

热点阅读