代理模式

原创
2016/03/29 13:00
阅读数 302

一、静态代理

   静态代理还是相对简单的,就是如果我是代理,我看中你的功能。我就和你联系(关联),然后我在做一些自己的工作,比如宣传推广,顺便赚取一些外快之类的事情。

   这中间最重要的两件事情一是:联系(关联),我要代理你的产品(功能),首先我要联系你。二是:我们对外做同样的事情(实现同样的接口),比如我代理12306卖车票,那么我们都提供了卖车票这个服务(实现这个接口)。这一点很容易就变成了装饰模式或者简单的依赖关系。

下面来一个例子,现在有一个卖药的接口如下:

public interface SaleMedicine {
   
    public void saleMedicine();
 
}

有一个药厂类实现了这个接口,他在卖药之前总是要生产药:

public class MedicineFactory implements SaleMedicine{
 
    @Override
    public void saleMedicine() {
       
        produceMedicine();
        System.out.println("卖药");
    }
   
    private void produceMedicine()
    {
        System.out.println("生产药品");
    }
 
}

   还有一个医院类,也实现了卖药接口,但是它不能生产药啊,所以它之后做代理,一是它联系一个药厂做药厂的代理(关联一个药厂类):

public class Hospital implements SaleMedicine {
   
    private SaleMedicine saleMedicine;
   
    @Override
    public void saleMedicine() {
        System.out.println("医生看病");
        saleMedicine.saleMedicine();
        System.out.println("收钱");
 
    }
 
    public SaleMedicine getSaleMedicine() {
        return saleMedicine;
    }
 
    public void setSaleMedicine(SaleMedicine saleMedicine) {
        this.saleMedicine = saleMedicine;
    }

   医院类在卖药之前,还是要找医生看看病的对吧?现在来换一个思路,你觉得医院不是为了卖药的啊,这不科学啊。医院就是为了看病的嘛。所以医院要实现一个看病的接口。先来一个治病接口:

public interface Heal {
   
    public void heal();
 
}

再来一个医院类:

public class KindHospital implements Heal{
 
    private SaleMedicine saleMedicine;
    @Override
    public void heal() {
       
        System.out.println("治病");
        saleMedicine.saleMedicine();
       
    }
    public SaleMedicine getSaleMedicine() {
        return saleMedicine;
    }
    public void setSaleMedicine(SaleMedicine saleMedicine) {
        this.saleMedicine = saleMedicine;
    }
 
}

再来一个测主类:

public class StaticProxy {
   
    public static void main(String[] args) {
       
        proxySaleMedicine();
        System.out.println("---------------------");
        relationSaleMedicine();
    }
   
    public static void proxySaleMedicine()
    {
        Hospital hospital = new Hospital();
        SaleMedicine saleMedicine = new MedicineFactory();
        hospital.setSaleMedicine(saleMedicine);
        hospital.saleMedicine();
    }
   
    public static void relationSaleMedicine()
    {
        KindHospital hospital = new KindHospital();
        SaleMedicine saleMedicine = new MedicineFactory();
        hospital.setSaleMedicine(saleMedicine);
        hospital.heal();
    }
 
}

         看这2个医院类没有什么区别对吧?的确没有太大的区别,从语义上来说就是侧重点不同而已。但是对于面向对象编程来说,这是有意义的,使用静态代理的方式更加符合面向接口编程的原则。它使得共同的服务抽象在了共同的接口中。这让我们在消费这一服务的时候有更加灵活的选择。

      静态代理可能在实际的使用中不是特别多,下面就来看一看动态代理,这非常有用。在springaop就是使用动态代理实现的。

二、动态代理

   动态代理相对于静态代理最大的特点是它不用实现和要代理对象相同的接口,而是在你需要代理的时候把要代理的接口注入进去就可以了。如果觉得抽象没有关系,先来看一个例子吧,现在有一个获取重对象的接口:

public interface HeavyObjectFactory {
 
    public Object getObject(String key);
}

 

然后有一个获取重对象的类实现了HeavyObjectFactory

public class HeavyObject implements HeavyObjectFactory {
 
    @Override
    public Object getObject(String key) {
        try {
            Thread.sleep(3000);//模拟重对象产生
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "创建重对象";
    }
}

   现在来一个动态生成代理对象的类,它可以代理实现了HeavyObjectFactory的类,如果要获取一个重对象就会加上缓存:

public class CacheProxy implements InvocationHandler {
 
    private Map<String,Object> cache = new HashMap<String,Object>();
   
    private static  Method getHeavyMethod;
   
    private static final Class<?> clazz = HeavyObjectFactory.class;
   
    static{
        getHeavyMethod = clazz.getMethods()[0];
    }
    //要被代理的对象,直接使用最顶层的Object类
    private Object target;
   
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
     {
 
        if(getHeavyMethod.equals(method))//如果是获取重对象的方法就使用缓存
        {
            String key = (String) args[0];
            Object value = cache.get(key);
            if(null == value)
            {
                value = method.invoke(target, args);
                cache.put(key, value);
            }
            return cache;
        }
        return method.invoke(target, args);
    }
    public Object getTarget() {
        return target;
    }
    public void setTarget(Object target) {
        this.target = target;
    }
   
    public Object getCacheProxy(Object target)
    {
        this.target = target;
        Class<? extends Object> clazz = target.getClass();
        //最好注入要代理的接口就可以了,不要注入所有接口,因为这个是在动态的生成一个对象
        Object object = Proxy.newProxyInstance(clazz.getClassLoader(), 
        clazz.getInterfaces(), this);
        return object;
    }
}

这个类有3个值得注意的地方:

1.  继承了java.lang.reflect.InvocationHandler这个类
2.  重写了public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
方法
3.  用Proxy的public static Object newProxyInstance(ClassLoader loader, 
 Class<?>[]interfaces, InvocationHandler h) throws IllegalArgumentException生成要代理的对象。

   先来看ProxynewProxyInstance方法,这个方法有3个参数,第1个是参数是被代理的对象的类加载器,因为代理类是动态生成的所以需要指定类加载器,jvm好加载。第2个是指定要代理的接口。动态生成的代理会代理这些接口的方法。第3个参数是InvocationHandler接口,这个接口只有一个invoke方法就是上面强调的第2点。

   现在好理解了吧,动态生成的代理最终都是调用的

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

这个方法,这个方法就是把要代理的对象传进来,把被代理的方法和参数传进来,就可以在执行要代理的方法method之前,先做一点自己的事情了对吧。

现在来看一下动态代理的主类,主要测试获取5次重对象使用缓存代理和不使用缓存代理的差别:

public class DynamicProxy {
   
    public static void main(String[] args) {
       
        HeavyObject heavy = new HeavyObject();
        long start = System.currentTimeMillis();
        for(int i =0;i<5;i++)
            heavy.getObject("key");
        long end = System.currentTimeMillis();
        System.out.println("no cache time:"+(end-start));
       
        CacheProxy cache = new CacheProxy();
        HeavyObjectFactory factory = (HeavyObjectFactory) cache.getCacheProxy(heavy);
       
        start = System.currentTimeMillis();
        for(int i =0;i<5;i++)
            factory.getObject("key");
        end = System.currentTimeMillis();
        System.out.println("caceh time:"+(end-start));
    }
 
}

   其实这种代理再配上注解功能更加强大,比如springCacheable注解。如果需要缓存,除了需要配置一下注解,其他基本上是透明的。看了spring aop都可以做的事情,你就会感叹原来动态代理可以这么强大。

展开阅读全文
打赏
1
4 收藏
分享
加载中
打赏
0 评论
4 收藏
1
分享
返回顶部
顶部