代理模式

阅读更多

本文主要从示例入手介绍代理模式

一、实现代理的方式继承、组合

二、动态代理

 

一、实现代理的方式继承、组合

 

1、引入:代理模式的实现类似于装饰者模式,都是对功能的增强。很多模式之间的实现都有类似点,不同点可能也是语义有差别

 

2、继承方式的代理

 

2.1、代码示例

   

    说明:Moveable接口,以及Tank实现类为以下所有示例公用

 

package net.oschina.design.proxy;

/**
 * 可移动的事物的接口
 * 
 * @author Freedom
 * 
 */
public interface Moveable {

	void move();
}

 

package net.oschina.design.proxy;

import java.util.Random;

/**
 * tank
 * 
 * @author Freedom
 * 
 */
public class Tank implements Moveable {

	@Override
	public void move() {
		System.out.println("tank start run...");
		try {
			Thread.sleep(new Random().nextInt(10000));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

    

    继承方式实现代理的代码

package net.oschina.design.proxy.v1;

import net.oschina.design.proxy.Tank;

/**
 * Tank1继承Tank目的是为了计算方法运行的时间
 * 
 * 方案1.可以直接修改Tank类中move方法,但弊端是,违反了开放关闭的原则, 最好是不要在之前开发好的代码中,为了实现功能的增强而去修改之前写好的代码
 * 
 * 方案2.功能的加强想到了通过继承的方式,子类重写父类的方法实现功能的加强
 * 
 * 需求修改,现在需要重新对父类功能进行加强。如:在父类move方法执行前后打印日志
 * 
 * 通过继承实现上述需求,则需要重新再写一个子类去继承Tank
 * 
 * @author Freedom
 * 
 */
public class Tank1 extends Tank {

	/**
	 * 重写move方法
	 */
	@Override
	public void move() {
		long start = System.currentTimeMillis();
		System.out.println("start: " + System.currentTimeMillis());
		// 调用父类的方法
		super.move();
		long end = System.currentTimeMillis();
		System.out.println("move run time: " + (end - start));
	}
}

 

package net.oschina.design.proxy.v1;

import net.oschina.design.proxy.Tank;

/**
 * Tank2继承Tank目的是为了在父类方法执行前后打印日志
 * 
 * 需求修改:如果现在想先实现move方法运行的时间,再记录日志?
 * 
 * 只需要再写一个子类比如说Tank3继承Tank1,然后重写父类方法可以实现该功能。
 * 
 * 重新思考该问题,不管出现是单一功能的增加需要继承,而且如果是增强之后各个功能之间进行组合功能也需要重新再写子类
 * 去继承父类,这样随着功能不断加强继承体系就会越来越庞大。会带来继承体系臃肿,因此为了解决该问题,可以通过组合的方式来实现代理功能
 * 
 * @author Freedom
 * 
 */
public class Tank2 extends Tank {
	/**
	 * 重写move方法
	 */
	@Override
	public void move() {
		System.out.println("[INFO]:tank start run... ");
		// 调用父类的方法
		super.move();
		System.out.println("[INFO]:tank tired stop run...");
	}
}

 

2.2、继承实现代理优缺点

优点:较之于对于流程中某一个方法功能进行加强时,直接修改方法中的代码,继承实现代理遵循开放关闭的原则;

缺点:程序流程中某一个方法的功能进行加强,加强的功能方式有多种多样而且任何功能之间可以互相组合。利用继承方式实现代理,每次功能加强需要继承被代理的类,功能直接任意组合也需要继承实现,这样就导致继承体系结果臃肿,不便于维护。

 

3、组合方式实现代理

      此处,实现代理方式有一个前提代理类与被代理类实现共同的接口。

 

3.1、代码示例

 

package net.oschina.design.proxy.v2;

import net.oschina.design.proxy.Moveable;

/**
 * 使用组合的方式实现代理
 * 
 * 代理类实现与被代理类相同的接口,目的也是为了让代理类能够成为被代理类的对象
 * 
 * @author Freedom
 * 
 */
public class Tank3 implements Moveable {

	// 为了后期维护方便,此处使用被代理对象的接口
	Moveable m;

	public Tank3(Moveable m) {
		this.m = m;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.oschina.design.proxy.Moveable#move() 实现时间上的代理
	 */
	@Override
	public void move() {
		long start = System.currentTimeMillis();
		System.out.println("start: " + System.currentTimeMillis());
		// 调用被代理类的方法
		m.move();
		long end = System.currentTimeMillis();
		System.out.println("move run time: " + (end - start));
	}

}
 
package net.oschina.design.proxy.v2;

import net.oschina.design.proxy.Moveable;

/**
 * 实现日志代理
 * 
 * 如果还需要实现诸如,事务,权限的代理,只需要代理类与被代理类实现共同的接口即可
 * 
 * @author Freedom
 * 
 */
public class Tank4 implements Moveable {

	// 为了后期维护方便,此处使用被代理对象的接口
	Moveable m;

	public Tank4(Moveable m) {
		this.m = m;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.oschina.design.proxy.Moveable#move() 日志的代理
	 */
	@Override
	public void move() {
		System.out.println("[INFO]:tank start run... ");
		// 调用被代理类的方法
		m.move();
		System.out.println("[INFO]:tank tired stop run...");
	}

} 
运行类:
package net.oschina.design.proxy.v2;

import net.oschina.design.proxy.Moveable;
import net.oschina.design.proxy.Tank;

public class Test {

	public static void main(String[] args) {
                // 被代理对象
		Moveable m = new Tank();
		// 时间代理
		Moveable timeTank = new Tank3(m);
		// timeTank.move();

		// 先日志代理,后时间代理
		Moveable logTank = new Tank4(timeTank);
		logTank.move();

	}

}
 

3.2、组合方式实现代理的优缺点:

优点:较之于继承方式实现代理,组合的方式实现代理更为灵活,不同代理对象之间的可以实现任意组合;

缺点:每次当有新的代理功能需要实现时,都需要新增加一个代理类,导致代理类过多。

 

二、动态代理

        前提:代理类与被代理类实现共同的接口

 

1、引入

      前面的不管是继承方式或者是组合的方式实现代理,无论如何都会导致随着功能加强而类体系越来越庞大,后期不易维护。因此,无非是对被代理对象某个功能增强而去创建代理对象,我们可以采取动态的方式来生成某一个接口的代理对象,而用户并不能感知到代理类以及代理对象创建。

 

2、动态代理实现的步骤:

(1)首先,需要获取到代理对象的源码(.java);

(2)将源码以字符串的形式输出到文件中;

(3)利用JDK6之后,JavaCompiler实现对源码的编译;

(4)将(3)中编译完成后的字节码文件通过类加载器加载到内存;

(5)利用反射机制创建代理对象

 

3、代码示例

      运行类以下所有不同阶段创建的代理类公用:

package net.oschina.design.proxy.dynamicproxy;

import net.oschina.design.proxy.Moveable;
import net.oschina.design.proxy.Tank;
import net.oschina.design.proxy.dynamicproxy.v6.Proxy;
import net.oschina.design.proxy.dynamicproxy.v6.TimeInvocation;

public class Client {

	public static void main(String[] args) throws Exception {
		Moveable o = (Moveable) Proxy.getProxyInstance(Moveable.class,
				new TimeInvocation(new Tank()));
		o.move();
	}

}

 

3.1、特定接口特定被代理对象特定功能(代理对象实现的功能)的代理类

package net.oschina.design.proxy.dynamicproxy.v3;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import net.oschina.design.proxy.Moveable;
import net.oschina.design.proxy.Tank;

/**
 * 
 * 动态代理v1,只能实现固定的类的代理,不能实现任何一个类的代理
 * 
 * 关于组合实现代理类
 * 
 * 组合方式实现代理:优点:方便扩展,便于多个功能模块之间互相组合 缺点:每次一个新的功能增加都需要新增一个代理类,这样代理类也会不断累加
 * 
 * 思考:动态代理,不需要知道代理对象是什么,只需要每次在使用的时候,直接由方法去产生
 * 
 * 实现:1.拿到代理对象的源码 2.对源码利用JDK提供的编译类进行编译 3.利用类加载器将字节码文件加载进内存 4.通过反射机制创建一个代理类对象
 * 
 * @author Freedom
 * 
 */
public class Proxy {

	private static final String RN = System.getProperty("line.separator");

	private static final String PATH = System.getProperty("user.dir")
			+ "/src/net/oschina/design/proxy/v2/TimeProxy.java";

	public static Object proxyInstance() throws Exception {

		// 1.源码
		String src = "package net.oschina.design.proxy.v2;"
				+ RN
				+ "import net.oschina.design.proxy.Moveable;"
				+ RN
				+ "public class TimeProxy implements Moveable {"
				+ RN
				+ "   Moveable m;"
				+ RN
				+ "   public TimeProxy(Moveable m) {"
				+ RN
				+ "       this.m = m;"
				+ RN
				+ "   }"
				+ RN
				+ "   @Override"
				+ RN
				+ "   public void move() {"
				+ RN
				+ "       long start = System.currentTimeMillis();"
				+ RN
				+ "       System.out.println(\"start: \" + System.currentTimeMillis());"
				+ RN
				+ "       m.move();"
				+ RN
				+ "       long end = System.currentTimeMillis();"
				+ RN
				+ "       System.out.println(\"move run time: \" + (end - start));"
				+ RN + "   }" + RN + "}";

		// 1.1 JDK编译之前先将文件输出到一个本地目录中
		writeStr2File(src, PATH);

		// 2.JDK编译工具编译该类,此处要使用JDK,不能使用JRE环境
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		// System.out.println(compiler);
		StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null,
				null, null);
		Iterable units = fileMgr.getJavaFileObjects(PATH);
		CompilationTask t = compiler.getTask(null, fileMgr, null, null, null,
				units);
		t.call();
		try {
			fileMgr.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

		// 3.将编译的字节码文件加载进内存,因为当前字节码文件在src下,所以,要使用URLClassLoader
		URL[] urls = new URL[] { new URL("file:/"
				+ System.getProperty("user.dir") + "/src") };
		URLClassLoader loader = new URLClassLoader(urls);
		Class c = loader.loadClass("net.oschina.design.proxy.v2.TimeProxy");
		System.out.println(c);

		// 4.通过反射机制创建该类的对象
		Constructor cons = c.getConstructor(Moveable.class);
		Object o = cons.newInstance(new Tank());

		return o;
	}

	private static void writeStr2File(String str, String path) {

		FileWriter writer = null;
		try {
			writer = new FileWriter(new File(path));
			writer.write(str);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				writer.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

}

 

3.2、任意接口的特定被代理对象、特定功能(代理对象实现的功能)的代理

package net.oschina.design.proxy.dynamicproxy.v4;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import net.oschina.design.proxy.Tank;

/**
 * 实现任何类的代理,代理类与被代理类都实现同一个接口,因此方法中传入接口对象即可实现任意类的代理
 * 
 * 1.当前该类只能传入Moveable接口对象,不能传入其他类的字节码文件对象(因为动态生成代码里面方法为Moveable接口的方法)
 * 
 * 2.解决该问题的办法:动态生成任意接口的方法
 * 
 * @author Freedom
 * 
 */
public class Proxy {
	private static final String RN = System.getProperty("line.separator");

	private static final String PATH = System.getProperty("user.dir")
			+ "/src/net/oschina/design/proxy/v2/TimeProxy.java";

	public static Object getProxyInstance(Class intef) throws Exception {
		// 1.源码
		String src = "package net.oschina.design.proxy.v2;"
				+ RN
				+ "import net.oschina.design.proxy.Moveable;"
				+ RN
				+ "public class TimeProxy implements "
				+ intef.getName()
				+ "{"
				+ RN
				+ "   Moveable m;"
				+ RN
				+ "   public TimeProxy(Moveable m) {"
				+ RN
				+ "       this.m = m;"
				+ RN
				+ "   }"
				+ RN
				+ "   @Override"
				+ RN
				+ "   public void move() {"
				+ RN
				+ "       long start = System.currentTimeMillis();"
				+ RN
				+ "       System.out.println(\"start: \" + System.currentTimeMillis());"
				+ RN
				+ "       m.move();"
				+ RN
				+ "       long end = System.currentTimeMillis();"
				+ RN
				+ "       System.out.println(\"move run time: \" + (end - start));"
				+ RN + "   }" + RN + "}";

		// 1.1 JDK编译之前先将文件输出到一个本地目录中
		writeStr2File(src, PATH);

		// 2.JDK编译工具编译该类,此处要使用JDK,不能使用JRE环境
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		// System.out.println(compiler);
		StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null,
				null, null);
		Iterable units = fileMgr.getJavaFileObjects(PATH);
		CompilationTask t = compiler.getTask(null, fileMgr, null, null, null,
				units);
		t.call();
		try {
			fileMgr.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

		// 3.将编译的字节码文件加载进内存,因为当前字节码文件在src下,所以,要使用URLClassLoader
		URL[] urls = new URL[] { new URL("file:/"
				+ System.getProperty("user.dir") + "/src") };
		URLClassLoader loader = new URLClassLoader(urls);
		Class c = loader.loadClass("net.oschina.design.proxy.v2.TimeProxy");
		System.out.println(c);

		// 4.通过反射机制创建该类的对象
		Constructor cons = c.getConstructor(intef);
		Object o = cons.newInstance(new Tank());

		return o;
	}

	private static void writeStr2File(String str, String path) {

		FileWriter writer = null;
		try {
			writer = new FileWriter(new File(path));
			writer.write(str);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				writer.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}
}

 

3.3、任意接口任意被代理对象任意功能的代理

 

    用于处理代理类方法的接口

package net.oschina.design.proxy.dynamicproxy.v6;

import java.lang.reflect.Method;

/**
 * 用于执行代理类对象的方法
 * 
 * @author Freedom
 * 
 */
public interface InvocationHandler {

	void invoke(Object o, Method m);
}

 

    处理代理类方法接口的实现类

package net.oschina.design.proxy.dynamicproxy.v6;

import java.lang.reflect.Method;

/**
 * 处理时间代理的方法的类
 * 
 * @author Freedom
 * 
 */
public class TimeInvocation implements InvocationHandler {

	// 传入被代理的对象,可以是任意对象
	Object mo;

	public TimeInvocation(Object mo) {
		this.mo = mo;
	}

	@Override
	public void invoke(Object o, Method m) {
		try {
			long start = System.currentTimeMillis();
			System.out.println("start: " + System.currentTimeMillis());

			// 被代理类的方法
			m.invoke(mo);

			long end = System.currentTimeMillis();
			System.out.println("move run time: " + (end - start));
			// m.invoke(o, new Object[] {});
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

    动态代理类

package net.oschina.design.proxy.dynamicproxy.v6;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

/**
 * 动态生成任意接口的
 * 
 * 为了实现方法的灵活处理,需要传入一个对象用于处理任意代理类方法的调用
 * 
 * @author Freedom
 * 
 */
public class Proxy {
	private static final String RN = System.getProperty("line.separator");

	private static final String PATH = System.getProperty("user.dir")
			+ "/src/net/oschina/design/proxy/v2/TimeProxy.java";

	public static Object getProxyInstance(Class intef, InvocationHandler h)
			throws Exception {

		/*
		 * 动态生成任意接口的方法
		 */
		String methodStr = "";

		Method[] methods = intef.getMethods();
		// for (Method m : methods) {
		// methodStr += "@Override"
		// + RN
		// + "public void "
		// + m.getName()
		// + "(){"
		// + RN
		// + "      long start = System.currentTimeMillis();"
		// + RN
		// +
		// "      System.out.println(\"start: \" + System.currentTimeMillis());"
		// + RN
		// + "      m.move();"
		// + RN
		// + "      long end = System.currentTimeMillis();"
		// + RN
		// + "      System.out.println(\"move run time: \" + (end - start));"
		// + RN + "}" + RN;
		// }

		for (Method m : methods) {
			methodStr += "@Override" + RN + "public void " + m.getName()
					+ "(){" + RN + "Method me;" + RN + "try{" + RN + "  me ="
					+ intef.getName() + ".class.getMethod(\"" + m.getName()
					+ "\");" + RN + "  h.invoke(this,me);" + RN + "}" + RN
					+ "catch(Exception e)" + RN + "{" + RN
					+ "e.printStackTrace();" + RN + "}" + RN + "}" + RN;
		}

		// System.out.println(methodStr);

		// 1.源码
		String src = "package net.oschina.design.proxy.v2;"
				+ RN
				+ "import java.lang.reflect.Method;"
				+ RN
				+ "import net.oschina.design.proxy.dynamicproxy.v6.InvocationHandler;"
				+ RN + "import net.oschina.design.proxy.Moveable;" + RN
				+ "public class TimeProxy implements " + intef.getName() + "{"
				+ RN + "   InvocationHandler h;" + RN
				+ "   public TimeProxy(InvocationHandler h) {" + RN
				+ "       this.h = h;" + RN + "   }" + RN + methodStr + RN
				+ "}";

		// 1.1 JDK编译之前先将文件输出到一个本地目录中
		writeStr2File(src, PATH);

		// 2.JDK编译工具编译该类,此处要使用JDK,不能使用JRE环境
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		// System.out.println(compiler);
		StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null,
				null, null);
		Iterable units = fileMgr.getJavaFileObjects(PATH);
		CompilationTask t = compiler.getTask(null, fileMgr, null, null, null,
				units);
		t.call();
		try {
			fileMgr.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

		// 3.将编译的字节码文件加载进内存,因为当前字节码文件在src下,所以,要使用URLClassLoader
		URL[] urls = new URL[] { new URL("file:/"
				+ System.getProperty("user.dir") + "/src") };
		URLClassLoader loader = new URLClassLoader(urls);
		Class c = loader.loadClass("net.oschina.design.proxy.v2.TimeProxy");
		System.out.println(c);

		// 4.通过反射机制创建该类的对象
		Constructor cons = c.getConstructor(InvocationHandler.class);
		Object o = cons.newInstance(h);

		return o;
	}

	private static void writeStr2File(String str, String path) {

		FileWriter writer = null;
		try {
			writer = new FileWriter(new File(path));
			writer.write(str);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				writer.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}
}

 

此文,仅为个人理解,欠缺的地方忘斧正...

你可能感兴趣的:(代理模式)