代理模式:指为其他对象提供一种代理来控制对这个对象的访问。
代理模式主要有两个目的:
①保护目标对象;
②增强目标对象。
它的类图如下:
举个栗子:儿子正在找对象,而父母希望儿女早点找到另一半,于是在儿子找对象的同时帮他物色:
interface Person{
void findLove();
}
public class Son implements Person {
public void findLove() {
System.out.println("儿子要求:是个女的");
}
}
父亲帮找:
public class Father {
private Son son;
public Father(Son son){
this.son = son;
}
public void findLove(){
System.out.println("父亲帮找");
this.son.findLove();
}
}
Client类:
public class Client {
public static void main(String[] args) {
Father father = new Father(new Son());
father.findLove();
}
}
运行:
我们现在想让父亲帮表妹也找找,此时代码就需要修改Father类,不符合开闭原则,所以做如下优化:
public class Father {
private Person person;
public Father(Person person){
this.person = person;
}
public void findLove(){
System.out.println("父亲帮找");
this.person.findLove();
}
}
当Father中的Son替换为Person时,我们就可以代理所有实现了Person接口的类,表妹也是人,所以只需要增加一个表妹类,Client传参时传入new BiaoMei()就可以了,此时的代码对修改关闭,对扩展开放,符合开闭原则。
如果父亲不仅要帮儿子找对象,也要帮很多人找对象,也就是媒婆,我们不可能创建上百个想表妹一样的类,所以有没有一种更加通用的解决方案呢?也许动态代理可以帮助我们。
创建消费者类Customer(在媒婆处消费):
interface Person{
void findLove();
}
public class Customer implements Person {
public void findLove() {
System.out.println("女的就行");
}
}
创建媒婆类(Meipo):
public class Meipo implements InvocationHandler {
//被代理的对象,保存起来方便invoke中的this.target的调用
private Object target;
public Object getInstance(Object target){
this.target = target;
Class> aClass = target.getClass();
//创建代理类并返回
return Proxy.newProxyInstance(aClass.getClassLoader(), aClass.getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
befor();
Object invoke = method.invoke(this.target, args);
after();
return invoke;
}
private void befor(){
System.out.println("媒婆帮找");
}
private void after(){}
}
创建Client类:
public class Client {
public static void main(String[] args) {
Person person = (Person) new Meipo().getInstance(new Customer());
person.findLove();
}
}
运行:
那么动态代理是如何实现的呢?
她其实就是通过字节重组,重新生成对象来代替原始对象,以达到代理的目的。
字节码重组的基本步骤如下:
①获取被代理对象的引用,利用反射获取到它的所有接口;
②JDK动态代理类Proxy重新生成一个新的类,此类要实现刚才获取到的所有接口;
③动态生成新类的Java代码;
④编译.java文件成.class文件;
⑤加载编译好的.class文件。
那么修改Client类,去查看一下生成好的.class文件:
public class Test {
public static void main(String[] args) {
try {
Person person = (Person) new Meipo().getInstance(new Customer());
person.findLove();
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
FileOutputStream fos = new FileOutputStream(new File("$Proxy0.class"));
fos.write(bytes);
fos.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
运行:
查看$Proxy0.class($开头的类是动态生成的):
public final class $Proxy0 extends Proxy implements Person {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
......
public final void findLove() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
......
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("cn.xupt.design_pattern.structure.proxy.动态代理.JDK.Person").getMethod("findLove");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
此类实现了Person接口、继承了Proxy类,并在findlove()中调用了父类Proxy的h.invoke(this, m3, (Object[])null),这个h的类型就是InvocationHandler。之后我们就是使用的这个类的对象去调用findLove()方法的。
了解JDK的原理后来手写它的实现:
创建Person接口:
public interface Person {
void findLove();
}
创建Customer类:
public class Customer implements Person{
public void findLove() {
System.out.println("女的就行");
}
}
创建MyInvocationHandler接口:
public interface MyInvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
自定义类加载器MyClassLoader:
public class MyClassLoader extends ClassLoader {
private File classPathFile;
public MyClassLoader(){
//class文件路径
String classpath = MyClassLoader.class.getResource("").getPath();
this.classPathFile = new File(classpath);
}
/**
* 寻找需要加载的class文件并返回类对象
* @param name :类名称
* @return :找到的Class对象
* @throws ClassNotFoundException
*/
protected Class> findClass(String name) throws ClassNotFoundException {
String className = MyClassLoader.class.getPackage().getName() + "." + name;
if(null != classPathFile){
File classFile = new File(classPathFile, name + ".class");
if(classFile.exists()){
FileInputStream fis = null;
ByteArrayOutputStream baos = null;
try{
fis = new FileInputStream(classFile);
baos = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len;
while((len = fis.read(buff)) != -1){
baos.write(buff, 0, len);
}
return defineClass(className, baos.toByteArray(), 0, baos.size());
}catch (Exception e){
e.printStackTrace();
}finally {
if(null != fis){
try {
if (null != fis) {
fis.close();
}
if (null != baos) {
baos.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}
return null;
}
}
自定义Proxy类MyProxy:
package cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy;
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;
import java.util.HashMap;
import java.util.Map;
public class MyProxy {
public static final String ln = "\r\n";
public static Object newMyProxyInstance(MyClassLoader classLoader, Class>[] interfaces, MyInvocationHandler handler){
try {
String src = generateSrc(interfaces);
//将Java文件输出到磁盘上
String filePath = MyProxy.class.getResource("").getPath() + "$Proxy0.java";
File file = new File(filePath);
FileWriter fw = new FileWriter(file);
fw.write(src);
fw.flush();
fw.close();
//编译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();
//加载类
Class proxyClass = classLoader.findClass("$Proxy0");
Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
file.delete();
return constructor.newInstance(handler);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 构造如下类
* package cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy;
* import cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy.Person;
* import java.long.reflect.*;
*
* public class $Proxy0 implements Person;
* MyInvocationHandler handler;
* public $Proxy0(MyInvocationHandler handler) {
* this.handler = handler;
* }
*
* public int createOrder(Order order){
* try{
* Method method = interfaces[0].getMethod("createOrder", new Class[]{Order.class});
* return ((Integer) this.handler.invoke(this, m, new Object[]{order})).intValue();
* }catch(Error _ex){}
* catch(Throwable e){
* throw new UndeclaredThrowableException(e);
* }
* return 0;
* }
* }
*
* @param interfaces
* @return String 返回创建好的Java源代码
*/
private static String generateSrc(Class>[] interfaces){
StringBuffer sb = new StringBuffer();
sb.append("package cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy;" + ln);
sb.append("import cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy.Person;" + ln);
sb.append("import java.lang.reflect.*;" + ln);
sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
sb.append("MyInvocationHandler handler;" + ln);
sb.append("public $Proxy0(MyInvocationHandler handler) {" + ln);
sb.append("this.handler = handler;");
sb.append("}" + ln);
for(Method m:interfaces[0].getMethods()){
//获得每个方法的形式参数
Class>[] params = m.getParameterTypes();
StringBuffer paramNames = new StringBuffer();
StringBuffer paramValus = new StringBuffer();
StringBuffer paramClasses = new StringBuffer();
for (int i = 0; i < params.length; i++) {
Class clazz = params[i];
String type = clazz.getName();
//将类写名称第一个字母小写 order
String paramName = toLowerFirstCase(clazz.getSimpleName());
//cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy.Order order
paramNames.append(type + " " + paramName);
paramValus.append(paramName);
//cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy.Order.class
paramClasses.append(clazz.getName() + ".class");
if (i > 0 && i < params.length - 1) {
paramNames.append(",");
paramClasses.append(",");
paramValus.append(",");
}
}
//构造各个方法
sb.append("public " + m.getReturnType().getName() + " " + m.getName() + " " + "(" +
paramNames.toString() + "){" + ln);
sb.append("try{" + ln);
sb.append("Method method = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName()
+ "\", new Class[]{" + paramClasses.toString() + "});" + ln);
sb.append((hasReturnValue(m.getReturnType()) ? "return " : "") + getCaseCode(
"this.handler.invoke(this, method, new Object[]{" + paramValus + "})", m.getReturnType()) + ";");
sb.append("}catch(Error _ex){}");
sb.append("catch(Throwable e){" + ln);
sb.append("throw new UndeclaredThrowableException(e);" + ln);
sb.append("}");
sb.append(getReturnEmptyCode(m.getReturnType()));
sb.append("}");
}
sb.append("}" + ln);
return sb.toString();
}
private static Map mappings = new HashMap();
static {
mappings.put(int.class, Integer.class);
}
private static String getReturnEmptyCode(Class> returnClass){
if(mappings.containsKey(returnClass)){
return "return 0";
}
else if(returnClass == void.class){
return "";
}
else {
return "return null";
}
}
private static String getCaseCode(String code, Class> returnClass){
if(mappings.containsKey(returnClass)) {
return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
}
return code;
}
/**
* @param clazz
* @return true 不是void类型
* @return false 是vodid类型
*/
private static boolean hasReturnValue(Class> clazz){
return clazz != void.class;
}
private static String toLowerFirstCase(String src){
char[] chars = src.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}
创建媒婆类MyMeipo:
public class MyMeipo implements MyInvocationHandler {
private Object target;
public Object getInstance(Object target){
this.target = target;
Class> aClass = target.getClass();
return MyProxy.newMyProxyInstance(new MyClassLoader(), aClass.getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
befor();
Object invoke = method.invoke(this.target, args);
after();
return invoke;
}
private void befor(){
System.out.println("我是媒婆");
}
private void after(){
System.out.println("找到了!");
}
}
创建Client类:
public class Test {
public static void main(String[] args) {
Person person = (Person) new MyMeipo().getInstance(new Customer());
person.findLove();
}
}
运行:
有一些遇到的问题我放在下面,以免老哥们踩坑:
1. 遇到FileNotFoundException?
解决:路径不能带中文,带中文会出错。
2. 遇到NoClassDefFoundError?
解决:jar包没有导入tools.jar,如何导入请看警告: Can't initialize javac processor due to (most likely) a class loader problem: java.lang.NoClassD_Hello_xzy_Word的博客-CSDN博客
3. 遇到Exception in thread "main" java.lang.IllegalAccessError: class cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy.$Proxy0 cannot access its superinterface cn.xupt.design_pattern.structure.proxy.handwriting_dynamic_proxy.Person?
解决:interface Person应加上public(单独写一个接口)。
还是以找对象为栗子:
创建CgMeipo类:
public class CgMeipo implements MethodInterceptor {
public Object getInstance(Class> clazz){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object obj = methodProxy.invokeSuper(o, objects);
after();
return obj;
}
private void before(){
System.out.println("我是媒婆");
}
private void after(){
}
}
创建Customer类(由于Cglib是通过动态继承被代理者来实现动态代理的,所以不需要实现Person接口):
public class Customer {
public void findLove(){
System.out.println("女的就行");
}
}
创建Client类:
public class Test {
public static void main(String[] args) {
try {
Customer customer = (Customer) new CgMeipo().getInstance(Customer.class);
customer.findLove();
}catch (Exception e){
e.printStackTrace();
}
}
}
运行:
我们可以在Client类中加入:
public class Test {
public static void main(String[] args) {
try {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cglib_classes/");
Customer customer = (Customer) new CgMeipo().getInstance(Customer.class);
customer.findLove();
}catch (Exception e){
e.printStackTrace();
}
}
}
将生成的.class文件输出到磁盘中,运行后:
发现有一个类继承了Customer,那说明这个类就是我们生成的代理类了:
public class Customer$$EnhancerByCGLIB$$b0a90fbe extends Customer implements Factory {
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$findLove$0$Method;
private static final MethodProxy CGLIB$findLove$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$finalize$1$Method;
private static final MethodProxy CGLIB$finalize$1$Proxy;
private static final Method CGLIB$equals$2$Method;
private static final MethodProxy CGLIB$equals$2$Proxy;
private static final Method CGLIB$toString$3$Method;
private static final MethodProxy CGLIB$toString$3$Proxy;
private static final Method CGLIB$hashCode$4$Method;
private static final MethodProxy CGLIB$hashCode$4$Proxy;
private static final Method CGLIB$clone$5$Method;
private static final MethodProxy CGLIB$clone$5$Proxy;
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("cn.xupt.design_pattern.structure.proxy.cglib_proxy.Customer$$EnhancerByCGLIB$$b0a90fbe");
Class var1;
Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$finalize$1$Method = var10000[0];
CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
CGLIB$equals$2$Method = var10000[1];
CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
CGLIB$toString$3$Method = var10000[2];
CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
CGLIB$hashCode$4$Method = var10000[3];
CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
CGLIB$clone$5$Method = var10000[4];
CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
CGLIB$findLove$0$Method = ReflectUtils.findMethods(new String[]{"findLove", "()V"}, (var1 = Class.forName("cn.xupt.design_pattern.structure.proxy.cglib_proxy.Customer")).getDeclaredMethods())[0];
CGLIB$findLove$0$Proxy = MethodProxy.create(var1, var0, "()V", "findLove", "CGLIB$findLove$0");
}
final void CGLIB$findLove$0() {
super.findLove();
}
public final void findLove() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$findLove$0$Method, CGLIB$emptyArgs, CGLIB$findLove$0$Proxy);
} else {
super.findLove();
}
}
......
可以发现,此类重写了父类Customer的所有方法,并且每一个方法都有MethodProxy与之对应,如findLove()的CGLIB$clone$5$Proxy,这些在findLove()方法中都有调用,如:
var10000.intercept(this, CGLIB$findLove$0$Method, CGLIB$emptyArgs, CGLIB$findLove$0$Proxy);
此时可以分析出cglib的调用流程了:
代理对象调用this.findLove() ---> 调用拦截器(此时执行before和after) ---> methodProxy.invokeSuper()方法 ---> CGLIB$findLove$0 ---> 被代理者的findLove()方法
那methodProxy是如何通过invokeSuper来获取代理方法的呢?点开invokeSuper():
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
......
private static class FastClassInfo {
FastClass f1;
FastClass f2;
int i1;
int i2;
private FastClassInfo() {
}
}
我们发现invokeSuper()调用了fci.f2.invoke(fci.f2, obj, args),点开invoke进入到FastClass类:
public abstract class FastClass {
......
public abstract Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException;
是一个抽象类,还记得前面生成的三个.class文件吗?它们其实就是分别为代理类和被代理类生成的FastClass。
点开一个:
public class Customer$$EnhancerByCGLIB$$b0a90fbe$$FastClassByCGLIB$$6a538f19 extends FastClass {
......
发现它继承了FastClass,并实现了invoke:
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
b0a90fbe var10000 = (b0a90fbe)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
return new Boolean(var10000.equals(var3[0]));
case 1:
return var10000.toString();
case 2:
return new Integer(var10000.hashCode());
case 3:
return var10000.newInstance((Callback)var3[0]);
case 4:
return var10000.newInstance((Callback[])var3[0]);
case 5:
return var10000.newInstance((Class[])var3[0], (Object[])var3[1], (Callback[])var3[2]);
case 6:
var10000.setCallback(((Number)var3[0]).intValue(), (Callback)var3[1]);
return null;
case 7:
var10000.findLove();
return null;
case 8:
......
从中看出方法的区分是通过第一个参数var1来进行的,这个参数其实就是FastClass机制生成的,它为代理类和被代理类各生成一个类,这个类会为代理类或被代理类的方法分配一个index(int),这个index当作一个入参,FastClass就可以直接定位方法,而不需要使用反射来获取,所以效率会更高。这个index生成在getIndex()中(getIndex在生成的两个类中,FastClass中的是抽象方法):
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -2055565910:
if (var10000.equals("CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
return 11;
}
break;
case -1725733088:
if (var10000.equals("getClass()Ljava/lang/Class;")) {
return 24;
}
break;
case -1457535688:
if (var10000.equals("CGLIB$STATICHOOK1()V")) {
return 20;
}
break;
......
FastClass并不是和代理类一起生成的,而是在第一次执行invokeSuper时生成并放入缓存内。
工作感悟:
2023/3/8:今天在修改一部分功能时,修改了持久化的一部分内容,是关于Exception的,这个Exception具体使用地方是使用了它的toString,但是我想让它使用getMessage代替toString,因为toString会打印出来类路径,我不想又类路径,但是如果我直接修改它的使用地方,因为Exception是一个很抽象的概念,使用它的地方也是抽象的,修改后肯定会有很大影响。所以我在我的持久化内容这里使用了动态代理,让只有我持久化生成的Exception代理类使用toString时改成getMessage。
其实上面的使用时有问题的,因为jdk的动态代理只能代理接口,而cglib又依赖不到,所以最后是用了ExceptionWrapperToString继承了Exception,重写toString,这样写了发现我的场景也没问题,所以也不需要用代理,以上是由工作联想的思考,故记录下来。
感谢观看0.0