使用场景,对已有类、或接口的方法进行扩展,比如前后添加日志代码。
比如已有如下接口
public interface IProduct {
void produce();
}
public class FoodProduct implements IProduct {
@Override
public void produce() {
System.out.println("FoodProduct");
}
}
方式一 利用继承
public class LogProduct extends FoodProduct {
@Override
public void produce() {
System.out.println("produce before");
super.produce();
System.out.println("produce after");
}
}
方式二 利用聚合,即静态代理
实现被代理类的接口,并引用被代理类,在代理类中,调用被代理类的方法即可
public class LogProxy implements IProduct {
private IProduct mProduct;
public LogProxy(IProduct product) {
this.mProduct = product;
}
@Override
public void produce() {
System.out.println("produce before");
mProduct.produce();
System.out.println("produce after");
}
}
下面比较下,继承和聚合方式的优缺点
有个需求,需要有2个代理类,分别是日志和时间,但两个代理的先后顺序可以变化。
继承,需要让日志和时间组件相互继承,来保证代码的先后执行顺序,即需要无限增加继承类。
public class TimeProduct extends LogProduct {
@Override
public void produce() {
long start = System.currentTimeMillis();
super.produce();
long stop = System.currentTimeMillis();
System.out.println("produce time " + (stop - start));
}
}
静态代理,只需要将接口作为构造参数即可,再实现时可以任意更改顺序。
public class TimeProxy implements IProduct {
private IProduct mProduct;
public TimeProxy(IProduct product) {
this.mProduct = product;
}
@Override
public void produce() {
long start = System.currentTimeMillis();
mProduct.produce();
long stop = System.currentTimeMillis();
System.out.println("produce time " + (stop - start));
}
}
看下调用方式
public class ProductClient {
public static void main(String[] args) {
IProduct logProxy = new LogProxy(new FoodProduct());
IProduct timeProxy = new TimeProxy(logProxy);
timeProxy.produce();
// 也可以更改LogProxy和TimeProxy顺序
// IProduct timeProxy = new TimeProxy(new FoodProduct());
// IProduct logProxy = new LogProxy(timeProxy);
// timeProxy.produce();
}
}
方式三 动态代理
为什么需要有动态代理,由上面的静态代理,只能完成特定接口的,特定方法,如果需要对其他接口、或方法做代理,则需要增加新的静态代理类,会导致代理类无线膨胀,不利于代码的通用和扩展。
比如还有如下接口
public interface IAnimal {
void eat();
}
public class DogAnimal implements IAnimal {
@Override
public void eat() {
System.out.println("Dog eats");
}
}
如果使用静态代理,则需要新增日志代理类
public class LogAnimalProxy implements IAnimal {
private IAnimal mAnimal;
public LogAnimalProxy(IAnimal animal) {
this.mAnimal = animal;
}
@Override
public void eat() {
System.out.println("eat before");
mAnimal.eat();
System.out.println("produce after");
}
}
动态代理,要求所有的被代理类,必须实现了一个接口。
使用Java动态代理代码如下:
通过定义InvocationHandler的子类对象,来实现对被代理类的统一处理,如对所有代理类添加日志等。
public class Main {
public static void main(String[] args) {
IProduct product = new Product();
IProduct productProxy = (IProduct) Proxy.newProxyInstance(product.getClass().getClassLoader(),
IProduct.class, new ProductInvocationHandler(product));
productProxy.produce();
}
}
四、Java动态代理的实现流程
会用到Java的反射机制,参考 juejin.cn/post/684490…
第一步:动态生成代理类的Java代码
(1) 使用字符串编写Java代码结构。
(2) 创建.java文件
(3) 将字符串代码写入到.java文件中
第二步:编译代理类的Java代码
(1) 设置编译后的.class文件目录
(2) 利用JavaCompiler编译
第三步:将.class加载到ClassLoader中,并创建代理对象
(1) 使用ClassLoader或URLClassLoader加载.class
(2) 使用getConstructor()方法,获取构造函数对象
(3) 利用Constructor对象,使用newInstance()创建代理对象
下面看具体代码:
public class Proxy {
private static final String rt = "\r\n";
private static final String proxyNm = "$Proxy1";
/**
*
* @param classLoader 被代理类的ClassLoader
* @param interfaces 被代理类实现的接口
* @param h 在生成的代理类中回调该接口的invoke方法,方便使用者,实现自己的代理方法。
* @return
*/
public static Object newProxyInstance(ClassLoader classLoader, Class<?> interfaces, InvocationHandler h) {
/**********第一步*********************
* 动态生成代理类的Java代码
* (1) 使用字符串编写Java代码结构。
* (2) 创建.java文件
* (3) 将字符串代码写入到.java文件中
************************************/
// 接口里的方法默认都是public abstract,所以继承时,只需要使用public即可
// TODO 方法返回类型和参数类型的设置
String methodStr = "";
Method[] methods = interfaces.getMethods();
for (Method m : methods) {
methodStr = "@Override" + rt
+ "public " + m.getReturnType().toGenericString() + " " + m.getName() + "() {" + rt
+ "try {" + rt
+ "java.lang.reflect.Method md = " + interfaces.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt
+ "h.invoke(this, md, null);" + rt
+ "} catch(Exception e) {" + rt
+ "} catch(Throwable e) {" + rt
+ "}" + rt
+ "}" + rt;
}
String classStr =
"package com.outman.example.proxy;" + rt
+ "public class " + proxyNm + " implements " + interfaces.getName() + " {" + rt
+ "com.outman.example.proxy.InvocationHandler h;" + rt
+ "public " + proxyNm + "(InvocationHandler h) {" + rt
+ "this.h = h;" + rt
+ "}" + rt
+ methodStr
+ "}";
System.out.println("begin generate java file");
String srcRoot = System.getProperty("user.dir");
System.out.print(srcRoot);
String srcPath = srcRoot + "/src/com/outman/example/proxy/" + proxyNm + ".java";
File file = new File(srcPath);
FileWriter writer = null;
try {
writer = new FileWriter(file);
writer.write(classStr);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("finish generate java file");
/**********第二步*********************
* 编译代理类的Java代码
* (1) 设置编译后的.class文件目录
* (2) 利用JavaCompiler编译
************************************/
System.out.println("begin compile java file");
String classOut = srcRoot + "/out/production/JavaTest/";
StandardJavaFileManager fileManager = null;
try {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
System.out.println(compiler);
fileManager = compiler.getStandardFileManager(null, null, null);
JavaFileManager.Location oLocation = StandardLocation.CLASS_OUTPUT;
// 设置编译的后的class路径
fileManager.setLocation(oLocation, Arrays.asList(new File[] { new File(classOut) }));
Iterable iterable = fileManager.getJavaFileObjects(file);
JavaCompiler.CompilationTask compilationTask = compiler.getTask(null, fileManager, null, null, null, iterable);
compilationTask.call();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileManager != null) {
try {
fileManager.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("finish compile java file");
/**********第三步*********************
* 将.class加载到ClassLoader中,并创建代理对象
* (1) 使用ClassLoader或URLClassLoader加载.class
* (2) 使用getConstructor()方法,获取构造函数对象
* (3) 利用Constructor对象,使用newInstance()创建代理对象
************************************/
try {
if (classLoader != null) {
System.out.println("begin load java class");
// URL[] urls = new URL[] {new URL("file:/" + classOut)};
// URLClassLoader urlClassLoader = new URLClassLoader(urls);
// Class clz = urlClassLoader.loadClass("com.outman.example.proxy." + proxyNm);
Class clz = classLoader.loadClass("com.outman.example.proxy." + proxyNm);
System.out.println("finish load java class");
Constructor constructor = clz.getConstructor(InvocationHandler.class);
return constructor.newInstance(h);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
下面看下自动生成的代理类的代码
/**
* 生成的代理类
* 和被代理类实现相同的接口
* 有一个InvocationHandler对象,用来回调给使用者
*/
public class $Proxy1 implements com.outman.example.proxy.IProduct {
com.outman.example.proxy.InvocationHandler h;
public $Proxy1(InvocationHandler h) {
this.h = h;
}
/**
* 实现被代理类接口的方法
*/
@Override
public void produce() {
try {
// 获取到被代理类的接口的方法对象
java.lang.reflect.Method md = com.outman.example.proxy.IProduct.class.getMethod("produce");
// 利用传进来的InvocationHandler对象,回调给使用者,代理的方法对象。
// 使用者可以利用回调中的Method对象反射调用该方法
h.invoke(this, md, null);
} catch(Exception e) {
} catch(Throwable e) {
}
}
}