一、背景
目前常用的代理有静态代理与动态代理,静态代理的方式以继承、聚合方式,动态代理以JDK动态代理、Cglib代理为主,直接剖析JDK动态代理有些突兀,本文由简入繁,通过自己实现的代理方式解释代理方式的一个变化过程。最终与JDK动态代理源码对比,大家肯定就能明白JDK动态代理的设计者为什么这么做了。
二、静态代理
本文的静态代理以聚合方式进行阐述,而本文给的例子以装饰者模式,大家可能很奇怪,其实两者本质没太多区别,为了让全文更加通俗易懂,描述变化过程,因此以装饰者模式来实现“代理”:
聚合方式是代理类与真正的实现类实现的是同一个接口,如以下代码所示:
UserDao接口:
public interface UserDao {
public void query()throws Exception;
}
UserDaoImpl实现类:
public class UserDaoImpl implements UserDao {
public void query() {
System.out.println("Dao query");
}
}
ProxyDao代理类:
以下可以看到 输出“代理实现” 就是我们代理类的增强措施
public class ProxyDao implements UserDao {
private UserDao dao;
public ProxyDao(UserDao dao){
this.dao = dao;
}
public void query() throws Exception{
System.out.println("代理实现");
dao.query();
}
}
测试类:
public class Test {
public static void main(String[] args) throws Exception{
//聚合方式
UserDao userDao = new ProxyDao(new UserDaoImpl());
userDao.query();
}
}
以上运行出来的结果相信大家都知道:
代理实现
Dao query
以上聚合方式的代理存在很大的缺点,如果需要代理的目标类有很多,那么我们需要写很多代理类,最终产生类爆炸,那我们下一步该如何做呢?如果我们的ProxyDao不需要每次都重写该多好呢,如果有一个类专门帮我们生成这个ProxyDao该多好呢。。。
二、自定义动态代理(代理类的方法固定)
大家可能好奇括号里面的是什么意思,比如第一步的静态代理中的代理类增强的措施是输出“代理实现”,而我们这次的自定义动态代理只是为了防止类爆炸,先不解决如何动态设置增强措施。带着第一节的疑问,如何动态生成这个ProxyDao,我们可以想到自己用代码生成一个动态类,那这个动态类如何来,我们正常的类是通过.java文件编译成.class文件,然后加载到jvm中,那么我们是否也可以?当然可以!!!
接口类、接口实现类不动,我们来写一个能够动态生成代理类的类:
public class ProxyUtil {
public static Object newInstance(Object object) throws Exception {
String line = "\n";
String tab = "\t";
//拿到目标类的接口
Class targetClazz = object.getClass().getInterfaces()[0];
Method[] methods = targetClazz.getDeclaredMethods();
String context="";
//import
String importContext = "import "+targetClazz.getName()+";"+line;
//第一行
String firstContext = "public class $Proxy implements "+ targetClazz.getSimpleName()+" {"+line;
//字段
String filedContext = tab+"private "+targetClazz.getSimpleName()+" target;"+line;
//构造方法
String constructContext = tab+"public $Proxy("+targetClazz.getSimpleName()+" target){"+line
+tab+tab+"this.target = target;"+line
+tab+"}"+line;
String methodContext = "";
for (Method method : methods){
//判断是否有方法参数
Class<?>[] classes = method.getParameterTypes();
String paramaterContext = "";
String paraContext = "";
if(classes.length>0){
for (int i=0;i<classes.length;i++){
paramaterContext+=classes[i].getSimpleName()+" v"+i+",";
paraContext+=("v"+i+",");
}
paramaterContext = paramaterContext.substring(0,paramaterContext.lastIndexOf(","));
paraContext = paraContext.substring(0,paraContext.lastIndexOf(","));
}
//这边没有写方法返回,在下一节会写,而且可以看到代理的增强措施被固定了
methodContext+= tab+"public void "+method.getName()+"("+paramaterContext+"){"+line+
tab+tab+"System.out.println(\"自定义动态代理实现\");"+line+
tab+tab+"target."+method.getName()+"("+paraContext+");"+line+
tab+"}"+line;
}
context = importContext+firstContext+filedContext+constructContext+methodContext+"}";
//设置编译参数
ArrayList<String> ops = new ArrayList<String>();
ops.add("-Xlint:unchecked");
//编译代码,返回class
ClassUtil.compile("/Users/Desktop/测试文件路径/proxy/$Proxy.java",context,ops);
FileClassLoader loader = new FileClassLoader("/Users/Desktop/测试文件路径/proxy/");
Class<?> clazz = loader.findClass("$Proxy");
Constructor<?> constructor = clazz.getConstructor(targetClazz);
return constructor.newInstance(object);
}
}
这边有两个类一个是ClassUtil,是负责生成.java文件并编译的,FileClassLoader是负责加载的:
ClassUtil:
public class ClassUtil {
private static final Log logger = LogFactory.getLog(ClassUtil.class);
private static JavaCompiler compiler;
static{
compiler = ToolProvider.getSystemJavaCompiler();
}
/**
* 获取java文件路径
* @param file
* @return
*/
private static String getFilePath(String file){
int last1 = file.lastIndexOf('/');
int last2 = file.lastIndexOf('\\');
return file.substring(0, last1>last2?last1:last2)+File.separatorChar;
}
/**
* 编译java文件
* @param ops 编译参数
* @param files 编译文件
*/
public static void javac(List<String> ops, String... files){
StandardJavaFileManager manager = null;
try{
manager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> it = manager.getJavaFileObjects(files);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, ops, null, it);
task.call();
if(logger.isDebugEnabled()){
for (String file:files)
logger.debug("Compile Java File:" + file);
}
}
catch(Exception e){
logger.error(e);
}
finally{
if(manager!=null){
try {
manager.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 生成java文件
* @param file 文件名
* @param source java代码
* @throws Exception
*/
private static void writeJavaFile(String file,String source)throws Exception{
if(logger.isDebugEnabled()){
logger.debug("Write Java Source Code to:"+file);
}
BufferedWriter bw = null;
try{
File dir = new File(getFilePath(file));
if(!dir.exists())
dir.mkdirs();
bw = new BufferedWriter(new FileWriter(file));
bw.write(source);
bw.flush();
}
catch(Exception e){
throw e;
}
finally{
if(bw!=null){
bw.close();
}
}
}
/**
* 加载类
* @param name 类名
* @return
*/
private static Class<?> load(String name){
Class<?> cls = null;
ClassLoader classLoader = null;
try{
classLoader = ClassUtil.class.getClassLoader();
cls = classLoader.loadClass(name);
if(logger.isDebugEnabled()){
logger.debug("Load Class["+name+"] by "+classLoader);
}
}
catch(Exception e){
logger.error(e);
}
return cls;
}
/**
* 编译代码并加载类
* @param filePath java代码路径
* @param source java代码
* @param ops 编译参数
* @return
*/
public static void compile(String filePath,String source,List<String> ops){
try {
writeJavaFile(filePath,source);
javac(ops,filePath);
}
catch (Exception e) {
logger.error(e);
}
}
/**
* 调用类方法
* @param cls 类
* @param methodName 方法名
* @param paramsCls 方法参数类型
* @param params 方法参数
* @return
*/
public static Object invoke(Class<?> cls,String methodName,Class<?>[] paramsCls,Object[] params){
Object result = null;
try {
Method method = cls.getDeclaredMethod(methodName, paramsCls);
Object obj = cls.newInstance();
result = method.invoke(obj, params);
}
catch (Exception e) {
logger.error(e);
}
return result;
}
}
FileClassLoader:
public class FileClassLoader extends ClassLoader {
private String rootDir;
public FileClassLoader(String rootDir){
this.rootDir = rootDir;
}
/**
* 重新编写findClass方法
* loadClass因为是从缓存中查询是否加载过,因此只能加载一次
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
//获取类的class文件字节数组
byte[] classData = getClassData(name);
if(null==classData){
throw new ClassNotFoundException();
}else {
//调用defineClass直接生成对象
return defineClass(name,classData,0,classData.length);
}
}
/**
* 编写获取class文件并转化为字节码流
* @param className
* @return
*/
private byte[] getClassData(String className){
//读取类文件的字节
String path = classNameToPath(className);
InputStream is = null;
try {
is = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
//读取类文件的字节码
while ((bytesNumRead = is.read(buffer))!=-1){
baos.write(buffer,0,bytesNumRead);
}
return baos.toByteArray();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 类文件的完全路径
* @param className
* @return
*/
private String classNameToPath(String className){
return rootDir+ File.separatorChar
+className.replace('.',File.separatorChar)+".class";
}
}
写完这个ProxyUtil,测试运行下:
public class Test {
public static void main(String[] args) throws Exception{
//普通代理模式
UserDao userDao = new ProxyDao(new UserDaoImpl());
userDao.query();
//自定义动态代理(增强方法固定)
UserDao userDao1 = (UserDao) ProxyUtil.newInstance(new UserDaoImpl());
userDao1.query();
}
}
我们可以看到输出:
代理实现
Dao query
自定义动态代理实现
Dao query
还以看到生成的文件$Proxy.java,这个就是我们动态生成的文件:
import com.wolf.test.spring.proxy.dao.UserDao;
public class $Proxy implements UserDao {
private UserDao target;
public $Proxy(UserDao target){
this.target = target;
}
public void query(){
System.out.println("自定义动态代理实现");
target.query();
}
}
四、自定义动态代理
上文我们可以看到虽然能够对任意的实现类实现代理,但是我们代理类中的增强措施是固定的,那么我们如何做到增强措施也由我们自定义呢?我们可以自己定义一个增强类,将代理类与增强的类隔离开,以后一切都执行增强类中的方法,可能有点模糊不清,没关系,直接上代码(我们为了验证下代理带返回参数的方法,因此重新写了一个接口):
IndexDao接口:
public interface IndexDao {
public String query() throws Exception;
}
IndexDaoImpl实现类:
public class IndexDaoImpl implements IndexDao {
public String query() throws Exception{
System.out.println("query");
return "index Dao";
}
}
自定义增强类接口:
public interface CustomHandler {
public Object invoke(Method method, Object...args) throws InvocationTargetException, IllegalAccessException;
}
增强类实现:
public class ProxyCustomHandler implements CustomHandler {
private Object object;
public ProxyCustomHandler(Object object){
this.object = object;
}
public Object invoke(Method method, Object... args) throws InvocationTargetException, IllegalAccessException {
System.out.println("代理");
return method.invoke(object,args);
}
}
细心的同学可能发现,变化很大,ProxyCustomHandler中为什么需要object,那是因为invoke方法中最后通过反射执行。
ProxyWithHandlerUtil:
public class ProxyWithHandlerUtil {
public static Object newInstance(Class targetClazz, CustomHandler h) throws Exception{
Object proxy = null;
String line = "\n";
String tab = "\t";
//拿到目标类的接口
Method[] methods = targetClazz.getDeclaredMethods();
String context="";
//import
String importContext = "import "+targetClazz.getName()+";"+line;
importContext+="import java.lang.reflect.Method;"+line;
importContext+="import com.wolf.test.spring.proxy.CustomHandler;"+line;
//第一行
String firstContext = "public class $Proxy implements "+ targetClazz.getSimpleName()+" {"+line;
//字段
String filedContext = tab+"private CustomHandler target;"+line;
//构造方法
String constructContext = tab+"public $Proxy(CustomHandler target){"+line
+tab+tab+"this.target = target;"+line
+tab+"}"+line;
String methodContext = "";
for (Method method : methods){
Class<?>[] classes = method.getParameterTypes();
Class returnType = method.getReturnType();
String paramaterContext = "";
String paraContext = "";
for (int i=0;i<classes.length;i++){
paramaterContext+=classes[i].getSimpleName()+" v"+i+",";
paraContext+=("v"+i+",");
}
if(method.getParameterTypes().length>0){
paramaterContext = paramaterContext.substring(0,paramaterContext.lastIndexOf(","));
paraContext = paraContext.substring(0,paraContext.lastIndexOf(","));
}
String methodCon = "";
//这个地方为什么用Class.forName,因为源码当中也是这样,而且源码当中加了一个classLoader
methodCon += tab+tab+"Method method = Class.forName(\""+targetClazz.getName()+"\").getDeclaredMethod(\""+method.getName()+"\");"+line;
methodContext+= tab+"public "+returnType.getSimpleName()+" "+method.getName()+"("+paramaterContext+") throws Exception{"+line+
methodCon+
tab+tab+"return ("+returnType.getSimpleName()+")target.invoke(method);"+line+
tab+"}"+line;
}
context = importContext+firstContext+filedContext+constructContext+methodContext+"}";
//设置编译参数
ArrayList<String> ops = new ArrayList<String>();
ops.add("-Xlint:unchecked");
//编译代码,返回class
ClassUtil.compile("/Users/Desktop/测试文件路径/proxy/$Proxy.java",context,ops);
FileClassLoader loader = new FileClassLoader("/Users/Desktop/测试文件路径/proxy/");
Class<?> clazz = loader.findClass("$Proxy");
Constructor<?> constructor = clazz.getConstructor(CustomHandler.class);
return constructor.newInstance(h);
}
}
Test类:
public class Test {
public static void main(String[] args) throws Exception{
//普通代理模式
UserDao userDao = new ProxyDao(new UserDaoImpl());
userDao.query();
System.out.println("---------------------");
//自定义动态代理(增强方法固定)
UserDao userDao1 = (UserDao) ProxyUtil.newInstance(new UserDaoImpl());
userDao1.query();
System.out.println("---------------------");
//自定义动态代理
try {
IndexDao indexDao = (IndexDao) ProxyWithHandlerUtil.newInstance(IndexDao.class,new ProxyCustomHandler(new IndexDaoImpl()));
System.out.println(indexDao.query());
} catch (Exception e) {
e.printStackTrace();
}
}
}
生成的.java文件:
import com.wolf.test.spring.proxy.dao.IndexDao;
import java.lang.reflect.Method;
import com.wolf.test.spring.proxy.CustomHandler;
public class $Proxy implements IndexDao {
private CustomHandler target;
public $Proxy(CustomHandler target){
this.target = target;
}
public String query() throws Exception{
Method method = Class.forName("com.wolf.test.spring.proxy.dao.IndexDao").getDeclaredMethod("query");
return (String)target.invoke(method);
}
}
最终结果:
代理实现
Dao query
---------------------
自定义动态代理实现
Dao query
---------------------
代理
query
index Dao
以上写法有不足之处,比如异常处理等,这个希望大家自己实现!!!
五、与JDK动态代理对比
JDK动态代理源码:
我们主要关注如何生成生成instance的:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
//核心之处,如何生成这个class对象
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
我们发现他的方法与我们自己实现的多了一个加载器,继续跟进源码可以发现这个classLoader的作用,本文自己实现的代理没有传递这个classLoader是因为本文主要以测试为主,哈哈。。。。
继续跟进会发现这个方法:
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class.
*/
//核心之处,如何生成类的
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}
我们发现有Class.forName(intf.getName,false,loader)方法,为什么这么做,相信大家都知道jvm中如何判断一个类是否唯一:根据类名+加载器,这就是方法为什么传classLoader;核心关心之处是ProxyGenerator.generateProxyClass方法,我们可以看到生成的是byte,其实这就是.class文件,只不过不像本文自己编译而已,可以看看如果用这个方法编译出来的.class文件是什么样子:
Test类:
public class Test {
public static void main(String[] args) throws Exception{
//利用jdk代理的方式,生成class文件
byte[] bytes = ProxyGenerator.generateProxyClass("$ProxyDao",new Class[]{IndexDao.class});
FileOutputStream fileOutputStream = new FileOutputStream("/Users/Desktop/测试文件路径/proxy/$ProxyDao.class");
fileOutputStream.write(bytes);
fileOutputStream.flush();
}
}
我们将生成的.class文件反编译:
public final class $ProxyDao extends Proxy implements IndexDao {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $ProxyDao(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String query() throws Exception {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (Exception | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.wolf.test.spring.proxy.dao.IndexDao").getMethod("query", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
与我们生成的$Proxy.java文件(关注构造方法与query方法),细细对比可以发现基本相同。
结束语:
本文从静态代理到自己实现的动态代理再与JDK动态代理做对比,由每一个代理存在的不足层层发展,最终描述JDK动态代理具体是如何实现的。
谢谢大家阅读,请大家多多指教文章中的不足,互相学习!!