Spring源码学习(十三)---静态代理模式和JDK、CGLIB动态代理

一. 代理模式介绍

  • 代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

在这里插入图片描述

代理模式中有三个角色:

  • Subject(抽象角色):声明真实对象和代理对象的共同接口。

  • Proxy(代理角色):代理对象与真实对象实现同样的接口,所以它可以在不论什么时刻都可以代理真实对象。代理角色内部包括有对真实对象的引用。所以她可以操作真实对象,同一时候也可以附加其它的操作,相当于对真实对象进行封装。

  • RealSubject(真实角色):它代表着真实对象。是我们终于要引用的对象

二. 静态代理

利用构造器注入实现类,调用实现类的方法

在这里插入图片描述

Count接口 【Subject抽象角色】

package com.xizi.proxy;

/**
 * 账户接口
 */
public interface Count {
	// 查询账户
	void queryCount();

	// 修改账户
	void updateCount();
}

CountImpl 实现类 【RealSubject(真实角色)】

package com.xizi.proxy;

public class CountImpl implements Count {
	@Override
	public void queryCount() {
		System.out.println("==查询账余额==");
	}

	@Override
	public void updateCount() {
		System.out.println("==更新账户余额==");
	}
}

代理类实现Count接口 【Proxy(代理角色)】

package com.xizi.proxy;

public class CountProxy implements Count {

	//使用构造器注入实现类
	private CountImpl countImpl;


	public CountProxy(CountImpl countImpl) {
		this.countImpl = countImpl;
	}

	@Override
	public void queryCount() {
		System.out.println("==查询账户开始==");
		// 调用真正的查询账户方法
		countImpl.queryCount();
		System.out.println("==查询账户结束==");
	}

	@Override
	public void updateCount() {
		System.out.println("==更新账户开始==");
		// 调用真正的修改账户操作
		countImpl.updateCount();
		System.out.println("==更新账户结束==");
	}
}

测试方法

    @Test
    public void test1() {
        // 静态代理
        CountImpl countImpl = new CountImpl();
        //将接口实现类传入代理类的构造函数中
        CountProxy countProxy = new CountProxy(countImpl);
        System.out.println("============updateCount============");
        countProxy.updateCount();
        System.out.println("============queryCount============");
        countProxy.queryCount();
    }

三. JDK动态代理

  • JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的业务实现类对象以及方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。
  • 动态代理使我们不必再手动维护代理类。为了保持代理类的灵活性,又在Proxy和target之间新增了InvocationHandler这层抽象。
  • 我们只需指定代理类的预处理、调用后操作即可。JDK的动态代理需要实现InvocationHandler接口,并重写invoke方法。并且被代理的类必须有接口。

在这里插入图片描述

编写接口【Subject抽象角色】

package com.xizi.proxy_jdk;

public interface JDKAnimal {
	void sayHello();
}

实现类【RealSubject(真实角色)】

package com.xizi.proxy_jdk;

public class JDKCat implements JDKAnimal {  
	@Override
	public void sayHello() {
		System.out.println("我是一只猫");
	}
}

自定义InvocationHandler

package com.xizi.proxy_jdk;

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

public class MyInvocationHandler implements InvocationHandler {

	// 目标对象
	private Object target;

	/**
	 * 构造方法
	 * @param target 目标对象
	 */
	public MyInvocationHandler(Object target) {
		super();
		this.target = target;
	}

	/**
	 * @param proxy  JDK动态生成的最终代理对象
	 * @param method 调用真实对象的某个方法的Method对象
	 * @param args   调用真实对象某个方法时接受的参数
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("==代理方法开始执行==");
		Object invoke = method.invoke(target, args);
		System.out.println("==代理方法结束执行==");
		return invoke;
	}

	/**
	 * 获取目标对象的代理对象
	 * @return 代理对象
	 */
	public Object getProxy() {
		/**
		 * 参数:
		 * 1、获取当前线程的类加载
		 * 2、获取接口
		 * 3、当前对象
		 */
		return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
				target.getClass().getInterfaces(),
				this);
	}

}

测试JDK动态代理

    @Test
    public void test2() {
        // JDK动态代理
        MyInvocationHandler handler = new MyInvocationHandler(new JDKCat());
        //获取代理对象
        JDKAnimal proxy = (JDKAnimal) handler.getProxy();
        proxy.sayHello();
    }

