代理模式:为目标对象提供一种代理以控制对这个对象的访问模式。
举个简单例子:一个客户想卖房子,这个过程需要了解房产市场,同买方协商,交易成功后还需要办理过户,而且他还一下找不到买家,这时他找到了房屋中介,希望房屋中介暂时代理他完成上面的操作并找到好买家卖个好价钱。这时卖房子客户就成为了代理的目标对象,而房屋介成为了代理对象。在卖房子过程中卖方无需关注上述繁琐的操作只需房屋中介找到买家后出售房屋即可,在出售房屋时候房屋中介就用代理的方式访问了目标对象--卖房者。在卖房子过程中了解市场,协商买方,办理过户这些操作都由房屋中介来完成,卖方只需要我的房子卖了多少钱这个业务逻辑了,这就是的代理模式。
代理模式有三个角色
代理模式一般涉及到的角色有:
抽象角色:声明真实对象和代理对象的共同接口;
代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。
参见文献:《Java与模式》
在业务逻辑开发过程中,代理模式被广泛应用,随着业务逻辑的深入我们会发现有许多相同的操作,比如业务日志,事务处理,异常处理,在每个业务逻辑中都有这些不可避免的操作,更多的时候让我们没有办法专著业务逻辑的实现。这时我们就想是否可以有一个模式在能暂时将业务日志,事务处理,异常处理等与业务逻辑分开开发,然后拼装后统一运行呢?当然有了这时就需要用到我们刚才说的代理模式。
下面我们以房屋买卖为例子讲解下代理模式
1.我们需要一个卖房接口,里面有一个卖房方法,带一个房屋价钱参数
public interface Ihouse
{
public void toSell(Object SellName,Double price);
}
2.创建一个实现类IhouseImplSell,实现Ihouse接口
public class IhouseImplSell implements Ihouse
{
public void toSell(Object SellName,Double price)
{
System.out.println(SellName+"的房子出售价钱="+price.toString());
}
}
这时我们用房屋中介代理卖房并收取服务费用,这时房屋的价格=卖方价格+房屋中介费用。
public class IhouseImplSellProxy implements Ihouse
{
//卖房接口,封装了toSell(Object sellName,Double price)方法
private Ihouse ihouse=null;
//中介提取服务费用
private Double price=null;
//无参构造方法
public IhouseImplSellProxy(){}
//带参构造方法
public IhouseImplSellProxy(Ihouse ihouse,Double price)
{
this.ihouse=ihouse;
this.price=price;
}
//实现卖房接口中卖房方法
public void toSell(Object SellName,Double price)
{
ihouse.toSell(SellName,this.price+price);
}
}
我们测试输出
public class Test
{
public static void main(String[] arge)
{
System.out.println("无代理模式输出");
Ihouse NoProxy=new IhouseImplSell();
NoProxy.toSell("tom",new Double(100));
System.out.println("房屋中介代理模式输出");
Ihouse proxy=new IhouseImplSellProxy(new IhouseImplSell(),new Double(200));
proxy.toSell("tom",new Double(100));
}
}
输出结果:
无代理模式输出
tom的房子出售价钱=100
房屋中介代理模式输出
tom的房子出售价钱=300
两种情况我们都使用了同一个接口同一个方法,但是结果确不同,在第二种情况中,房屋中介代理的卖方并收取了中介费用,代理模式很好用吧,可是问题来了,我们发现在进行代理时候我们需要为同一个类中每一方法都需要创建代理类。那么时候有一个模式来实现动态的代理一个类中的所有方法呢。当然有了,这就是我们下面介绍的动态代理。
JDK1.3以后,Java提供了动态代理技术,允许开发者在运行期间创建接口的代理实例。
JDK的don动态代理主要涉及到Java.lang.reflect包中的两个类,Proxy和InvocationHandler,其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。而Proxy利用InvocationHandler动态创建一个符合某一个接口的实例,生成目标类的代理对象。下面我们将房屋买卖的例子用JDK动态代理重新写一遍。
1.我们需要一个卖房接口,里面有一个卖房方法,带一个房屋价钱参数,一个买房方法,带一个买方价钱方法。
public interface Ihouse
{
public void toSell(Object SellName,Double price);
public void toBuy(Object BuyName,Double price);
}
2.创建一个实现类IhouseImplSell,实现Ihouse接口
public class IhouseImplSell implements Ihouse
{
public void toSell(Object SellName,Double price)
{
System.out.println(SellName+"的房子出售价钱="+price.toString());
}
public void toBuy(BuyName,Double price)
{
System.out.println(BuyName+"可以出的价钱="+price.toString());
}
}
3.创建一个代理类,也就是中介需要办理的业务部分---了解市场,过户等等。这里我们对买房和卖房的都加收200元的中介费用
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class IhouseImplSellProxy implements InvocationHandler
{
//卖房接口,封装了toSell(Object sellName,Double price)方法
private Object ihouse=null;
//中介提取服务费用
private Double price=null;
//无参构造方法
public IhouseImplSellProxy(){}
//带参构造方法
public IhouseImplSellProxy(Ihouse ihouse,Double price)
{
this.ihouse=ihouse;
this.price=price;
}
//通过反射机制调用目标类的方法
public Object invoke(Object proxy,Method method,Object[] arges)throws Throwable
{
Object result=null;
Double firstPrice=(Double)arges[1];
arges[1]=firstPrice+price;
try
{
result=method.invoke(ihouse,arges);
}
catch(Exception ex)
{
ex.printStackTrace();
}
return result;
}
}
其中proxy是最终生成的代理实例,一般不会被用到,method是被代理目标实例的某个具体方法,通过阿可以发起目标实例方法的反射调用。
arges是通过被代理实例某一个方法的入参,在方法反射调用时使用.
4.创建代理实例并执行
import java.lang.reflect.Proxy;
public class test
{
public static void main(String[] arge)
{
System.out.println("房屋中介代理模式输出");
//创建目标类实例
Ihouse trager=new IhouseImplSell();
//创建目标类代理实例,将代理业务通目标类业务逻辑编织在一起
IhouseImplSellProxy proxyFactory=new IhouseImplSellProxy(trager,new Double(200));
//通过代理实例获得目标类接口
Ihouse proxy=(Ihouse)Proxy.newProxyInstance
(trager.getClass().getClassLoader(),
trager.getClass().getInterfaces(),proxyFactory);
//调用目标类方法执行
proxy.toSell("me",new Double(100));
proxy.toBuy("tom",new Double(200));
}
}
其中Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
trager.getClass().getClassLoader()反回目标类加载器,
trager.getClass().getInterfaces()确定此对象所表示的类或接口实现的接口。
proxyFactory整合代理业务和目标类业务实例。
执行结果:
房屋中介代理模式输出
me的房子出售价钱=300;
tom可以出的价钱=400;
代理模式小结:
我们虽然使用JDK的动态代理完成了代理模式,但是这种实现方式有几个明显需要改进的地方:
1.目标类的所有方法都添加到代理逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定方法添加代理业务。
2.我们通过硬编码的方式指定了织入代理业务的织入点,即在目标类业务方法的开始和结束前织入代码。
3.我们手工编写代理实例的创建过程,为不同创建代理时,需要分别编写相应的创建代码,无法做到通用。
以上三个问题在AOP中占有重要地位,应为Spring AOP的主要工作就是围绕以上三点展开:Spring AOP 通过Pointcut(切点)指定在那些类的那些方法上添加横切逻辑,通过Advice(增强)描述横切逻辑和方法的具体织入点(方法前,方法后,方法的两端等),此外Spring还通过Advisor(切面)将Pointcut和Advice两者组装起来,有了Advisor的信息,Spring就可以利用JDK或CGLib的动态代理技术采用统一的方式为目标Bean创建织入切面的代理对象了.