代理模式(静态代理、动态代理、CGLIB代理)

目录

一、代理模式

通俗说法:代理就是我们平常在现实中见到的中介

严谨说法:代理模式就是给每一个需要代理的对象,提供一个代理对象,而且,由代理对象对被代理对象进行控制,替被代 理对象做一些事情。

图示:

在这里插入图片描述

二、为什么要用代理模式

中介隔离作用:

在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。

开闭原则,增加功能:

代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

三、如何使用代理模式

按照代理创建的时期分类,两种:静态代理、动态代理。

静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。

动态代理是在程序运行时通过反射机制动态创建的。

1、静态代理模式

静态代理类只能替一个主题接口进行代理工作。

如果主题接口不同,代理工作相同,也需要编写两个代理类。

优点:

可以做到在符合开闭原则的情况下对目标对象进行功能扩展。

缺点:

我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。

图示:

在这里插入图片描述
在这里插入图片描述
示例:

package com.xxj.staticagency;

public class TestStaticAgency {

	public static void main(String[] args) {
		
		
		CarDao cd = new CarDao();
		Proxy proxy = new Proxy(cd);
		proxy.addCar();
		
		ConsumerDao con = new ConsumerDao();
		Proxy proxy2 = new Proxy(con);
		proxy2.addCar();
		
	}

}

//主题接口
interface Dao{
	void addCar();
}


//被代理类
class CarDao implements Dao{

	@Override
	public void addCar() {
		System.out.println("进口车辆已经到位");
	}
	
}

class ConsumerDao implements Dao{

	@Override
	public void addCar() {
		System.out.println("我要买车");
		
	}
	
}


//代理类
class Proxy implements Dao{
	
	private Dao target;
	
	

	public Proxy(Dao target) {
		super();
		this.target = target;
	}



	@Override
	public void addCar() {
		
		long start = System.currentTimeMillis();
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		target.addCar();//核心业务逻辑交还给被代理者
		long end = System.currentTimeMillis();
		System.out.println("时间差:" +(end-start));
	}
	
}

2、动态代理模式(由JDK负责)----spring框架中的AOP(面向切面编程)

静态代理类只能替一个主题接口进行代理工作。如果主题接口不同,代理工作相同,也需要编写两个代理类。

动态代理可以替不同的主题接口进行代理工作,只要他们的代理工作内容相同。

步骤:

(1)主题接口

(2)被代理类

(3)动态代理的代理工作处理器

要求必须实现:java.lang.reflect.InvocationHandler接口,重写

Object invoke(Object proxy, Method method,Object[] args)

(4)创建代理类对象

java.lang.reflect.Proxy类型的静态方法

newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h )

(5)调用对用的方法

图解:
在这里插入图片描述

示例:

 /* 步骤:
 * 1、编写主题接口(和静态代理一样)
 * 2、编写被代理类(和静态代理一样)
 * 3、编写代理工作处理器:即代理类要替被代理类做什么事情
 * 要求:必须实现InvocationHandler,重写
 *   Object invoke(Object proxy, Method method, Object[] args)
 *   第一个参数:代理类对象
 *   第二个参数:被代理类和代理类   要执行的方法
 *   第三个参数:要执行方法的实参列表
 *   
 *   这个invoke方法不是程序员调用,当代理类对象执行对应的代理方法时,自动调用的
 *   
 * 4、创建代理类及其对象
 *   需要:Proxy:提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
 *   
 *    static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 
 *    第一个参数:被代理类的类加载器,我们希望被代理和代理类使用同一个类加载器
 *    第二个参数:被代理类实现的接口们
 *    第三个参数:代理工作处理器对象
 *    
 * 5、调用被代理的方法   
 */
public class TestProxy2 {
	@Test
	public void test2(){
		//被代理对象
		YongHuDAO sd = new YongHuDAO();
		
		ClassLoader loader = sd.getClass().getClassLoader();//被代理者的类加载器对象
		Class<?>[] interfaces = sd.getClass().getInterfaces();//被代理者实现的接口们
		TimeInvocationHandler h = new TimeInvocationHandler(sd);//代理工作处理器对象
		
		//创建代理类及其对象
		//proxy是代理类的对象,代理类是编译器自动编译生成的一个类
		Object proxy = Proxy.newProxyInstance(loader, interfaces, h);
		
		//这里强转的目的是为了调用增、删、改、查的方法
		//为什么这里强转可以成功了,因为代理类与被代理类实现了相同的主题接口
		DBDAO d = (DBDAO) proxy;
		d.add();
		d.update();
		d.delete();
		d.select();
	}
	
