package com.learn.proxy;
public class Client {
public static void main(String[] args) throws Exception {
/**
* 首先写一个被代理对象出来
*/
Tank t = new Tank();
/**
* 如果是实现其他接口的呢,产生实现任意接口的一种代理
* 那我这个时候该怎么办,我们现在产生的代理是动态代理,
* 是实现Moveable接口的动态代理,就是实现任意接口的这样一个代理,我该怎么办,
* 把接口也传进去,我们现在要求必须实现Moveable接口
*
* 我要是想实现别的接口的代理,你要实现代理是可以实现任何接口的代理的
* 你只要把接口传进去就可以了,我现在模拟的就是JDK的动态代理是怎么回事
* 我们往里面传了interface了,可以根据interface来生成里面的内容了
* 你只要往里面传任何接口,我就能产生实现这个接口的这个类的对象
* 我的动态代理又更近了一步,Spring的动态代理是直接使用JDK的动态代理生成的
* 现在可以生成实现任意接口的代理了
*
*/
// Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class);
/**
* 我们写一个处理逻辑,new的时候把我们的t交进去
* 说明我们处理的是对这个对象的处理
* new TimeHandler的时候我们直接把这个t交给他
* 我自己定义了一个代理的处理逻辑,而这个代理的逻辑是处理t这个对象的
* 然后我们把h交给我们的代理生成类,他给我一个具体的代理对象
* 生成了之后他怎么办呢,当你调里面move方法的时候,
*/
InvocationHandler h = new TimeHandler(t);
/**
* 实现Serializable的接口,IO里面的一个类
* Serializable里面没有任何实现的方法,所以我们仍然传Moveable
* 我现在换一个接口,换成Comparable,这个是JDK里面的一个接口了
*
* 我们调用newProxyInstance方法,返回给我一个对象
* 这个对象我要求他给我一个接口,所以我就可以把它转成Moveable的对象
* 具体这个对象是什么源码,看不见,具体里面怎么实现看不见,这个类名叫什么我看不见
* 因为如果你不看代码你是不知道的,但是无所谓,就可以让他去move了,
* 而且里面就是有代理的内容产生了,哪个文件在哪里跟我有关系了,没关系,
* 放哪儿有什么关系呢,我们有一些工具可以直接生成二进制文件,
* 如果是这样他就不要生成文件,我们终于自己写了一个Proxy,
* 目前我们在自己生成的动态代理里边,有一个很严重的问题,我们的逻辑是固定的
*
* 我要对方法进行什么样的处理,前后加时间的处理
*
* 由于我自己new的TimeHandler,这是我自己往里new的,
* 我自己往里实现什么代理的实现是我自己做的
* TimeHandler不满意我自己实现就可以了
* 我想实现Log的就实现Log的
* 想实现其他的就实现其他的
* 理论上来向我们可以调用move
*
* 现在我们生成了一个新的代理,而且处理逻辑是我们自己指定的
* 不过现在move还是有问题,原因非常简单,
* 因为在我们生成代理的里头我们要代理哪个对象啊
* 实际上他都没有,就是被代理的对象就突然不见了
*
* 传进来的invoke也是h的TimeHandler的invoke,
* TimeHandler的invoke可以自己加逻辑
* 先把原来的move方法调了,后面再加上后面的逻辑,
* 我们最后的功能我就实现了
*
* 麻烦了这么半天有什么好处呢
*
* 我们从开始生成静态的代理,然后慢慢的生成动态的代理,
* 我们把这个过程整理一下,我们现在有一个Tank对象
* Tank对象里有一个move方法,因为它实现了moveable接口
*
* 可以对任意的对象,任意的方法,实现任意的代理
*
* 现在我看不到Proxy的内容,也看不到InvocationHandler的内容的
* 我们想直接用这两个类
*
*/
Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class, h);
m.move();
}
}
package com.learn.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.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class Proxy {
/**
* 我们在产生newProxyInstance()的时候告诉我你要实现哪个接口
* 请你告诉我你要实现哪个接口,JDK里面你是可以往里面传多个接口
* 实现哪个接口的动态代理,所以你往里面传要实现什么接口,他就实现什么接口
* 你只要把接口的类型往里面传就可以了
*
* 但是我们写的方法还是move,那么说只有moveable的方法才能写这个玩意
* 严格来讲我们应该是这么写的,它里面有多少个方法,你传进来的这个接口里面有多少个方法,
* 我们就应该给多少个方法,生成这种代码,我必须得知道在这个class里面,
* 到底有多少个方法,那么怎么知道在这个class里面有多少个方法呢,
* 我直接在生成的代码上改
*
* 你必须告诉我你产生是什么类型的代理,你产生是Time类型,infce这个接口的方法进行什么样的处理
* 大家知道h是处理方法用的,进行什么样的处理呢,就看你往里面传的是什么样的InvocationHandler
*
* 我们在产生动态代理的时候不仅可以指定我会产生的是哪个接口的动态代理,
* 而且还可以指定这些接口的方法进行什么样的处理,
* 当我进行方法调用的时候对于方法处理的代码,这个方法是要求你有两个参数,
* 第一个参数是Object是对于哪个对象来调,第二个是调对象的哪个方法,
* 当我们代理实现每一个方法的时候,我们第一个参数该怎么写
*
* 根据你传给我需要实现什么接口的代理,根据你传给我要对接口的方法做什么样的处理,
* 然后动态的生成这么一个类,这个类我们会生成一个新的对象,
* 返回给你就会调里面的方法了
* 作为客户自己是知道的,因为传入接口了,
* 所以我就可以强制转换成什么接口,
*
* @return
* @throws Exception
*/
public static Object newProxyInstance(Class infce,InvocationHandler h) throws Exception {
/**
* 我单独生成这部分的字符串,叫methodString
* 慢慢的把这部分的代码给构建出来
*/
String methodStr = "";
String rt = "\r\n";
/**
* 因为你传进来的已经是接口对象了
* 拿到interface里面所有的method
* 我们根据名字来生成特定的代码
* 我们要生成一个动态的字符串,这个动态的字符串包括什么
* 我们根据方法的名字来生成,但是重要的是在方法的里面怎么写,
*
*/
Method[] methods = infce.getMethods();
for (Method method : methods) {
/**
* 我现在生成代理只能生成一种,一种时间上的代理,
* 我要向生成一种LOG代理,这个方法不行,
* 我得写一个newProxyInstance2,虽然你可以实现任何接口了,
* 可以对对象进行代理了,但是还是不行,我们怎么让这部分让客户能灵活的
* 进行指定,这个事情又麻烦了,这个时候当我产生动态代理的时候,
* 我就不是说产生这段代码就可以了,最好还能产生一个别人指定的处理方式
* 任何 一个方法在调用的时候,你应该给我一个别人指定给我的处理方式,
* 由于我们现在对于任何接口的代理现在只能写死,这个是不行的,
* 当我在代理里写这段代码的时候,在方法前面写什么,后面写什么,
* 有别人能帮我动态的指定,也就是说这段代码是由别人来执行
* 而执行的过程由别人自己来定,凡是说由什么自己来定是离不开多态的,
* 所以我们可以首先来做这样一个东西,我们先不管动态代理到底怎么实现
* 先把大问题切换成小问题,不管怎么样我现在需要这样一个东西,
* 我先需要一个动态可以指定对某一个方法进行处理的这样的一种东西,
*
*
* 我们想让其他人帮我们实现这个逻辑,我们就可以通过实现InvocationHandler的方式来实现
* 在我们动态生成的这段代码里面,你就必须要发生改变了,怎么改变呢
* 其实这个改变并不难
*
*/
/*methodStr += "@Override" + rt +
"public void " + method.getName() + "() {" + rt +
" long start = System.currentTimeMillis();" + rt +
" System.out.println(\"starttime:\" + start);" + rt +
" t."+method.getName()+"();" + rt +
" long end = System.currentTimeMillis();" + rt +
" System.out.println(\"time:\" + (end-start));" + rt +
"}";*/
/**
* 首先第一点,当你产生新代理的时候,你必须告诉我你产生的是什么类型的代理
* invoke里面有两个参数,第一个是Object,第二个是方法
* 这两个参数应该怎么写
* this就是代理的对象,对代理对象调什么方法呢
* method就是方法的对象
* 我们Handler里面传的时候应该是传md进来
* 动态生成文件里面必须把h保存起来
* 在我们动态生成的文件里面,除了我们生成Moveable t之外,
*
*/
methodStr += "@Override" + rt +
"public void " + method.getName() + "() {" + rt +
/**
* 这段必须报告一个异常
*
*/
" try {" + rt +
" Method md = " + infce.getName() + ".class.getMethod(\"" + method.getName() +"\");" + rt +
" h.invoke(this, md);" + rt +
" }catch(Exception e){e.printStackTrace();}" + rt +
"}";
}
// System.out.println(methodStr);
String src =
"package com.learn.proxy;" +
/**
* 我需要引入这个类,不然他生成的代码编译不了
*/
" import java.lang.reflect.Method;" + rt +
/**
* 这里叫$Proxy1,其实它叫什么名没什么关系
* 因为我们看不到这类的对象
*
*/
"public class $Proxy1 implements "+infce.getName()+" {" + rt +
"public $Proxy1(InvocationHandler h) {" + rt +
" super();" + rt +
" this.h = h;" + rt +
"}" + rt +
/**
* 保存一个子级的成员变量
* 在生成的类里面保存一个内部的变量
* 当我们new TankTimeProxy的时候,在我们的构造方法里面把h加入进来
* 内部我们生成代码的时候有一个h,
*/
" com.learn.proxy.InvocationHandler h;" + rt +
methodStr +
"}";
/**
* 当然你load的时候也要在同一个地方
* 这样就不会产生冲突了
* 这个目录必须手动创建
*/
String fileName = "d:/src/com/learn/proxy/$Proxy1.java";
File f = new File(fileName);
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable units = fileManager.getJavaFileObjects(fileName);
CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
t.call();
fileManager.close();
/**
* 我们把文件放在D盘srouce下面,其实放在哪都没关系的
*/
URL[] urls = new URL[] {new URL("file:/" + "d:/src/")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("com.learn.proxy.$Proxy1");
System.out.println(c);
Constructor ctr = c.getConstructor(InvocationHandler.class);
/**
* 我们生成这个类的一个对象,然后把这个对象反馈回去就可以了
* 我也不知道你实现的是哪个接口
*/
// Moveable m = (Moveable) ctr.newInstance(new Tank());
Object obj = ctr.newInstance(h);
// m.move();
/**
* 然后把返回值返回给调用的这个人就可以了
*/
return obj;
}
}
package com.learn.proxy;
import java.lang.reflect.Method;
/**
* 我们想知道一个接口里面有多少个方法,这是比较简单
* @author Leon.Sun
*
*/
public class Test2 {
public static void main(String[] args) {
/**
* 站在ClassLoader的角度,Method也是一系列的对象
* 一个接口里面会有好多好多的方法,这个对象是反射机制里面的
* 引入我们就能看到了,通过Moveable.class.getMethods()这个二进制码里头
* 有多少个方法
* public abstract void com.learn.proxy.Moveable.move()
* 这个就是我们拿到的方法,当然我们根据getName可以拿到更简单的方法
* 我们能不能拿到接口里的方法,对于我动态代理的生成,
*
*/
Method[] methods = Moveable.class.getMethods();
for (Method method : methods) {
System.out.println(method);
System.out.println(method.getName());
}
}
}
package com.learn.proxy; import java.lang.reflect.Method;
public class TankTimeProxy implements com.learn.proxy.Moveable {
public TankTimeProxy(InvocationHandler h) {
super();
this.h = h;
}
com.learn.proxy.InvocationHandler h;
@Override
public void move() {
try {
Method md = com.learn.proxy.Moveable.class.getMethod("move");
h.invoke(this, md);
}catch(Exception e){e.printStackTrace();}
}}
package com.learn.proxy;
import java.lang.reflect.Method;
/**
* 这个Handler我给他指定为interface
* 既然你是方法调用的处理器,当然他应该有一个方法
*
* @author Leon.Sun
*
*/
public interface InvocationHandler {
/**
* 在这里我先不管它的返回值,因为我们只是模拟
* 并不是真真正正的写一个JDK动态代理,
* 既然我能处理任何方法的调用,你只要给我一个method,
* 我就能对方法进行处理,特殊处理的方式是由谁来决定呢,
* 是由它的子类来决定
*/
public void invoke(Object o,Method method);
}
package com.learn.proxy;
import java.lang.reflect.Method;
/**
* 既然我是TimeHandler,对于我来说这个方法前后只要加上时间久行了
* 我们可以对一个方法进行自定义的处理
* 写一个方法,把另一个方法作为参数传给我,这是你想调的方法
* 然后在房钱之前或者之后来实现
*
* 我们要去实现一个TimeHanlder的时候,
*
* 然后我就做一个代理的逻辑处理
*
* @author Leon.Sun
*
*/
public class TimeHandler implements InvocationHandler {
/**
* SUN要求这样的,这是被代理的对象
* 注意这是我们被代理的对象
* 严格意义上来讲我们这里可以代理任何的对象
* TimeHandler是可以代理任意对象的,不一定是坦克
* 所以我们这里写Object而不要写Tank
* 我们现在可以对任何的对象任何的方法生成任意的代理
*
* 这是我们真正被代理的对象
*/
private Object target;
/**
* 我们直接指定对哪个对象进行代理
*/
public TimeHandler(Object target) {
super();
this.target = target;
}
/**
* invoke里面必须有一个内容是Object,
* 就是你一定要告诉我,你现在是对哪个对象调的方法
* 你要想让它执行的话你必须要有一个对象产生才可以,
*
* invoke的时候我已经在前面加了逻辑了,后面也加了逻辑了
* 原来我们是对着o来调,其实不是o,因为这里是一个代理对象,
* 也就是target的这个方法
* 我们在invoke里面首先加自己的逻辑,
* 相当于target.m的方法,然后加上结束的逻辑
* 这样我们就可以处理任何的类任何的方法
* 所以在这里稍微有点麻烦,首先定义自己的处理
* 第一个你想对哪个对象进行代理
* 加上自己的代理逻辑,对被代理对象调原来的方法
* 调完了之后再加上后面的逻辑
* 所以当你有了InvocationHandler之后
*
* 我前后定义了自己的逻辑
* 我会把坦克的对象交给TimeHandler的对象
* 这样TimeHandler对象调用方法的时候,
* 它会先执行它自己的逻辑,然后再调用我tank的方法
* 然后再是后面的逻辑,这里有一个小小的疑问
* Object是谁,Object是我们的代理对象
* 因为我们传的是this,在代理里写的是this
*
* 他可以加自己的逻辑,调我这个对象的方法
* 这个逻辑是他自己可以定义的,
*
*/
@Override
public void invoke(Object o,Method m) {
long start = System.currentTimeMillis();
System.out.println("start: " + start);
/**
* 这个就是距离代理的类,在JDK里面叫$Proxy1
* com.learn.proxy.$Proxy1
* 名字没什么关系,
*/
System.out.println(o.getClass().getName());
/**
* 调用的时候牵涉到反射了
* 当我有这么一个接口之后呢,那我怎么写实现都可以
* 比如我写Log的实现,那我就写一个LogHandler,
* 比如我写一个权限的实现,我就写一个AccessHandler
* 写Transaction的实现,就写TransactionHandler
* 所以这里头就更灵活了,我们定义了一个接口,
* 下面我们谈方法的调用,对于方法的调用,我们一定要知道当前调用的对象是谁
* 你没有对象是没有方法调用方法的,除了静态方法,所以我必须得有一个对象
* 所以再invoke里面必须得有一个内容,对o调用
* 因为我们现在没有参数,指的是你往方法里面传什么参数,空的Object数组
* 我们现在有一个小小的假设,现在方法里面没有任何的参数,因为这个只是牵涉到细节
* 为了让大家把主要的代码看得清楚一点,我就直接try...catch...Exception
* 现在我们就实现了这样一种机制,这个接口可以处理方法,这个处理的方式由我们子类去实现
* 我想这个应该是没有什么问题的
*
* 当我们在里面调方法的时候,
*/
try {
m.invoke(target, new Object[] {});
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("time: " + (end-start));
}
}
package com.learn.proxy;
import java.util.Random;
public class Tank implements Moveable {
/**
* Tank里面有一个move方法,我们想在move方法的前后加上一些逻辑
* 而这些逻辑由我自己来定,所以我们定义了我们自己的逻辑,这个逻辑是我们的TimeHandler
*
* 有一个对象我们要做代理
*/
@Override
public void move() {
// long start = System.currentTimeMillis();
System.out.println("Tank Moving.....");
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
// long end = System.currentTimeMillis();
// System.out.println("time:" + (end-start));
}
}
package com.learn.proxy;
/**
* 在里面有一个方法,实现了一个接口
*
* @author Leon.Sun
*
*/
public class UserManagerImpl implements UserManager {
@Override
public void addUser() {
/**
* 由于没连数据库我就不连数据库了
* 这两个操作要同时完成,也可以同时不完成,都是一回事
* 那我想在方法的前后控制他到底要不要同时完成
* 这个就叫做控制Transaction,
* 如果你不理解什么意思,就理解为前后做日志就行了
* 我现在要做一个Transaction,你原来是直接在代码上加
* 我现在没有这个必要了,
*/
System.out.println("1: 第一步插入记录到user表");
System.out.println("2: 做日志在另外一张表");
}
}
package com.learn.proxy;
/**
* interface没有实现
* @author Leon.Sun
*
*/
public interface UserManager {
void addUser();
}
package com.learn.proxy;
import java.lang.reflect.Method;
/**
* 这个InvocationHandler不要和JDK的InvocationHandler混淆
* 这是我们自己写的,这个写法是固定的
* @author Leon.Sun
*
*/
public class TransactionHandler implements InvocationHandler {
private Object target;
public TransactionHandler(Object target) {
super();
this.target = target;
}
@Override
public void invoke(Object o, Method m) {
/**
* 我是真的从数据库拿出session来,
* 使用session.getTransaction方法
* 最后才session.commit();
*/
System.out.println("Transaction Start");
try {
m.invoke(target, new Object[] {});
} catch (Exception e) {
e.printStackTrace();
}
/**
* 在这里简单做一个日志就行了
* 因为这里的写法是你自己写的,想怎么写就怎么写
*
*/
System.out.println("Transaction Commit");
}
}
package com.learn.proxy;
/**
* 我可以对任意的对象的任意方法
*
* 动态代理有有一个非常大的作用,
* 我不需要修改原来的实现的代码,我就可以在原来的基础之上插入内容
* 我现在有一个方法正在运行,有个方法正在顺序的运行,
* 但是我想在方法上加上逻辑的时候,原来我是直接在代码上加
* 影响原来的代码,不过现在没关系了,我可以在前面加逻辑
* 后面加点逻辑,方法自己知道吗,不知道,所以什么叫做面向切面编程
* 也就是AOP,Aspect Oriented Programming 面向切面编程
* 可以在中间插入一些逻辑,所以就叫做面向切面编程,
* 而且这些逻辑是不影响原来的代码的,而且还有一点这个逻辑是可插拔的
* 如果我们把TransactionHandler写在配置文件里
* 找个配置文件给他写进去,你要我查权限我就可以实现权限的逻辑
* 你要我查Transaction,我就可以实现Transaction的逻辑
* 你让我记日志我就实现日志的逻辑
* 而且互相之间还可以叠加的,因为我在new一个新的Handler出来
* TimeHandler h2 = new TimeHandler(h);
* 作为我下一个代理的对象传进来,很多的代理就叠加在一起了
*
* 我们来和JDK的动态代理比较一下
* JDK的类有两个类来构成,第一个叫Proxy,就是newProxyInstance,
* 他后面的两个参数是和我们一样的,前面是一个ClassLoader
* classLoader的意思是用哪个ClassLoader把它给Load进来
* 我们现在用的classLoader是我们自己写好的URLClassLoader,
* 当你真正调用的时候是用你自己类的ClassLoader,
* 所以这个不是重点,只要能够load进来就可以了
* 如果我们用别人传给我们的ClassLoader
* 得保证我们生成代理的类特定的目录里才能load进来
*
* InvocationHandler的invoke方法
* 第一个是代理类Proxy,第二个是Method,前面的两个参数是一样的
* 第三个是方法的参数,咱们现在模拟的都是没有参数的
* JDK类里面是没有参数的,得把这个参数给我传进去,
* 就这点区别,其他的没有区别了
* 动态代理一般是怎么产生的
* 第一个什么叫动态代理,
* 第二个动态代理是怎么产生的,
* 怎么生成动态代理的,动态代理有什么用,
* 它的作用随便举例,事务Transaction,
* 而且是可配置的事务,AOP权限的控制
* 日志,可以在不改变代码的情况下可以插入任何的功能
*
* 第一个是你想知道一个方法的运行时间怎么办?
* 两种方式,继承的代理和聚合的代理,聚合好,
*
* 想知道多个类多个方法的运行时间
*
* 可不可以重用代理的写法而不必产生过多的代理类?
*
* JDK本身是怎么实现的?
*
* 它是如何实现的呢?
* 1. JDK Compiler API
* 2. CGLIB ASM -> 直接生成class二进制码
*
* 扒开JDK的动态代理来看看
*
* 我想这两个类该怎么用你应该明白了,日志安全,Transaction
* 想想有多大,动态代理就有多大
*
* 如果有同学已经了解JDBC了,请你自己取模拟一下他的Transaction
*
*
*
*
* @author Leon.Sun
*
*/
public class TestClient {
/**
* 任意接口的任意方法进行任意逻辑上的处理
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
UserManager userManager = new UserManagerImpl();
/**
* 接下来我们写一个自己的TransactionHandler
*/
InvocationHandler h = new TransactionHandler(userManager);
TimeHandler h2 = new TimeHandler(h);
/**
* 然后我对他生成一个代理
* 首先我们要知道interface是谁,当然是UserManager.class
* 他就会给我生成一个对象,这个对象一定是实现UserManager接口的
*
* 最后我们的Handler是h2
* 我们先不考虑叠加的事情,两个代理是可以叠加的
* 相互替换的,而且是可以插拔的
* 我什么时候不用了,我就配置文件一改,
* 这就是动态代理的具体的功能
* 这就是Spring为什么可以管理AOP,可以管理Transaction
* 还可以管理其他的各种各样的内容,
*
*/
UserManager u = (UserManager) Proxy.newProxyInstance(UserManager.class, h);
/**
* 既然实现了接口,那就一定可以实现addUser
* 而且u这个对象一定是代理生成的,前后加了Transaction的逻辑的
*/
u.addUser();
}
}