java代理机制详解(动态代理原理解析,简单易懂!)

版权声明:---原创作者:风行云K ~转载请说明 https://blog.csdn.net/qq_31149079/article/details/81531337

一.代理机制概念

1.代理机制是一种设计模式,分为静态代理动态代理.

2.特征:代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。(自身体验是网络请求的统一处理!)

image

二:静态代理

1.概念:在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

举例理解:
假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费

实现思路:这里我们需要三个东西:

这里写图片描述

/**
* 1.创建PushMoney接口
* @author ZhuK
*/
public interface PushMoney {
//上交班费
void giveMoney();
}

//2.学生类,实现交钱PushMoney接口
public class Student implements PushMoney {
private String name;
public Student(String name) {
    this.name = name;
}
@Override
public void giveMoney() {
   System.out.println(name + "上交班费50元");
}  } 

/**
* 3.学生代理类,也实现了PushMoney接口,在构造方法保存一个学生实体,这样可以代理学生产生行为
*
*/
public class StudentsProxy implements PushMoney{
//被代理的学生
Student stu;

public StudentsProxy(PushMoney stu) {
    // 只代理学生对象
    if(stu.getClass() == Student.class) {
        this.stu = (Student)stu;
    }
}

//代理上交班费,调用被代理学生的上交班费行为
public void giveMoney() {
    stu.giveMoney();
}
}

##### 好的,那下面我们在一个测试类中实现:

    public class StaticProxyTest {
    public static void main(String[] args) {
    //被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
    Person zhangsan = new Student("张三");   
    //生成代理对象,并将张三传给代理对象
    Person monitor = new StudentsProxy(zhangsan);

    //班长代理上交班费
    monitor.giveMoney();
}
}

运行结果:
image

这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。
理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),
代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。
就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到:

public class StudentsProxy implements PushMoney{
//被代理的学生
Student stu;

public StudentsProxy(PushMoney stu) {
    // 只代理学生对象
    if(stu.getClass() == Student.class) {
        this.stu = (Student)stu;
    }
}

//代理上交班费,调用被代理学生的上交班费行为
public void giveMoney() {
    System.out.println("张三最近学习有进步!");
    stu.giveMoney();
}
}

可以看到,只需要在代理类中帮张三上交班费之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。

三.动态代理

1.动态代理

1.1代理类在程序运行时创建的代理方式被成为动态代理

1.2上面静态代理的例子中,代理类(studentProxy)是自己定义好的对象student,
而是在运行时根据我们在Java代码中实现时的“指示”动态生成的。 (这里有点像DIP设计思想,根据上面的需求去改实现底层)

1.3动态代理的优势在于可以很方便的对抽象代理类的函数进行统一的处理,而不用修改每个代理类中的方法。

比如上面我们指定了班长指定了是代理学生,而当我们代理多个对象时呢?
比如这是我要分为两个被代理对象,一个是男生,一个是女生,
那该如何去统一管理呢!所以这时候就用到我们的动态代理了!

例:
想要在每个代理的方法前加上一个处理方法,我就需要去每个实现处去添加,比如有10个就要加10,不够优雅!:

 public void giveMoney() {
    //调用被代理方法前加入处理方法
    beforeMethod();
    stu.giveMoney();
 }

这里只有一个giveMoney方法,就写一次beforeMethod方法,但是如果除了giveMonney还有很多其他的方法,那就需要写很多次beforeMethod方法,很麻烦。那看看下面动态代理如何实现。

###### 2.动态代理 简单实现

 生成调用接口
 public interface PushMoney {

public void pushMoney();

}

男孩真实类
public class BoySubject implements PushMoney {
@Override
public void pushMoney() {
    System.out.println("我是男孩交钱");
}

}
女孩真实类
public class GirlSubject Subject implements PushMoney {
@Override
public void pushMoney() {
    System.out.println("我是女孩交钱");
}
}

生成代理类并实现InvocationHandler接口
每一个动态代理类都必须要实现InvocationHandler这个接口,
并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,
这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。

public class DynamicProxy implements InvocationHandler {
//  这个就是我们要代理的真实对象
private Object subject;

// 构造方法,给我们要代理的真实对象赋初值
public DynamicProxy(Object subject) {
    this.subject = subject;
}

//在下面的例子TEST中实现真实类接口时会转移到这里!
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
    //   在代理真实对象前我们可以添加一些自己的操作

    System.out.println("Method:before" + method);

    // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
    method.invoke(subject, args);

    //   在代理真实对象后我们也可以添加一些自己的操作
    System.out.println("after rent house");

    return null;
}
}

最后在测试类中我们来实现这个代理

public class Test{
public static void     main(String[] args) {
    // 我们要代理的真实对象
Subject bogSubject = new BoySubject();
Subject girlSubject = new GirlSubject();
     我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的

InvocationHandler handlerBoy
= new DynamicProxy(boySubject);

InvocationHandler handlerGirl
=new DynamicProxy(girlSubject);

    /*
     * 通过Proxy的newProxyInstance方法来创建我们的代理对象,
     * 我们来看看其三个参数 第一个参数 handler.getClass().getClassLoader()
     * 我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
     * 第二个参数boySubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,
     * 表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了 
     * 第三个参数handler, 我们这里将这个代理对象关联到了上方的InvocationHandler这个对象上
     */
Subject boySubjectProxy = (Subject) Proxy.newProxyInstance(
handlerBoy.getClass().getClassLoader(),  
boySubject.getClass().getInterfaces(),
handlerBoy);
System.out.println(subject.getClass().getName());
boySubjectProxy.pushMoney();

Subject girlSubjectProxy = (Subject) Proxy.newProxyInstance(
handlerGirl.getClass().getClassLoader(),
girlSubject.getClass().getInterfaces(),
handlerGirl);
System.out.println(subject.getClass().getName());
girlSubjectProxy.pushMoney();

}

~~好,到这里我们就成功的把男女代理对象的交钱操作给完成了,
**并且这里会调用DynamicProxy 实现InvocationHandler接口的invoce方法,
并在执行前后调用我们写进去的操作.**

上面可以看到,执行Test类–>

1.创建一个被代理的对象boySubject真实对象
2.然后再创建一个InvocationHandler对象handleBoy通过我们自定义实现的InvocationHandler接口比如:DynamicProxy

3.创建一个代理对象boySubjectProxy通过Proxy的newProxyInstance(Proxy是一个代理实现集合类,里面包含了很多代理的实现方法,newProxyInstance是用的最多的一个)

那当我们调用代理对象boySubjectProxy和girlSubjectProxy的pushMoney方法
就会自动转移到InvocationHandle的invoke方法,
是不是很神奇,不用急,我们下面会来解析一下他的原理!

这里写图片描述

四.动态代理原理解析

1、Java动态代理创建出来的动态代理类

上面我们利用Proxy类的newProxyInstance方法创建了一个动态代理对象,查看该方法的源码,发现它只是封装了创建动态代理类的步骤(重要~):

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);
*/
    //*注意~~~* 
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
    //*重要1~~~* 
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

    //重要2~~~
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        //重要3~~~

        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

由于CSDN博客长度限制,只能上图了
由于CSDN博客长度限制,只能上图了

总结:>生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了Proxy类,所以也就决定了java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。

上面的动态代理的例子,其实就是AOP的一个简单实现了,在目标对象的方法执行之前和执行之后进行了处理,对方法耗时统计。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。

              --菜鸟一个"希望共同学习一起在路上!"

猜你喜欢

转载自blog.csdn.net/qq_31149079/article/details/81531337
今日推荐