什么叫代理? 什么是静态代理?什么是动态代理?
我觉得用代码可以解释这一切。。
直接看代码:
1.现在我们来建立一个java项目,叫Proxy,建立一个类,叫Tank,继续建立一个接口,叫Moveable,我们用Tank 来实现Moveable(意思就是坦克实现移动)
publicinterface Moveable {void move();//移动接口}
publicclass Tank implements Moveable {
@Overridepublic void move() {System.out.println( "Tank moving....");//简单的输出一句话就代表移动了}
}
我们再建立一个测试的服务端 叫Client类
public class Client {
public static void main(String[]
args) {
Moveable m=new Tank();
m.move();
}
}
运行一下,结果:
Tank moving....
证明我们第一步成功了
2.现在假设我们把这段代码提交到了一个地方,我们不可以去修改源码了,我们又想添加新的功能怎么办?
一般有两种方法:继承和聚合
来先看下继承:
我们来建立一个Tank2类来继承Tank,并且增加一个新的功能,叫计时的功能,就是方法开始运行就记下当前时间,结束的时候也记下结束的时间,然后相减,就得到了运行时间
public class Tank2 extends Tank{
@Override
public void move()
{
long start=System.currentTimeMillis();
super.move();
long end=System.currentTimeMillis();
System. out.println((end-start));
}
}
我们修改测试类来看一下:
public class Client
{
public static void main(String[]
args) {
Moveable m= new Tank2();
m.move();
}
}
结果:
Tank moving....
0
结果是0ms,这样看不出效果,那我们再加一个线程的睡眠
修改代码:
public class Tank implements Moveable
{
@Override
public void move()
{
System.out.println( "Tank
moving....");
try {
Thread. sleep(new Random().nextInt(10000));
} catch (InterruptedException
e) {
e.printStackTrace();
}
}
}
运行测试类:
Tank moving....
2995
这样就看出了运行时间
这上面的功能就是代理功能
我们在来看聚合
我们要聚合,就要先建立一个TankTimeProxy类,并且实现moveable接口,这个就是来计算tank移动方法运行的时间
看代码:
public class TankTimeProxy implements Moveable{
public TankTimeProxy(Tank
t) {
this.t =
t;
}
Tank t;//这里给一个tank对象进来,这就聚合(在一个类中引入另一个类的对象)
@Override
public void move()
{
long start=System.currentTimeMillis();
t.move();
long end=System.currentTimeMillis();
System. out.println((end-start));
}
}
修改测试类:
public class Client {
public static void main(String[]
args) {
Tank m= new Tank();
TankTimeProxy time= new TankTimeProxy(m);
time.move();
}
}
结果:
Tank moving....
923
3.这两个哪个好喃?
如果现在我们还要增加一个日志的功能,如果是继承,我们还要写一个类来继承Tank2,但是用户又说,我要求先日志,在计算时间,那么是不是又要写个类来实现movaable接口,来修改喃,这样就会造成类的无限增长,这显然是不合理的,所以我们要用聚合。。聚合,无论你增加多少功能,我都可以互相交换
下面来看下聚合的代码:
我们先增加一个加 TankLogProxy的类,同样也实现了moveable接口
public class TankLogProxy implements Moveable
{
public TankLogProxy(Tank
t) {
this.t =
t;
}
Tank t;
@Override
public void move()
{
System. out.println("Tank
start" );
t.move();
System. out.println("Tank
end" );
}
}
如果我们要想互相交换,那我们还需要修改一下代码:
public class TankLogProxy implements Moveable
{
public TankLogProxy(Moveable
t) {
this.t =
t;
}
Moveable t;
@Override
public void move()
{
System. out.println("Tank
start" );
t.move();
System. out.println("Tank
end" );
}
}
public class TankTimeProxy implements Moveable {
public TankTimeProxy( Moveable t)
{
this.t =
t;
}
Moveable t ;
@Override
public void move()
{
long start=System.currentTimeMillis();
t.move();
long end=System.currentTimeMillis();
System. out.println((end-start));
}
}
我们都把我们要传进来的对象变成Moveable的对象,因为我们都是实现了moveable接口
我们来写测试类
先日志,在时间
public class Client
{
public static void main(String[]
args) {
Tank t= new Tank();
TankLogProxy log= new TankLogProxy(t);//这里传的是tank的对象。tank也是moveable的对象
TankTimeProxy time= new TankTimeProxy(log);
Moveable m=time;
m.move();
}
}
结果:
Tank start
Tank moving....
Tank end
3639
满足我们的要求,如果现在我们要先时间,再日志,我们只需要修改一下测试类就oK
看代码:
public class Client {
public static void main(String[]
args) {
Tank t= new Tank();
TankTimeProxy time= new TankTimeProxy(t);
TankLogProxy log= new TankLogProxy(time);
Moveable m=log;
m.move();
}
}
结果:
Tank start
Tank moving....
484
Tank end
是不是很方便的就修改了我们的代码
这上面的就可以叫静态代理
4.现在有出现了一个问题?如果我现在有多个类,那我是不是要去实现多个计时,多个日志,那不是和刚才的继承一样,造成了类的大量产生(重复),这样显然是不合理的,那我们带怎么办喃?
我们现在就可以使用动态代理
我们来自己写一个动态代理类,名字叫Proxy
源码:
public class Proxy
{
//这个类的作用就是用来产生新的代理类
public static Object
newProxyInstance(){
/*把这个类当成一个string的字符串(源码)
现在我们假设,我们能把这字符串编译,生成类,放在内存,来产生对象
动态代理就是你看不到代理类,你只需要调用一个方法( Proxy的newProxyInstance()方法),
会自动给你返回一个代理类对象,这个对象的产生是由内部动态的生成一段代码,编译完成的*/
String src=
"package com.text;"+
"public class TankTimeProxy implements
Moveable{"+
"public TankTestProxy(Moveable t) {" +
" this.t = t;" +
" }"+
" Moveable t;" +
" @Override" +
" public void move() {" +
" long start=System.currentTimeMillis();"+
" t.move();" +
" long end=System.currentTimeMillis();" +
" System.out.println((end-start));" +
" }"+
"}";
return null ;
}
}
上面的注释解释的很清楚了。。
现在我们就来动态的编译这段代码
一般动态编译文件有这些方法(用JDK6的complier API(大于1.6都行,只是这个是1.6的新特性),CGlib,ASM(直接生成二进制的class文件))
我们直接用JDK的 complier
我们要做的步骤:
- 把字符串进行编译
- 生成一个类
- 写入内存
- 生成对象
我们先写一个测试类,叫Test1.java
源码:
第一步:
public class Test1
{
public static void main(String[]
args)throws Exception {
//来测试怎么编译这段代码
String src=
"package com.text;"+
"public class TankTimeProxy implements
Moveable{"+
"public TankTimeProxy(Moveable
t) {"+
" this.t = t;" +
" }"+
" Moveable t;" +
" @Override" +
" public void move() {" +
" long start=System.currentTimeMillis();"+
" t.move();" +
" long end=System.currentTimeMillis();"+
" System.out.println((end-start));" +
" }"+
"}";
//获取当前系统目录(就是项目根目录)
String fileName=System.getProperty("user.dir") ;
System.out.println(fileName);//先输出我们获取的根目录
}
}
第二步:
我们输出了根目录之后,我们就来编译这段字符串
修改
String fileName=System.getProperty( "user.dir" )+"/src/com/text/TankTimeProxy.java";
这样做的目的是为了让生成的代码就在我们的项目的文件里
添加以下代码:
File f=new File(fileName);
FileWriter writer= new FileWriter(f);
writer.write(src);
writer.flush();
writer.close();
在做这一步之前,如果你的文件里有TankTimeProxy.java文件,你把它删除了,不需要了,因为我可以动态的来生成了。
运行代码,完成之后,右键项目,刷新,你会看到出现了一个TankTimeProxy.java文件.OK,第二步完成
第三步:
我们来生成一个类
//这句话的作用就是获取系统当前默认的编译器(其实就 javac)
JavaCompiler compiler=ToolProvider.getSystemJavaCompiler(); //拿到java的编译器
System. out.println(compiler.getClass().getName());
StandardJavaFileManager filemag=compiler.getStandardFileManager(null, null, null);//文件的 管理器
Iterable untis= filemag.getJavaFileObjects(fileName);//找到文件,把文件放在 Iterable(数组)中
CompilationTask t=compiler.getTask( null,
filemag, null, null , null, untis );//定好编译文件任务
t.call(); //编译文件
filemag.close();//关闭文件管理器
运行:
编译之后,我们打开window show View 选择Navigator(这个可以看到类详细的变化,就是看得到class文件的产生)
就会看到多了一个TankTimeProxy.class 文件,第三步成功
第四步:
我们把文件加入内存(原本一般的做法是class.loader,就OK了,但是调用这个方法的前提就是,你的class文件目录必须在classpath的文件目录下),我们这里用一种通用的做法
//这里使用url加载器
URL [] urls=new URL[]{ new URL("file:/"+System.getProperty( "user.dir")+"/src" )};
URLClassLoader ul= new URLClassLoader(urls);//这里需要一个数组地址
Class c=ul.loadClass("com.text.TankTimeProxy" );//把类加到内存
System. out.println(c);
测试:输出c,OK,第四步完成
最后一步,生成对象
//反射来创建对象
Constructor ctr=c.getConstructor(Moveable.class) ;//获取构造方法
Moveable m=(Moveable)ctr.newInstance( new Tank());
m.move();
运行:
看下结果:
com.sun.tools.javac.api.JavacTool
class com.text.TankTimeProxy
Tank moving....
2004
Ok,我们要求的功能全部实现了。
5.如果现在我们实现不是一个特定的接口(意思就是不是实现Moveable接口,而是实现的其他接口),那我们怎么办喃?
那我们把接口也当参数传进来
修改代码:
//把接口也当做一个参数传进来
public static Object
newProxyInstance(Class infc) throws Exception{//把接口也传进去
String methodsString= "";//定义一个变量
Method methods[]=infc.getMethods();//获取方法(反射)
for(Method
m: methods){
methodsString=methodsString+"@Override"+
" public void "+m.getName()+"() {"+
" long start=System.currentTimeMillis();"+
" t."+m.getName()+"();"+
" long end=System.currentTimeMillis();"+
" System.out.println((end-start));"+
" }";
/*把这个类当成一个string的字符串(源码)
现在我们假设,我们能把这字符串编译,生成类,放在内存,来产生对象
动态代理就是你看不到代理类,你只需要调用一个方法( Proxy的newProxyInstance()方法),
会自动给你返回一个代理类对象,这个对象的产生是由内部动态的生成一段代码,编译完成的*/
//现在我们就来动态的编译这段代码(JDK6,complier API,CGlib,ASM(直接生成二进制的class文件))
String src=
"package com.text;"+
"public class TankTimeProxy implements "+infc.getName()+"{"+
"public TankTimeProxy(Moveable h) {"+
" this.h = h;" +
" }"+
//" Moveable t;"+
"Moveable h ; " +
methodsString
+
"}";
//获取当前系统目录(就是项目根目录)
String fileName=System.getProperty("user.dir")+ "/src/com/text/TankTimeProxy.java" ;
//System.out.println(fileName);
File f= new File(fileName);
FileWriter writer= new FileWriter(f);
writer.write(src);
writer.flush();
writer.close();
//看是否生成代码,右键项目,刷新就OK了
/*****************编译********************/
//这句话的作用就是获取系统当前默认的编译器(其实就 javac)
JavaCompiler compiler=ToolProvider.getSystemJavaCompiler(); //拿到java的编译器
System. out.println(compiler.getClass().getName());
StandardJavaFileManager filemag=compiler.getStandardFileManager(null, null, null);//文件的管理器
Iterable untis= filemag.getJavaFileObjects(fileName);//找到文件,把文件放在 Iterable中
CompilationTask t=compiler.getTask( null,
filemag, null, null , null, untis );//编译文件任务
t.call(); //编译文件
filemag.close();
//编译之后,我们打开window show View 选择Navigator(这个可以看到类详细的变化,就是看得到class文件的产生)
/********************load 到内存,和创建对象*************************/
//如果要使用class.loader,就必须保证这个class在 classpath的路径下
//因此我们要用一个特殊的loader
URL [] urls= new URL[]{new URL("file:/"+System.getProperty( "user.dir")+"/src" )};
URLClassLoader ul= new URLClassLoader(urls);//这里需要一个数组地址
Class c=ul.loadClass("com.text.TankTimeProxy" );//把类加到内存
System. out.println(c);
//反射来创建对象
Constructor ctr=c.getConstructor(Moveable.class) ;//获取构造方法
Object m=ctr.newInstance( h);
return m;
}
}
即使这样,我们还是遇到一个问题,那就是每个接口的方法不是一样的,有的多,有的少,这样有怎么办喃?
那我们把方法也修改了,就是上面的那个段代码:
for (Method m: methods){
methodsString=methodsString+"@Override"+
" public void "+m.getName()+"() {"+
" long start=System.currentTimeMillis();"+
" t."+m.getName()+"();"+
" long end=System.currentTimeMillis();"+
" System.out.println((end-start));"+
" }";
这样就能保证,即使方法不统一,我也可以让每个方法都执行计时功能哦
6.现在我们来解决下一个问题,我们每个接口都是来实现计时功能的?显然不是,那肯定还有其他功能三
那我们怎么样来做,才可以是我们想实现什么功能,就实现什么功能喃?动态代理?
我们按照一贯的做法,继续把功能也传经来。。
看代码:
public class Proxy
{
//这个类的作用就是用来产生新的代理类
public static Object
newProxyInstance(Class infc,InvocationHandler h)throws Exception{//把接口也传进去
String methodsString= "";
Method methods[]=Moveable. class.getMethods();
for(Method
m: methods){
/*methodsString=methodsString+"@Override"+
" public void "+m.getName()+"() {"+
" long start=System.currentTimeMillis();"+
" t."+m.getName()+"();"+
" long end=System.currentTimeMillis();"+
" System.out.println((end-start));"+
" }";*/
methodsString=methodsString+ "@Override"+
" public void "+m.getName()+"()
{"+
"Method md="+infc.getName()+".class.getMethod(\""+m.getName()+ "\");"+
"h.invoke(this,md) ;"+
" }";
}
/*把这个类当成一个string的字符串(源码)
现在我们假设,我们能把这字符串编译,生成类,放在内存,来产生对象
动态代理就是你看不到代理类,你只需要调用一个方法( Proxy的newProxyInstance()方法),
会自动给你返回一个代理类对象,这个对象的产生是由内部动态的生成一段代码,编译完成的*/
//现在我们就来动态的编译这段代码(JDK6,complier API,CGlib,ASM(直接生成二进制的class文件))
String src=
"package com.text;"+
"public class TankTimeProxy implements
"+infc.getName()+"{"+
"public TankTimeProxy(InvocationHandler
h) {"+
" this.h = h;" +
" }"+
//" Moveable t;"+
"com.text.InvocationHandler h ; " +
methodsString
+
"}";
//获取当前系统目录(就是项目根目录)
String fileName=System.getProperty("user.dir")+ "/src/com/text/TankTimeProxy.java" ;
//System.out.println(fileName);
File f= new File(fileName);
FileWriter writer= new FileWriter(f);
writer.write(src);
writer.flush();
writer.close();
//看是否生成代码,右键项目,刷新就OK了
/*****************编译********************/
//这句话的作用就是获取系统当前默认的编译器(其实就 javac)
JavaCompiler compiler=ToolProvider.getSystemJavaCompiler(); //拿到java的编译器
System. out.println(compiler.getClass().getName());
StandardJavaFileManager filemag=compiler.getStandardFileManager(null, null, null);//文件的管理器
Iterable untis= filemag.getJavaFileObjects(fileName);//找到文件,把文件放在 Iterable中
CompilationTask t=compiler.getTask( null,
filemag, null, null , null, untis );//编译文件任务
t.call(); //编译文件
filemag.close();
//编译之后,我们打开window show View 选择Navigator(这个可以看到类详细的变化,就是看得到class文件的产生)
/********************load 到内存,和创建对象*************************/
//如果要使用class.loader,就必须保证这个class在 classpath的路径下
//因此我们要用一个特殊的loader
URL [] urls= new URL[]{new URL("file:/"+System.getProperty( "user.dir")+"/src" )};
URLClassLoader ul= new URLClassLoader(urls);//这里需要一个数组地址
Class c=ul.loadClass("com.text.TankTimeProxy" );//把类加到内存
System. out.println(c);
//反射来创建对象
Constructor ctr=c.getConstructor(InvocationHandler.class) ;//获取构造方法
Object m=ctr.newInstance(h);
return m;
}
}
我在代码中增加一个叫InvocationHandler 的类,它的功能就是告诉我,什么对象要来实现什么功能
来理一下思路: 我要实现什么功能?(这里是我要?如果其他人喃?)
那就变成:什么人要实现什么功能?(object o ,Method m)
public interface InvocationHandler {//指定方法(你需要时间,日志,还是其他)
public void invoke(Object
o,Method m) throws Exception;//告诉那个对象去执行这个方法
}
比如说我要实现计时的功能
我传递的参数就是(I, time)
我们来写一下实现类
public class TimeHandler implements InvocationHandler{
@Override
public void invoke(Object
o,Method m) throws Exception{
long start=System.currentTimeMillis();
m.invoke(o, null);
long end=System.currentTimeMillis();
System. out.println((end-start));
}
}
我们运行下代码,发现出错了
这是因为我们不是代理 一个具体类来实现功能吗?但是现在却看到具体类的影子?
比如:我们要代理Tank 实现计时功能,就要传入Tank类
我们来修改一下代码:
public class TimeHandler implements InvocationHandler{
private Object t;
public TimeHandler(Object
t) {
super();
this.t =
t;
}
@Override
public void invoke(Object
o,Method m) throws Exception{
long start=System.currentTimeMillis();
m.invoke( t);
long end=System.currentTimeMillis();
System. out.println((end-start));
}
}
测试类:
public class Client
{
public static void main(String[]
args) throws Exception{
Tank t= new Tank();
TimeHandler h= new TimeHandler(t);
Moveable m=(Moveable)Proxy.newProxyInstance(Moveable. class,h);//这句话就是我的Moveable接口的实现类Tant
要实现计时功能
m.move();
}
}
运行:
结果:
com.sun.tools.javac.api.JavacTool
class com.text.TankTimeProxy
Tank moving....
6775
我们来整理一下我们的思路
首先我们有一个Tank对象,它实现了一个moveable接口,它就具有了一个move移动的方法,我们想在move方法的前后加上一些逻辑,这些逻辑由我们来定,所以我们首先定义自己的逻辑,我们就建立了TimeHandler类,当调用这个类的时候,就会把Tank的代理类的实体传进来,我们就可以在move方法的前后加上一些逻辑。
这样做的好处就是可以对任意的对象,任意的接口,实现任意的代理
我们现在来写一个Person 类
public class Person implements Moveable{
@Override
public void move()
{
System. out.println("我正在走路" );
try {
Thread. sleep(new Random().nextInt(10000));
} catch (InterruptedException
e) {
// TODO Auto-generated
catch block
e.printStackTrace();
}
}
}
我们也来实现计时
测试:
public class Client {
public static void main(String[]
args) throws Exception{
Person person= new Person();
TimeHandler h= new TimeHandler(person);
Moveable m=(Moveable)Proxy.newProxyInstance(Moveable. class,
h);
m.move();
}
}
结果:
com.sun.tools.javac.api.JavacTool
class com.text.TankTimeProxy
我正在走路
com.text.TankTimeProxy
8240
我想你发现代理类的好处了吧。。。。。就是只需要写一次,你就可以在任意的类中完成任意的代理
再来测试不同的接口
public interface Run
{
public void run();//跑
}
我们用Person来实现这个接口,并计时
public class Person implements Pao {
@Override
public void rrrrun()
{
System. out.println("我正在跑步" );
try {
Thread. sleep(new Random().nextInt(10000));
} catch (InterruptedException
e) {
// TODO Auto-generated
catch block
e.printStackTrace();
}
}
}
测试:
public class Client
{
public static void main(String[]
args) throws Exception{
Person person= new Person();
TimeHandler h= new TimeHandler(person);
Pao m=(Pao)Proxy. newProxyInstance(Pao.class,
h);
m.rrrrun();
}
}
结果:
com.text.Paollll
com.sun.tools.javac.api.JavacTool
class com.text.TankTimeProxy
public void com.text.TankTimeProxy.rrrrun()
我正在跑步
com.text.TankTimeProxy
8171