Java的代理模式

一、代理模式的概念

为其他对象提供一种代理以控制对这个对象的访问,代理对象起到中介的作用,可去掉或增加额外的服务。
举个例子:我要买火车票,我可以在火车站买,但是火车站太远了,此时我们就可以通过第三方去购买,当前第三方也可以响应的收取一定的手续费(当然国内没有一家第三方收费,说这个是为了更好理解接下来代理模式的强化对象功能)。

二、常见几种代理模式

虚拟代理:根据需要将资源消耗很大的对象进行延迟,真正需要的时候进行创建
远程代理:类似于客户端服务器模式,为不同地理对象,提供局域网代表对象
保护代理:控制对一个对象的访问权限(权限控制)
智能代理:提供对目标对象的额外服务

三、静态代理

概念:代理和被代理对象在代理之前是确定的。他们都是实现相同的接口或者继承相同的抽象类。
接下来我们实现一个过程,就是汽车行驶,同时记录汽车行驶的时间
没有代理我们是这样实现的

public class Car implements Moveable {
	@Override
	public void move() {
		long startTime = System.currentTimeMillis();
		System.out.println("汽车开始行驶");
		//通过线程睡眠表示汽车行驶
		try {
			Thread.sleep(new Random().nextInt(1000));
			System.out.println("汽车行驶中...");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		long stopTime = System.currentTimeMillis();
		System.out.println("汽车结束行驶...时间"+(stopTime-startTime)+"毫秒 ");
	}

}

这样写虽然没什么问题,但违背了java的单一职责,车只要会动就行了,为什么要让他记录运行时间呢。
那么静态代理如何处理呢,我们可以创建一个记录仪类,记录时间的功能就交给它来完成。

public class Recorder implements Moveable{
    private Car car;
    public  Recorder(Car car) {
    this.car = car;
	}
	@Override
	public void move() {
		long startTime = System.currentTimeMillis(); System.out.println("汽车开始行驶");
		car.move();
		long stopTime = System.currentTimeMillis();
		 System.out.println("汽车结束行驶...时间"+(stopTime-startTime)+"毫秒 ");
	}    
}

缺点: 毫无疑问,这样写就是把方法写死了,如果汽车还有其他方法需要加上记录时间的功能呢,或者还有其他实现类如火车,飞机,轮船,那么这些代理类越来越多,增大了代码量。
其他:例子中这种方式是聚合方式,还一种继承方式(就是直接继承car,这种方式会当功能叠加的是类会无限增大,所有不推荐使用)

三、JDK动态代理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
实现

public class JDKTimeHandler implements InvocationHandler {
    private Object target;
	public JDKTimeHandler(Object target){
		super();
		this.target = target;
	}
	/**
	 * agr0:被代理对象
	 * arg1:被代理对象的方法
	 * arg2:方法的参数
	 * 
	 * 返回值:被代理对象的方法返回值
	 */
	@Override
	public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
		long startTime = System.currentTimeMillis(); 
		System.out.println("汽车开始行驶");
		arg1.invoke(target);
		long stopTime = System.currentTimeMillis();
		System.out.println("汽车结束行驶...时间"+(stopTime-startTime)+"毫秒 ");
		return null;
	}
}

测试类:

	Car car = new Car();
		InvocationHandler ih = new JDKTimeHandler(car);
		Moveable m = (Moveable) Proxy.newProxyInstance(car.getClass().getClassLoader(), car.getClass().getInterfaces(),ih);
        m.move();

Proxy.newProxyInstance(loader,interface,h)
三个参数的意义:loader 类加载器,interface类的实现接口,h InvocationHandler

四、CGLIB动态代理

区别:
这个代理与jdk代理区别在于jdk只能代理实现了接口的类,而CGLIB则针对类来实现代理,指定目录类产生一个子类,通过方法拦截所有父类方法的调用
实现:

