1、关于静态代理和动态代理的区别
静态代理:
由程序员事先生成源代码再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理类:
在程序运行时,运用 Java 反射机制动态创建而成。
两者比较:
- 静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
- 静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
- 动态代理是实现JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过Proxy里的newProxyInstance得到代理对象。
- 还有一种动态代理CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。
2、JDK 动态代理的实现
- Person.java 接口类
package com.leitan.architect.pattern.proxy.sta;
/**
* @Author: tan.lei
* @Date: 2018-10-17 14:45
*/
public interface Person {
void findPartner();
void findHouse();
void findJob();
void shopping();
}
- Xiaoming.java 被代理类
package com.leitan.architect.pattern.proxy.jdk;
import com.leitan.architect.pattern.proxy.sta.Person;
/**
* @Author: tan.lei
* @Date: 2018-10-17 14:52
*/
public class Xiaoming implements Person {
@Override
public void findPartner() {
System.out.println("需要肤白、貌美、大长腿的伴侣....");
}
@Override
public void findHouse() {
System.out.println("需要环境安静的房子");
}
@Override
public void findJob() {
System.out.println("需要高薪的工作");
}
@Override
public void shopping() {
System.out.println("需要160m²的海景别墅");
}
}
- MyInvocationHandler.java 处理接口,类似参考 JDK 的 InvocationHandler
package com.leitan.architect.pattern.proxy.mine;
import java.lang.reflect.Method;
/**
* @Author: tan.lei
* @Date: 2018-10-17 16:49
*/
public interface MyInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
- MyMeipo.java 媒婆的实现类
package com.leitan.architect.pattern.proxy.mine;
import com.leitan.architect.pattern.proxy.sta.Person;
import java.lang.reflect.Method;
/**
* @Author: tan.lei
* @Date: 2018-10-18 10:09
*/
public class MyMeipo implements MyInvocationHandler {
private Person target;// 被代理的对象,把引用保存下来
public Object getInstance(Person target) throws Exception {
this.target = target;
Class<?> clazz = target.getClass();
// 用来生成一个新对象(字节码重组来实现)
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.target, args);// 目标类的方法调用,需要找媳妇的条件
System.out.println("如果找到符合要求的就安排给你!");
return null;
}
}
根据媒婆的要求,目前还需要
MyProxy.java
需要动态生成的代理类
MyClassLoader.java
自定义的类加载器
- MyProxy.java 代理类
package com.leitan.architect.pattern.proxy.mine;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* @Author: tan.lei
* @Date: 2018-10-17 16:48
*/
public class MyProxy {
private static final String LN = "\r\n";// 换行
/**
* 获取动态代理类
*
* @param classLoader 自定义的类加载器
* @param interfaces 被代理的目标接口
* @param handler 实际的业务处理实现
* @return
*/
public static Object newProxyInstance(MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler handler) {
try {
// 1.动态生成源代码.java文件
String src = generateSrc(interfaces);
// 2.Java文件输出到磁盘
String filePath = MyProxy.class.getResource("").getPath();
System.out.println("FilePath = " + filePath);
File f = new File(filePath + "$Proxy0.java");
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
// 3.把.java文件编译成.class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
Iterable iterable = manager.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
task.call();
manager.close();
// 4.把.class加载到JVM中
Class proxyClass = classLoader.findClass("$Proxy0");
Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
f.delete();// 编译完成首删除.java原文件
// 5.返回字节码重组后新生成的对象
return constructor.newInstance(handler);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 拼接需要动态生成代理类的字符串内容
*
* @param interfaces 被代理类的接口
* @return
*/
private static String generateSrc(Class<?>[] interfaces) {
StringBuffer sb = new StringBuffer();
sb.append("package com.leitan.architect.pattern.proxy.mine;" + LN);
sb.append(LN);
sb.append("import " + interfaces[0].getName() + ";" + LN);
sb.append("import java.lang.reflect.Method;" + LN);
sb.append(LN);
sb.append("public class $Proxy0 implements " + interfaces[0].getSimpleName() + " {" + LN);
sb.append(LN);
sb.append("public MyInvocationHandler handler;" + LN);
sb.append(LN);
sb.append("public $Proxy0(MyInvocationHandler handler) {" + LN);
sb.append("this.handler = handler;" + LN);
sb.append("}" + LN);
sb.append(LN);
for (Method method : interfaces[0].getMethods()) {
sb.append("public " + method.getReturnType().getName() + " " + method.getName() + "() {" + LN);
sb.append("try {" + LN);
sb.append("Method m = " + interfaces[0].getSimpleName() + ".class.getMethod(\"" + method.getName() + "\", new Class[]{});" + LN);
sb.append("this.handler.invoke(this, m, null);" + LN);
sb.append("} catch(Throwable e) { " + LN);
sb.append("e.printStackTrace();" + LN);
sb.append("}" + LN);
sb.append("}" + LN);
sb.append(LN);
}
sb.append("}" + LN);
return sb.toString();
}
}
- MyClassLoader.java 自定义类加载器
package com.leitan.architect.pattern.proxy.mine;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/**
* @Author: tan.lei
* @Date: 2018-10-17 16:50
*/
public class MyClassLoader extends ClassLoader {
private File classPathFile;// class的存放路径
public MyClassLoader() {
String classPath = MyClassLoader.class.getResource("").getPath();
this.classPathFile = new File(classPath);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> resClass = null;
if (classPathFile != null) {
File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class");
if (classFile.exists()) {
FileInputStream in = null;
ByteOutputStream out = null;// 把class转换成字节码写到JVM中
try {
in = new FileInputStream(classFile);
out = new ByteOutputStream();
byte[] buff = new byte[1024];
int len;
while ((len = in.read(buff)) != -1) out.write(buff, 0, len);
String className = MyClassLoader.class.getPackage().getName() + "." + name;
resClass = defineClass(className, out.getBytes(), 0, out.size());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (in != null) in.close();
if (out != null) out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return resClass;
}
}
- MyProxyTest.java 测试类
package com.leitan.architect.pattern.proxy.mine;
import com.leitan.architect.pattern.proxy.jdk.Xiaoming;
import com.leitan.architect.pattern.proxy.sta.Person;
/**
* @Author: tan.lei
* @Date: 2018-10-18 10:07
*/
public class MyProxyTest {
public static void main(String[] args) {
try {
Person obj = (Person) new MyMeipo().getInstance(new Xiaoming());
System.out.println(obj.getClass());// 这个类是字节码动态生成的
// 你在这里看似调用目标类的方法,实际上是调用动态生成的代理类方法,目标类的方法已经经过了动态类的逻辑包装
obj.findPartner();
//obj.findHouse();
//obj.findJob();
//obj.shopping();
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果如下:
FilePath = /Users/leitan/Worker/IdeaSpace/architect/target/classes/com/leitan/architect/pattern/proxy/mine/
class com.leitan.architect.pattern.proxy.mine.$Proxy0
媒婆:请给我你的需求,开始帮你物色…
需要肤白、貌美、大长腿的伴侣…
如果找到符合要求的就安排给你!Process finished with exit code 0
- 根据上面的接口已经实现了找媳妇的动态代理,这里再把动态生成的代理类的源码贴出一下,能够看得更加清楚明白
package com.leitan.architect.pattern.proxy.mine;
import com.leitan.architect.pattern.proxy.sta.Person;
import java.lang.reflect.Method;
public class $Proxy0 implements Person {
public MyInvocationHandler handler;
public $Proxy0(MyInvocationHandler handler) {
this.handler = handler;
}
public void findHouse() {
try {
Method m = Person.class.getMethod("findHouse", new Class[]{});
this.handler.invoke(this, m, null);
} catch (Throwable e) {
e.printStackTrace();
}
}
public void shopping() {
try {
Method m = Person.class.getMethod("shoping", new Class[]{});
this.handler.invoke(this, m, null);
} catch (Throwable e) {
e.printStackTrace();
}
}
public void findJob() {
try {
Method m = Person.class.getMethod("findJob", new Class[]{});
this.handler.invoke(this, m, null);
} catch (Throwable e) {
e.printStackTrace();
}
}
public void findPartner() {
try {
Method m = Person.class.getMethod("findPartner", new Class[]{});
this.handler.invoke(this, m, null);
} catch (Throwable e) {
e.printStackTrace();
}
}
}