JAVA动态代理

代理模式

在学习java动态代理之前,先需要知道什么是代理模式,代理其实在很多领域都有涉及到,他扮演的是一个中间商的角色,简单来说理解为A向B发送信息,中间有个C来转发A的信息给B,那么C就扮演的是一个代理的角色,这样听着挺简单,但java的代理模式又有一点不同,接下来就来看看JAVA的代理是怎么实现的

JAVA代理模式

如果根据字节码的创建时机来进行分类,可以分为静态代理和动态代理

所谓的静态代理就是程序运行前就已经存在代理类的字节码文件,代理类和真正实现业务逻辑的类的关系在运行前就确定了;对于动态代理来说,它的源码是在运行期间由JVM根据反射等机制动态生成,所以在运行前并不存在代理类的字节码文件。可以结合这张图来理解

静态代理

静态代理的实现很简单,首先创建一个接口

public interface Hello {
    void Hello();
}

然后创建一个类去实现这个接口

public class HelloCom implements Hello {
    @Override
    public void Hello(){
        System.out.println("Hello!");
    }
}

接着创建一个代理类,结合上面的图来看,代理类也需要实现这个接口,同时需要传入实现类的对象,结合代理类的方法来实现代理

public class Proxy implements Hello{
    private Hello a = new HelloCom();
    @Override
    public void Hello(){
        System.out.println("1");
        a.Hello();
        System.out.println("2");
    }
}

最后创建主类

package com.c1oud;

public class a {
    public static void main(String[] args){
       Hello test = new Proxy();
       test.Hello();


    }
}

很简单的代理,基本上一看就懂,就不做过多解释了

很明显的看到静态代理更像是一对一的代理,看起来比较安全,但是也比较固定,换句话说,当我想实现更多的类的代理的时候,必须要实现更多的代理类,都要实现不同的接口,并且当需要改变接口的时候,需要去修改代理类和实现类的接口方法,这看起来是一件很麻烦的事情,所以才引出了JAVA的动态代理

JAVA动态代理

代理类在程序运行时创建的代理方式称为动态代理,简答来说,动态代理就是要想办法根据接口或目标对象来计算出代理类的字节码,然后再加载到JVM中去使用。相比于静态代理,动态代理可以很方便地对代理类的函数进行统一处理。

JAVA的动态代理分为两种

1.通过实现接口->JDK动态代理(Java原生支持,不需要任何外部依赖)
2.通过继承类->CGLIB动态代理(无法处理final情况)

这里先只学习第一种JDK动态代理,在学习之前需要先了解一些基础概念

JDK动态代理通过反射包java.lang.reflect实现,里面有三个关键点:Proxy类、InvocationHandler接口和Method

InvocationHandler接口是一个增强的功能,表示代理要做什么,接口里的的invoke()方法作用是表示代理对象要执行的功能代码,简单来说代理类就要写在这里面。并且当调用实现InvocationHandler接口类里面的任何方法时都会去调用invoke()方法。

invoke()方法原型:public Object invoke(Object proxy, Method method, Object[] args)

参数:

Object proxy: jdk创建的代理对象,无需赋值
Method method: 目标类中的方法,jdk提供method对象的
Object[] args:目标类中方法的参数,jdk提供的

Method类表示目标类中的方法,通过Method可以执行某个目标类的方法:method.invoke(目标对象, 方法的参数),这里运用了反射的知识

最后一个Proxy类是用来创建代理对象的,他和InvocationHandler一起打配合完成了动态代理的实现

用Proxy类的方法newProxyInstance()来代替new使用,

方法原型:public static Object newProxyInstance( ClassLoader loader,Class<?>[] interfaces,InvocationHandler h),返回值是代理对象

newProxyInstance的参数:

ClassLoader loader:类加载器,负责向内存中加载对象,使用反射机制获取对象的ClassLoader
Class<?>[] interfaces: 代表接口,目标对象实现的接口,也是反射获取的
InvocationHandler h :自己写的代理类要完成的功能

这里其实才是实现代理的关键方法,第一个参数只需要获取一个classloder类加载器来加载类就可以了,所以第一个参数A.class.getClassLoader()中这个A可以随便传。

值得注意的是,第二个参数才是实现代理的接口,

第三个参数是一个拦截器,说直白点,第三个参数就是用来调用invoke方法的。意思是 第三个参数应该是一个拥有invoke方法的对象,也就是代理的对象,当调用代理对象的方法的时候,就会调用第三个参数的invoke方法。而代理对象肯定会有传入被代理对象的,执行invoke方法的时候也就会执行到被代理对象的相关内容。

可能看到这里还比较疑惑,我举个列子来大概了解一下动态代理具体是怎么实现的

假如有一个女生叫做王美丽,当有一个男生想约她约会的时候,需要经过王美丽的家人同意才可以,这里王美丽的家人就叫做代理,但是具体实现的时候又可能需要经过她妈妈的同意,也有可能需要经过她爸爸的同意,但具体是谁并不确定。姑且先这样理解,后面根据代码来细化。

首先创建一个女孩儿的接口,里面定义了两个方法

