一.动态代理
动态代理的作用:就是在不破坏已经写好的类的情况下,去增强其中某些方法的功能,比如在调用方法之前或之后,做一些额外的操作.(像权限校验,等)
动态代理的两种实现:
1.根据接口实现的动态代理(JDK)
2.根据类实现的动态代理(CGLIB)
二.JDK中的proxy-使用
1.先创建需要被代理的接口--其中eat()方法
public interface IEat {
public void eat(Apple apple);
}
2.再创建类去实现该方法--打印一段话
public class Eat implements IEat{
@Override
public void eat(Apple apple) {
System.out.println("I eat a "+apple.getColor()+" "+apple.getName());
}
}
3.创建Apple类--参数类
public class Apple {
private String name;
private String color;
public Apple(String name, String color) {
this.name = name;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
4.创建一个xxhandler 去实现 InvocationHandler--实现invoke方法
public class AppleHandler implements InvocationHandler {
private Object target;
// 传入需要被代理的对象,获得增强后的对象
public <T> T getInstance(Object target) throws Exception {
this.target = target;
Class<?> targetClass = target.getClass();
return (T) Proxy.newProxyInstance(targetClass.getClassLoader(), targetClass.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用原有方法之前,进行调用(比如:权限控制,或者打印入参日志等)
before();
// 调用原有方法
Object obj = method.invoke(this.target, args);
// 在调用原有方法之后,进行调用(比如:打印出参日志,数据库回滚或提交操作等)
after();
return obj;
}
private void before() {
System.out.println("洗苹果");
}
private void after() {
System.out.println("吃完了");
}
}
5.创建一个测试类进行测试
public class Test {
public static void main(String[] args) throws Exception {
IEat ie = new Eat();
ie.eat(new Apple("apple","red"));
System.out.println("============================");
// 获取加载地址,获取类接口,再获取重写后的增强方法,返回一个增强类
IEat obj = new AppleHandler().getInstance(new Eat());
obj.eat(new Apple("apple","red"));
}
}
6.测试结果---可以看出,并未对原有IEat接口,Eat类,Apple类,进行更改,但采用代理方式,却多做了一些处理
三.JDK中的proxy-分析
1.我们去生成一下代理类(利用ProxyGenerator.generateProxyClass()进行代理类生成),再用反编译去查看下代理类的具体情况
public class Test {
public static void main(String[] args) throws Exception {
IEat ie = new Eat();
ie.eat(new Apple("apple","red"));
System.out.println("============================");
// 获取加载地址,获取类接口,再获取重写后的增强方法,返回一个增强类
IEat obj = new AppleHandler().getInstance(new Eat());
obj.eat(new Apple("apple","red"));
//$Proxy0
byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{IEat.class});
FileOutputStream os = new FileOutputStream("E://$Proxy0.class");
os.write(bytes);
os.close();
}
}
2.代理类的具体情况--(精简化后)
import com.gper.vip.homework.proxy.Apple;
import com.gper.vip.homework.proxy.IEat;
import java.lang.reflect.*;
public final class $Proxy0 extends Proxy implements IEat{
public $Proxy0(InvocationHandler invocationhandler){
super(invocationhandler);
}
// 实现eat()方法
public final void eat(Apple apple){
try{
// 其中的 h ,其实就为传入invocationhandler
// 就是调用的invocationhandler中的invoke(this, m3, new Object[] { apple })方法
super.h.invoke(this, m3, new Object[] { apple });
return;
}catch(Error _ex) {
}catch(Throwable throwable){
throw new UndeclaredThrowableException(throwable);
}
}
private static Method m3;
static {
try{
// 获取IEat中的eat方法
m3 = Class.forName("com.gper.vip.homework.proxy.IEat").getMethod("eat", new Class[] {
Class.forName("com.gper.vip.homework.proxy.Apple")
});
}catch(NoSuchMethodException nosuchmethodexception){
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}catch(ClassNotFoundException classnotfoundexception){
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
从上述代理类中可以看出,
1.proxy其实就是通过,新生成一个代理类($Proxy0)去继承IEat接口,实现eat()方法.
2.而在代理类在eat()方法中,去调用的是InvocationHandler.invoke()方法,而InvocationHandler是通过代理类的构造函数传入的
3.而InvocationHandler,就是Proxy.newProxyInstance(targetClass.getClassLoader(), targetClass.getInterfaces(), this);
就是上面我们生成代理类时,传入的this,也就是我们写好的AppleHandler
4.调用逻辑 $Proxy0.eat()-->AppleHandler.invoke()-->Eat.eat()
所以我们就可以在AppleHandler.invoke()中增强,接下来需要调用的Eat.eat()方法了
四.仿照JDK中的proxy-进行编写
1.首先进行Proxy.newProxyInstance的编写,整体逻辑为:
a.动态生成源文件-(targetClass.getInterfaces())
b.生成Java文件输出到磁盘
c.将生成的java文件编译成.class文件
d.把编译成的.class文件加载到jvm中
e.返回通过字节码重组以后生成的新的代理对象
建立一个MyProxy并且创建一个newProxyInstance(MyClassLoader loader, Class<?>[] interf, MyInvocationHandler h)方法
public class MyProxy {
private static final String ln = "\r\n";
public static Object newProxyInstance(MyClassLoader myClassloader, Class<?>[] interfaces, MyInvocationHandler h) throws Exception {
// 1.动态生成源文件
String str = generateEatSrc(interfaces);
// 2.Java文件输出到磁盘
String filePath = MyProxy.class.getResource("").getPath();
File file = new File(filePath + "$Proxy0.java");
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(str);
fileWriter.flush();
fileWriter.close();
// 3.把生成的.java文件编译成.class文件
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null, null, null);
Iterable it = standardJavaFileManager.getJavaFileObjects(file);
JavaCompiler.CompilationTask task = javaCompiler.getTask(null, standardJavaFileManager, null
, null, null, it);
task.call();
standardJavaFileManager.close();
// 4.把编译成的.class文件加载到jvm中
Class proxyClass = myClassloader.findClass("$Proxy0");
// 5.返回字节码重组以后生成新的代理对象
Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
file.delete();
return c.newInstance(h);
}
// 比较low的方法,直接按照代理类进行拼接,写死了
private static String generateEatSrc(Class<?>[] interfaces) {
StringBuffer sb = new StringBuffer();
sb.append("package com.gper.vip.homework.proxy.myproxy;" + ln);
sb.append("import com.gper.vip.homework.proxy.Apple;" + ln);
sb.append("import com.gper.vip.homework.proxy.IEat;" + ln);
sb.append("import java.lang.reflect.*;" + ln);
sb.append("public final class $Proxy0 implements "+interfaces[0].getName()+"{" + ln);
sb.append(" MyInvocationHandler h;" + ln);
sb.append(" public $Proxy0(MyInvocationHandler h){" + ln);
sb.append(" this.h=h;" + ln);
sb.append(" }" + ln);
sb.append(" public final void eat(Apple apple){" + ln);
sb.append(" try{" + ln);
sb.append(" h.invoke(this, m3, new Object[] {apple});" + ln);
sb.append(" return;" + ln);
sb.append(" }" + ln);
sb.append(" catch(Error _ex) { }" + ln);
sb.append(" catch(Throwable throwable){ throw new UndeclaredThrowableException(throwable);}" + ln);
sb.append(" }" + ln);
sb.append(" private static Method m3;" + ln);
sb.append(" static {" + ln);
sb.append(" try{" + ln);
sb.append(" m3 = Class.forName(\"com.gper.vip.homework.proxy.Eat\").getMethod(\"eat\", new Class[] {" + ln);
sb.append(" Class.forName(\"com.gper.vip.homework.proxy.Apple\")});" + ln);
sb.append(" }catch(NoSuchMethodException nosuchmethodexception){" + ln);
sb.append(" throw new NoSuchMethodError(nosuchmethodexception.getMessage());" + ln);
sb.append(" }catch(ClassNotFoundException classnotfoundexception){" + ln);
sb.append(" throw new NoClassDefFoundError(classnotfoundexception.getMessage());" + ln);
sb.append(" }" + ln);
sb.append(" }" + ln);
sb.append("}" + ln);
return sb.toString();
}
public static void main(String[] args) throws Exception {
IEat ie = (IEat) new MyAppleHandler().getInstance(new Eat());
ie.eat(new Apple("apple", "red"));
}
}
2.再创建一个MyClassLoader类,继承ClassLoader(由于需要用到defineClass()方法,而自己又写不出),并重写findClass()方法
defineClass就是将将一个byte数组实例化成一个类类型
public class MyClassLoader extends ClassLoader {
private File classPathFile;
public MyClassLoader(){
// 获取MyClassLoader所在本地文件地址
String classPath = MyClassLoader.class.getResource("").getPath();
this.classPathFile = new File(classPath);
}
// 找到.class文件,并将其加载进来
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 拼接类的全类名
String className = MyClassLoader.class.getPackage().getName()+"."+name;
// 获取类文件所在磁盘位置
File file = new File(classPathFile,name+".class");
// 读取类文件,并写入缓存中
FileInputStream in = null;
ByteArrayOutputStream out = null;
try{
in = new FileInputStream(file);
out = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len;
while((len = in.read(buff))!= -1){
out.write(buff,0,len);
}
// Converts an array of bytes into an instance of class <tt>Class</tt>
return defineClass(className,out.toByteArray(),0,out.size());
}catch (Exception e){
}
return null;
}
}
3.创建一个MyInvocationHandler接口
public interface MyInvocationHandler {
// 代理对象,方法,参数
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException, Exception;
}
4.创建一个MyAppleHandler去实现MyInvocationHandler接口
public class MyAppleHandler implements MyInvocationHandler {
private Object target;
public Object getInstance(Object target) throws Exception {
this.target = target;
Class<?> targetClass = target.getClass();
// 使用MyProxy , MyClassLoader , MyInvocationHandler 去生成代理类
return MyProxy.newProxyInstance(new MyClassLoader(), targetClass.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
befor();
Object invoke = method.invoke(this.target, args);
after();
return invoke;
}
private void after() {
System.out.println("执行方法之后...");
}
private void befor() {
System.out.println("执行方法之前....");
}
}
5.运行测试方法
public static void main(String[] args) throws Exception {
IEat ie = (IEat) new MyAppleHandler().getInstance(new Eat());
ie.eat(new Apple("apple", "red"));
}
6.测试结果--还原JDK的proxy功能
从上述就能看出整条逻辑线为:
1.通过传入的interfaces,myInvocationHandler参数,经过反射+拼接字符串,生成代理类(一长串字符串)
a.代理类实现了--需要被代理类所实现的接口,以及在实现的接口方法中,调用的是myInvocationHandler.invoke()方法
b.代理类只有一个有参构造(必须传MyInvocationHandler)
2.将(一长串字符串)输出到磁盘上,形成.java文件
3.再使用javaCompile将.java文件编译成.class文件
4.再将.class加载到jvm中
5.再到jvm中找到加载的代理类,并通过有参构造函数初始化(传入MyAppleHandler)一个代理类并返回该代理类
五.关于JDK动态代理源码中要求目标类实现的接口数量不能超过65535个的问题?
答:是和Class类文件的结构中的接口计数器有关,接口计数器是u2类型,u2又是2个字节的,16位,最大就是2^16-1=65535 ,因为超过65535时,接口技术器就爆掉了,所以动态代理中要求目标类实现的接口数量不能超过65535个~
参考-类文件结构