	@Test
	public void test1(){
		//被代理对象
		ShangPinDAO sd = new ShangPinDAO();
		
		ClassLoader loader = sd.getClass().getClassLoader();
		Class<?>[] interfaces = sd.getClass().getInterfaces();
		TimeInvocationHandler h = new TimeInvocationHandler(sd);
		
		//创建代理类及其对象
		//proxy是代理类的对象,代理类是编译器自动编译生成的一个类
		Object proxy = Proxy.newProxyInstance(loader, interfaces, h);
		
		//这里强转的目的是为了调用增、删、改、查的方法
		//为什么这里强转可以成功了,因为代理类与被代理类实现了相同的主题接口
		DBDAO d = (DBDAO) proxy;
		d.add();
		d.update();
		d.delete();
		d.select();
	}
}
//代理工作处理器
class TimeInvocationHandler implements InvocationHandler{
	private Object target;//被代理对象

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

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		long start = System.currentTimeMillis();
		
		//被代理对象的xx方法被调用
		/*
		 * 没有反射:  被代理对象.xx方法(args实参列表)
		 * 有了反射:  方法对象.invoke(被代理对象,args实参列表)
		 */
		Object returnValue = method.invoke(target, args);
		
		long end = System.currentTimeMillis();
		System.out.println(method.getName() + "方法运行时间:" + (end-start));
		
		return returnValue;
	}
	
}


//主题接口
interface DBDAO{
	void add();
	void update();
	void delete();
	void select();
}
//被代理类1
class ShangPinDAO implements DBDAO{
	public void add(){
		System.out.println("添加商品");
	}

	@Override
	public void update() {
		System.out.println("修改商品");
	}

	@Override
	public void delete() {
		System.out.println("删除商品");
	}

	@Override
	public void select() {
		System.out.println("查询商品");
	}
}
//被代理类2
class YongHuDAO implements DBDAO{
	public void add(){
		System.out.println("添加用户");
	}

	@Override
	public void update() {
		System.out.println("修改用户");
	}

	@Override
	public void delete() {
		System.out.println("删除用户");
	}

	@Override
	public void select() {
		System.out.println("查询用户");
	}
}

3、CGLIB代理模式-----也是spring框架中的AOP(面向切面编程)的基础

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,
如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,
其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,
顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。
JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

什么是CGLIB?

CGLIB是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。

为什么使用CGLIB?
CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。关于Java动态代理,可以参者这里Java动态代理分析

CGLIB组成结构
在这里插入图片描述
CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它需要对Java字节码的格式足够的了解

总结:

CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,
但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,
因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。
同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

示例:

执行对象类

	/**
	 * 购房者
	 * 
	 * @author Administrator
	 */
	class FangNu3 {
	
		public void maifang(String houseName, double money) {
			System.out.println("购房者买房,成为房奴.");
		}
	
	}

编写代理类

	/**
	 * 中介 -- 代理
	 * cglib是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理。因为采用的是继承,所以不能对final修饰的类进行代理。
	 * @author Administrator
	 */
	class ZhongJieProxy3 implements MethodInterceptor {
		Object target;// 业务类对象,供代理方法中进行真正的业务方法调用
	
		// 相当于JDK动态代理中的绑定
		public Object getInstance(Object target) {
			this.target = target; // 给业务对象赋值
			Enhancer enhancer = new Enhancer(); // 创建加强器,用来创建动态代理类
			enhancer.setSuperclass(this.target.getClass()); // 为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
			// 设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
			enhancer.setCallback(this);
			// 创建动态代理类对象并返回
			return enhancer.create();
		}
	
		//实现回调方法 
		@Override
		public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
			System.out.println("静态代理前置内容 >> 中介挑选房子 ... ");
			System.out.println("Method:" + method);
	
			Object obj = methodProxy.invokeSuper(proxy, args);//调用业务类(父类中)的方法
	
			// 在代理真实对象后我们也可以添加一些自己的操作
			System.out.println("静态代理后置内容 >> 中介收取中介费,办手续交房 ... ");
			return obj;
		}		
	}

四、总结

静态代理是通过在代码中显式定义一个业务实现类一个代理,在代理类中对同名的业务方法进行包装,用户通过代理类调用被包装过的业务方法;

JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法;

CGLIB(Code Generation Library)动态代理(Spring)是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值