面试题-反射(二)

时间:2021-6-12 作者:qvyue

四、反射

问题来自于https://blog.csdn.net/sufu1065/article/details/88051083

3.动态代理是什么?有哪些应用?

我们首先来了解一下什么是代理,通俗的将就是中介。消费者不直接调用生产者,而调用生产者的代理商,这样除了生产的事情都可以由代理者来执行,能够提升代码的纯度,降低代码的冗余,降低耦合。
我们再来说一说为什么要存在代理。当我们在一些功能执行前或执行后,需要统一的处理一些事情,比如打印日志、增加验证、过滤拦截、数据统计等一些核心功能以外的附加功能时,把这些处理方法写到核心代码中显然是不合适的,不仅会造成代码的冗余,还会增加代码的耦合程度,不利于今后的代码维护与修改。此时,我们能想到的办法就是将这些功能交由其他的类来实现,这个类既要能满足额外的功能,还能调用原有需要实现的功能。这个类就被称之为代理类。
例如Spring中的AOP就是使用动态代理实现的。
在Java中,代理可以分为静态代理和动态代理。下面我们看一下代理类的模型

面试题-反射(二)
代理模型图

由上图我们可以看到,代理类ProxySubject与实际类RealSubject都继承了Subject接口,所以对客户来说是没有感觉的。客户调用Subject的接口功能,随后ProxySubject处理额外的功能之后再调用实际类相应的方法就完成了代理。
静态代理:
静态代理顾名思义,就是在编译期确定要代理的类,并编写出他对应的代理类。代理类的生成是在编译期确定好的。需要开发人员自行编写ProxySubject类,并重写Subject的方法。在其方法中再调用RealSubject的方法即可。
优点:对于客户端来说,并不知道实际执行的类是哪些,只需要调用代理类的方法即可,在一定程度上降低耦合度
缺点:静态代理需要开发人员手动写代理类和实现方法,当需要代理的方法较多时,会出现大量的重复代码,提高冗余度。
动态代理:
为了解决静态代理当需要代理的类和接口过多时产生的代码冗余,出现了动态代理模式,动态代理即在运行期间,通过反射机制动态创造出各种类型的代理类。
jdk动态代理如下

interface Animal{
  public void eat();
}

class Cat implements Animal{
  @Override
  public void eat(){
    System.out.println("吃鱼");
  }
}

class MyInvocationHandler implements InvocationHanlder{
  private Object target;
  public Object bind(Object target){
    this.target = target;
    return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
    System.out.println("代理类");
    return method.invoke(targey, args);
  }
}

public class Test{
  public static void main(String[] args){
    Animal animal = (Animal) new MyInvocationHandler().bind(new Cat());
    animal.eat();
  }
}

上述代码为简单的jdk动态代理,解释一下Proxy.newProxyIntance(),该方法会根据入参在运行期间生成代理类对象。生成的代理类会实现所有参数传入的接口。并重写他们的方法,并切包含成员变量InvocationHandler即为第三个参数。在实现每个接口方法时,方法体调用的是InvocationHandler中的invoke方法,并传入自身,方法名称,参数。
所以在主函数的第一行,animal即为Proxy生成的代理类,该代理类实现了传入参数Cat类的所有接口,并重写了所有的方法,方式实现为调用传入参数MyInvocationHandler的invoke方法。这样通过animal方法调用任何方法,都会执行MyInvocationHandler的invoke方法,在invoke方法中处理其他的事情之后又调用了target-cat类的对应方法,至此完成动态代理功能。
值得一提的是Java中的AOP面向切片编程是一种编程思想,其实现就是根据动态代理完成的。
动态代理将接口中声明的所有方法都完成了代理,并且在接口较多的时候,也不需要我们手动写大量的代理类。可以说动态代理处理更加单一,复用性更强。

总结:
不论是动态代理或者是静态代理,其根本思想都是为了在某一些情况,将一些功能与核心功能分开,或者不想让调用方直接调用核心功能。在核心功能的基础上再包装一层从而交给调用方。并且再包装层可以处理一些额外的功能。这就是代理的功能。

4.怎么实现动态代理?

