来一个非常好理解的实例来写一个JDK动态代理:假设有一对象有“唱,跳,rap,打篮球”的功能,我们将该对象的功能抽象化为一个接口,假设我们现在需要对该对象打篮球之前和之后做一些动作,比如扭秧歌,掉带等等,这时就对该功能进行代理。
一、首先来一个接口定义该对象有的功能:
public interface MyService {
/**
*这里就演示一个“打篮球”的功能
*/
//有“唱 ”的功能
// void sing();
//有“跳”的功能
//void dance();
//有“rap ”的功能
//void rap();
//有“唱 ”
void basketball();
}
二、来一个类去实现该功能:
public class MyServiceImpl implements MyService {
@Override
public void basketball() {
System.out.println("篮球大师打篮球!");
}
}
三、来一个代理对象来帮我们完成任务:
public class MyHanderImpl implements MyInvocationHander {
private MyService myService;
public Object getInstance(MyService myService)throws Exception{
this.myService = myService;
Class clazz = myService.getClass();
System.out.println("被代理的对象为"+clazz);
return MyProxy.newProxyInstance(new MyClassLoader(),clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("扭秧歌!动起来!");
method.invoke(this.myService,args);
System.out.println("掉白带!扭起来!");
return null;
}
}
四、来一个测试类看看效果
public class MyTest {
public static void main(String[] args) {
try {
MyService service = (MyService) new MyHanderImpl().getInstance(new MyServiceImpl());
System.out.println(service.getClass());
service.basketball();
} catch (Exception e) {
e.printStackTrace();
}
}
}
你以为结束了吗?不,核心代码还没开始呢。会发现在进行代理时是通过代理对象的getInstance来得到对应的需要的代理对象,在该方法里面会发现使用代理类Proxy去创建新的字节码文件,然后将ClassLoad加载器和接口对象和handle传入,通过ClassLoad加载器去将新的字节码文件加载到内存。
五、来一个自实现handle接口
public interface MyInvocationHander {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
六、来一个自定义的代理类Proxy
//生成代理对象代码
public class MyProxy {
private static String LN = "\r\n";
public static Object newProxyInstance(MyClassLoader loader,
Class<?>[] interfaces,
MyInvocationHander h)
throws IllegalArgumentException{
//1.生成源代码
String proxySrc = MygeneralPath(interfaces);
//2.再将生成的源代码写到相关路径生成.java文件
String filePath = MyProxy.class.getResource("").getPath();
File file = new File(filePath+"$Proxy0.java");
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(file);
fileWriter.write(proxySrc);
fileWriter.flush();
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
//3.将.java源文件编译成。class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null,null,null);
Iterable iterator = manager.getJavaFileObjects(file);
JavaCompiler.CompilationTask task = compiler.getTask(null,manager,null,null,null,iterator);
task.call();
manager.close();
//4.再将编译完的字节码动态加载到JVM
Class proxyClass = new MyClassLoader().findClass("$Proxy0.class");
Constructor constructor = proxyClass.getConstructor(MyInvocationHander.class);
//5.将该动态生成的代理对象返回
return constructor.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 使用字符拼接,重新生成代理源文件
* @param interfaces
* @return
*/
private static String MygeneralPath(Class<?>[] interfaces) {
StringBuffer src = new StringBuffer();
src.append("package jdk.proxy.jdk;"+LN);
src.append("import java.lang.reflect.Method;"+LN);
src.append("public class $Proxy0 implements "+interfaces.getClass().getName()+"{"+LN);
src.append("MyInvocationHander h;"+LN);
src.append("public $Proxy0(MyInvocationHander h){"+LN);
src.append("this.h = h;"+LN);
src.append("}"+LN);
for(Method method:interfaces.getClass().getMethods()){
src.append("public "+method.getReturnType().getName()+" "+method.getName()+"{"+LN);
src.append("Method m = "+interfaces.getClass().getName()+".class.getMethod(\""+method.getName()+"\",new Class[]{});"+LN);
src.append("this.h.invoke(this,method,null);"+LN);
src.append("}"+LN);
}
src.append("}"+LN);
return src.toString();
}
}
七、重新生成字节码文件之后,自定义一个类加载器将字节码文件加入到内存:
public class MyClassLoader extends ClassLoader{
private File baseDir;
public MyClassLoader(){
String basePath = MyClassLoader.class.getResource("").getPath();
this.baseDir = new File(basePath);
}
/**
* 判断字节码class文件是否存在,如果存在就将该字节码文件写入到内存
* @param s
* @return
*/
@Override
protected Class<?> findClass(String s) {
String className = MyClassLoader.class.getPackage().getName()+"."+s;
if(baseDir!=null){
File classfile = new File(baseDir,s.replaceAll("\\.","/")+".class");
if(classfile.exists()){
FileInputStream in = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
in = new FileInputStream(classfile);
byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int len;
while((len = in.read(bytes)) != -1){
byteArrayOutputStream.write(bytes,0,len);
}
return defineClass(className,byteArrayOutputStream.toByteArray(),0,byteArrayOutputStream.size());
} catch (IOException e) {
e.printStackTrace();
}finally {
if(in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(byteArrayOutputStream!=null){
try {
byteArrayOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return null;
}
}
ok,全部功能完成,测试类实现功能。简单分析就是,对实现接口的类进行动态代理,主要关键就是对需要代理的方法进行字节码的重组和加载到内存。