JDK静态代理、JDK动态代理、Cglib动态代理区别

一、代理模式介绍

什么是代理模式呢?我很忙,忙的没空理你,那你要找我呢就先找我的代理人吧,那代理人总要知道被代理人能做哪些事情不能做哪些事情吧,那就是两个人具备同一个接口,代理人虽然不能干活,但是被代理的人能干活呀。说白了经纪人懂吧,整不好就绿你的那种。

二、JDK静态代理

场景比如西门庆找潘金莲,那潘金莲不好意思答复呀,咋办,找那个王婆做代理呀,然后就绿了大郎。。。

第一步:定义一种类型的女人,王婆和潘金莲就属于这个类型的女人

/**
* 定义一种类型的女人,王婆和潘金莲就属于这个类型的女人
 */
public interface KindWomen {

    //这种类型的女人能做什么事情呢?
    public void makeEyesWithMan(); //抛媚眼


    public void happyWithMan(); //你懂吧...

}

第二步:定义一个潘金莲是什么样的女人

//定义一个潘金莲是什么样的女人
public class PanJinLian implements KindWomen {
    @Override
    public void makeEyesWithMan() {
        System.out.println("潘金莲抛媚眼");
    }

    @Override
    public void happyWithMan() {
        System.out.println("潘金莲在和男人做那个.....");
    }
}

第三步:定义一个王婆,作为经纪人。

public class WangPo implements KindWomen {
    private KindWomen kindWomen;

    public WangPo() { //潘金莲的代理
        this.kindWomen = new PanJinLian();
    }

    //她可以是KindWomen的任何一个女人的代理,只要你是这一类型
    public WangPo(KindWomen kindWomen) {
        this.kindWomen = kindWomen;
    }

    public void happyWithMan() {
        this.kindWomen.happyWithMan(); //自己老了,干不了,可以让年轻的代替
    }

    public void makeEyesWithMan() {
        this.kindWomen.makeEyesWithMan(); //王婆这么大年龄了,谁看她抛媚眼?他只能帮忙传递一下了。。
    }
}

第四步:整一个西门庆。

/**
 * 定义一个西门庆,他是个什么都懂吧。
 * 用快递小哥的话:您是什么东西?
 * 用垃圾分拣阿姨的话:您是个什么垃圾?
 */
public class XiMenQing {

    /**
     * 看过水浒传的都知道,潘金莲抛了个媚眼,然后西门庆就把持不住了,
     * 然后西门庆去找王婆撮合,王婆因为贪图西门庆的钱财就答应做潘金莲的经纪人了,    
     * 然后绿了的大郎。。
     * 那我们假设一下:
     * 如果没有王婆在中间牵线,这两个不要脸的能成吗?难说的很 !
     */
    public static void main(String[] args) {
        //把王婆叫出来
        WangPo wangPo = new WangPo();

        //然后西门庆就说,我要和潘金莲happy,然后王婆就安排了西门庆那出戏。
        wangPo.makeEyesWithMan(); //看到没,虽然表面上时王婆在做,实际上happy的是潘金莲
        wangPo.happyWithMan();
    }
}

由此可见:代理模式是对开闭原则的典型实践,对扩展开放,对修改关闭

缺点:一个代理类只能代理一个业务接口,如果要代理多个业务接口需要定义多个实现类和代理类,如果在调用代理业务类前后的代码是一样的,则多个代理类会有很多冗余代码。

三、JDK动态代理

动态代理是指,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理 对象只是由代理生成工具(不是真实定义的类)在程序运行时由 JVM 根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。

对比静态代理,静态代理是指在程序运行前就已经定义好了目标类的代理类。代理类与目标类的代理关系在程序运行之前就确立了。

动态代理的实现方式常用的有两种:使用 JDK Proxy,与通过 CGLIB 生成代理。

Jdk 的动态要求目标对象必须实现接口,这是 java 设计上的要求。 jdk1.3 以来,java 语言通过 java.lang.reflect 包提供三个类支持代理模式 Proxy, Method InovcationHandler

