AOP实现原理——动态代理
2016-03-31 17:52
267 查看
前几天阿里面试问AOP是怎么实现的,感觉自己当时答的不好,于是回来重新研究了一下,找了下资料,现在来做个分享.
Spring两大核心IOC与AOP.IOC负责将对象动态的注入到容器,让容器来管理bean,AOP就是可以让容器中的对象都享有容器中的公共服务(如日志记录等等).那么AOP是怎么实现的,下面讲一下我的理解——动态代理。
动态代理就是利用反射和动态编译将代理模式变成动态的.原理跟动态注入一样,代理模式在编译的时候就已经确定代理类将要代理谁,而动态代理在运行的时候才知道自己要代理谁。下面看代码:
假设我们要对下面这个用户管理类进行代理:
按照代理模式的实现方式,一般是用一个代理类,让它也实现UserService接口,然后在其内部声明一个UserServiceImpl,然后分别调用addUser和delUser等方法,并在调用前后加上我们需要的其他操作。但是这样都是写死的,不能做到动态呢。要实现代理,那么代理类跟被代理类都要实现同一接口,但是动态代理的话根本不知道我们将要代理谁,也就不知道要实现哪个接口。这时候就应该动态生成代理类!
来来一个方法来接收被代理类,这样我们就可以通过反射知道它的信息。
实现动态代理的关键部分,通过Proxy动态生成我们具体的代理类:
这个类的主要功能就是,根据被代理对象的信息,动态组装一个代理类,生成Proxy1.java文件,然后将其编译成Proxy1.java文件,然后将其编译成Proxy1.class。这样就可以在运行的时候,根据具体的被代理对象生成需要的代理类了。这样一来,就算不知道需要代理谁,也能生成相应的代理类。
然后写一些代理信息,我这里写的是事务和时间:
现在开始测试一下:
看看输出:
OK,动态代理完成,这也就是AOP的实现方式。
Spring两大核心IOC与AOP.IOC负责将对象动态的注入到容器,让容器来管理bean,AOP就是可以让容器中的对象都享有容器中的公共服务(如日志记录等等).那么AOP是怎么实现的,下面讲一下我的理解——动态代理。
动态代理就是利用反射和动态编译将代理模式变成动态的.原理跟动态注入一样,代理模式在编译的时候就已经确定代理类将要代理谁,而动态代理在运行的时候才知道自己要代理谁。下面看代码:
假设我们要对下面这个用户管理类进行代理:
package com.kevindai.AOP; public interface UserService { /** * * @Title: addUser * @Description: 增加user * @param * @author kevindai * @return void 返回类型 * @throws */ public void addUser(); /** * * @Title: delUser * @Description: 删除user * @param * @author kevindai * @return void 返回类型 * @throws */ public void delUser(); /** * * @Title: updateUser * @Description: 修改user * @param * @author kevindai * @return void 返回类型 * @throws */ public void updateUser(); } package com.kevindai.AOP; public class UserServiceImpl implements UserService { /** * * @Title: addUser * @Description: 增加User * @param * @author kevindai * @return * @throws */ public void addUser() { System.out.println("addUser...."); } /** * * @Title: delUser * @Description: 删除User * @param * @author kevindai * @return * @throws */ public void delUser() { System.out.println("delUser...."); } /** * * @Title: updateUser * @Description: 删除User * @param * @author kevindai * @return * @throws */ public void updateUser() { System.out.println("updateUser...."); } }
按照代理模式的实现方式,一般是用一个代理类,让它也实现UserService接口,然后在其内部声明一个UserServiceImpl,然后分别调用addUser和delUser等方法,并在调用前后加上我们需要的其他操作。但是这样都是写死的,不能做到动态呢。要实现代理,那么代理类跟被代理类都要实现同一接口,但是动态代理的话根本不知道我们将要代理谁,也就不知道要实现哪个接口。这时候就应该动态生成代理类!
来来一个方法来接收被代理类,这样我们就可以通过反射知道它的信息。
package com.kevindai.AOP; import java.lang.reflect.Method; public interface InvocationService { public void invoke(Object o, Method m); }
实现动态代理的关键部分,通过Proxy动态生成我们具体的代理类:
package com.kevindai.AOP; 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.JavaCompiler.CompilationTask; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; /** * * @ClassName: Proxy * @Description: 通过动态代理实现AOP * @author kevindai * @date 2016-3-31 下午3:56:58 * */ public class Proxy { /** * * @Title: proxyInterface * @Description: TODO(这里用一句话描述这个方法的作用) * @param @param 传入的接口 * @param @param 代理类 * @param @return * @author kevindai * @return Object 返回类型 * @throws */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static Object proxyInterface(Class clazz,InvocationService invo) throws Exception{ StringBuffer method = new StringBuffer(); String br = "\r\n"; Method[] methods = clazz.getMethods(); for(Method m : methods){ method //.append(" @Override").append(br) .append(" public ").append(m.getReturnType()).append(" ").append(m.getName()).append("(){").append(br) .append(" try{").append(br) .append(" Method md = ").append(clazz.getName()).append(".class.getMethod(\"").append(m.getName()).append("\");").append(br) .append(" invo.invoke(this,md);").append(br) .append(" }catch(Exception e){e.printStackTrace();}").append(br) .append(" }").append(br).append(br); } //根据要代理的方法信息来生成java源文件 StringBuffer codeSrc = new StringBuffer(); codeSrc.append("package com.kevindai.AOP;").append(br) .append("import java.lang.reflect.Method;").append(br) .append("public class $Proxy1 implements ").append(clazz.getName()).append("{").append(br) .append(" public $Proxy1(InvocationService invo){").append(br) .append(" this.invo = invo;").append(br) .append(" }").append(br) .append(" com.kevindai.AOP.InvocationService invo;").append(br) .append(method).append(br) .append("}"); String fileName = "C:/myeclipse/starsinoWs/Test/src/com/kevindai/AOP/$Proxy1.java"; File f = new File(fileName); FileWriter fw = new FileWriter(f); fw.write(codeSrc.toString()); 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:/" + "C:/myeclipse/starsinoWs/Test/src/")}; URLClassLoader ul = new URLClassLoader(urls); Class c = ul.loadClass("com.kevindai.AOP.$Proxy1"); Constructor ctr = c.getConstructor(InvocationService.class); Object m = ctr.newInstance(invo); return m; } }
这个类的主要功能就是,根据被代理对象的信息,动态组装一个代理类,生成Proxy1.java文件,然后将其编译成Proxy1.java文件,然后将其编译成Proxy1.class。这样就可以在运行的时候,根据具体的被代理对象生成需要的代理类了。这样一来,就算不知道需要代理谁,也能生成相应的代理类。
然后写一些代理信息,我这里写的是事务和时间:
package com.kevindai.AOP; import java.lang.reflect.Method; public class TransactionService implements InvocationService { private Object target; public TransactionService(Object target) { super(); this.target = target; } public void invoke(Object o, Method m) { System.out.println("开启事务....."); try { m.invoke(target); } catch (Exception e) { e.printStackTrace(); } System.out.println("提交事务....."); } } package com.kevindai.AOP; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Date; public class TimeService implements InvocationService { private Object target; public TimeService(Object target) { super(); this.target = target; } public void invoke(Object o, Method m) { System.out.println("开始时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss E").format(new Date())); try { m.invoke(target); } catch (Exception e) { e.printStackTrace(); } System.out.println("开始时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss E").format(new Date())); } }
现在开始测试一下:
package com.kevindai.AOP; public class Test { public static void main(String[] args) throws Exception { UserService userService = new UserServiceImpl(); //为用户管理添加事务处理 InvocationService h = new TransactionService(userService); UserService u = (UserService)Proxy.proxyInterface(UserService.class,h); //为用户管理添加显示方法执行时间的功能 TimeService h2 = new TimeService(u); u = (UserService)Proxy.proxyInterface(UserService.class,h2); u.addUser(); System.out.println("\r\n==================================\r\n"); u.delUser(); } }
看看输出:
开始时间:2016-03-31 17:48:23 星期四 开启事务..... addUser.... 提交事务..... 开始时间:2016-03-31 17:48:23 星期四 ================================== 开始时间:2016-03-31 17:48:23 星期四 开启事务..... delUser.... 提交事务..... 开始时间:2016-03-31 17:48:23 星期四
OK,动态代理完成,这也就是AOP的实现方式。
相关文章推荐
- linux系统软件安装
- hadoop集群部署(yarn)
- Linux下多个Tomcat服务器的部署
- 在ARM-linux上实现4G模块simcom7100c 的PPP拨号上网
- 修改hadoop源码后,hadoop和spark的编译过程
- ArchSummit北京2015 移动应用架构趋势纪要
- Linux中的一条命令详解
- 7个基于Linux命令行的文件下载和网站浏览工具
- 7个基于Linux命令行的文件下载和网站浏览工具
- hbase shell基础和常用命令详解
- 阿里云centos6.7搭建lamp及php编译configure error
- Unix/Linux环境C编程新手教程(12) openSUSECCPP以及Linux内核驱动开发环境搭建
- docker 安装 webpack 时报错 EPROTO: protocol error, symlink
- [Linux] Linux的环境变量
- linux(centos)用户与权限
- Centos 7.0 安装nginx
- Linux下安装gcc 、g++ 、gfortran编译器
- arm-linux内核编译出错
- chkconfig命令详解
- How to install experimental Docker versions on my Ubuntu 16.04 LTS