反射与动态代理

反射与动态代理_第1张图片

java中反射与动态代理是java程序员不可忽略的一门功课,关于其概念、基本使用方式,网上一大堆,本文主要讲解本人对于这两者的认识,以及关于开源项目中典型用法。

反射

Java反射机制:在程序运行时获取已知名称的类或已有对象的相关信息的一种机制,包括类的方法、属性、父类等,还包括实例的创建和实例类型的判断等。

是不是很抽象?来看个例子,在Spring中经常有如下的配置:


(在此不讨论具体Spring容器启动细节,只是讨论与反射相关的知识点。)

可以想象到的是Spring框架会读取上面的配置,获取类的名称。然后根据已知类的名称创建实例类型。是否很熟悉描述,在看看反射机制的描述。

看不懂?没事,下面一个实际的例子

  /**
  * 此字符串的获取你可以想象是从配置文件中,读取XMl,获取相关字符串。
  **/
String className = "com.linuer.test.Person";  
Class cls = Class.forName(className);
Object object = cls.newInstance();  //创建实例

上面的操作,用大白话说就是:根据一字符串 来得到类信息或者类方法,或者创建类实例。

至于这个 字符串 你是从哪里得来,配置文件?网络?都行,只要你想象到的方式。

除了上面操作,在实际使用过程中还有许多使用的方式。

在实际学习中关于反射相关的类主要关注:

Class类、类得属性:Field、类得方法: Method 。

动态代理

说动态代理之前,先说代理这个行为。
代理简单来说就是通过中间人来做。
来个图来说明吧


反射与动态代理_第2张图片
代理.png

图中可以看出,有个代理之后依旧是送花这个动作,可是送花的动作的含义与原先不相同了哦。
大家都知道,代理分为:静态代理与动态代理,具体有什么区别呢?以下结合代码来说:

public interface Calculator {
   public void add(Integer x, Integer y);
    public void div(Integer x, Integer y);
}
--------------------------------------------------------------------------------------------------
public class CalculatorImpl implements Calculator{
 @Override
    public void add(Integer x, Integer y)
    {
        System.out.println(x+y);
    }

    @Override
    public void div(Integer x, Integer y)
    {
        System.out.println(x-y);
    }
}
--------------------------------------------------------------------------------------------------
//正常的使用方式(对应与图中的上面两个小人的情况)
  Calculator calculator = new CalculatorImpl();
  calculator.add(7,6)
输出:
  13

上面是计算数值,现在我想在计算数值的前后,分别要输出一些信息,那该怎么做呢?直接在CalculatorImpl 类中的方法添加,这么做肯定不够优雅,作为立志成为资深程序员的我们,是绝对不能这么干的。
我们要用代理模式来做。来看看静态代理怎么做的吧

public class CalcProxy implements Calculator {
  private Calculator calc;

    public CalcProxy(){
        calc = new CalculatorImpl();
    }

    @Override
    public void add(Integer x, Integer y) {
        before();
        calc.add(x,y);
        after();
    }


    @Override
    public void div(Integer x, Integer y) {
        before();
        calc.div(x,y);
        after();
    }

    private void before() {
        System.out.println("before");
    }

    private void after() {
        System.out.println("after");
    }
}
---------------------------------------------------------------------
   Calculator calc = new CalcProxy();
   calc.add(7,6);
输出:
before
13
after
8

既然静态代理能够完成工作,为什么还需要改进,变成动态代理呢?原因在于:一个静态代理只能代理一个接口,这样就导致项目中到处都是XxxProxy。看动态代理是如何解决这个方案的。

public class DynamicProxy implements InvocationHandler{
    private Object target;

    public DynamicProxy(Object target){
        this.target = target;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target,args);
        after();
        return result;
    }
.......
}
代码中target变量就是我们被代理的目标对象,如果用来做Calculator 代理的话,就是Calculator 对象。
调用:
  Calculator calculator = new CalculatorImpl();
  DynamicProxy dynamicProxy = new DynamicProxy(calculator);
  Calculator calc = (Calculator) Proxy.newProxyInstance(
                calculator.getClass().getClassLoader(),
                calculator.getClass().getInterfaces(),
                dynamicProxy
        );
        calc.add(7,6);
输出:
before
13
after

上面的意思在于用dynamicProxy 去包装CalculatorImpl实例,然后调用JDK给我们提供的Proxy类的newProxyInstance去动态创建一个Calculator 接口的代理类。
对于newProxyInstance方法的参数真是让人“蓝廋”。

  • 参数1:ClassLoader
  • 参数2:该实现类的所有接口
  • 参数3:动态代理对象
    其实这块可以进一步封装下,避免程序中到处调用newProxyInstance
public class DynamicProxy implements InvocationHandler{
   .....
   public  T getProxy(){
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this
        );
    }
  ......
}
----------------------
调用:
  Calculator calculator = new CalculatorImpl();
  DynamicProxy dynamicProxy = new DynamicProxy(calculator);
  Calculator calc = dynamicProxy.getProxy();
  calc.add(7,6);
这样是不是简单很多了。

不知道你们注意到了没有,在DynamicProxy 中不管其代理的对象,如CalculatorImpl实例中接口增加或者减少,DynamicProxy 都不会改动,这也算是动态代理这个优点。但是如果被代理对象,是没有任何接口的类,那么它是不是就没有用武之地了呢?是的。那有没什么办法解决呢?依旧使用动态代理,但是其被代理的对象不必要是一个接口类。答案是:有的。CGLIB的动态代理。

public class CGLibProxy implements MethodInterceptor {

    public  T getProxy(Class cls){
        return (T) Enhancer.create(cls,this);
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(obj,args);
        after();
        return result;
    }
......
}
我们需要实现CGLIB给我们提供的MethodInterceptor 实现类,并编写intercept方法,
此方法的最后一个参数MethodProxy值得注意,这个是一个方法的代理。什么意思?
看下面的调用:
CGlibProxy cgLibProxy = new CGlibProxy();
Calculator calc = cgLibProxy.getProxy(CalculatorImpl.class);
calc.add(7,6);
注意到没,在传入参数的时候,并没有传入接口信息。

关于反射与动态代理讲解远远不止这些,还有许多实际的应用,接下来的两篇文章中会针对动态代理的应用进行论述。
由于本人水平有限,有什么问题可以评论,喜欢的可以关注。

你可能感兴趣的:(反射与动态代理)