博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
新秀学习SSH(十四)——Spring集装箱AOP其原理——动态代理
阅读量:4644 次
发布时间:2019-06-09

本文共 5503 字,大约阅读时间需要 18 分钟。

之前写了一篇文章IOC该博客——《》,今天再来聊聊AOP。大家都知道Spring的两大特性是IOC和AOP。

IOC负责将对象动态的注入到容器,从而达到一种须要谁就注入谁,什么时候须要就什么时候注入的效果,可谓是招之则来,挥之则去。

想想都认为爽,假设现实生活中也有这本事那就爽歪歪了。至于有多爽,各位自己脑补吧。而AOP呢,它实现的就是容器的还有一大优点了。就是能够让容器中的对象都享有容器中的公共服务。

那么容器是怎么做到的呢?它怎么就能让在它里面的对象自己主动拥有它提供的公共性服务呢?答案就是我们今天要讨论的内容——动态代理。

动态代理事实上并非什么新奇的东西,学过设计模式的人都应该知道代理模式,代理模式是一种静态代理。而动态代理就是利用反射和动态编译将代理模式变成动态的。

原理跟动态注入一样,代理模式在编译的时候就已经确定代理类将要代理谁,而动态代理在执行的时候才知道自己要代理谁。

Spring的动态代理有两种:一是JDK的动态代理。还有一个是cglib动态代理(通过改动字节码来实现代理)。今天咱们主要讨论JDK动态代理的方式。JDK的代理方式主要就是通过反射跟动态编译来实现的,以下咱们就通过代码来看看它详细是怎么实现的。

假设我们要对以下这个用户管理进行代理:

//用户管理接口package com.tgb.proxy;public interface UserMgr {	void addUser();	void delUser();}//用户管理的实现package com.tgb.proxy;public class UserMgrImpl implements UserMgr {	@Override	public void addUser() {		System.out.println("加入用户.....");	}	@Override	public void delUser() {		System.out.println("删除用户.....");	}	}
依照代理模式的实现方式,肯定是用一个代理类,让它也实现UserMgr接口,然后在其内部声明一个UserMgrImpl。然后分别调用addUser和delUser方法,并在调用前后加上我们须要的其它操作。可是这样非常显然都是写死的。我们怎么做到动态呢?别急,接着看。

我们知道,要实现代理,那么我们的代理类跟被代理类都要实现同一接口,可是动态代理的话我们根本不知道我们将要代理谁,也就不知道我们要实现哪个接口,那么要怎么办呢?我们仅仅有知道要代理谁以后,才干给出对应的代理类,那么我们何不等知道要代理谁以后再去生成一个代理类呢?想到这里,我们好像找到了解决的办法,就是动态生成代理类!

这时候我们亲爱的反射又有了用武之地。我们能够写一个方法来接收被代理类。这样我们就能够通过反射知道它的一切信息——包含它的类型、它的方法等等(假设你不知道怎么得到。请先去看看我写的反射的博客《》《》)。

JDK动态代理的两个核心各自是InvocationHandler和Proxy,以下我们就用简单的代码来模拟一下它们是怎么实现的:

InvocationHandler接口:

package com.tgb.proxy;import java.lang.reflect.Method;public interface InvocationHandler {	public void invoke(Object o, Method m);}

实现动态代理的关键部分,通过Proxy动态生成我们详细的代理类:

