Coin163

首页 > java 设计模式 动态代理

java 设计模式 动态代理

2021腾讯云限时秒杀,爆款1核2G云服务器298元/3年!(领取2860元代金券),
地址https://cloud.tencent.com/act/cps/redirect?redirect=1062

2021阿里云最低价产品入口+领取代金券(老用户3折起),
入口地址https://www.aliyun.com/minisite/goods

相关推荐:Java反射API与动态代理

源:http://www.infoq.com/cn/articles/cf-java-reflection-dynamic-proxy 基本用法 Java 反射API的第一个主要作用是获取程序在运行时刻的内部结构。这对于程序的检查工具和调试器来说,是非常实用的功能。只需要短短的十几行代码,就可以遍历出来一个Java类的

动态代理的工作是:

在一段程序的外面在包上一段其他程序,这样就会比较方便

这里要注意,是指的是在仅仅是方法外层包上一层其他方法,而不是抱在jdk把方法实例化后再包上其他方法

如果实例化后,这里会包上一层jdk 为程序运行加上的虚拟机程序


首先应该想到的是继承:重写类的方法,调用父类方法用super.xxxx(),然后加入新的“包括” 方法

其次应该想到聚合(一个类里头有另外一个类的对象):

申明另一个类实现同样的接口,在类里拥有“被包装类”A(代理了类A的),然后在实现里头调用类A的方法,然后在调用前后加入想“包括”的方法


聚合比继承更灵活


申明所有Proxy类统一实现一个方法接口,里面只有一个申明方法;

在所有Proxy类中写 申明 “接口类型” 的 一个对象;并生成构造方法

其他“包括”方法就包在 调用 “对象里方法”的  外面

写法:

Tank t=new Tank();
TankTimeProxy ttp=new TankTimeProxy(t);
TankLogProxy tlp=new TankLogProxy(ttp);
  Movable m  = tlp;

m.move()


但是如果接口里有两个以上的方法,或者要求某个类实现的“包括”方法能代理所有的类型的对象

这里默认这些类都实现了某个特定的接口,用接口做代理,而不用特定的类


动态代理:连类名都可以全部隐藏,因为类信息是动态生成的(动态编译 jdk Compiler api ; CGLib ; ASM)

一个例子

申明一个类:把类的代码全部拷贝(包括引用和包名)作为一个字符串加入类中;特殊字符要转义

写法:

//项目的根目录+src 路径下的文件目录(写入文件)
String fileName=System.getProperty("user.dir")    
+"/src/com/bjsxt/proxy/TankTimeProxy.java";
File f=new File(fileName);
FileWriter fw=new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();


//动态编译
JavaCompiler compiler =ToolProvider.getSystemJavaCompiler();
System.out.println(compiler.getClass().getName());
StandardJavaFileManager fileMag=compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> units=fileMag.getJavaFileObjects(fileName);

CompilationTask t=compiler.getTask(null, fileMag, null, null, null, units);
t.call();
fileMag.close();


怎么看有没有生成类:show view navigator 会在 目录下生成一个.class 文件


//生成的.class 文件不在bin 目录下,而在src 目录下。这个用之前的loaderclass 是load不了的

//也可以干脆把类生成到bin 目录下。

//载入内存并且创建新实例(注意classloader导入class的时候必须保证在classpath里面,
//在源码路径不在bin目录里,bin里的东西都可以删掉。所以改用urlclassloader,甚至可以从网络上load传过来的class类)
URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src")};
URLClassLoader ul =new URLClassLoader(urls);
Class c=ul.loadClass("com.bjsxt.proxy.TankTimeProxy");
System.out.println(c);

Constructor ctr=c.getConstructor(Movable.class);//拿到参数类型为Movable的构造方法  

//注意区别,如果用c.newInstance() 调用类里参数为空的构造方法,但是这里没有!!!
Movable m=(Movable)ctr.newInstance(new Tank());//得到的构造方法里把被代理对象  new Tank()参数传进去 ,因为实现了Movable 接口,所以可强制转换
m.move();//这里调用的是TankTimeProxy里头的move 在由这个方法里再去调用Tank里的move方法,甚至连名字TankTimeProxy都不重要,随意取