package main.java.com.baidu;

public interface Girl {
    void data();
    void watchMovie();
}

接着写一个王美丽类去实现这个接口,现在王美丽相当于是一个被代理的对象了。

package main.java.com.baidu;

public class WML implements Girl{
    public void data(){
        System.out.println("今天很开心");
    }
    public void watchMovie(){
        System.out.println("电影好看呐");
    }
}

现在需要一个代理类去代理WML,先看,再来挨着解释

package main.java.com.baidu;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Wmlproxy implements InvocationHandler {
    private Girl girl;
    public Wmlproxy(Girl girl){
        super();
        this.girl=girl;
    }
    public Object invoke(Object proxy, Method method,Object[] args) throws Exception{
        doSomeThingBefore();
        Object ret = method.invoke(girl,args);
        doSomethingEnd();
        return ret;
    }
    public void doSomeThingBefore(){
        System.out.println("111");
    }
    public void doSomethingEnd(){
        System.out.println("222");
    }
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(girl.getClass().getClassLoader(),girl.getClass().getInterfaces(),this);
    }
}

这里首先创建了一个Wmlproxy类继承了InvocationHandler接口,这个接口就是上面所说的起增强作用,里面的invoke方法里面应该包含被代理的对象,反射后通过调用ret来执行里面的方法,里面还做了前置增强和后置增强,通俗说就是增加的代码。

最后来到最后定义的一个方法getProxyInstance,他返回了Proxy.newProxyInstance(girl.getClass().getClassLoader(),girl.getClass().getInterfaces(),this);这样一个结果,我们挨着来分析。

我们说了invoke()方法是起的增强作用,那么真正实现代理的方法也就是Proxy.newProxyInstance,他返回一个动态代理对象(应该是通过加载字节码之类的获取 我猜的

第一个参数需要传入被代理对象的类加载器,也就是WML的类加载器,第二个参数需要获取的是被代理对象的接口,
第三个参数应该传代理对象

问题是这样写有什么用呢?先简单阐述一下,当调用代理类的getProxyInstance()方法的时候,由于实现了InvocationHandler接口,调用getProxyInstance()方法的时候会调用到invoke()方法。

先往下看,最后是写主类了

package main.java.com.baidu;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;


public class c1oud {
    public static void main(String[] args){
        Girl girl = new WML();
        Wmlproxy family = new Wmlproxy(girl);
        Girl mother = (Girl)family.getProxyInstance();
      //  Girl mother = (Girl)Proxy.newProxyInstance(Girl.class.getClassLoader(),new Class[]{Girl.class},family);
        mother.data();
        System.out.println("---------------");
        mother.watchMovie();
    }
}

首先要一个被代理类的对象,这里用了接口的回调实例化了一个WML类不过是Girl类型的,不太重要,往下看,接着把对象传到代理类里面,并且得到一个代理类的对象family,再然后就是调用方法了,调用family的getProxyInstance()后得到一个代理后的实例,记得要强制转化为接口的类型,接着调用方法就可以了。

梳理一下:
打个断点调试一下可以发现流程是这样的:
因为是用接口定义的类型创建的代理对象,所以当调用到接口的方法的时候,会调用第三个参数的invoke方法,而invoke方法里面用发射重新执行了刚才的方法,不过这次是。被代理对象 重写过接口里的方法。

可能看到这里还是比较懵,甚至没看懂怎么实现了动态代理。假如这个时候我们要代理另一个类,这里简单点看,假如也是实现的Girl接口,这时候怎么办,如果是静态代理的话,会创建一个新类去继承,假如是小美,同时也会修改代理类里面的内容或者增加一个代理类,还需要改主类里面的调用,基本换完了。

但是如果是动态代理的话同样增加一个接口的实现类

package main.java.com.baidu;

public class xm implements Girl{
    public void data(){
        System.out.println("我是小美");
    }
    public void watchMovie(){
        System.out.println("我也喜欢电影");
    }
}

但是现在不用去改代理类的东西了,直接改主方法就可以了,因为我们可以通过主方法对代理类里面实现的动态代理来代理新的东西。

package main.java.com.baidu;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;


public class c1oud {
    public static void main(String[] args){
        Girl girl = new WML();
        Wmlproxy family = new Wmlproxy(girl);
        Girl mother = (Girl)family.getProxyInstance();
      //  Girl mother = (Girl)Proxy.newProxyInstance(Girl.class.getClassLoader(),new Class[]{Girl.class},family);
        mother.data();
        System.out.println("---------------");
        mother.watchMovie();
        System.out.println("下面是小美");
        Girl xm = new xm();
        Wmlproxy fam = new Wmlproxy(xm);
        Girl moth = (Girl)fam.getProxyInstance();
        moth.data();
        System.out.println("-------------");
        moth.watchMovie();
    }
}

最后看结果

这样就实现了JDK的动态代理。可能很绕,总之需要自己多梳理就好了。

参考文章

http://arsenetang.com/2022/01/15/Java篇之Java动态代理/
http://blog.o3ev.cn/yy/1095