public class Train {
 public void move() {
	 System.out.println("火车行驶中");
 }
}
public class CGLIBHandler implements MethodInterceptor{
	private Enhancer enhancer = new Enhancer();
	public Object getProxy(Class clazz) {
		// 设置创建子类的类
		enhancer.setSuperclass(clazz);
		// 设置回调
		enhancer.setCallback(this);
		// 创建子类
		return enhancer.create();
	}
	/**
	 * arg0 目标类的实例
	 * arg1 目标方法的反射对象
	 * arg2 方法参数
	 * arg3 代理类的实例
	 */
	@Override
	public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
		long startTime = System.currentTimeMillis(); 
		System.out.println("汽车开始行驶");
		// 代理类调用父类方法
		arg3.invokeSuper(arg0, arg2);
		long stopTime = System.currentTimeMillis();
		System.out.println("汽车结束行驶...时间"+(stopTime-startTime)+"毫秒 ");
		return null;
	}

}

	CGLIBHandler ch = new CGLIBHandler();
		Train t = (Train) ch.getProxy(Train.class);
		t.move();

五、动态代理实现的原理

思路: 通过Proxy的newProxyInstance实现代理对象
1、声明一段源码(动态产生代理)
2、编译源码(JDK Compiler API),产生新的类(代理类)
3、将这个类load到内存中,产生一个新的对象(代理对象)
4、return 代理对象
实现:
我也创建一个proxy类

public class Proxy {
   public static Object newProxyInstance() {
	   
	   return null;
   }
}

把之前写的聚合代码放进去

public class Proxy {
   public static Object newProxyInstance() {
	   public class Recorder implements Moveable{
	      package month0408;
		    private Car car;
		    public  Recorder(Car car) {
		    this.car = car;
			}
			@Override
			public void move() {
				long startTime = System.currentTimeMillis(); System.out.println("汽车开始行驶");
				car.move();
				long stopTime = System.currentTimeMillis();
				 System.out.println("汽车结束行驶...时间"+(stopTime-startTime)+"毫秒 ");
			}    
		}

   }
}

把上述变成一个字符串

public class Proxy {
   public static Object newProxyInstance() {
	  // windows下的换行符
	   String rt = "\r\n";
	   String str = 
	    "package month0408;"+rt+
	    "public class $Proxy0 implements Moveable {"+rt+
	    "private Moveable m;"+rt+
		"public  $Proxy0(Moveable m) {"+rt+
		"super();"+rt+
		"this.m = m;"+rt+
		"}"+rt+
		"@Override"+rt+
		"public void move() {"+rt+
		"long startTime = System.currentTimeMillis();"+rt+
		"System.out.println(\"汽车开始行驶\");"+rt+
		"m.move();"+rt+
		"long stopTime = System.currentTimeMillis();"+rt+
		"System.out.println(\"汽车结束行驶...时间\"+(stopTime-startTime)+\"毫秒 \");"+rt+
		"}}";
	   return null;
   }
}

其实字符串就是源码(小提示:jdk官方动态代理类的名字为$Proxy0)
在加入源码地址

  // 创建源代码的路径
	   String fileName = System.getProperty("user.dir")+"/src/main/java/month0408/$Proxy0.java";
	   
	   File file = new File(fileName);
	   try {
		FileUtils.write(file, str);
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}

FileUtils是commons-io的方法
运行结果:
在这里插入图片描述
在方法上我们添加一个传入接口的参数