A、通过 JDK java.lang.reflect.Proxy 类实现动态代理,会使用其静态方法 newProxyInstance(),依据目标对象、业务接口及调用处理器三者,自动生成一 个动态代理对象。

public static newProxyInstance ( ClassLoaderloader, Class<?>[]interfaces, InvocationHandlerhandler)

loader:目标类的类加载器,通过目标对象的反射可获取

interfaces:目标类实现的接口数组,通过目标对象的反射可获取

handler:调用处理器。

BInvocationHandler 是个接口,其具体介绍如下:

实现了 InvocationHandler 接口的类用于加强目标类的主业务逻辑。这个接口中有一个 方法 invoke(),具体加强的代码逻辑就是定义在该方法中的。程序调用主业务逻辑时,会自动调用 invoke()方法。

//Object proxy:代理对象

//Method m :调用的方法

//Object [] args: 调用方法的参数

public Object invoke(Object proxy, Method m, Object[] args)

CMethod 类对象,该类有一个方法也叫 invoke(),可以调用目标类的目标方法。 这两个 invoke()方法,虽然同名,但无关。 public Object invoke ( Object obj, Object... args) obj:表示目标对象 args:表示目标方法参数,就是其上一层 invoke 方法的第三个参数

JDK动态代理实现如下:

场景:计算每个方法的执行时间

第一步:创建业务接口类

public interface SomeService {
	void doSome();
	void doOther();
}

第二步:创建业务接口的实现类

public class SomeServiceImpl implements SomeService {
	
	public void doSome(){
		int sum = 0;
		for(int i=1;i<9000000;i++){
			sum += i;
		}
		System.out.println("do some...");
	}
	
	public void doOther(){
		int sum = 0;
		for(int i=1;i<7000000;i++){
			sum += i;
		}
		System.out.println("do other...");
	}
}

第三步:创建代理类

public class TimeInvocationHandler implements InvocationHandler {
	
	//目标对象
	private Object target;

	public TimeInvocationHandler(Object obj){
		this.target = obj;
	}

	/**
	 * proxy:
	 * 		代理对象的引用
	 * method:
	 * 		目标对象的方法
	 * args:
	 * 		目标方法的实际参数列表(实参)
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

		long begin = System.currentTimeMillis();
		//无论是反射机制还是传统方式调用一个方法,必须具备四个要素:对象、方法、参数、返回值。

		//比较传统的方法调用方式:
		//	boolean loginSuccess = userService.login("zhangsan" , "123");
		//	返回值 = 对象.方法(实参);

		//使用反射机制调用方法:
		//	返回值 = 方法.调用(对象, 参数);
		Object retValue = method.invoke(target, args);

		long end = System.currentTimeMillis();

		System.out.println(method.getName() + "耗时" + (end - begin) + "毫秒");

		return retValue;
	}

	//实例方法,绑定业务对象并返回一个代理类
	public Object getProxy(){
		// 通过反射机制,创建一个代理类对象并返回实例,用户进行方法调用时使用
		// 创建代理对象时,需要传递该业务类的类加载器(用来获取业务实现类的元数据,调用真的的业务方法)、接口、handler实现类(this是当前对象)
		return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
	}
}

第四步:创建测试类

public class Test {

	public static void main(String[] args) {
		
		//目标对象(真正执行/处理业务的对象)
		SomeService someService = new SomeServiceImpl();
		
		//代理对象(JDK的动态代理只能代理接口)
		/*
		 * loader:类加载器
		 * 		动态代理当中,代理类是反射机制构造的,在硬盘上看不见,反射机制中拼接的class字节码,
		 * 		虽然看不到,但是在内存中会生成临时的“代理类.class”
		 * 		只要是类,一定要通过类加载器加载才行,所以第一个参数必须传类加载器。
		 * 		规定:代理类和目标类必须通过同一个类加载加载。
		 * 
		 * interfaces:指定代理的哪些接口
		 * 		代理类和目标类所实现的接口应该是一样的。
		 * 
		 * h: 调用处理器(InvocationHandler)
		 * 		调用处理器中有一个invoke方法。
		 *		这个invoke方法什么时候执行?
		 *			SUN规定:
		 *			   当代理对象调用代理方法的时候,“注册”在调用处理器当中的invoke方法会被自动调用。
		 */
		/*
		SomeService someServiceProxy = (SomeService)Proxy.newProxyInstance(
				someService.getClass().getClassLoader(), 
				someService.getClass().getInterfaces(), 
				new TimeInvocationHandler(someService));
		*/
		SomeService someServiceProxy = (SomeService)new TimeInvocationHandler(someService).getProxy();
		