package com.tgb.proxy;import java.io.File;import java.io.FileWriter;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.StandardJavaFileManager;import javax.tools.ToolProvider;import javax.tools.JavaCompiler.CompilationTask;public class Proxy {	/**	 * 	 * @param infce 被代理类的接口	 * @param h 代理类	 * @return	 * @throws Exception	 */	public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception { 		String methodStr = "";		String rt = "\r\n";				//利用反射得到infce的全部方法,并又一次组装		Method[] methods = infce.getMethods();			for(Method m : methods) {			methodStr += "    @Override" + rt + 						 "    public  "+m.getReturnType()+" " + m.getName() + "() {" + rt +						 "        try {" + rt +						 "        Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +						 "        h.invoke(this, md);" + rt +						 "        }catch(Exception e) {e.printStackTrace();}" + rt +												 "    }" + rt ;		}				//生成Java源文件		String srcCode = 			"package com.tgb.proxy;" +  rt +			"import java.lang.reflect.Method;" + rt +			"public class $Proxy1 implements " + infce.getName() + "{" + rt +			"    public $Proxy1(InvocationHandler h) {" + rt +			"        this.h = h;" + rt +			"    }" + rt +						"    com.tgb.proxy.InvocationHandler h;" + rt +										methodStr + rt +			"}";		String fileName = 			"d:/src/com/tgb/proxy/$Proxy1.java";		File f = new File(fileName);		FileWriter fw = new FileWriter(f);		fw.write(srcCode);		fw.flush();		fw.close();				//将Java文件编译成class文件		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();		StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);		Iterable units = fileMgr.getJavaFileObjects(fileName);		CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);		t.call();		fileMgr.close();				//载入到内存,并实例化		URL[] urls = new URL[] {new URL("file:/" + "d:/src/")};		URLClassLoader ul = new URLClassLoader(urls);		Class c = ul.loadClass("com.tgb.proxy.$Proxy1");				Constructor ctr = c.getConstructor(InvocationHandler.class);		Object m = ctr.newInstance(h);		return m;	}	}
这个类的主要功能就是,依据被代理对象的信息,动态组装一个代理类,生成$Proxy1.java文件,然后将其编译成$Proxy1.class。

这样我们就能够在执行的时候,依据我们详细的被代理对象生成我们想要的代理类了。这样一来。我们就不须要提前知道我们要代理谁。也就是说。你想代理谁。想要什么样的代理,我们就给你生成一个什么样的代理类。

然后,在client我们就能够任意的进行代理了。

package com.tgb.proxy;public class Client {	public static void main(String[] args) throws Exception {		UserMgr mgr = new UserMgrImpl();				//为用户管理加入事务处理		InvocationHandler h = new TransactionHandler(mgr);		UserMgr u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h);				//为用户管理加入显示方法执行时间的功能		TimeHandler h2 = new TimeHandler(u);		u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h2);				u.addUser();		System.out.println("\r\n==========华丽的切割线==========\r\n");		u.delUser();	}}

执行结果:

開始时间:2014年-07月-15日 15时:48分:54秒开启事务.....加入用户.....提交事务.....结束时间:2014年-07月-15日 15时:48分:57秒耗时:3秒==========华丽的切割线==========開始时间:2014年-07月-15日 15时:48分:57秒开启事务.....删除用户.....提交事务.....结束时间:2014年-07月-15日 15时:49分:00秒耗时:3秒
这里我写了两个代理的功能,一个是事务处理,一个是显示方法执行时间的代理。当然都是非常easy的写法,仅仅是为了说明这个原理。当然,我们能够想Spring那样将这些AOP写到配置文件。由于之前那篇已经写了怎么通过配置文件注入了,这里就不反复贴了。

到这里。你可能会有一个疑问:你上面说,仅仅要放到容器里的对象。都会有容器的公共服务,我怎么没看出来呢?好,那我们就继续看一下我们的代理功能:

事务处理:

package com.tgb.proxy;import java.lang.reflect.Method;public class TransactionHandler implements InvocationHandler {		private Object target;		public TransactionHandler(Object target) {		super();		this.target = target;	}	@Override	public void invoke(Object o, Method m) {		System.out.println("开启事务.....");		try {			m.invoke(target);		} catch (Exception e) {			e.printStackTrace();		}		System.out.println("提交事务.....");	}}
从代码中不难看出,我们代理的功能里没有涉及到不论什么被代理对象的详细信息。这样有什么优点呢?这种优点就是将代理要做的事情跟被代理的对象全然分开,这样一来我们就能够在代理和被代理之间任意的进行组合了。也就是说同一个功能我们仅仅须要一个。相同的功能仅仅有一个,那么这个功能不就是公共的功能吗?无论容器中有多少给对象。都能够享受容器提供的服务了。这就是容器的优点。

不知道我讲的够不不够清楚。欢迎积极交流、商讨。

版权声明:本文博主原创文章。博客,未经同意不得转载。

转载于:https://www.cnblogs.com/zfyouxi/p/4818632.html

你可能感兴趣的文章
Maven的安装
查看>>
angular初步认识一
查看>>
springmvc3.2+spring+hibernate4全注解方式整合(一)
查看>>
Elgg网站迁移指南
查看>>
素数筛法优化
查看>>
installshield 注册dll
查看>>
Sublime Text 3 及Package Control 安装(附上一个3103可用的Key)
查看>>
LTE QCI分类 QoS
查看>>
Get MAC address using POSIX APIs
查看>>
bzoj2120
查看>>
基于uFUN开发板的心率计(一)DMA方式获取传感器数据
查看>>
【dp】船
查看>>
oracle, group by, having, where
查看>>
⑥python模块初识、pyc和PyCodeObject
查看>>
object-c中管理文件和目录:NSFileManager使用方法
查看>>
Kibana:分析及可视化日志文件
查看>>
nodejs pm2使用
查看>>
cocos2d-x 3.10 PageView BUG
查看>>
装饰器的基本使用:用户登录
查看>>
CSS选择器总结
查看>>