什么叫代理? 什么是静态代理?什么是动态代理?
我觉得用代码可以解释这一切。。
直接看代码:
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