JDK动态代理
静态代理的缺点
1、会产生很多的代理类
2、产生的代理类只能代理既定的接口
动态代理
动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。
动态代理相比于静态代理的优点:
抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。
JDK 动态代理
JDK 的动态代理是基于拦截器和反射来实现的。JDK代理是不需要第三方库支持的,只需要 JDK 环境就可以进行代理
- 必须实现 InvocationHandler 接口
- 使用 Proxy.newProxyInstance 产生代理对象
- 被代理的对象必须要实现接口
JDK 动态代理不能对类进行代理:因为代理类($Proxy0)已经继承了 Proxy 类,由于 Java 语言只支持单继承,所以 JDK 动态代理不能对类进行代理
JDK 动态代理简单实现
接口
public interface MDao {
public String query(int id, String name);
}
接口实现类
@Slf4j(topic = "e")
public class MemberDao implements MDao {
@Override
public String query(int id, String name) {
log.debug("MemberDao--------------query-------------logic");
return name+"-return";
}
}
实现 InvocationHandler
接口
@Slf4j(topic = "e")
public class CustomInvocationHandler implements InvocationHandler {
//目标对象
Object object;
public CustomInvocationHandler(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理的before业务逻辑
log.debug("before----xxxxxxxxxxx----JDKProxy");
//执行目标对象的方法
Object invoke = method.invoke(object, args);
//代理的after业务逻辑
log.debug("after----xxxxxxxxxxx----JDKProxy");
return invoke;
}
}
测试类
@Slf4j(topic = "e")
public class TestApp {
public static void main(String[] args) {
MDao mDao = new MemberDao();
// new ProxyCustom().createProxy(MDao.class);
MDao dao = (MDao) Proxy.newProxyInstance(TestApp.class.getClassLoader(),
new Class[]{MDao.class},
new CustomInvocationHandler(mDao));
log.debug("{}",dao.query(12,"aa"));
}
}
- 此时调用的dao,query()方法,实际上是代理对象增强后的query()方法
newProxyInstance()方法的实现: 1、他会产生一段字符串代理类的源码 2、把这个字符串输出到一个.java ($Proxy.java)文件当中 3、会把这个$Proxy.java动态编译他成为一个$Proxy.class 4、会通过一个类加载器把这个$Proxy.cLass加载到JVM当中 5、CLass.forname("xxxx").newInstance反射实例化这个对象 proxyObject (产生的代理对象是对目标对象的增强) JDK省略1,2步,直接再内存中产生.class字节码文件
动态生成代理类的逻辑
public class ProxyCustom {
/*
* 他能返回一个对象--符合我们期望的代理对象
* class文件
* java 文件
* 代码
*
* io把我们的代码写道一个.java文件当中,然后再手动把这个.java文件编译
* 编译完成之后肯定会产生一个.class文件,继而把这个.class文件Loader到门JVM当中
* 然后通过反射区实例化这个对象,最终返回出去
* */
public Object createProxy(Class infce){
// 方法的字符串
String methodStr = "";
String rt = "\r\n";
String tab = "\t";
String r = "\n";
//获取接口当中的所有方法,方便后面遍历方法构建代理类的字符串
Method[] methods = infce.getMethods();
//每个方法的返回类型
String rtype = "";
//表示该方法有几个参数
int args = 0;
for (Method m : methods) {
// 得到方法的返回类型的字符串
rtype = m.getReturnType().getSimpleName();
// 参数的字符串有可能有参或者无参
String argsStr = "";
// 最后执行invoke方法的时候需要传入的参数值
String argsValueStr = "";
//得到这个方法所有的参数个数
int parameterCount = m.getParameterCount();
Class[] classesParamArr = null;
//再反射得到目标方法的时候需要的参数个数和类型的字符串
String getMethodParamStr = "new Class[]{ ";
if (parameterCount>0){
classesParamArr = new Class[parameterCount];
// 得到所有的参数个数和类型
Class<?>[] parameterTypes = m.getParameterTypes();
int pc = 0;
for (Class<?> parameterType : parameterTypes) {
// clssesParamArr[pc]=parameterType;
getMethodParamStr += parameterType.getSimpleName() + ".class,";
argsStr += parameterType.getSimpleName() + " p" + pc + ",";
argsValueStr += "p" + pc + ", ";
pc++;
}
//截取最后一个逗号
getMethodParamStr = getMethodParamStr.substring(0,getMethodParamStr.length()-1);
//截取最后一个逗号
argsStr = argsStr.substring(0, argsStr.length()-1);
//截取最后一个逗号
argsValueStr = argsValueStr.substring(0, argsValueStr.length()-1);
}
getMethodParamStr += "}";
rtype = m.getReturnType().getSimpleName();
String endReturnStr = "";
String returnStr= "";
//是否需要强转类型
String convertStr = "";
if( !rtype.equals("void")) {
returnStr = "return ";
endReturnStr = "return null;";
convertStr = "(" + rtype + ")";
}
methodStr += tab+"@Override" + rt +
tab+"public " + rtype +" "+ m.getName() + "("+argsStr+") {" + rt +tab+tab+"try {" + rt +
tab+tab+tab+"Method md = " + infce.getSimpleName() + ".class.getMethod(\""+ m.getName()+"\"," + getMethodParamStr+");" + rt +
tab+tab+tab+returnStr+ convertStr + "h.invoke(this, md,new Object[] {" +argsValueStr+"});" + rt +
tab+tab+"}catch(Exception e) {"+rt+
tab+tab+tab+"e.printStackTrace(); " + r + tab+tab+" }"+r+
tab+tab+ endReturnStr + r +
tab+"}";
}
String src =
"package com.it.proxy ;" +rt +
"import java.lang.reflect.Method;" +rt +
"import "+infce.getName()+";" + rt +
"import com.it.proxy.CInvocationHandler;" + rt +
"public class $Proxy1 implements " + infce.getSimpleName() + "{" + rt +
tab + "CInvocationHandler h;" + rt +
tab + "public $Proxy1(CInvocationHandler h) {" + rt +
tab + tab+ "this.h = h;" + rt +
tab + "}" + rt +
methodStr
+ r +"}"
;
// System.out.println(str);
}
在内存动态生成的类
package com.it.test ;
import java.lang.reflect.Method;
import com.it.dao.MDao;
import com.it.proxy.CInvocationHandler;
public class $Proxy1 implements MDao{
CInvocationHandler h;
public $Proxy1(CInvocationHandler h) {
this.h = h;
}
@Override
public String query(int p0,String p1) {
try {
Method md = MDao.class.getMethod("query",new Class[]{ int.class,String.class});
return h.invoke(this, md,new Object[] {p0, p1,}).toString();
}catch(Exception e) {
e.printStackTrace();
}
return null;
}
}
JDK 动态代理原理总结
- 在程序运行的过程中,根据被代理的接口来动态生成代理类 $Proxy0 的 class 字节码文件
- 产生的代理类 $Proxy0 继承了 Proxy 类,同时实现了被代理类的接口(如 MDao);所以才能强制将代理对象转换为被代理类的接口的类型(如 MDao),然后可以调用 $Proxy0 中的 query() 方法
- 而在代理对象 $Proxy0 调用其实现了接口方法(如 query())时,其本质就是使用了代理对象 $Proxy0 调用了 invoke() 方法
- 而 invoke() 方法的实现是自定义的,如 CustomInvocationHandler类,在这个类中可以处理相应的代理业务逻辑
- JDK 动态代理不能对类进行代理:因为代理类($Proxy0)已经继承了 Proxy 类,由于 Java 语言只支持单继承,所以 JDK 动态代理不能对类进行代理,只能对接口类型进行代理