【设计模式】代理模式详解

一、理解代理模式

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.

这里使用到编程中的一个思想: 不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。

举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的。明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决。这就是代理思想在现实中的一个例子

二、静态代理

静态代理主要有四个元素组成
1、抽象主题类(Subject):通过接口或抽象类声明真实主题和代理对象实现的业务方法
2、真实主题(RealSubject):即目标类,实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
3、代理类(Proxy):实现了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
4、测试用例

代理模式的结构图如下:
在这里插入图片描述
举例详解
我们想邀请某明星蔡先生来为我们表演,蔡先生表示,工作事宜请联系经纪人。
1、接口

public interface PlayBasketball{
	public void playBasketball();
}

2、目标类

public class MrCai implements PlayBasketball{
	@Override
	public void playBasketball() {
		System.out.println("鸡你太美");
	}
}

3、代理类(扩展功能)

//3、代理人(扩展功能)
public class Proxy implements PlayBasketball{
	public PlayBasketball target;	//用于保存被代理的对象,因为被代理的对象都需要实现PlayBasketball接口,所以可以PlayBasketball作为对象类型
	public Proxy(PlayBasketball target) {
		super();
		this.target = target;
	}
	@Override
	public void playBasketball() {
		System.out.println("谈价格");	
		target.playBasketball();
		System.out.println("收钱");
	}
}

4、测试类

public class Test1 {
	public static void main(String[] args) {
		PlayBasketball playBasketball = new Proxy(new MrCai());
		playBasketball.playBasketball();		
	}
}

测试结果

分析以上用例:
(1)PlayBasketball接口中声明了业务类型就是playBasketball()
(2)MrCai(明星)和Proxy(经纪人)都实现了PlayBasketball接口。但是MrCai和Proxy实现playBasketball()方法的实现细节明显是不同的。对于MrCai来说,他的主要任务就是表演,对于Proxy经纪人来说,他的任务是首先要先谈好工作,然后让MrCai来表演,最后收钱。即在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
(3)在使用时,我们联系好经纪人并指定要邀请的明星,然后开始整个流程。

静态代理总结
1、可以做到在不修改目标对象的前提下,对目标功能进行扩展

思考这个问题:如果我有很多种不同类型的类型的艺人,他们的业务类型各不一样,唱、跳、rap都有,那么他们需要实现不同的业务接口,则同时需要给他们各配置对应的经纪人。但实际上,对于每个经纪人来说,谈工作和收钱这两部分工作都是一样的。这样的话,给不同类型艺人分别设置代理,会产生许多冗余的代码。而且一旦工作流程改变,则每个代理的代码都要改变。维护起来十分不便。

那么如何解决这个问题呢?下文介绍的动态代理可以解决上述缺点~

二、动态代理

2.1 理解动态代理

可以参考动态代理详解
上文说到,使用静态代理,我们需要为实现不同接口的目标类创建对应的代理类,维护不便。这种情况下,我们就会想:
问:既然代理类处理流程是一致的,那有没有一个类是可以代理所有目标类的?
答:不同目标类实现不同接口呀?同一个类怎么代理?
问:那有没有办法由一个工厂类来动态的生成代理对象呢?并且动态编译呢?
答:嗯…怎么生成动态代理对象呢?下面将解决这个问题!

2.2 jdk代理

我们将目标对象传递给一个工厂类,工厂类生产出代理对象,我们利用该代理对象来调用目标方法。
JDK动态代理是基于Java的反射机制实现的,主要涉及到java.lang.reflect包中的Proxy和InvocationHandler。
InvocationHandler是一个接口,通过实现这个接口定义一个横切的逻辑!然后通过反射机制调用目标类的方法,这样就能动态的把非业务逻辑和业务逻辑动态的拼接在一起!
Proxy则利用InvocationHandler创建代理实例,来间接的调用代理的方法。
我们来看下Java的api文档对这两个类是怎么描述的:
InvocationHandler:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy:指我们所代理的那个目标对象
method:指我们所要代理的目标对象的某个方法的Method对象
args:指代的是调用真实对象某个方法时需要的参数

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

这个方法的作用就是得到一个动态的代理对象,共接收三个参数,分别为
loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h:</>一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

调用创建代理的方法时,会根据classloader、interfaces、handler创建一个实现了interfaces的代理类。也就是说在此处该代理类实现了Subject。在InvocationHandler中会创建代理类的对象,并将代理对象以参数的方式传递invoke方法。当使用代理类对象调用接口中的方法时,会自动触发调用invoke方法,并将代理对象、被调用的方法、参数传递给invoke方法,然后在invoke方法中调用被代理对象的方法。

下面来看一个栗子。
1、接口

public interface Subject
{
    public void rent();
    public void hello(String str);
}

2、目标类

public class RealSubject implements Subject
{
    @Override
    public void rent()
    {
        System.out.println("I want to rent my house");
    }
    @Override
    public void hello(String str)
    {
        System.out.println("hello: " + str);
    }
}

3、工厂类(动态代理类)

public class DynamicProxy 
{
    // 这个就是我们要代理的真实对象
    private Object subject;
    //  构造方法,给我们要代理的真实对象赋初值
    public DynamicProxy(Object subject)
    {
        this.subject = subject;
    }
    //创建代理类对象
    public Object getProxyObject(){
		return Proxy.newProxyinstance(
				this.getClass().getClassLoader(), 
				subject.getClass().getInterfaces(),
				new InvocationHandler(){
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						//在代理真实对象前我们可以添加一些自己的操作
						System.out.println("开启事务");
						//执行目标类的方法,在代理对象调用真实对象的方法时,其会自动的调转到代理对象关联的handler对象的invoke方法来调用
						Object resultValue = method.invoke(subject, args);
						//在代理真实对象后我们也可以添加一些自己的操作
						System.out.println("提交事务");
						return resultValue;
					}
				}
			);  
    }
}

4、测试类

public class Test1 {
	public static void main(String[] args) {
		Object proxy = new DynamicProxy(new RealSubject()).getProxyObject();
		System.out.println("proxy的类名:"+proxy.getClass());
		System.out.println("-------------------------------------");
		System.out.print("proxy的属性:");
		Field[] fileds = proxy.getClass().getDeclaredFields();
		for(Field f:fileds) {
			System.out.print(f.getName()+",");
		}
		System.out.println("\n-------------------------------------");
		System.out.print("proxy的方法:");
		Method[] methods = proxy.getClass().getDeclaredMethods();
		for(Method m:methods) {
			System.out.print(m.getName()+",");
		}
		System.out.println("\n-------------------------------------");
		System.out.println("proxy的父类是:"+proxy.getClass().getSuperclass());  
        System.out.print("proxy实现的接口是:");  
        Class<?>[] interfaces=proxy.getClass().getInterfaces();  
        for(Class<?> i:interfaces){  
            System.out.print(i.getName()+", ");  
        }  
        System.out.println("\n-------------------------------------");
		((Subject) proxy).hello("world!");
		((Subject) proxy).rent();
	}
}

测试结果
在这里插入图片描述
首先我们来看下为什么代理对象的类名是$Proxy呢?

Proxy.newProxyInstance(this.getClass().getClassLoader(),realSubject.getClass().getInterfaces(), handler);

通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行时动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。

jdk代理需要目标类去实现一个接口,动态创建的代理类实现这个接口,所以,代理类可以看作是目标类的兄弟类。那么如果目标类没有实现任何接口,那么应该什么方式创建代理类和代理对象呢?

2.3 cglib代理

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值