1、JDK的动态代理
动态代理中的几个角色:
1、被代理类
在jdk中被代理类必须实现接口
2、代理类
在jdk中代理类是运行时动态生成的,所以你在工程中看不到代理类
3、通知类也叫advice
在jdk中通知类必须实现invocationHandler接口
在这里我们举个例子帮助理解
比如,张三是一个IT狗,到了适婚年龄但是没有对象,平时工作忙又没有时间找对象,这时候张三的父母就着急了张罗着给张三介绍对象,这时候张三的父母就拿着张三的照片、身份证等等可以代表张三的资料去媒婆那。这个就是最典型的代理,张三的父母代张三找对象,而不是张三本人,其实张三本人就只需要结婚就行了。找对象和照顾孩子的事情就有张三的父母代理了。
从这个例子中,我们提取出几个点:
1、张三,肯定是被代理对象,他要做的一件事情就是结婚。这里我们抽象下,张三是一个人,所以他需要实现people接口
2、张三的父母,张三的父母是具体的执行者,比如,代理张三找对象,张三结婚后帮忙张三照顾孩子,所以我们这里抽象出一个parent类,parent中有一个before方法,一个after方法。parent需要实现invocationHandler接口,它是一个通知类,负责具体执行
3、代理类,这是动态代理的难点,因为它是看不到摸不着的,它是存在在内存中的,且听博主慢慢道来。
OK~~~看代码吧。
people接口:
public interface People {
public void zhaoduixiang() throws Throwable;
}
张三类:
public class ZhangSan implements People {
/*
* 张三这个人,平时加班很忙,
* 他有生理问题,要解放双手
* 只关心有没有一个帮他解放双手这个人
* 至于怎么去找,他不关心
*/
@Override
public void zhaoduixiang() {
System.out.println("=============================找到对象,解放双手(你们懂的),然后结婚=====================");
}
}
parent类:
/**
* @Description TODO
* @ClassName Parent
* @Date 2018年9月26日 下午8:12:35
* @Author zg-jack
*
* 这个就是张三的父母类
* 这个类的目的就是为了增强张三的zhaoduixiang()方法
*
*
* 张三的父母需要持有张三这个人的引用
*
*
*/
public class Parent implements InvocationHandler {
private People people;
public Parent(People people) {
this.people = people;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//前置增强
before();
//2、调用张三的这个人的zhaoduixiang方法
method.invoke(people, args);
//3、调用after()
after();
return null;
}
/**
* @Description TODO
* @param 参数
* @return void 返回类型
* @throws
*
* 在张三找到对象之前,帮助张三找对象
*/
public void before() {
System.out.println("&&&&&&&&&&&&&&&张三的父母,帮助张三找对象&&&&&&&&&&&&&&&&");
}
/**
* @Description TODO
* @param 参数
* @return void 返回类型
* @throws
*
* 找到对象要以后,张三父母帮他操持结婚
*/
public void after() {
System.out.println("&&&&&&&&&&&&&&&张三的父母,帮助张三操持结婚的事情&&&&&&&&&&&&&&&&");
}
}
测试类:
public class MyTest {
public static void main(String[] args) throws Throwable {
//这个返回的对象就是张三这个人的代理对象
People proxyPeople = (People)Proxy.newProxyInstance(MyTest.class.getClassLoader(),
new Class<?>[] {People.class},
new Parent(new ZhangSan()));
createProxyClassFile();
proxyPeople.zhaoduixiang();
}
/**
* @Description TODO
* @param 参数
* @return void 返回类型
* @throws
*
* 用流的方式把内存中的代理对象的字节码输出到.class文件中
*/
public static void createProxyClassFile() {
byte[] data = ProxyGenerator.generateProxyClass("$Proxy0",
new Class[] {People.class});
try {
FileOutputStream out = new FileOutputStream("$Proxy0.class");
out.write(data);
out.close();
}
catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
测试结果:
&&&&&&&&&&&&&&&张三的父母,帮助张三找对象&&&&&&&&&&&&&&&&
=============================找到对象,解放双手(你们懂的),然后结婚=====================
&&&&&&&&&&&&&&&张三的父母,帮助张三操持结婚的事情&&&&&&&&&&&&&&&&
从这个测试结果,我们仅仅用了一行调用代码
proxyPeople.zhaoduixiang();就出现了这个结果,而这个结果明显又是parent类中的invoke方法的打印结果,那么这里问题来了,谁调用了parent中的invoke方法???OK,带着这个问题,我们看一下图,从图中了解动态代理的调用过程,回答前面的代理类问题,如图:
从图中我们可以看出,proxypeople.zhaoduixiang()调用的实际上是内存中的代理对象,这个对象是程序在运行时的时候动态加载到内存里的,所以程序员看不到,那么有什么办法可以看到这个内存中的代理对象的代码呢,有办法~~~这个博主待会儿讲。从图中我们可以看出,带代理对象中,有一个属性,invocationHandler h属性,而h属性的值就是parent对象,而在代理对象中的方法zhaoduixiang中就只有一行代理,h.invoke(.......),其实就是掉到了parent对象中的invoke方法。。。这里就回答了刚刚那个问题,谁调了parent对象的invoke方法? 这下我们知道了,是内存中的代理对象调到了invoke方法。。。
那么还是回到前面的问题,内存中的代理对象,我们能拿到吗??OK,贴代码
/**
* @Description TODO
* @param 参数
* @return void 返回类型
* @throws
*
* 用流的方式把内存中的代理对象的字节码输出到.class文件中
*/
public static void createProxyClassFile() {
byte[] data = ProxyGenerator.generateProxyClass("$Proxy0",
new Class[] {People.class});
try {
FileOutputStream out = new FileOutputStream("$Proxy0.class");
out.write(data);
out.close();
}
catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
我们可以拿到字节码,然后用流的方式输出到.class文件中,然后用反编译工具反编译一下就可以了。。
为了让大家彻底领悟动态代理技术,我打算自己定义一个动态代理,不用jdk的proxy类和invocationHandler接口,这样才能彻底领悟
贴代码吧:
自定义的JackInvocationHandler接口,类似于JDK中的InvocationHandler接口
public interface JackInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
最关键的是获取代理对象,因为这个代理对象在内存里面,在JDK中我们用Proxy这个类获取代理对象
People proxyPeople = (People)Proxy.newProxyInstance(MyTest.class.getClassLoader(),
new Class<?>[] {People.class},
new Parent(new ZhangSan()));
那么我们自己定义的动态代理里面,这个代理对象是如何获取呢??
MyParent类,类似于parent类
public class MyParent implements JackInvocationHandler {
private People people;
public MyParent(People people) {
this.people = people;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//前置增强
before();
//2、调用张三的这个人的zhaoduixiang方法
method.invoke(people, args);
//3、调用after()
after();
return null;
}
/**
* @Description TODO
* @param 参数
* @return void 返回类型
* @throws
*
* 在张三找到对象之前,帮助张三找对象
*/
public void before() {
System.out.println("&&&&&&&&&&&&&&&张三的父母,帮助张三找对象&&&&&&&&&&&&&&&&");
}
/**
* @Description TODO
* @param 参数
* @return void 返回类型
* @throws
*
* 找到对象要以后,张三父母帮他操持结婚
*/
public void after() {
System.out.println("&&&&&&&&&&&&&&&张三的父母,帮助张三操持结婚的事情&&&&&&&&&&&&&&&&");
}
}
MyProxy类,这个是最关键的核心代码,其功能类似于JDK中的Proxy类
public class MyProxy {
private static String rt = "\r\n";
/**
* @Description TODO
* @param @param loader
* @param @param interfaces
* @param @param h
* @param @return 参数
* @return Object 返回类型
* @throws
*
* 这个方法最终是需要返回一个代理对象
* 1、运行时动态生成
* 2、它是一个类
* 3、它要实现people接口
* 4、是以字节码的方式加载到内存中
*
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, JackInvocationHandler h) {
String fileName = "F:/workspace/proxy/src/main/java/com/zhuguang/jack/myproxy/$Proxy0.java";
//1、我们要生成一个类,以字符串拼凑的方式,拼凑出一个类
String javaClassStr = getJavaStr(interfaces);
//2、通过流的方式生成java文件
createJavaFile(javaClassStr, fileName);
//3、动态编译java文件,生成.class文件
compilerJava(fileName);
//4、要把磁盘里面的.class文件内容加载到jvm的内存
Object instance = LoadClass(h);
return instance;
}
/**
* @Description TODO
* @param @param h
* @param @return 参数
* @return Object 返回类型
* @throws
*
* 自定义一个类加载器
*/
private static Object LoadClass(JackInvocationHandler h) {
JackClassLoader jackClassLoader = new JackClassLoader(
"F:/workspace/proxy/src/main/java/com/zhuguang/jack/myproxy");
try {
//返回的是一个被代理对象的Class对象
Class<?> findClass = jackClassLoader.findClass("$Proxy0");
Constructor<?> constructor = findClass.getConstructor(JackInvocationHandler.class);
Object newInstance = constructor.newInstance(h);
return newInstance;
}
catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private static void compilerJava(String fileName) {
JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager standardFileManager = systemJavaCompiler.getStandardFileManager(null,
null,
null);
Iterable<? extends JavaFileObject> javaFileObjects = standardFileManager.getJavaFileObjects(fileName);
CompilationTask task = systemJavaCompiler.getTask(null,
standardFileManager,
null,
null,
null,
javaFileObjects);
task.call();
try {
standardFileManager.close();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static void createJavaFile(String javaClassStr, String fileName) {
try {
File f = new File(fileName);
FileWriter fw;
fw = new FileWriter(f);
fw.write(javaClassStr);
fw.flush();
fw.close();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static String getJavaStr(Class<?>[] interfaces) {
Method[] methods = interfaces[0].getMethods();
String proxyClassStr = "package com.zhuguang.jack.myproxy;" + rt
+ "import java.lang.reflect.Method;" + rt
+ "public class $Proxy0 implements " + interfaces[0].getName()
+ "{" + rt + "JackInvocationHandler h;" + rt
+ "public $Proxy0(JackInvocationHandler h) {" + rt
+ "this.h=h;" + rt + "}"
+ getMethodString(methods, interfaces[0]) + rt + "}";
return proxyClassStr;
}
private static String getMethodString(Method[] methods, Class intf) {
String proxyMe = "";
for (Method method : methods) {
proxyMe += "public void " + method.getName()
+ "() throws Throwable {" + rt + "Method md = "
+ intf.getName() + ".class.getMethod(\"" + method.getName()
+ "\",new Class[]{});" + rt
+ "this.h.invoke(this,md,null);" + rt + "}" + rt;
}
return proxyMe;
}
}
在这个类中有几个步骤:
1、生成代理类,我们用字符串拼凑的方式把类中的内容拼凑成String类型
2、通过流的方式把String写到java文件中
3、动态编译java文件,把它编译成.class文件
4、把磁盘中的.class文件加载到内存中并返回代理类的对象
通过这几个步骤,我们就可以拿到内存中的代理类,如此一个自己定义的动态代理就搞定了,是不是没有那么难?
希望博主的整理能够帮助到大家,如果大家觉得OK的话,麻烦点个赞,或者写个评论什么的,好歹我也要士气来 继续坚持我的原创博客之路啊,哈哈~~~~~