设计模式-13 代理模式(结构型模式)

一 代理设计模式

 本文讲解内容的源码下载链接 : http://onl5wa4sd.bkt.clouddn.com/SpringAopPro.rar

1 代理模式的定义

  为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
  是是个是真正的你要访问的对象(目标类),一个是代理对象,真正对象与代理对象实现同一个接口,先访问代理类再访问真正要访问的对象。

主要解决:直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

关键代码: 实现与被代理类组合。

使用场景:

租房子的时候去找中介

 

类图 :

2 为什么需要代理设计模式

  假设需实现一个计算的类Math、完成加、减、乘、除功能,如下所示:

Math.java

package com.xinping;

public class Math {
    
    //
    public int add(int param1, int param2){
        int result = param1 + param2;
        System.out.println(param1 + "+" + param2 +"=" + result);
        return result;
    }
    
    //
    public int sub(int param1, int param2){
        int result = param1 - param2;
        System.out.println(param1 + "-" + param2 +"=" + result);
        return result;
    }    
    
    //
    public int mul(int param1, int param2){
        int result = param1 * param2;
        System.out.println(param1 + "*" + param2 +"=" + result);
        return result;
    }
    
    //
    public int div(int param1, int param2){
        int result = param1 / param2;
        System.out.println(param1 + "/" + param2 +"=" + result);
        return result;
    }
    
}

  现在需求发生了变化,要求项目中所有的类在执行方法时输出执行耗时。最直接的办法是修改源代码,如下所示:

package com.xinping;

import java.util.Random;

public class Math {
    public Math(){
        
    }
    
    //加
    public int add(int param1, int param2){
        //开始时间
        long start = System.currentTimeMillis();
        this.lazy();
        int result = param1 + param2;
        System.out.println(param1 + "+" + param2 +"=" + result);
        long end = System.currentTimeMillis();
        long consumeTime = end - start;
        System.out.println("共耗时: "+ consumeTime + " 毫秒");
        return result;
    }
    
    //减
    public int sub(int param1, int param2){
        //开始时间
        long start = System.currentTimeMillis();
        this.lazy();
        int result = param1 - param2;
        System.out.println(param1 + "-" + param2 +"=" + result);
        long end = System.currentTimeMillis();
        long consumeTime = end - start;
        System.out.println("共耗时: "+ consumeTime + " 毫秒");
        return result;
    }    
    
    //乘
    public int mul(int param1, int param2){
        //开始时间
        long start = System.currentTimeMillis();
        this.lazy();
        int result = param1 * param2;
        System.out.println(param1 + "*" + param2 +"=" + result);
        long end = System.currentTimeMillis();
        long consumeTime = end - start;
        System.out.println("共耗时: "+ consumeTime + " 毫秒");
        return result;
    }
    
    //除
    public int div(int param1, int param2){
        //开始时间
        long start = System.currentTimeMillis();
        this.lazy();
        int result = param1 / param2;
        System.out.println(param1 + "/" + param2 +"=" + result);
        long end = System.currentTimeMillis();
        long consumeTime = end - start;
        System.out.println("共耗时: "+ consumeTime + " 毫秒");
        return result;
    }
    
    
    public void lazy(){
        Random random = new Random();
        int n = random.nextInt(500);
        try {
            Thread.sleep(n);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }
 
}

测试运行:

package com.xinping.test;

import junit.framework.TestCase;
import com.xinping.Math;

public class TestRunMath extends TestCase {

    protected void setUp() throws Exception {
        super.setUp();
    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }

    public void testRunMath(){
         Math math = new Math();
         math.add(5, 3);
         math.sub(10, 3);
         math.mul(3, 6);
         math.div(4, 2);
    }
    
}

运行结果:

5+3=8
共耗时: 251 毫秒
10-3=7
共耗时: 144 毫秒
3*6=18
共耗时: 153 毫秒
4/2=2
共耗时: 271 毫秒

注意消耗时间会随着 每次的运行而不同,这是模拟的加载时间,是个随机值。

缺点:
1、工作量特别大,如果项目中有多个类,多个方法,则要修改多次。
2、违背了设计原则:开闭原则(OCP),对扩展开放,对修改关闭,而为了增加功能把每个方法都修改了,也不便于维护。
3、违背了设计原则:单一职责(SRP),每个方法除了要完成自己本身的功能,还要计算耗时、延时;每一个方法引起它变化的原因就有多种。
4、违背了设计原则:依赖倒转(DIP),抽象不应该依赖细节,两者都应该依赖抽象。而在Test类中,Test与Math都是细节。
  使用静态代理可以解决部分问题。

二、静态代理

 1、定义抽象主题接口。

IMath.java

package com.xinping.staticproxy;

/***
 * 接口
 * 抽象主题
 * 
 * */
public interface IMath {
    //
    public int add(int param1, int param2);
    