四. JDK动态代理原理分析

  • 在MyInvocationHandler类中,通过实现InvocationHandler并重写invoke方法,实现了对JDKCat类的动态代理。在测试类中通过JDKAnimal proxy = (JDKAnimal) handler.getProxy();获取了代理类的实例,但是当调用proxy.sayHello();方法时,却会调用MyInvocationHandler的invoke方法。

生产JDK代理的Class文件 工具类

package com.xizi.proxy_jdk;

import org.junit.Test;
import sun.misc.ProxyGenerator;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;


public class GenJdkProxyClass {

	 // 生成代理类的名称
	private static String DEFAULT_CLASS_NAME = "$Proxy";

	// 默认生成的文件全路径
	private static String DEFAULT_FILE_PATH = "D:/IDEA/JavaWorkSpace/Spring-study/" + DEFAULT_CLASS_NAME + ".class";


	// 使用ProxyGenerator生成代理类.class文件
	public static void genProxyClass(String path) {

		byte[] classFile = ProxyGenerator.generateProxyClass(DEFAULT_CLASS_NAME, new Class[]{JDKAnimal.class});
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(path);
			fos.write(classFile);
			fos.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	@Test
	public void TestGenProxyClass() {
		GenJdkProxyClass.genProxyClass(DEFAULT_FILE_PATH);
	}
}

$Proxy代理类

package com.xizi.proxy_jdk;

import com.xizi.proxy_jdk.JDKAnimal;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy extends Proxy implements JDKAnimal {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy(InvocationHandler var1)   {
        super(var1);
    }

    public final boolean equals(Object var1)  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    //代理类中实现了sayHello接口
    public final void sayHello()  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString()   {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode()  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.xizi.proxy_jdk.JDKAnimal").getMethod("sayHello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}


  • 代理类$Proxy被声明为final类,继承Proxy类并实现了JDKAnimal接口,重写了equals、toString、hashCode等方法,当然最重要的还是实现了JDKAnimal接口中的sayHello方法,并通过静态代码块拿到了sayHello方法的信息

使用JDK动态代理测试反编译后的生成代理类

  @Test
    public void test3() {

        MyInvocationHandler handler = new MyInvocationHandler(new JDKCat());
        // 使用JDK动态代理测试反编译后的生成代理类
        $Proxy $proxy = new $Proxy(handler);
        $proxy.sayHello();
    }

在这里插入图片描述

反编译后的生成代理类进行分析

  • new $Proxy(handler)构造函数接受的就是我们自定义的MyInvocationHandler,所以当代码运行到$Proxy的sayHello方法时,this.h.invoke(this, m3, null); this.h就是MyInvocationHandler的实例,所以自然就会调用到invoke方法了,因为JDKAnimal proxy = (JDKAnimal) handler.getProxy();获取到的是代理类的实例,而不是JDKAnimal的实例。

在这里插入图片描述

四.CGLIB动态代理

  • GLIB是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理。因为采用的是继承,所以不能对final修饰的类进行代理。 在使用的时候需要引入cglib和asm的jar包,并实现MethodInterceptor接口。

导入CGLIB的maven依赖

在这里插入图片描述

pom.xml导入依赖

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

被代理类

package com.xizi.proxy_cglib;

public class CglibCat {
	public void sayHello() {
		System.out.println("我是一只猫。。。");
	}
}

自定义MethodInterceptor

package com.xizi.proxy_cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyCglibProxy implements MethodInterceptor {

	private Enhancer enhancer = new Enhancer();

	// 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
	public Object getInstance(Class clazz) {
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(this);
		// 返回代理对象
		return enhancer.create();
	}

	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		System.out.println("==代理方法开始执行");
		Object result = methodProxy.invokeSuper(proxy, args);
		System.out.println("==代理方法结束执行");
		return result;
	}

}

测试CGLIB动态代

    @Test
    public void test4() {
        // CGLIB动态代理
        CglibCat cat = (CglibCat) new MyCglibProxy().getInstance(CglibCat.class);
        cat.sayHello();
    }

在这里插入图片描述

  • 如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制)

  • 如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值