新秀学习SSH(十四)——Spring集装箱AOP其原理——动态代理
之前写了一篇文章IOC该博客——《Spring容器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("提交事务.....");
}
}
从代码中不难看出,我们代理的功能里没有涉及到不论什么被代理对象的详细信息。这样有什么优点呢?这种优点就是将代理要做的事情跟被代理的对象全然分开,这样一来我们就能够在代理和被代理之间任意的进行组合了。也就是说同一个功能我们仅仅须要一个。相同的功能仅仅有一个,那么这个功能不就是公共的功能吗?无论容器中有多少给对象。都能够享受容器提供的服务了。这就是容器的优点。
不知道我讲的够不不够清楚。欢迎积极交流、商讨。
版权声明:本文博主原创文章。博客,未经同意不得转载。
新秀学习SSH(十四)——Spring集装箱AOP其原理——动态代理的更多相关文章
- 十四 Spring的AOP的基于AspectJ的注解开发
Spring的AOP的基于AspectJ的注解开发 创建项目,引入jar包 编写目标类.切面类 配置目标类.切面类 在注解文件里开启AOP的开发 <?xml version="1.0& ...
- Spring AOP实现原理-动态代理
目录 代理模式 静态代理 动态代理 代理模式 我们知道,Spring AOP的主要作用就是不通过修改源代码的方式.将非核心功能代码织入来实现对方法的增强.那么Spring AOP的底层如何实现对方法的 ...
- Spring学习(十)--Spring的AOP
1.Spring AOP拦截器 (1)设计原理 Spring AOP在通过JDK的Proxy或者CGLIB方式生成代理对象的时候,拦截器的相关信息就配置到代理对象中了. 1)如果使用JDK的Proxy ...
- java之Spring(AOP)前奏-动态代理设计模式(下)
在上一章我们看到了,新增的三种类都能实现对原始功能类进行添加功能的事务处理,这三种类就是一个代理. 但是这种代理是写死的,怎样实现对任意接口添加自定义的代理呢? 我们先来看一下之前的代理实现: pub ...
- java之Spring(AOP)前奏-动态代理设计模式(上)
我们常常会遇到这样的事,项目经理让你为一个功能类再加一个功能A,然后你加班为这个类加上了功能A: 过了两天又来了新需求,再在A功能后面加上一个新功能B,你加班写好了这个功能B,加在了A后面:又过 了几 ...
- Hibernate学习--hibernate延迟加载原理-动态代理(阿里电面)
在正式说hibernate延迟加载时,先说说一个比较奇怪的现象吧:hibernate中,在many-to-one时,如果我们设置了延迟加载,会发现我们在eclipse的调试框中查看one对应对象时,它 ...
- python3.4学习笔记(十四) 网络爬虫实例代码,抓取新浪爱彩双色球开奖数据实例
python3.4学习笔记(十四) 网络爬虫实例代码,抓取新浪爱彩双色球开奖数据实例 新浪爱彩双色球开奖数据URL:http://zst.aicai.com/ssq/openInfo/ 最终输出结果格 ...
- Linux学习之十四、管线命令
Linux学习之十四.管线命令 地址:http://vbird.dic.ksu.edu.tw/linux_basic/0320bash_6.php
- 风炫安全WEB安全学习第二十四节课 利用XSS钓鱼攻击
风炫安全WEB安全学习第二十四节课 利用XSS钓鱼攻击 XSS钓鱼攻击 HTTP Basic Authentication认证 大家在登录网站的时候,大部分时候是通过一个表单提交登录信息. 但是有时候 ...
随机推荐
- 教你如何使用U盘装系统
首先,你必须有一个4G以上U菜,然后,U光盘制作软件(这里我们使用url=KRVS0FUdaNAMKPUXUxjEijxBMalUjaJHph-tL-x4gXGSwVNUW3fj6RfuZtrMg1Y ...
- Django操作model时刻,一个错误:AttributeError:’ProgrammingError’ object has no attribute ‘__traceback__’
原因:在Django项目下对应的应用以下的models.py配置的model(也就是class)没有创建成对应的表. 这是怎么回事呢? 首先,将models.py里面的model创建成相应的数据库表的 ...
- 趣味Java算法题(附答案)
[程序1] 题目:古典问题:有一对兔子,从出生后第3个月起每一个月都生一对兔子,小兔子长到第三个月后每一个月又生一对兔子,假如兔子都不死,问每一个月的兔子总数为多少? //这是一个菲波拉契 ...
- List Set Map用法和区别
List,Set,Map是否继承自Collection接口? 答:List,Set是,Map不是.如图: Collection ├List │├LinkedList │├ArrayList │└Vec ...
- zookeeper错误KeeperErrorCode = ConnectionLoss解决
原因: 一般是由于连接还未完成就执行zookeeper的get/create/exsit操作引起的. 解决方法: 利用"CountDownLatch 类 + zookeeper的watche ...
- 【原创】leetCodeOj ---Convert Sorted List to Binary Search Tree 解题报告
原题地址: https://oj.leetcode.com/problems/convert-sorted-list-to-binary-search-tree/ 题目内容: Given a sing ...
- java中间==、equals和hashCode差额
java于==.equals().hashCode()和比较两个对象. 关于== ==是easy理解的.java设计java就是要比較两个对象是不是同一个对象. 对于引用变量而言.比較的时候两个引用变 ...
- Android学习路径(十)如何将Action Bar堆放在布局
默认情况下,action bar出如今activity窗体的顶部,稍微降低了activity布局的总空间.假设你想隐藏或者显示action bar,在这堂用户体验的课程中,你能够通过调用hide() ...
- DV工作流
- WPF设置VistualBrush的Visual属性制作图片放大镜效果
原文:WPF设置VistualBrush的Visual属性制作图片放大镜效果 效果图片:原理:设置VistualBrush的Visual属性,利用它的Viewbox属性进行缩放. XAML代码:// ...