public class Proxy {
   public static Object newProxyInstance(Class infce) {
	  // windows下的换行符
	   String rt = "\r\n";
	   String str = 
	    "package month0408;"+rt+
	    // 之前的接口Moveable改成通过参数的getName方法获取
	    "public class $Proxy0 implements "+infce.getSimpleName() +"{"+rt+
	    // 属性的类型通过参数的getName方法获取
	    "private "+infce.getSimpleName()+" m;"+rt+
	    // 参数类型通过参数的getName方法获取
		"public  $Proxy0("+infce.getSimpleName()+" m) {"+rt+
		"super();"+rt+
		"this.m = m;"+rt+
		"}"+rt+
		"@Override"+rt+
		"public void move() {"+rt+
		"long startTime = System.currentTimeMillis();"+rt+
		"System.out.println(\"汽车开始行驶\");"+rt+
		"m.move();"+rt+
		"long stopTime = System.currentTimeMillis();"+rt+
		"System.out.println(\"汽车结束行驶...时间\"+(stopTime-startTime)+\"毫秒 \");"+rt+
		"}}";
	   // 创建源代码的路径
	   String fileName = System.getProperty("user.dir")+"/src/main/java/month0408/$Proxy0.java";
	   
	   File file = new File(fileName);
	   try {
		FileUtils.write(file, str);
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	   return null;
   }
}

这样就实现了任何接口,同理被代理的方法名字也是这样

public class Proxy {
   public static Object newProxyInstance(Class infce) {
	  // windows下的换行符
	   String rt = "\r\n";
	   StringBuffer methodStr = new StringBuffer();
	   for(Method m:infce.getMethods()) {
		   methodStr.append("@Override"+rt+
		    // 方法名字
			"public void "+m.getName()+"() {"+rt+
			"long startTime = System.currentTimeMillis();"+rt+
			"System.out.println(\"汽车开始行驶\");"+rt+
			// 方法名字
			"m."+m.getName()+"();"+rt+
			"long stopTime = System.currentTimeMillis();"+rt+
			"System.out.println(\"汽车结束行驶...时间\"+(stopTime-startTime)+\"毫秒 \");"+rt+
			"}};");
	   }
	   String str = 
	    "package month0408;"+rt+
	    // 之前的接口Moveable改成通过参数的getName方法获取
	    "public class $Proxy0 implements "+infce.getSimpleName() +"{"+rt+
	    // 属性的类型通过参数的getName方法获取
	    "private "+infce.getSimpleName()+" m;"+rt+
	    // 参数类型通过参数的getName方法获取
		"public  $Proxy0("+infce.getSimpleName()+" m) {"+rt+
		"super();"+rt+
		"this.m = m;"+rt+
		"}"+rt+ methodStr.toString();
		
	   // 创建源代码的路径
	   String fileName = System.getProperty("user.dir")+"/src/main/java/month0408/$Proxy0.java";
	   
	   File file = new File(fileName);
	   try {
		FileUtils.write(file, str);
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	   return null;
   }
}

现在源代码文件有了,但是如何编译呢?

	// 获得当前系统的编译器
		JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
		/*diagnosticListener:监听器
		 * locale, charset都是国际化的参数
		 */
		// 文件管理者
		StandardJavaFileManager sjfm = 
				jc.getStandardFileManager(null,null,null);
		//获取文件
		Iterable units = sjfm.getJavaFileObjects(fileName);
		//编译任务
		/**
		 * out:输出的位置
		 * fileManager:文件管理者
		 * compilationUnits:需要编译的文件
		 * 其他的参数:都是国际化参数,这里设为null
		 */
		CompilationTask t = jc.getTask(null, sjfm, null, null, null, units);
		//进行编译
		t.call();
		try {
			sjfm.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

运行一下
在这里插入图片描述
已生成对应的class文件,表示编译成功
接下来就是创建代理类

// load到内存中
		ClassLoader cl = ClassLoader.getSystemClassLoader();
		Class c = cl.loadClass("month0408.$Proxy0");
		//创建代理类
		//获得代理类的构造器,参数就是我们的接口
        Constructor ctr = c.getConstructor(infce);
		return  ctr.newInstance(new Car());

运行
Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class); m.move();
结果
在这里插入图片描述

接下来需要继续完善的是业务逻辑,如何变成活的呢,此时我们需要一个事物处理器InvocationHandler来专门处理方法

public interface InvocationHandler {
    public void inkove(Object o,Method m,Object[] arg2);
}

同时一样创建一个实现类

public class TimeHandler implements InvocationHandler {
	private Object target;

	public TimeHandler(Object target) {
		super();
		this.target = target;
	}