jdk动态代理的实现见问题3的回答
通过CGLIB实现动态代理:
CGLIB github地址:点击我进入GitHub
CGLIB是一个动态代理的第三方jar包,其通过ASM框架操作Java字节码来动态生成对象,完成动态代理功能。
使用方式:

public class GBLibTest{
  public static void main(String[] args){
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Cat.class);
    enhancer.setCallback(new MethodInterceptor(){
      @Override
      public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("hello");
        return methodProxy.invokeSuper(o, objects);
      }
    });
    Cat cat = (Cat)enhancer.create();
    cat.eat();
  }
}
class Cat{
  public void eat(){
    System.out.println("eat fish");
  }
}

使用cglib与jdk最大的区别就在于,cglib可以动态代理类,而jdk只能代理接口。这是因为cglib从机制上使用了继承被代理类的方式实现的。因为该实现方式,也让被代理类的final与static方法无法被代理。
其次cglib的操作也更加的简单了,只需要将被代理的类、增强方法等设置到Enhancer中,即可调用create生成代理类。
需要注意的是在增强方法中,与InvocationHandler多了MethodProxy。而我们调用需要用MethodProxy的invokeSuper方法调用被代理类的方法。否则会造成循环调用invoke方法出现OOM。
原因是MethodProxy与Method的invoke方法,都是调用了Object的对应方法,而Object又是代理类,所以造成了递归调用。而invokeSuper则是调用代理类父类的方法。这样就不会存在产生问题。
总结:cglib与jdk的动态代理,cglib可以代理任何类,而jdk只能代理接口。
其次cglib使用了ASM框架操作JVM字节码来生成对象,而jdk则是利用反射生成对象。在生成对象方面jdk更加高效,操作方法方面cglib更有效率。
cglib操作字节码生成的动态代理类会存在Java的永久堆(永久代)当中,使用过多时会造成永久堆满触发GC。Java8已经移除了永久代,取代他的是名为native的内存区。(真实性有待考究,可以参考SpringAop是否存在该问题,如果真有因该已经被修复掉了,网上资料较少)

其次说一说cglib的callback与callbackFilter
在cglib中可以使用的callback有:
FixedValue:需要重写loadobject方法,切代理类的任何方法都会调用到该方法,不会调用到原来的方法中。
InvocationHandler:与jdk代理相同,需要注意的是在invoke方法中切记不要直接使用传入的对象执行方法,在method.invoke方法中需要传入参数对象的父类对象,否则会造成invoke递归调用发生OOM。
MethodInterceptor:该类是cglib最常用的callback,需要重写intercept方法,该方法即代理方法,可以使用methodProxy.invokeSuper传入代理类对象与参数,即可自动调用代理类父类方法。
NoOp:不需要任何实现,如果callback是这个的话,那么调用任何方法都相当于调用原方法。
LazyLoader:该类含有loadObject方法,方法返回一个对象,之后调用方法都会调用这个对象的方法。
Dispatcher:与LazyLoader相同,也需要实现loadObject方法,不过LazyLoader如名所示,比较lazy,只有第一次调用时会初始化对象,后续调用方法都会使用第一次初始化的对象嗲用。而Dispatcher则是每次调用方法都会调用loadObject方法。
在cglib中可以使用的callbackFilter有:
callbackFilter也是cglib与jdk的差别之一,jdk不支持过滤器的设置。
CallbackFilter:在了解CallbackFilter之前,我们需要知道Enhancer中除了setCallback还可以Callbacks,所以我们可以在一个Enhancer中设置多个callback。而哪个方法需要调用哪个callback就是CallbackFilter所做的事情。
继承CallbackFIlter之后需要实现accpet方法,该方法包含入参Method,即调用代理类的方法,传入之后我们可以根据Method的特性,来返回特定的数字,如0,1,2,返回的数字就是Enhancer当中callbacks数组当中callback的位置。如下所示

public class MyCallbackFilter implements CallbackFilter{
  @Override
  public Object accept(Method method){
    if(method.getName().equals("eat")){
      return 0;
    }
    return 1;
  }
}

我们也可以使用CallbackHelper,该类是CallbackFilter的子抽象类,需要我们实现accept方法。不过该类的构造函数允许我们传入父类和接口,灵活度要高于CallbackFilter。

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。