java中动态代理主要有JDK和CGLIB两种方式。 区别主要是JDK是代理接口,而CGLIB是代理类。
JDK的动态代理调用了Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 方法。通过该方法生成字节码,动态的创建了一个代理类,interfaces参数是该动态类所继承的所有接口,而实现InvocationHandler 接口的类则是实现在调用代理接口方法前后的具体逻辑, 我们可以通过重写实现InvocationHandler 接口的类的invoke()方法,在调用调用代理接口方法前后可以添加自己的逻辑;
根据源码我们可以了解到newProxyInstance方法执行了以下几种操作:
- 生成一个实现了参数interfaces里所有接口且继承了Proxy的代理类的字节码,然后用参数里的classLoader加载这个代理类。
- 使用代理类父类的构造函数 Proxy(InvocationHandler h)来创造一个代理类的实例,将我们自定义的InvocationHandler的子类传入。
- 返回这个代理类实例。
1. 模拟静态代理
1.1 委托类和代理类需要实现相同的接口, 代理类通过实现该接口, 重写方法, 在方法中调用委托类的方法
package com.lic.dao;
public interface UserDao {
public void query();
public String query(String str);
}
1.2 委托类的实现
package com.lic.dao.impl;
import com.lic.dao.UserDao;
public class UserDaoImpl implements UserDao{
@Override
public void query() {
System.out.println("模拟查询...");
}
@Override
public String query(String str) {
str+="123";
return str;
}
}
1.3 代理类的实现
package com.lic.dao.impl;
import com.lic.dao.UserDao;
public class Proxy implements UserDao{
//代理类中维护了一个委托类的接口,用来接收委托类, 以便在重写的方法中调用委托类的方法
private UserDao userDao;
public Proxy(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void query() {
userDao.query();
System.out.println("模拟增强");
}
@Override
public String query(String str) {
return str+"_模拟增强";
}
}
1.4测试类
package com.lic.test;
import com.lic.dao.impl.Proxy;
import com.lic.dao.impl.UserDaoImpl;
public class ProxyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy(new UserDaoImpl());
proxy.query();
}
}
动态代理和静态代理的区别?
1、静态代理在代理前就知道要代理的是哪个对象,而动态代理是运行时才知道;
2、静态代理一般只能代理一个类,而动态代理能代理实现了接口的多个类;
静态代理需要代理类实现和委托类相同的接口,并使用构造方法传递委托类对象的引用,重写接口中的方法; 而动态代理则是在运行时生成一个实现了参数interfaces里所有接口且继承了Proxy的代理类的字节码文件,然后用参数里的classLoader加载这个代理类。
2. 模拟动态代理_V1(单纯的代理委托类, 对于代理类扩展的程序采用硬编码)
我们可以基于上面的示例模拟一个简单的动态代理, 在动态代理中Proxy类是动态生成的,而在上面的示例中是静态生成的, 那我我们创建一个类, 在运行时动态的创建一个代理类就可以了
思路:
通过接口反射生成一个类文件,然后调用第三方的编译技术,动态编译这个产生的类文件成class文件,继而利用UrlclassLoader(因为这个动态产生的class不在工程当中所以需要使用UrlclassLoader)把这个动态编译的类加载到jvm当中,最后通过反射把这个类实例化。
流程图:
newInstance(Object target)方法的实现:
package com.lic.util;
import java.io.File;
import java.io.FileWriter;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class ProxyUtil {
public static Object newInstance(Object target) {
Object proxy = null;
//格式调整
String line="\n";
String tab="\t";
//获取目标类的接口, 代理类需要与目标类实现统一接口
Class<?> targetInt = target.getClass().getInterfaces()[0];
//目标类接口名称
String targetIntName = targetInt.getSimpleName();
//1. 包声明
String packageContent = "package com.lic.proxy;"+line+line;
//2. 包导入
String importContent = "import " + targetInt.getName() + ";"+line+line;
//3. 类声明
String classContent = "public class $Proxy implements "+targetIntName+"{"+line+line;
//4. 属性声明
String propertyDefine =tab+ "private "+targetIntName +" target;"+line+line;
//5. 构造函数
String constructContent =tab+ "public $Proxy ("+targetIntName+" target){"+line
+tab+tab+"this.target = target;"+line
+tab+ "}"+line+line;
//6.实现接口的所有方法
String methodsContent = "";
//获取目标类的接口的所有方法
Method[] methods = targetInt.getDeclaredMethods();
//6.1 遍历每个方法
for (Method method : methods) {
//a. 获取方法返回类型名称
String returnTypeName=method.getReturnType().getSimpleName();
//b. 获取方法名称
String methodName=method.getName();
//c. 获取方法参数
Class<?>[] args = method.getParameterTypes();
//d. 拼接参数
String argsContent="";
String argsContent1="";
int argCount=0;
if(args.length>0){
for (Class<?> arg : args) {
//String arg0, String arg1,
argsContent+=arg.getSimpleName()+" arg"+argCount+", ";
//arg0,arg1,
argsContent1+="arg"+argCount+",";
argCount++;
}
argsContent=argsContent.substring(0, argsContent.lastIndexOf(","));
argsContent1=argsContent1.substring(0,argsContent1.lastIndexOf(","));
}
methodsContent+=tab+ "public "+returnTypeName+" "+methodName+"("+argsContent+"){"+line
+tab+tab+"System.out.println(\"模拟增强中...\");"+line;
if("void".equals(returnTypeName)){
methodsContent+=tab+tab+"target."+methodName+"("+argsContent1+");"+line;
}else{
methodsContent+=tab+tab+"return target."+methodName+"("+argsContent1+");"+line;
}
methodsContent+=tab+ "}"+line+line;
}
//7. 拼接代理类内容
String proxyContent=packageContent+importContent+classContent+propertyDefine+constructContent+methodsContent
+"}"+line;
//8. 根据包路径生成文件夹
File folder = new File("E:\\com\\lic\\proxy");
folder.mkdirs();
//9. 创建代理类的文件
File file = new File("E:\\com\\lic\\proxy\\$Proxy.java");
try{
if(!file.exists()){
file.createNewFile();
}
//10. 输出代理类
FileWriter fw = new FileWriter(file);
fw.write(proxyContent);
fw.flush();
fw.close();
//11.根据.java文件对代理类进行编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(file);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
URL[] urls = new URL[]{new URL("file:E:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class clazz = urlClassLoader.loadClass("com.lic.proxy.$Proxy");
Constructor constructor = clazz.getConstructor(targetInt);
proxy = constructor.newInstance(target);
}catch(Exception e){
e.printStackTrace();
}
return proxy;
}
}
测试类:
package com.lic.test;
import com.lic.dao.UserDao;
import com.lic.dao.impl.UserDaoImpl;
import com.lic.util.MyInvocationHandler;
import com.lic.util.ProxyUtil;
import com.lic.util.ProxyUtil_V2;
public class Test {
public static void main(String[] args) {
UserDao proxy = (UserDao) ProxyUtil.newInstance(new UserDaoImpl());
proxy.query();
System.out.println("------------");
System.out.println(proxy.query("测试"));
}
}
3. 模拟动态代理_V2
由于上面的模拟对于代理类扩展的程序是采用硬编码, 我们无法扩展代理类; 那么我们可以根据JDK动态代理自定义一个InvocationHandler接口, 并实现它; 在实现类中的invoke()方法中编写代理类的逻辑, 在调用newInstance()方法中传入, 在动态生成代理的java文件时, 将直接调用实例类对应的invoke()方法, 即可完成对委托类的代理, 又可以对委托类进行扩展
3.1 JDK动态代理的实现
package com.lic.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler {
//被代理的目标对象
private Object target;
public JDKInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增强中...");
//调用目标对象的方法
return method.invoke(target, args);
}
}
测试类:
package com.lic.test;
import java.lang.reflect.Proxy;
import com.lic.dao.UserDao;
import com.lic.dao.impl.UserDaoImpl;
import com.lic.util.JDKInvocationHandler;
public class JDKProxy {
public static void main(String[] args) {
//利用JDK动态代理对UserDaoImpl对象进行代理
UserDao userDao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),
new Class[]{UserDao.class},
new JDKInvocationHandler(new UserDaoImpl()));
System.out.println(userDao.query("132"));
}
}
3.2 模拟JDK动态代理的实现
1.自定义InvocationHandler 接口
package com.lic.util;
import java.lang.reflect.Method;
public interface CustomeInvocationHandler {
public Object invoke(Method method, Object[] args);
}
2. 创建InvocationHandler 接口的实现类, 重写invoke()方法
package com.lic.util;
import java.lang.reflect.Method;
public class MyInvocationHandler implements CustomeInvocationHandler {
// 被代理的目标对象
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Method method,Object[] args) {
System.out.println("牛逼了...");
try {
//利用反射调用委托类的方法
return method.invoke(target,args);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
3. 实现newInstance()方法
package com.lic.util;
import java.io.File;
import java.io.FileWriter;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class ProxyUtil_V2 {
public static Object newInstance(Object target,CustomeInvocationHandler h) {
Object proxy = null;
//格式调整
String line="\n";
String tab="\t";
//获取目标类的接口, 代理类需要与目标类实现统一接口
Class<?> targetInt = target.getClass().getInterfaces()[0];
//目标类接口名称
String targetIntName = targetInt.getSimpleName();
//1. 包声明
String packageContent = "package com.lic.proxy;"+line+line;
//2. 包导入
String importContent = "import " + targetInt.getName() + ";"+line
+"import com.lic.util.CustomeInvocationHandler;"+line
+"import java.lang.Exception;"+line
+"import java.lang.reflect.Method;"+line+line;
//3. 类声明
String classContent = "public class $Proxy implements "+targetIntName+"{"+line+line;
//4. 属性声明
String propertyDefine =tab+ "private CustomeInvocationHandler h;"+line+line;
//5. 构造函数
String constructContent =tab+ "public $Proxy (CustomeInvocationHandler h){"+line
+tab+tab+"this.h = h;"+line
+tab+ "}"+line+line;
//6.实现接口的所有方法
String methodsContent = "";
//获取目标类的接口的所有方法
Method[] methods = targetInt.getDeclaredMethods();
//6.1 遍历每个方法
for (Method method : methods) {
//a. 获取方法返回类型名称
String returnTypeName=method.getReturnType().getSimpleName();
//b. 获取方法名称
String methodName=method.getName();
//c. 获取方法参数
Class<?>[] args = method.getParameterTypes();
//d. 拼接参数
String argsContent="";
String argsContent1="";
String argsClassContents="";
int argCount=0;
if(args.length>0){
argsContent1+=",new Object[]{";
for (Class<?> arg : args) {
//String arg0, String arg1,
argsContent+=arg.getSimpleName()+" arg"+argCount+", ";
//arg0,arg1,
argsContent1+="arg"+argCount+",";
argsClassContents+=","+arg.getSimpleName()+".class";
argCount++;
}
argsContent=argsContent.substring(0, argsContent.lastIndexOf(","));
argsContent1=argsContent1.substring(0,argsContent1.lastIndexOf(","));
argsContent1+="}";
}
String castContent="";
if(! "void".equals(returnTypeName)){
castContent+="("+returnTypeName+")";
}
methodsContent+= tab+ "public "+returnTypeName+" "+methodName+"("+argsContent+"){"+line
+tab+tab+"try{"+line
+tab+tab+tab+"Method method = Class.forName(\""+targetInt.getName()+"\").getDeclaredMethod(\""+methodName+"\""+argsClassContents+");"+line;
if(!"void".equals(returnTypeName))
methodsContent+=tab+tab+tab+"return "+castContent+" h.invoke(method"+argsContent1+");"+line;
else{
methodsContent+=tab+tab+tab+castContent+"h.invoke(method, null);"+line;
}
methodsContent+=tab+tab+"} catch (Exception e) {"+line
+tab+tab+tab+"e.printStackTrace();"+line
+tab+tab+"}"+line;
if(!"void".equals(returnTypeName))
methodsContent+=tab+tab+"return null;"+line;
methodsContent+=tab+ "}"+line+line;
}
//7. 拼接代理类内容
String proxyContent=packageContent+importContent+classContent+propertyDefine+constructContent+methodsContent
+"}"+line;
//8. 根据包路径生成文件夹
File folder = new File("E:\\com\\lic\\proxy");
folder.mkdirs();
//9. 创建代理类的文件
File file = new File("E:\\com\\lic\\proxy\\$Proxy.java");
try{
if(!file.exists()){
file.createNewFile();
}
//10. 输出代理类
FileWriter fw = new FileWriter(file);
fw.write(proxyContent);
fw.flush();
fw.close();
//11.根据.java文件对代理类进行编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(file);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
URL[] urls = new URL[]{new URL("file:E:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class clazz = urlClassLoader.loadClass("com.lic.proxy.$Proxy");
Constructor constructor = clazz.getConstructor(CustomeInvocationHandler.class);
proxy = constructor.newInstance(h);
}catch(Exception e){
e.printStackTrace();
}
return proxy;
}
}
- 测试类
package com.lic.test;
import com.lic.dao.UserDao;
import com.lic.dao.impl.UserDaoImpl;
import com.lic.util.MyInvocationHandler;
import com.lic.util.ProxyUtil;
import com.lic.util.ProxyUtil_V2;
public class Test {
public static void main(String[] args) {
UserDao proxy = (UserDao) ProxyUtil_V2.newInstance(new UserDaoImpl(),new MyInvocationHandler(new UserDaoImpl()));
proxy.query();
System.out.println("------------");
System.out.println(proxy.query("测试"));
}
}
注意:
在JDK动态代理中, 是直接生成字节码文件, 然后交由JVM去动态加载的;
JDK与Cglib动态代理对比?
1、JDK动态代理只能代理实现了接口的类,没有实现接口的类不能实现JDK的动态代理;
2、Cglib动态代理是针对类实现代理的,运行时动态生成被代理类的子类拦截父类方法调用,因此不能代理声明为final类型的类和方法;
Spring如何选择两种代理模式的?
1、如果目标对象实现了接口,则默认采用JDK动态代理;
2、如果目标对象没有实现接口,则使用Cglib代理;
3、如果目标对象实现了接口,但强制使用了Cglib,则使用Cglib进行代理
下一篇: CGLIB动态代理实现原理