共计 5398 个字符,预计需要花费 14 分钟才能阅读完成。
看完上一个章节,相信你已经掌握了一些编写并发代码编写的要领了。今天我们来聊一个新的话题。
猿思考是一个原创系列文章,帮助你从一个小白快速掌握基础知识,很多基础知识,在于思考的变通,更多精彩内容,敬请大家关注公主号 猿人工厂 ,点击 猿人养成获取!
开动你的小脑筋,你怎样才能代替我去卖面膜?先看下面的代码, 卖面膜是一种行为,定义成一个接口,MaiMianMo, 还有一个实现类 MaiMianMoImpl,具体怎么卖面膜由 MaiMianMoImpl 去做。
package com.pz.se.demo; | |
public interface MaiMianMo {public Object maiMianMo(Object obj); | |
} | |
packagecom.pz.se.demo; | |
public class MaiMianMoImpl implements MaiMianMo { | |
@Override | |
public Object maiMianMo(Object obj) {System.out.println("我正在售卖 面米:"+obj); | |
return obj; | |
} | |
} |
嗯,你的意思是代替你去卖面膜,这还不简单,编写一个类实现 MaiMianMo 接口,代替你嘛,我就是你的代理,我直接在新的类中使用 MaiMianMoImpl 的方法就好了,名字嘛就叫 MaiMianMoProxy 好了,talk is cheap show me your code…
package com.pz.se.demo; | |
public class MaiMianMoProxyimplements MaiMianMo {private MaiMianMo maiMianMoImpl = new MaiMianMoImpl(); | |
@Override | |
public Object maiMianMo(Object obj) {System.out.println("我是微商,我卖面膜"); | |
return maiMianMoImpl.maiMianMo(obj); | |
} | |
public static void main(String args[]){MaiMianMo maiMianMo = newMaiMianMoProxy(); | |
maiMianMo.maiMianMo(new Object()); | |
} | |
} |
所谓动态代理,是指利用 Java 的反射技术 (JavaReflection) 生成字节码,在运行时创建一个新的类给指定接口或接口引用,从而实现代理了或接口的职责。
JDK 提供了动态代理,都在 Java.lang.reflect 包下,一个接口 InvocationHandler,一个类 Proxy。
InvocationHandler:该接口中仅定义了一个方法 Object:invoke(Object obj,Method method,Object[] args)。在实际使用时,第一个参数 obj 一般是指代理类,method 是被代理的方法,args 为该方法的参数数组。这个抽象方法在代理类中动态实现。
Proxy 类就是动态代理类,用于在程序运行时,生产生产代理对象。我们可以看下使用 JDK 的动态代理,来帮助我们成为微商,售卖面膜。
第一步,实现 InvocationHandler 接口。
package com.pz.se.demo; | |
import java.lang.reflect.InvocationHandler; | |
import java.lang.reflect.Method; | |
public class MaiMianMoInvocationHandler implements InvocationHandler { | |
private MaiMianMo maiMianMo; | |
publicMaiMianMoInvocationHandler(MaiMianMo maiMianMo) {this.maiMianMo = maiMianMo;} | |
@Override | |
public Object invoke(Object proxy, Methodmethod, Object[] args) throws Throwable { | |
// 微商嘛自然喜欢干点别的事情 | |
// 做还是不做都在于你 | |
Object invoke =method.invoke(maiMianMo, args); | |
System.out.println("我是微商我想干嘛干嘛的"); | |
System.out.println("我甚至不调用你能拿我怎样?"); | |
System.out.println("到了我手里我想干嘛就干嘛的"); | |
return invoke; | |
} | |
} |
第二步,使用 Proxy 类生产代理对象,然后调用就好。
package com.pz.se.demo; | |
importjava.lang.reflect.Proxy; | |
public classTestProxyMaiMianMo {public static void main(String args[]){MaiMianMoInvocationHandler handler =new MaiMianMoInvocationHandler(new MaiMianMoImpl()); | |
MaiMianMo maiMianMo= (MaiMianMo)Proxy.newProxyInstance(MaiMianMo.class.getClassLoader(),newClass[]{MaiMianMo.class},handler); | |
maiMianMo.maiMianMo(new Object()); | |
} | |
} |
根据 JDK 提供的动态代理,看起来只是针对接口来说的,我们可以想想一下,代理到底是什么?无非是代替某个类的功能而已,一个类有属性和方法,那么编写一个类继承要实现的类不就好了? 结合反射来看,可以拿到太多的信息了,无非输出一个源程序文件,然后吧,假如编译了呢?然后吧,编译,然后加载然后创建了对象了呢?下面就是个例子稍微改改,妙用无穷噢。
public class ProxyUtil {public static Object proxyClass(Class clazz){String packageNam=clazz.getPackage().getName(); | |
return null; | |
} | |
private static StringappendPackageStr(Class clazz){return "package"+clazz.getPackage().getName()+";\n";} | |
private static StringappendClassBodyBegin(Class clazz){return "public class"+clazz.getSimpleName()+"Proxy extends" +clazz.getName()+"{\n";} | |
private static String getFileName(Class clazz){returnclazz.getSimpleName()+"Proxy.java"; | |
} | |
private static String appendClassBodyEnd(){return "}\n"; | |
} | |
private static String appendImports(Classclazz) {StringBuilder builder = newStringBuilder(); | |
for(Method method:clazz.getDeclaredMethods()){Class<?>[] parameterTypes=method.getParameterTypes(); | |
for(Class param:parameterTypes){builder.append("import"+param.getName()+";\n"); | |
} | |
} | |
return builder.toString();} | |
private static String appendMethod(Methodmethod,String codes) {StringBuilder builder = newStringBuilder(); | |
builder.append("public"); | |
builder.append(method.getReturnType().getName() +" "); | |
builder.append(method.getName()+""); | |
builder.append("("); | |
Class<?>[] parameterTypes=method.getParameterTypes(); | |
for(int i =0;i<parameterTypes.length;i++){if(i==parameterTypes.length-1){builder.append(parameterTypes[i].getName() +" "+parameterTypes[i].getSimpleName()); | |
builder.append(")"); | |
} | |
} | |
builder.append("{\n"); | |
builder.append(codes); | |
builder.append("}\n"); | |
return builder.toString();} | |
public static ObjectcomplierAndReturnProxyObject(Class clazz, String content) throws IOException {StringfileName=System.getProperty("user.dir")+"/target/classes"+"/"+clazz.getPackage().getName().replace(".","/")+"/"+getFileName(clazz); | |
File file = new File(fileName); | |
if(!file.exists()){file.createNewFile(); | |
} | |
FileOutputStream fos = newFileOutputStream(fileName); | |
fos.write(content.getBytes()); | |
fos.close(); | |
try { | |
// 动态编译 | |
JavaCompiler javac =ToolProvider.getSystemJavaCompiler(); | |
int status = javac.run(null, null,null, "-d",System.getProperty("user.dir")+"/target/classes",fileName); | |
if(status!=0){System.out.println("没有编译成功!"); | |
} | |
return Class.forName(clazz.getName()+"Proxy").newInstance();} catch (Exception e) {e.printStackTrace(); | |
} | |
return null; | |
} | |
public static void main(String args[])throws IOException {StringBuilder builder = newStringBuilder(); | |
builder.append(appendPackageStr(MaiMianMoImpl.class)); | |
builder.append(appendImports(MaiMianMoImpl.class)); | |
builder.append(appendClassBodyBegin(MaiMianMoImpl.class)); | |
Method[] methods=MaiMianMoImpl.class.getDeclaredMethods(); | |
for(Method method: methods){builder.append(appendMethod(method,"return null;\n")); | |
} | |
builder.append(appendClassBodyEnd()); | |
String codes =builder.toString(); | |
System.out.println(codes); | |
MaiMianMo maiMianMo = (MaiMianMo)complierAndReturnProxyObject(MaiMianMoImpl.class,codes); | |
maiMianMo.maiMianMo(new Object()); | |
} | |
} |
以上的方式都是运行时期来生产代理类的。当然除了 jdk 的代理之外,还有很多工具也可以做到这一点。比如经常提到的开源项目 cglib,ASM,Javassist, 这些框架能够更容易的做到,在运行时期,扩展一个类,总比咱自己 JDK 硬写编译要强吧?
那还有没有是什么时候可以做手脚去扩展一个代理类出来呢?我们之前学过 java 代码的执行套路,一个代码需要执行,是需要编译的吧?我们也可以在编译时期做一些手脚嘛,反正原理无非就是,读取一个类的内容,然后加入自己想加入的内容就好了。Java 也有一些开源项目,比如 AspectJ 可以轻松的做到这一点。
介于篇幅,以上的开源项目的使用,我就暂时不做展示了。后续的内容,我会给出例子的。
不过最重要的一点需要记住,代理后,做任何事情都可以,被代理的方法只关心返回值,要不你可能学 AOP 只能停留在打日志的阶段了噢。