代理模式
概述
代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
代理模式是一种对象结构型模式。在代理模式中引入了一个新的代理对象,代理对象在客户端对象和目标对象之间起到中介作用,它去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务。
静态代理
代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式中引入抽象层,代理模式的结构如下所示:
由图可知,代理模式包含三个角色。
- 抽象主题角色(Sing):它声明了真实主题角色和抽象主题角色的共同接口,这样一来,在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主体角色进行编程。
- 代理主题角色(Broker):它包含了对真实主题的应用,从而可以在任何时候操作真实主题的对象;在代理主体角色中提供了一个与真实主体角色相同的接口,以便在任何时候都可以替代真实主题;代理主体角色还可以控制对真实主题角色的使用,负责在需要的时候创建和删除真实主体对象,并对真实主体对象的使用加以约束。通常,在代理主体角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯的调用真实主体对象中的操作。
- 真实主题角色(Singer):它定义了代理角色所代表的真实对象,在真实主体角色中实现了真实的业务操作,客户端可以通过代理主体角色间接调用真实主体角色中定义的操作,
package DesignPatterns.ProxyPatterns.StaticProxy;
/**
* 创建sing接口,声明歌手所要做的事情。
* @author lhang
* @create 2019-07-05 15:13
*/
public interface Sing {
public abstract void sing();// 唱歌
}
package DesignPatterns.ProxyPatterns.StaticProxy;
/**
* 歌手扮演被代理的对象,实现Sing接口。
* @author lhang
* @create 2019-07-05 15:14
*/
public class Singer implements Sing {
@Override
public void sing() {
System.out.println("歌手 唱歌....");
}
}
package DesignPatterns.ProxyPatterns.StaticProxy;
/**
* 经纪人扮演代理类的对象,实现Sing接口。
* @author lhang
* @create 2019-07-05 15:15
*/
public class Broker implements Sing {
private Singer singer;
public Broker(Singer singer) {
this.singer = singer;
}
private void confer() {
System.out.println("经纪人面谈");
}
private void signContract() {
System.out.println("经纪人签合同");
}
private void bookTicket() {
System.out.println("经纪人订票");
}
public void sing() {
confer();
signContract();
bookTicket();
singer.sing();
collectMoney();
}
private void collectMoney() {
System.out.println("经纪人收钱");
}
}
package DesignPatterns.ProxyPatterns.StaticProxy;
/**
* 对静态代理结构进行测试
* @author lhang
* @create 2019-07-05 15:29
*/
public class ProxyTest {
public static void main(String[] args) {
Singer singer = new Singer();
Broker broker = new Broker(singer);
broker.sing();
}
}
/*运行结果:
经纪人面谈
经纪人签合同
经纪人订票
歌手 唱歌....
经纪人收钱
*/
动态代理
动态代理可以让系统在运行时根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实主题类而且可以代理不同的方法。动态代理是一种较为高级的代理模式,他在事务管理、Aop等领域都发挥了重要的作用。
java语言实现动态代理时需要用到位于java.lang.reflect包中的一些类,下面简要介绍一下Proxy类和InvocationHandler接口。
1.Proxy类
Proxy类提供了用于创建动态代理类和实例对象的方法,它是所创建的动态代理类的父类,它最常用的方法如下。
(1) public static class <?> getProxyClass ( ClassLoader loader,Class <?>… interfaces): 该方法用于返回一个Class类型的代理类,在参数中需要提供类加载器,并需要指定代理的接口数组(与真实主题类的接口列表一致)。
(2)public static Object newProxyInstance(ClassLoader loader,Class<?> [] interfaces,InvocationHandler h): 该方法用于返回一个动态创建的代理类的实例,方法中的第1个参数loader表示代理类的类加载器,第2个参数interfaces表示代理类所实现的接口列表(与真实主题类的接口列表一致),第3个参数h表示所指派的调用处理程序类。
2.InvocationHandler 接口
InvocationHandler 接口是代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(InvocationHandler 接口的子类)。在接口中声明了如下方法:
public Object invoke(Object proxy, Method method ,Object[] args.)
该方法用于处理对代理类实例的方法调用并返回相应的结果,当一个代理实例中的业务方法被调用时将自动调用该方法。invoke()方法包含三个参数,其中第1个参数proxy表示代理类的实例,第2个参数method表示需要代理的方法,第3个参数args表示代理方法的参数数组。
动态代理类需要在运行时指定所代理真实主题类的接口,客户端在调用动态代理对象的方法时调用请求会将请求自动转发给InvocationHandler 的对象的invoke()方法,由invoke()方法来实现对请求的统一处理。
下面使用java实现动态代理:
package com.atguigu.java;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
*
* 动态代理的举例
* @author lhang
* @create 2019-07-05 15:13
*/
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}
class HumanUtil{
public void method1(){
System.out.println("====================通用方法一====================");
}
public void method2(){
System.out.println("====================通用方法二====================");
}
}
class ProxyFactory{
//调用此方法,返回一个代理类的对象。解决问题一
public static Object getProxyInstance(Object obj){//obj:被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;//需要使用被代理类的对象进行赋值
public void bind(Object obj){
this.obj = obj;
}
//当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
//将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
HumanUtil util = new HumanUtil();
util.method1();
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj,args);
util.method2();
//上述方法的返回值就作为当前类中的invoke()的返回值。
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("烤冷面");
System.out.println("*****************************");
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
proxyClothFactory.produceCloth();
}
}
适用环境
代理模式是常用的结构型设计模式之一,它为对象的间接访问提供了一个解决方案,可以对对象的访问进行控制。代理模式的类型较多,其中远程代理,虚拟代理,保护代理等在软件开发中的应用非常广泛。在java RMI、EJB、Web Service、Spring AOP等技术和框架中都使用了代理模式。代理模式的类型较多,不同类型的代理模式有不同的优缺点,它们应用于不同的场合:
- 当客户端对象需要访问远程主机中的对象时,可以使用远程代理。
- 当需要用一个消耗资源较少的对象来代替一个消耗资源较多的对象,从而降低系统开销,缩短运行时间时可以使用虚拟代理,比如一个对象需要很长时间才能完成加载时。
- 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理,通过使用缓冲代理,系统无需在客户端每次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。
- 当需要控制对一个对象的访问为不同用户提供不同级别的访问权限时,可以使用保护代理。
- 当需要为一个对象的访问提供一些额外的操作时,可以使用智能引用代理,客户端共享访问这些结果时可以使用缓冲代理,通过使用缓冲代理,系统无需在客户端每次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。