	@Override
	public void inkove(Object o, Method m, Object[] arg2) {
		long startTime = System.currentTimeMillis();
		System.out.println("汽车开始行驶");
		try {
			m.invoke(target);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		long stopTime = System.currentTimeMillis();
		System.out.println("汽车结束行驶...时间" + (stopTime - startTime) + "毫秒 ");
	}

}

其实这个事物处理器的move就是我们要调用,因为它强化了被代理的对象,所以我们只要把以前写的代理类中的infce有关的内容全改成InvocationHandler就行

package month0408;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

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

import org.apache.commons.io.FileUtils;

public class Proxy {
	public static Object newProxyInstance(Class subject, Class infce, InvocationHandler ih)
			throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException,
			InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		// windows下的换行符
		String rt = "\r\n";
		StringBuffer methodStr = new StringBuffer();
		for (Method m : infce.getMethods()) {
			methodStr.append("@Override" + rt +
			// 方法名字
					"public void " + m.getName() + "() {" + rt +
					/*
					 * // 方法名字 "m." + m.getName() + "();" + rt +
					 */
					"try{" + rt + "Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt
					+ "h.invoke(this,md,null);" + rt + "}catch(Exception e){e.printStackTrace();}" + rt + "}");
		}
		String str = "package month0408;" + rt +
		// 引入自己写的InvocationHandler
				"import month0408.InvocationHandler;" + rt + "import java.lang.reflect.Method;" + rt +
				// 之前的接口Moveable改成通过参数的getName方法获取
				"public class $Proxy0 implements " + infce.getSimpleName() + "{" + rt + "private InvocationHandler h;"
				+ rt + "public  $Proxy0(InvocationHandler h) {" + rt + "super();" + rt + "this.h = h;" + rt + "}" + rt
				+ methodStr.toString() + "};";
		// 以前的move用不上所以去掉
		/*
		 * // 属性的类型通过参数的getName方法获取 "private " + infce.getSimpleName() + " m;" + rt + //
		 * 参数类型通过参数的getName方法获取 "public  $Proxy0(" + infce.getSimpleName() + " m) {" +
		 * rt + "super();" + rt + "this.m = m;" + rt + "}" + rt + methodStr.toString() +
		 * "};";
		 */

		// 创建源代码的路径
		String fileName = System.getProperty("user.dir") + "/src/main/java/month0408/$Proxy0.java";

		File file = new File(fileName);

		FileUtils.write(file, str);

		// 获得当前系统的编译器
		JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
		/*
		 * diagnosticListener:监听器 locale, charset都是国际化的参数
		 */
		// 文件管理者
		StandardJavaFileManager sjfm = jc.getStandardFileManager(null, null, null);
		// 获取文件
		Iterable units = sjfm.getJavaFileObjects(fileName);
		// 编译任务
		/**
		 * out:输出的位置 fileManager:文件管理者 compilationUnits:需要编译的文件 其他的参数:都是国际化参数,这里设为null
		 */
		CompilationTask t = jc.getTask(null, sjfm, null, null, null, units);
		// 进行编译
		t.call();

		sjfm.close();

		// load到内存中
		ClassLoader cl = ClassLoader.getSystemClassLoader();
		Class c = cl.loadClass("month0408.$Proxy0");
		// 创建代理类
		// 获得代理类的构造器,参数就是我们的接口
		Constructor ctr = c.getConstructor(InvocationHandler.class);
		return ctr.newInstance(ih);
	}
}

编译后我们看看生成的代理类

package month0408;

import month0408.InvocationHandler;
import java.lang.reflect.Method;

public class $Proxy0 implements Moveable {
	private InvocationHandler h;

	public $Proxy0(InvocationHandler h) {
		super();
		this.h = h;
	}

	@Override
	public void move() {
		try {
			Method md = month0408.Moveable.class.getMethod("move");
			h.invoke(this, md, null);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
};

当然博主到时没搞懂官方的第一个参数ClassLoader loader,用来干什么,谁知道麻烦给我留言一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值