		//代理对象执行代理方法
		someServiceProxy.doSome(); //这个doSome()是代理对象的代理方法。
		someServiceProxy.doOther();
	}

}

JDK动态代理:通过传进来的业务实现类和方法进行调用业务实现类的同名方法

缺点:JDK动态代理的代理对象在创建时,需要有业务实现类所实现的接口作为参数(因为后面代理方法需要根据接口内的方法名进行调用)。如果业务实现类没有实现接口而是直接定义接口的话,或者该业务实现类中增加了接口没有的方法(因为无法调用),就无法使用JDK动态代理。

四、CGLIB 动态代理

CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展Java 类与实现 Java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP

使用 JDK Proxy 实现代理,要求目标类与代理类实现相同的接口。若目标类不存在接口,则无法使用该方式实现。但对于无接口的类,要为其创建动态代理,就要使用 CGLIB 来实现。

CGLIB 代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。所以,使用 CGLIB 生成动态代理,要求目标类必须能够被继承,即不能是 final 类。

CGLIB 经常被应用在框架中,例如 Spring Hibernate 等。cglib 的代理效率高于 jdk项目中直接使用动态代理的地方不多。一般都使用框架提供的功能。

场景:目标类返回值改变为大写

第一步:创建目标类

//业务目标类
public class SomeService {

	public String doSome(){
		System.out.println("业务方法doSome");
		return "abcd";
	}
}

第二步:创建方法拦截器的类

//定义方法拦截器的类, 需要实现cglib的接口 MethodInterceptor(等同于jdk中的InvocationHandler)
public class MyMethodInterceptor implements MethodInterceptor {

	private Object target;
	
	public MyMethodInterceptor(Object target) {
		this.target = target;
	}

	/**
	 * intercept特点:截取对目标方法的调用
	 * 参数:
	 *  Object obj:代理对象
	 *  Method method:目标方法
	 *  Object[] args:方法参数
	 *  MethodProxy proxy:方法的代理对象
	 * 返回值:
	 *  Object:目标方法的执行结果
	 */
	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("=========intercept=========");
		//调用目标方法
		Object result = method.invoke(target, args);
		if( result != null){
			String str = (String)result;
			result = str.toUpperCase();
		}
		
		return result;
	}

	
}

第三步:创建获取代理对象的工具类

/**
 * 创建代理对象
 */
public class ProxyFactory {

	public Object createProxy(Object target){
		//使用Enhancer对象创建目标类的代理对象
		//创建Enhancer对象
		Enhancer en  = new Enhancer();
		//指定目标类
		en.setSuperclass(SomeService.class);
		//指定方法拦截器对象, 
		en.setCallback(new MyMethodInterceptor(target));
		//创建代理对象
		return en.create();
		
	}
}

第四步:创建测试类

public class MyTest {

	public static void main(String[] args) {
		//创建目标对象
		SomeService target = new SomeService();
		
		//创建工具类
		ProxyFactory factory  = new ProxyFactory();

		//创建代理对象
		SomeService proxy  = (SomeService) factory.createProxy(target);

		System.out.println("代理对象父类名称:"+proxy.getClass().getSuperclass().getName());
		System.out.println("代理类名称:"+proxy.getClass().getName());

		//通过代理对象执行方法。实现功能增强
		String str = proxy.doSome();
		System.out.println("str:"+str);

	}

}

运行:查看代理对象父类名可以看出是继承自目标类的

在Spring框架中AOP动态代理机制,如果被代理对象实现了需要被代理的接口,则使用JDK的动态代理,如果没有实现接口则使用CGLIB动态代理,spring会自动在JDK动态代理和CGLIB动态代理之间来回切换, 如果需要强制使用CGLIB动态代理可以在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值