    //
    public int sub(int param1, int param2);
    
    //
    public int mul(int param1, int param2);
     
    //
    public int div(int param1, int param2);
        
}

2、主题类,算术类,实现抽象接口。

Math.java

package com.xinping.staticproxy;

public class Math implements IMath{

    //
    public int add(int param1, int param2){
        int result = param1 + param2;
        System.out.println(param1 + "+" + param2 +"=" + result);
        return result;
    }
    
    //
    public int sub(int param1, int param2){
        int result = param1 - param2;
        System.out.println(param1 + "-" + param2 +"=" + result);
        return result;
    }    
    
    //
    public int mul(int param1, int param2){
        int result = param1 * param2;
        System.out.println(param1 + "*" + param2 +"=" + result);
        return result;
    }
    
    //
    public int div(int param1, int param2){
        int result = param1 / param2;
        System.out.println(param1 + "/" + param2 +"=" + result);
        return result;
    }
    
    
}

3、代理类

package com.xinping.staticproxy;

import java.util.Random;

import com.xinping.staticproxy.IMath;
import com.xinping.staticproxy.Math;

/***
 * 静态代理类
 * 
 * */
public class StaticMathProxy implements IMath {
     //被代理的对象
    IMath math = new  Math();
    
    @Override
    public int add(int param1, int param2) {
        //开始时间
        long start = System.currentTimeMillis();
        this.lazy();
        int result = math.add(param1, param2);
        System.out.println(param1 + "+" + param2 +"=" + result);
        //结束时间
        long end = System.currentTimeMillis();
        long consumeTime = end - start;
        System.out.println("共耗时: "+ consumeTime + " 毫秒");
        return result;
    }

    @Override
    public int sub(int param1, int param2) {
        //开始时间
        long start = System.currentTimeMillis();
        this.lazy();
        int result = math.sub(param1, param2);
        System.out.println(param1 + "+" + param2 +"=" + result);
        //结束时间
        long end = System.currentTimeMillis();
        long consumeTime = end - start;
        System.out.println("共耗时: "+ consumeTime + " 毫秒");
        return result;
    }

    @Override
    public int mul(int param1, int param2) {
        //开始时间
        long start = System.currentTimeMillis();
        this.lazy();
        int result = math.mul(param1, param2);
        System.out.println(param1 + "+" + param2 +"=" + result);
        //结束时间
        long end = System.currentTimeMillis();
        long consumeTime = end - start;
        System.out.println("共耗时: "+ consumeTime + " 毫秒");
        return result;
    }

    @Override
    public int div(int param1, int param2) {
        //开始时间
        long start = System.currentTimeMillis();
        this.lazy();
        int result = math.div(param1, param2);
        System.out.println(param1 + "+" + param2 +"=" + result);
        //结束时间
        long end = System.currentTimeMillis();
        long consumeTime = end - start;
        System.out.println("共耗时: "+ consumeTime + " 毫秒");
        return result;
    }

    public void lazy(){
        Random random = new Random();
        int n = random.nextInt(500);
        try {
            Thread.sleep(n);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }
    
}

4. 测试运行

package com.xinping.test01;

import com.xinping.staticproxy.IMath;
import com.xinping.staticproxy.StaticMathProxy;

import junit.framework.TestCase;
public class TestRunStaticMathProxy extends TestCase {

    protected void setUp() throws Exception {
        super.setUp();
    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }

    public void testRunMath(){
         IMath math = new StaticMathProxy();
         math.add(5, 3);
         math.sub(10, 3);
         math.mul(3, 6);
         math.div(4, 2);
    }
}

测试结果:

5+3=8
共耗时: 296 毫秒
10-3=7
共耗时: 491 毫秒
3*6=18
共耗时: 68 毫秒
4/2=2
共耗时: 195 毫秒

5、小结
通过静态代理,是否完全解决了上述的4个问题:
已解决:
5.1、解决了“开闭原则(OCP)”的问题,因为并没有修改Math类,而扩展出了MathProxy类。
5.2、解决了“依赖倒转(DIP)”的问题,通过引入接口。
5.3、解决了“单一职责(SRP)”的问题,Math类不再需要去计算耗时与延时操作,但从某些方面讲MathProxy还是存在该问题。

未解决:
5.4、如果项目中有多个类,则需要编写多个代理类,工作量大,不好修改,不好维护,不能应对变化。
  如果要解决上面的问题,可以使用动态代理。

三、动态代理,使用JDK内置的Proxy实现

  只需要一个代理类,而不是针对每个类编写代理类。

package com.xinping.test02;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;

/**
 * 动态代理类
 * 
 * **/
public class DynamicMathProxy implements InvocationHandler  {

    //被代理的对象,也叫目标类。
    private Object targetObject;
    
