动态代理

一.动态代理

动态代理的作用:就是在不破坏已经写好的类的情况下,去增强其中某些方法的功能,比如在调用方法之前或之后,做一些额外的操作.(像权限校验,等)

动态代理的两种实现:

       1.根据接口实现的动态代理(JDK)

       2.根据类实现的动态代理(CGLIB)

二.JDK中的proxy-使用

1.先创建需要被代理的接口--其中eat()方法

public interface IEat {

    public void eat(Apple apple);

}

2.再创建类去实现该方法--打印一段话

public class Eat implements IEat{
    @Override
    public void eat(Apple apple) {
        System.out.println("I eat a "+apple.getColor()+" "+apple.getName());
    }
}

3.创建Apple类--参数类

public class Apple {

    private String name;

    private String color;

    public Apple(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

4.创建一个xxhandler 去实现 InvocationHandler--实现invoke方法

public class AppleHandler implements InvocationHandler {

    private Object target;

    // 传入需要被代理的对象,获得增强后的对象
    public  T getInstance(Object target) throws Exception {
        this.target = target;
        Class targetClass = target.getClass();
        return (T) Proxy.newProxyInstance(targetClass.getClassLoader(), targetClass.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用原有方法之前,进行调用(比如:权限控制,或者打印入参日志等)
        before();
        // 调用原有方法
        Object obj = method.invoke(this.target, args);
        // 在调用原有方法之后,进行调用(比如:打印出参日志,数据库回滚或提交操作等)
        after();
        return obj;
    }

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

    private void after() {
        System.out.println("吃完了");
    }
}

5.创建一个测试类进行测试

public class Test {

    public static void main(String[] args) throws Exception {
        IEat ie = new Eat();
        ie.eat(new Apple("apple","red"));

        System.out.println("============================");

        // 获取加载地址,获取类接口,再获取重写后的增强方法,返回一个增强类
        IEat obj = new AppleHandler().getInstance(new Eat());
        obj.eat(new Apple("apple","red"));

    }
}

6.测试结果---可以看出,并未对原有IEat接口,Eat类,Apple类,进行更改,但采用代理方式,却多做了一些处理

动态代理_第1张图片

三.JDK中的proxy-分析

1.我们去生成一下代理类(利用ProxyGenerator.generateProxyClass()进行代理类生成),再用反编译去查看下代理类的具体情况

public class Test {

    public static void main(String[] args) throws Exception {
        IEat ie = new Eat();
        ie.eat(new Apple("apple","red"));

        System.out.println("============================");

        // 获取加载地址,获取类接口,再获取重写后的增强方法,返回一个增强类
        IEat obj = new AppleHandler().getInstance(new Eat());
        obj.eat(new Apple("apple","red"));

        //$Proxy0
            byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{IEat.class});
            FileOutputStream os = new FileOutputStream("E://$Proxy0.class");
            os.write(bytes);
            os.close();
    }
}

2.代理类的具体情况--(精简化后)

import com.gper.vip.homework.proxy.Apple;
import com.gper.vip.homework.proxy.IEat;
import java.lang.reflect.*;

public final class $Proxy0 extends Proxy implements IEat{

    public $Proxy0(InvocationHandler invocationhandler){
        super(invocationhandler);
    }

    // 实现eat()方法
    public final void eat(Apple apple){
        try{
            // 其中的 h ,其实就为传入invocationhandler
            // 就是调用的invocationhandler中的invoke(this, m3, new Object[] { apple })方法
            super.h.invoke(this, m3, new Object[] { apple });
            return;
        }catch(Error _ex) { 
        }catch(Throwable throwable){
            throw new UndeclaredThrowableException(throwable);
        }
    }

    private static Method m3;

    static {
        try{
            // 获取IEat中的eat方法
            m3 = Class.forName("com.gper.vip.homework.proxy.IEat").getMethod("eat", new Class[] {
                Class.forName("com.gper.vip.homework.proxy.Apple")
            });
        }catch(NoSuchMethodException nosuchmethodexception){
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }catch(ClassNotFoundException classnotfoundexception){
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}

从上述代理类中可以看出,

1.proxy其实就是通过,新生成一个代理类($Proxy0)去继承IEat接口,实现eat()方法.

2.而在代理类在eat()方法中,去调用的是InvocationHandler.invoke()方法,而InvocationHandler是通过代理类的构造函数传入的

3.而InvocationHandler,就是Proxy.newProxyInstance(targetClass.getClassLoader(), targetClass.getInterfaces(), this);

    就是上面我们生成代理类时,传入的this,也就是我们写好的AppleHandler

4.调用逻辑 $Proxy0.eat()-->AppleHandler.invoke()-->Eat.eat()

   所以我们就可以在AppleHandler.invoke()中增强,接下来需要调用的Eat.eat()方法了

四.仿照JDK中的proxy-进行编写

1.首先进行Proxy.newProxyInstance的编写,整体逻辑为:
    a.动态生成源文件-(targetClass.getInterfaces())
    b.生成Java文件输出到磁盘
    c.将生成的java文件编译成.class文件
    d.把编译成的.class文件加载到jvm中
    e.返回通过字节码重组以后生成的新的代理对象

建立一个MyProxy并且创建一个newProxyInstance(MyClassLoader loader, Class[] interf, MyInvocationHandler h)方法

public class MyProxy {

    private static final String ln = "\r\n";

    public static Object newProxyInstance(MyClassLoader myClassloader, Class[] interfaces, MyInvocationHandler h) throws Exception {
        // 1.动态生成源文件
        String str = generateEatSrc(interfaces);
        // 2.Java文件输出到磁盘
        String filePath = MyProxy.class.getResource("").getPath();
        File file = new File(filePath + "$Proxy0.java");
        FileWriter fileWriter = new FileWriter(file);
        fileWriter.write(str);
        fileWriter.flush();
        fileWriter.close();
        // 3.把生成的.java文件编译成.class文件
        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null, null, null);
        Iterable it = standardJavaFileManager.getJavaFileObjects(file);

        JavaCompiler.CompilationTask task = javaCompiler.getTask(null, standardJavaFileManager, null
                , null, null, it);

        task.call();
        standardJavaFileManager.close();

        // 4.把编译成的.class文件加载到jvm中
        Class proxyClass = myClassloader.findClass("$Proxy0");
        // 5.返回字节码重组以后生成新的代理对象
        Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
        file.delete();
        return c.newInstance(h);
    }

    // 比较low的方法,直接按照代理类进行拼接,写死了
    private static String generateEatSrc(Class[] interfaces) {
        StringBuffer sb = new StringBuffer();
        sb.append("package com.gper.vip.homework.proxy.myproxy;" + ln);
        sb.append("import com.gper.vip.homework.proxy.Apple;" + ln);
        sb.append("import com.gper.vip.homework.proxy.IEat;" + ln);
        sb.append("import java.lang.reflect.*;" + ln);
        sb.append("public final class $Proxy0 implements "+interfaces[0].getName()+"{" + ln);
        sb.append("    MyInvocationHandler h;" + ln);
        sb.append("    public $Proxy0(MyInvocationHandler h){" + ln);
        sb.append("        this.h=h;" + ln);
        sb.append("    }" + ln);
        sb.append("    public final void eat(Apple apple){" + ln);
        sb.append("        try{" + ln);
        sb.append("            h.invoke(this, m3, new Object[] {apple});" + ln);
        sb.append("            return;" + ln);
        sb.append("        }" + ln);
        sb.append("        catch(Error _ex) { }" + ln);
        sb.append("        catch(Throwable throwable){ throw new UndeclaredThrowableException(throwable);}" + ln);
        sb.append("    }" + ln);
        sb.append("    private static Method m3;" + ln);
        sb.append("    static {" + ln);
        sb.append("        try{" + ln);
        sb.append("            m3 = Class.forName(\"com.gper.vip.homework.proxy.Eat\").getMethod(\"eat\", new Class[] {" + ln);
        sb.append("                Class.forName(\"com.gper.vip.homework.proxy.Apple\")});" + ln);
        sb.append("        }catch(NoSuchMethodException nosuchmethodexception){" + ln);
        sb.append("            throw new NoSuchMethodError(nosuchmethodexception.getMessage());" + ln);
        sb.append("        }catch(ClassNotFoundException classnotfoundexception){" + ln);
        sb.append("            throw new NoClassDefFoundError(classnotfoundexception.getMessage());" + ln);
        sb.append("        }" + ln);
        sb.append("    }" + ln);
        sb.append("}" + ln);
        return sb.toString();
    }

    public static void main(String[] args) throws Exception {
        IEat ie = (IEat) new MyAppleHandler().getInstance(new Eat());
        ie.eat(new Apple("apple", "red"));
    }
}

2.再创建一个MyClassLoader类,继承ClassLoader(由于需要用到defineClass()方法,而自己又写不出),并重写findClass()方法

defineClass就是将将一个byte数组实例化成一个类类型

public class MyClassLoader extends ClassLoader {

    private File classPathFile;

    public MyClassLoader(){
        // 获取MyClassLoader所在本地文件地址
        String classPath = MyClassLoader.class.getResource("").getPath();
        this.classPathFile = new File(classPath);
    }

    // 找到.class文件,并将其加载进来
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        // 拼接类的全类名
        String className = MyClassLoader.class.getPackage().getName()+"."+name;
        // 获取类文件所在磁盘位置
        File file = new File(classPathFile,name+".class");
        // 读取类文件,并写入缓存中
        FileInputStream in = null;
        ByteArrayOutputStream out = null;
        try{
            in = new FileInputStream(file);
            out = new ByteArrayOutputStream();
            byte[] buff = new byte[1024];
            int len;
            while((len = in.read(buff))!= -1){
                out.write(buff,0,len);
            }
            // Converts an array of bytes into an instance of class Class
            return defineClass(className,out.toByteArray(),0,out.size());
        }catch (Exception e){

        }
            return null;
    }
}

3.创建一个MyInvocationHandler接口

public interface MyInvocationHandler {

    // 代理对象,方法,参数
    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException, Exception;
}

4.创建一个MyAppleHandler去实现MyInvocationHandler接口

public class MyAppleHandler implements MyInvocationHandler {


    private Object target;

    public Object getInstance(Object target) throws Exception {
        this.target = target;
        Class targetClass = target.getClass();
        // 使用MyProxy , MyClassLoader , MyInvocationHandler 去生成代理类
        return MyProxy.newProxyInstance(new MyClassLoader(), targetClass.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
        befor();
        Object invoke = method.invoke(this.target, args);
        after();
        return invoke;
    }

    private void after() {
        System.out.println("执行方法之后...");
    }

    private void befor() {
        System.out.println("执行方法之前....");
    }

}

5.运行测试方法

    public static void main(String[] args) throws Exception {
        IEat ie = (IEat) new MyAppleHandler().getInstance(new Eat());
        ie.eat(new Apple("apple", "red"));
    }

6.测试结果--还原JDK的proxy功能

动态代理_第2张图片

从上述就能看出整条逻辑线为:

1.通过传入的interfaces,myInvocationHandler参数,经过反射+拼接字符串,生成代理类(一长串字符串)

   a.代理类实现了--需要被代理类所实现的接口,以及在实现的接口方法中,调用的是myInvocationHandler.invoke()方法

   b.代理类只有一个有参构造(必须传MyInvocationHandler)

2.将(一长串字符串)输出到磁盘上,形成.java文件

3.再使用javaCompile将.java文件编译成.class文件

4.再将.class加载到jvm中

5.再到jvm中找到加载的代理类,并通过有参构造函数初始化(传入MyAppleHandler)一个代理类并返回该代理类

五.关于JDK动态代理源码中要求目标类实现的接口数量不能超过65535个的问题?

答:是和Class类文件的结构中的接口计数器有关,接口计数器是u2类型,u2又是2个字节的,16位,最大就是2^16-1=65535 ,因为超过65535时,接口技术器就爆掉了,所以动态代理中要求目标类实现的接口数量不能超过65535个~

参考-类文件结构

你可能感兴趣的:(设计模式)