如果要实现任意接口的代理:(把接口类型作为参数传入):

public static Object newProxyInstance(Class intfac)throws Exception{//jdk6 Compiler api,CGLib,ASM 
String methodStr ="";
String rt="\n\t";

//我在想md 是不是可以换成m.getName();
for (Method m : methods) {
methodStr+="@Override"+rt+"public void "+m.getName()+"(){"+rt+
"long start =System.currentTimeMillis();"+rt+
"t."+m.getName()+"();"+rt+
"long end=System.currentTimeMillis();"+rt+
"System.out.println(\"time:\"+(end-start));"+rt+
"}";
}
//动态编译这段代码,然后把生成的类加入内存
String src=
"package com.bjsxt.proxy;"+rt+
"import java.lang.reflect.Method;"+rt+
"public class TankTimeProxy implements "+intfac.getName()+" {"+
"public TankTimeProxy(Movable t) {"+
"super();"+
"this.t = t;"+
"}"+

"Movable t;"+
methodStr+
"}";

//项目的根目录+src 路径下的文件目录(写入文件)
String fileName=System.getProperty("user.dir")
+"/src/com/bjsxt/proxy/TankTimeProxy.java";// 避免loadclass混淆这里路径也要改
File f=new File(fileName);
FileWriter fw=new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();

//动态编译
JavaCompiler compiler =ToolProvider.getSystemJavaCompiler();
System.out.println(compiler.getClass().getName());
StandardJavaFileManager fileMag=compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> units=fileMag.getJavaFileObjects(fileName);

CompilationTask t=compiler.getTask(null, fileMag, null, null, null, units);
t.call();
fileMag.close();

//载入内存并且创建新实例(注意classloader导入class的时候必须保证在classpath里面,
//在源码路径不在bin目录里,bin里的东西都可以删掉。所以改用urlclassloader,甚至可以从网络上load传过来的class类)
URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src")};//如果要loadclass避免混淆,这里路径要改,写法上也要注意,路径文件夹加上/
URLClassLoader ul =new URLClassLoader(urls);
Class c=ul.loadClass("com.bjsxt.proxy.TankTimeProxy");
System.out.println(c);


Constructor ctr=c.getConstructor(Movable.class);
Object m=ctr.newInstance(new Tank);

return m;

main 方法里写:

Movable m = (Movable) Proxy.newProxyInstance(Movable.class);
m.move();




更进一步,要求连“包括”方法 的内容,也交由用户去指定:

申明一个handler 接口,里面有一个申明方法:void revoke (Object o,Method m)// 对哪个对象调用的m方法,把对象也传进去

子类handler 实现接口,

private Object target;//这里写上被代理对象

public TimeHandler(Object target) {
super();
this.target = target;
}

@Override
public void invoke(Object o,Method m) {

long start =System.currentTimeMillis();
System.out.println("starttime:"+start);
System.out.println(o.getClass().getName());
try {
m.invoke(target);//系统函数固定写法,函数类型对象调用。相当于调用被代理对象里的m方法;
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end =System.currentTimeMillis();
System.out.println("time:"+(end-start));

}

新的newProxyInstance

public static Object newProxyInstance(Class intfac,InvocationHandler h)throws Exception{//jdk6 Compiler api,CGLib,ASM 

String methodStr ="";
String rt="\n\t";

//我在想md 是不是可以换成m.getName();
Method[] methods=intfac.getMethods();
for (Method m : methods) {
methodStr+="@Override"+rt+"public void "+m.getName()+"(){"+rt+
"try{"+rt+
"Method md="+intfac.getName()+".class.getMethod(\""+m.getName()+"\");"+rt+
"h.invoke(this,md);"+rt+
"}catch(Exception e){e.printStackTrace();}"+rt+
"}";
}
//动态编译这段代码,然后把生成的类加入内存
String src=
"package com.bjsxt.proxy;"+rt+
"import java.lang.reflect.Method;"+rt+
"public class TankTimeProxy implements "+intfac.getName()+" {"+
"public TankTimeProxy(InvocationHandler h) {"+
"super();"+
"this.h = h;"+
"}"+

"com.bjsxt.proxy.InvocationHandler h;"+rt+

methodStr+

"}";

//项目的根目录+src 路径下的文件目录(写入文件)
String fileName=System.getProperty("user.dir")
+"/src/com/bjsxt/proxy/TankTimeProxy.java";
File f=new File(fileName);
FileWriter fw=new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();

//动态编译
JavaCompiler compiler =ToolProvider.getSystemJavaCompiler();
System.out.println(compiler.getClass().getName());
StandardJavaFileManager fileMag=compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> units=fileMag.getJavaFileObjects(fileName);

CompilationTask t=compiler.getTask(null, fileMag, null, null, null, units);
t.call();
fileMag.close();

//载入内存并且创建新实例(注意classloader导入class的时候必须保证在classpath里面,
//在源码路径不在bin目录里,bin里的东西都可以删掉。所以改用urlclassloader,甚至可以从网络上load传过来的class类)
URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src")};
URLClassLoader ul =new URLClassLoader(urls);
Class c=ul.loadClass("com.bjsxt.proxy.TankTimeProxy");
System.out.println(c);

Constructor ctr=c.getConstructor(InvocationHandler.class);  //得到参数为movable的构造方法
Object m=(Object)ctr.newInstance(h);
return m;

}


main 方法里:

//Movable m = (Movable) Proxy.newProxyInstance(Movable.class,new TimeHandler());

//m.move();  //调用invokationHandler  然后在调用子类的实现方法

换成终板:

Tank t=new Tank();
InvocationHandler h=new TimeHandler(t);//因为构造方法赋值了
Movable m = (Movable) Proxy.newProxyInstance(Movable.class,h);
m.move();



再来一个项目,重用上面的invokationhandler 接口 和Proxy 类

client:

package com.bjsxt.proxy.test;

import com.bjsxt.proxy.InvocationHandler;
import com.bjsxt.proxy.Proxy;

public class Client {
public static void main(String[] args) throws Exception {
UserMgr mgr=new UserMgrImpl();
InvocationHandler h=new TransactionHandler(mgr);
//好奇怪哦,这个动态代理一定要提前先生成TankTimeProxy.java文件才可以运行
UserMgr u=(UserMgr) Proxy.newProxyInstance(UserMgr.class, h);
u.addUser();
}
}


TransactionHandler

package com.bjsxt.proxy.test;

import java.lang.reflect.Method;
import com.bjsxt.proxy.InvocationHandler;

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("Transaction Start");
try {
m.invoke(target);
}catch(Exception e){
e.printStackTrace();
}
System.out.println("Transaction submit");
}

}


UserMgr 接口

package com.bjsxt.proxy.test;

public interface UserMgr {
void addUser();
}


UserImpl 实现接口

package com.bjsxt.proxy.test;

public class UserMgrImpl implements UserMgr {
@Override
public void addUser() {
System.out.println("1:插入记录到user表");
System.out.println("2:做日志,插入另一张表");
}

}




相关推荐:Java代理模式详解,静态代理和动态代理的实现

Java代理模式 使用代理模式创建代理对象,让代理对象控制目标对象的访问,并且在不改变目标对象的情况下添加一些额外的功能,感觉跟Java中的持有对象类似,从设计模式上说应该Java中有2中获取其他类功能的方法1、持有对方的对象(个人感觉就是静态代理)2、继
















































































原文

动态代理的工作是: 在一段程序的外面在包上一段其他程序,这样就会比较方便 这里要注意,是指的是在仅仅是方法外层包上一层其他方法,而不是抱在jdk把方法实例化后再包上其他方法 如果实例化后

------分隔线----------------------------