    /**
     * 获得被代理后的对象
     * 
     * @p object 被代理的对象
     * @return 代理后的对象
     * 
     * */
    public Object getProxyObject(Object object){
        this.targetObject  = object;
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), //类加载器
                    targetObject.getClass().getInterfaces(),//获得被代理对象的所有接口
                    this);//InvaocationHandler对象
    }
    
     /**
      * 当用户调用对象中的每个方法时都通过下面的方法执行,方法必须在接口
        proxy 被代理后的对象
        method 将要被执行的方法信息(反射)
        args 执行方法时需要的参数
    */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //被织入的内容,开始时间
        long start=System.currentTimeMillis();
        lazy();
         
        //使用反射在目标对象上调用方法并传入参数
         Object result=method.invoke(targetObject, args);
         //被织入的内容,结束时间
         Long span= System.currentTimeMillis()-start;
         System.out.println("共用时:"+span);
         
         return result;
    }
    
    //模拟延时
    public void lazy(){
        Random random = new Random();
        int n = random.nextInt(500);
        try {
            Thread.sleep(n);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }
}

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

测试运行:

package com.bank.aop;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.bank.aop.dyna.DynamicMathProxy;
import com.bank.aop.staticproxy.IMath;
import com.bank.aop.staticproxy.Math;

public class TestRunDynamicMathProxy
{

    @Before
    public void setUp() throws Exception
    {
    }

    @After
    public void tearDown() throws Exception
    {
    }

    @Test
    public void test()
    {
        Math mathTarget = new Math();
        DynamicMathProxy dynaProxy = new DynamicMathProxy();
        IMath math = (IMath) dynaProxy.getProxyObject(mathTarget);
        math.add(5, 3);
        math.sub(10, 3);
        math.mul(3, 6);
        math.div(4, 2);
    }

}

 

四 CGLIB

  JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。 

1 新建业务类Math,但是没有接口

Math.java

package com.bank.aop.cglib;

public class Math  
{
       //
    public int add(int param1, int param2){
        int result = param1 + param2;
        System.out.println(param1 + "+" + param2 +"=" + result);
        return result;
    }
    
    //
    public int sub(int param1, int param2){
        int result = param1 - param2;
        System.out.println(param1 + "-" + param2 +"=" + result);
        return result;
    }    
    
    //
    public int mul(int param1, int param2){
        int result = param1 * param2;
        System.out.println(param1 + "*" + param2 +"=" + result);
        return result;
    }
    
    //
    public int div(int param1, int param2){
        int result = param1 / param2;
        System.out.println(param1 + "/" + param2 +"=" + result);
        return result;
    }

}

  创建Cglib代理类

package com.bank.aop.cglib;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/** 
 * 使用cglib动态代理 
 *  
 *  
 */  
public class MathCglib  implements MethodInterceptor
{
    private Object target;  
     
   /** 
     * 创建代理对象 
     *  
     * @param target 
     * @return 
     */  
    public Object getInstance(Object target) {  
        this.target = target;  
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(this.target.getClass());  
        // 回调方法  
        enhancer.setCallback(this);  
        // 创建代理对象  
        return enhancer.create();  
    }  
        
    @Override
     // 回调方法  
    public Object intercept(Object obj, Method method, Object[] args,  
            MethodProxy proxy) throws Throwable {  
        System.out.println("事务开始");  
        Object object = proxy.invokeSuper(obj, args);  
        System.out.println("事务结束");  
        return object;  
    }  

}

测试方法:

package com.bank.aop;

import org.junit.Before;
import org.junit.Test;

import com.bank.aop.cglib.MathCglib;
import com.bank.aop.cglib.Math;

public class TestRunCglibProxy
{

    @Before
    public void setUp() throws Exception
    {
    }

    @Test
    public void test()
    {
        Math math = new Math();
        MathCglib mathCglib = new MathCglib();
        Math mathProxy = (Math) mathCglib.getInstance(math);
        
        mathProxy.add(5, 3);
        mathProxy.sub(10, 3);
        mathProxy.mul(3, 6);
        mathProxy.div(4, 2);
        
        
    }

}

 

小结:

  JDK内置的Proxy动态代理可以在运行时动态生成字节码,而没必要针对每个类编写代理类。中间主要使用到了一个接口InvocationHandler与Proxy.newProxyInstance静态方法,参数说明如下:
  使用内置的Proxy实现动态代理有一个问题:被代理的类必须实现接口,未实现接口则没办法完成动态代理
  如果项目中有些类没有实现接口,则不应该为了实现动态代理而刻意去抽出一些没有实例意义的接口,通过cglib可以解决该问题。

 

posted @ 2016-08-09 22:01  王硕的小屋  阅读(414)  评论(0编辑  收藏  举报