- 浏览: 134652 次
- 性别:
- 来自: 上海
文章分类
- 全部博客 (68)
- EJB3 (1)
- JPA (1)
- Java IDE工具 (1)
- Struts 2.x (2)
- Ant&ivy (0)
- Maven (1)
- Hibernate (0)
- Spring (4)
- Java Web技术 (0)
- Oracle 10g + (3)
- JDK1.5+ (10)
- Java EE容器 (1)
- 设计模式 (1)
- Junit (0)
- 邮件协议 (1)
- BlockingQueue---DelayQueue (1)
- Mongodb (3)
- Spring中的destroy-method (1)
- Linux&Unix (6)
- JAVA (16)
- RabbitMQ (2)
- 操作系统 (1)
- 开源项目 (5)
- spring 4 (1)
- ZooKeeper (1)
- JVM (2)
- DDD (1)
最新评论
Java 动态代理机制分析及扩展,第 2 部分
原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-proxy2/
级别: 中级
王 忠平
, 软件工程师, IBM
何 平
, 软件工程师, IBM
2010 年 1 月 27 日
相信通过阅读“Java 动态代理机制分析和扩展,第 1 部分 ”,读者已经对 Java 动态代理机制有了一定的了解。本文将在上一篇的基础上,针对 Java 动态代理仅支持接口代理这一局限进行扩展,实现对类的代理。
本文希望将 Java 动态代理机制从接口扩展到类,使得类能够享有与接口类似的动态代理支持。
新扩展的类名为 ProxyEx,将直接继承于 java.lang.reflect.Proxy,也声明了与原 Proxy 类中同名的 public 静态方法,目的是保持与原代理机制在使用方法上的完全一致。
与原代理机制最大的区别在于,动态生成的代理类将不再从 Proxy 类继承,改而继承需被代理的类。由于 Java 的单继承原则,扩展代理机制所支持的类数目不得多于一个,但它可以声明实现若干接口。包管理的机制与原来相似,不支持一个以上的类和接口同时为非 public;如果仅有一个非 public 的类或接口,假设其包为 PackageA,则动态生成的代理类将位于包 PackageA;否则将位于被代理的类所在的包。生成的代理类也被赋予 final 和 public 访问属性,且其命名规则类似地为“父类名 +ProxyN”(N 也是递增的阿拉伯数字)。最后,在异常处理方面则与原来保持完全一致。
通过对 Java 动态代理机制的推演,我们已经获得了一个通用的方法模板。可以预期的是,通过模板来定制和引导代理类的代码生成,是比较可行的方法。我们将主要使用两个模板:类模板和方法模板。
package &Package; final public class &Name &Extends &Implements { private java.lang.reflect.InvocationHandler handler = null; &Constructors &Methods } |
类模板定制了代理类的代码框架。其中带“&”前缀的标签位被用来引导相应的代码替换。在此预留了包(&Package)、类 名(&ClassName)、类继承(&Extends)、接口实现(&Implements)、构造函数集 (&Constructors)及方法集(&Methods)的标签位。类模板还同时声明了一个私有型的调用处理器对象作为类成员。
&Modifiers &ReturnType &MethodName(&Parameters) &Throwables { java.lang.reflect.Method method = null; try { method = &Class.getMethod( \"& MethodName\", &ParameterTypes ); } catch(Exception e){ } Object r = null; try{ r = handler.invoke( this, method, &ParameterValues ); }&Exceptions &Return } |
方法模板定制了代理类方法集合中各个方法的代码框架,同样的带“&”前缀的标签位被用来引导相应的代码替换。在此预留了修饰符 (&Modifiers)、返回类型(&ReturnType)、方法名(&MethodName)、参数列表 (Parameters)、异常列表(&Throwables)、方法的声明类(&Class)、参数类型列表 (&ParameterTypes)、调用处理器的参数值列表(&ParameterValues),异常处理 (&Exceptions)及返回值(&Return)的标签位。
|
|
有了类模板和方法模板,代码生成过程就变得有章可依。基本过程可分为三步:1)生成代理类的方法集合;2)生成代理类的构造函数;3)最后生成整个代理类。
第一步,通过反射获得被代理类的所有 public 或 protected 且非 static 的 Method 对象列表,这些方法将被涵盖的原因是它们是可以被其他类所访问的。
第二步,遍历 Method 对象列表,对每个 Method 对象,进行相应的代码生成工作。
String declTemplate = "&Modifiers &ReturnType &MethodName(&Parameters) &Throwables"; String bodyTemplate = "&Declaration &Body"; // 方法声明 String declare = declTemplate.replaceAll("&Modifiers", getMethodModifiers( method )) .replaceAll("&ReturnType", getMethodReturnType( method )) .replaceAll("&MethodName", method.getName()) .replaceAll("&Parameters", getMethodParameters( method )) .replaceAll("&Throwables", getMethodThrowables( method )); // 方法声明以及实现 String body = bodyTemplate.replaceAll("&Declaration", declare ) .replaceAll("&Body", getMethodEntity( method )); |
这里涉及了一些 ProxyEx 类的私有的辅助函数如 getMethodModifiers 和 getMethodReturnType 等等,它们都是通过反射获取所需的信息,然后动态地生成各部分代码。函数 getMethodEntity 是比较重要的辅助函数,它又调用了其他的辅助函数来生成代码并替换标签位。
清单 4. ProxyEx 的静态方法 getMethodEntity()
private static String getMethodEntity( Method method ) { String template = "\n{" + "\n java.lang.reflect.Method method = null;" + "\n try{" + "\n method = &Class.getMethod( \"&MethodName\", &ParameterTypes );" + "\n }" + "\n catch(Exception e){" + "\n }" + "\n Object r = null;" + "\n try{" + "\n r = handler.invoke( this, method, &ParameterValues );" + "\n }&Exceptions" + "\n &Return" + "\n}"; String result = template.replaceAll("&MethodName", method.getName() ) .replaceAll("&Class", method.getDeclaringClass().getName() + ".class") .replaceAll("&ParameterTypes", getMethodParameterTypesHelper(method)) .replaceAll("&ParameterValues", getMethodParameterValuesHelper(method) ) .replaceAll("&Exceptions", getMethodParameterThrowablesHelper(method)) .replaceAll("&Return", getMethodReturnHelper( method ) ); return result; } |
当为 Class 类型对象生成该类型对应的字符代码时,可能涉及数组类型,反推过程会需要按递归方法生成代码,这部分工作由 getTypeHelper 方法提供
清单 5. ProxyEx 的静态方法 getTypeHelper()
private static String getTypeHelper(Class type) { if( type.isArray() ) { Class c = type.getComponentType(); return getTypeHelper(c) + "[]"; } else { return type.getName(); } } |
第三步,将所生成的方法保存进一个 map 表,该表记录的是键值对(方法声明,方法实现)。由于类的多态性,父类的方法可能被子类所覆盖,这时以上通过遍历所得的方法列表中就会出现重复的方法对 象,维护该表可以很自然地达到避免方法重复生成的目的,这就维护该表的原因所在。
相信读者依然清晰记得代理类是通过其构造函数反射生成的,而构造时传入的唯一参数就是调用处理器对象。为了保持与原代理机制的一致性,新的代理类的构造函数也同样只有一个调用处理器对象作为参数。模板简单如下
public &Constructor(java.lang.reflect.InvocationHandler handler) { super(&Parameters); this.handler = handler; } |
需要特别提一下的是 super 方法的参数值列表 &Parameters 的生成,我们借鉴了 Mock 思想,侧重于追求对象构造的成功,而并未过多地努力分析并寻求最准确最有意义的赋值。对此,相信读者会多少产生一些疑虑,但稍后我们会提及改进的方法,请 先继续阅读。
通过以上步骤,构造函数和所有需被代理的方法的代码已经生成,接下来就是生成整个代理类的时候了。这个过程也很直观,通过获取相关信息并对类模板中各个标签位进行替换,便可以轻松的完成整个代理类的代码生成。
|
|
等等,似乎遗忘了什么?从调用者的角度出发,我们希望代理类能够作为被代理类的如实代表呈现在用户面前,包括其内部状态,而这些状态通常是由类变量所体现出来的,于是就涉及到类变量的代理问题。
要解决这个问题,首先需要思考何时两者的类变量可能出现不一致?回答了这个问题,也就找到了解决思路。回顾代理类的构造函数,我们以粗糙的方 式构造了代理类实例。它们可能一开始就已经不一致了。还有每次方法调用也可能导致被两者的类变量的不一致。如何解决?直观的想法是:1)构造时需设法进行 同步;2)方法调用之前和之后也需设法进行同步。这样,我们就能够有效避免代理类和被代理类的类变量不一致的问题的出现了。
但是,如何获得被代理类的实例呢?从当前的的设计中已经没有办法做到。既然如此,那就继续我们的扩展之旅。只不过这次扩展的对象是调用处理器 接口,我们将在扩展后的接口里加入获取被代理类对象的方法,且扩展调用处理器接口将以 static 和 public 的形式被定义在 ProxyEx 类中。
清单 7. ProxyEx 类内的静态接口 InvocationHandlerEx
public static interface InvocationHandlerEx extends InvocationHandler { // 返回指定 stubClass 参数所对应的被代理类实体对象 Object getStub(Class stubClass); } |
新的调用处理器接口具备了获取被代理类对象的能力,从而为实现类变量的同步打开了通道。接下来还需要的就是执行类变量同步的 sync 方法,每个动态生成的代理类中都会被悄悄地加入这个私有方法以供调用。每次方法被分派转发到调用处理器执行之前和之后,sync 方法都会被调用,从而保证类变量的双向实时更新。相应的,方法模板也需要更新以支持该新特性。
Object r = null; try{ // 代理类到被代理类方向的变量同步 sync(&Class, true); r = handler.invoke( this, method, &ParameterValues ); // 被代理类到代理类方向的变量同步 sync(&Class, false); }&Exceptions &Return |
sync 方法还会在构造函数尾部被调用,从而将被代理类对象的变量信息同步到代理类对象,实现类似于拷贝构造的等价效果。相应的,构造函数模板也需要更新以支持该新特性。
public &Name(java.lang.reflect.InvocationHandler handler) { super(&Parameters); this.handler = handler; // 被代理类到代理类方向的变量同步 sync(null, false); } |
接下来介绍 sync 方法的实现,其思想就是首先获取被代理类的所有 Field 对象的列表,并通过扩展的调用处理器获得方法的声明类说对应的 stub 对象,然后遍历 Field 对象列表并对各个变量进行拷贝同步。
private synchronized void sync(java.lang.Class clazz, boolean toStub) { // 判断是否为扩展调用处理器 if( handler instanceof InvocationHandlerEx ) { java.lang.Class superClass = this.getClass().getSuperclass(); java.lang.Class stubClass = ( clazz != null ? clazz : superClass ); // 通过扩展调用处理器获得stub对象 Object stub = ((InvocationHandlerEx)handler).getStub(stubClass); if( stub != null ) { // 获得所有需同步的类成员列表,遍历并同步 java.lang.reflect.Field[] fields = getFields(superClass); for(int i=0; fields!=null&&i<fields.length; i++) { try { fields[i].setAccessible(true); // 执行代理类和被代理类的变量同步 if(toStub) { fields[i].set(stub, fields[i].get(this)); } else { fields[i].set(this, fields[i].get(stub)); } } catch(Throwable e) { } } } } } |
这里涉及到一个用于获取类的所有 Field 对象列表的静态辅助方法 getFields。为了提高频繁查询时的性能,配合该静态方法的是一个静态的 fieldsMap 对象,用于记录已查询过的类其所包含的 Field 对象列表,使得再次查询时能迅速返回其对应列表。相应的,类模板也需进行更新。
清单 11. 增加了静态 fieldsMap 变量后的类模板
package &Package; final public class &Name &Extends &Implements { private static java.util.HashMap fieldsMap = new java.util.HashMap(); private java.lang.reflect.InvocationHandler handler = null; &Constructors &Methods } |
清单 12. 声明在动态生成的代理类内部的静态方法 getFields
private static java.lang.reflect.Field[] getFields(java.lang.Class c) { if( fieldsMap.containsKey(c) ) { return (java.lang.reflect.Field[])fieldsMap.get(c); } java.lang.reflect.Field[] fields = null; if( c == java.lang.Object.class ) { fields = c.getDeclaredFields(); } else { java.lang.reflect.Field[] fields0 = getFields(c.getSuperclass()); java.lang.reflect.Field[] fields1 = c.getDeclaredFields(); fields = new java.lang.reflect.Field[fields0.length + fields1.length]; System.arraycopy(fields0, 0, fields, 0, fields0.length); System.arraycopy(fields1, 0, fields, fields0.length, fields1.length); } fieldsMap.put(c, fields); return fields; } |
|
|
代码生成以后,需要经过编译生成 JVM 所能识别的字节码,而字节码还需要通过类装载器载入 JVM 才能最终被真正使用,接下来我们将阐述如何动态编译及装载。
首先是动态编译。这部分由 ProxyEx 类的 getProxyClassCodeSource 函数完成。该函数分三步进行:第一步保存源代码到 .java 文件;第二步编译该 .java 文件;第三步从输出的 .class 文件读取字节码。
清单 13. ProxyEx 的静态方法 getProxyClassCodeSource
private static byte[] getProxyClassCodeSource( String pkg, String className, String declare ) throws Exception { // 将类的源代码保存进一个名为类名加“.java”的本地文件 File source = new File(className + ".java"); FileOutputStream fos = new FileOutputStream( source ); fos.write( declare.getBytes() ); fos.close(); // 调用com.sun.tools.javac.Main类的静态方法compile进行动态编译 int status = com.sun.tools.javac.Main.compile( new String[] { "-d", ".", source.getName() } ); if( status != 0 ) { source.delete(); throw new Exception("Compiler exit on " + status); } // 编译得到的字节码将被输出到与包结构相同的一个本地目录,文件名为类名加”.class” String output = "."; int curIndex = -1; int lastIndex = 0; while( (curIndex=pkg.indexOf('.', lastIndex)) != -1 ) { output = output + File.separator + pkg.substring( lastIndex, curIndex ); lastIndex = curIndex + 1; } output = output + File.separator + pkg.substring( lastIndex ); output = output + File.separator + className + ".class"; // 从输出文件中读取字节码,并存入字节数组 File target = new File(output); FileInputStream f = new FileInputStream( target ); byte[] codeSource = new byte[(int)target.length()]; f.read( codeSource ); f.close(); // 删除临时文件 source.delete(); target.delete(); return codeSource; } |
得到代理类的字节码,接下来就可以动态装载该类了。这部分由 ProxyEx 类的 defineClassHelper 函数完成。该函数分两步进行:第一步通过反射获取父类 Proxy 的静态私有方法 defineClass0;第二步传入字节码数组及其他相关信息并反射调用该方法以完成类的动态装载。
清单 14. ProxyEx 的静态方法 defineClassHelper
private static Class defineClassHelper( String pkg, String cName, byte[] codeSource ) throws Exception { Method defineClass = Proxy.class.getDeclaredMethod( "defineClass0", new Class[] { ClassLoader.class, String.class, byte[].class, int.class, int.class } ); defineClass.setAccessible(true); return (Class)defineClass.invoke( Proxy.class, new Object[] { ProxyEx.class.getClassLoader(), pkg.length()==0 ? cName : pkg+"."+cName, codeSource, new Integer(0), new Integer(codeSource.length) } ); } |
|
|
原动态代理机制中对接口数组有一些有趣的特点,其中之一就是接口的顺序差异会在一定程度上导致生成新的代理类,即使其实并无必要。其中的原因 就是因为缓存表是以接口名称列表作为关键字,所以不同的顺序就意味着不同的关键字,如果对应的关键字不存在,就会生成新但是作用重复的代理类。在 ProxyEx 类中,我们通过主动排序避免了类似的问题,提高动态生成代理类的效率。而且,如果发现数组中都是接口类型,则直接调用父类 Proxy 的静态方法 getProxyClass 生成代理类,否则才通过扩展动态代理机制生成代理类,这样也一定程度上改进了性能。
|
|
接下来需要考虑的是与原代理机制的兼容性问题。曾记否,Proxy 中还有两个静态方法:isProxyClass 和 getInvocationHandler,分别被用于判断 Class 对象是否是动态代理类和从 Object 对象获取对应的调用处理器(如果可能的话)。
清单 15. Proxy 的静态方法 isProxyClass 和 getInvocationHandler
static boolean isProxyClass(Class cl) static InvocationHandler getInvocationHandler(Object proxy) |
现在的兼容性问题,主要涉及到 ProxyEx 类与父类 Proxy 在关于动态生成的代理类的信息方面所面临的如何保持同步的问题。曾介绍过,在 Proxy 类中有个私有的 Map 对象 proxyClasses 专门负责保存所有动态生成的代理类类型。Proxy 类的静态函数 isProxyClass 就是通过查询该表以确定某 Class 对象是否为动态代理类,我们需要做的就是把由 ProxyEx 生成的代理类类型也保存入该表。这部分工作由 ProxyEx 类的静态方法 addProxyClass 辅助完成。
清单 16. ProxyEx 的静态方法 addProxyClass
private static void addProxyClass( Class proxy ) throws IllegalArgumentException { try { // 通过反射获取父类的私有 proxyClasses 变量并更新 Field proxyClasses = Proxy.class.getDeclaredField("proxyClasses"); proxyClasses.setAccessible(true); ((Map)proxyClasses.get(Proxy.class)).put( proxy, null ); } catch(Exception e) { throw new IllegalArgumentException(e.toString()); } } |
相对而言,原来 Proxy 类的静态方法 getInvocationHandler 实现相当简单,先判断是否为代理类,若是则直接类型转换到 Proxy 并返回其调用处理器成员,而扩展后的代理类并不非从 Proxy 类继承,所以在获取调用处理器对象的方法上需要一些调整。这部分由 ProxyEx 类的同名静态方法 getInvocationHandler 完成。
清单 17. ProxyEx 的静态方法 getInvocationHandler
public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException { // 如果Proxy实例,直接调父类的方法 if( proxy instanceof Proxy ) { return Proxy.getInvocationHandler( proxy ); } // 如果不是代理类,抛异常 if( !Proxy.isProxyClass( proxy.getClass() )) { throw new IllegalArgumentException("Not a proxy instance"); } try { // 通过反射获取扩展代理类的调用处理器对象 Field invoker = proxy.getClass().getDeclaredField("handler"); invoker.setAccessible(true); return (InvocationHandler)invoker.get(proxy); } catch(Exception e) { throw new IllegalArgumentException("Suspect not a proxy instance"); } } |
|
|
受限于 Java 的类继承机制,扩展的动态代理机制也有其局限,它不能支持:
- 声明为 final 的类;
- 声明为 final 的函数;
- 构造函数均为 private 类型的类;
|
|
阐述了这么多,相信读者一定很想看一下扩展动态代理机制是如何工作的。本文最后将以 2010 世博门票售票代理为模型进行演示。
首先,我们定义了一个售票员抽象类 TicketSeller。
public abstract class TicketSeller { protected String theme; protected TicketSeller(String theme) { this.theme = theme; } public String getTicketTheme() { return this.theme; } public void setTicketTheme(String theme) { this.theme = theme; } public abstract int getTicketPrice(); public abstract int buy(int ticketNumber, int money) throws Exception; } |
其次,我们会实现一个 2010 世博门票售票代理类 Expo2010TicketSeller。
public class Expo2010TicketSeller extends TicketSeller { protected int price; protected int numTicketForSale; public Expo2010TicketSeller() { super("World Expo 2010"); this.price = 180; this.numTicketForSale = 200; } public int getTicketPrice() { return price; } public int buy(int ticketNumber, int money) throws Exception { if( ticketNumber > numTicketForSale ) { throw new Exception("There is no enough ticket available for sale, only " + numTicketForSale + " ticket(s) left"); } int charge = money - ticketNumber * price; if( charge < 0 ) { throw new Exception("Money is not enough. Still needs " + (-charge) + " RMB."); } numTicketForSale -= ticketNumber; return charge; } } |
接着,我们将通过购票者类 TicketBuyer 来模拟购票以演示扩展动态代理机制。
public class TicketBuyer { public static void main(String[] args) { // 创建真正的TickerSeller对象,作为stub实体 final TicketSeller stub = new Expo2010TicketSeller(); // 创建扩展调用处理器对象 InvocationHandler handler = new InvocationHandlerEx() { public Object getStub(Class stubClass) { // 仅对可接受的Class类型返回stub实体 if( stubClass.isAssignableFrom(stub.getClass()) ) { return stub; } return null; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object o; try { System.out.println(" >>> Enter method: " + method.getName() ); o = method.invoke(stub, args); } catch(InvocationTargetException e) { throw e.getCause(); } finally { System.out.println(" <<< Exit method: " + method.getName() ); } return o; } }; // 通过ProxyEx构造动态代理 TicketSeller seller = (TicketSeller)ProxyEx.newProxyInstance( TicketBuyer.class.getClassLoader(), new Class[] {TicketSeller.class}, handler); // 显示代理类的类型 System.out.println("Ticket Seller Class: " + seller.getClass() + "\n"); // 直接访问theme变量,验证代理类变量在对象构造时同步的有效性 System.out.println("Ticket Theme: " + seller.theme + "\n"); // 函数访问price信息 System.out.println("Query Ticket Price..."); System.out.println("Ticket Price: " + seller.getTicketPrice() + " RMB\n"); // 模拟票务交易 buyTicket(seller, 1, 200); buyTicket(seller, 1, 160); buyTicket(seller, 250, 30000); // 直接更新theme变量 System.out.println("Updating Ticket Theme...\n"); seller.theme = "World Expo 2010 in Shanghai"; // 函数访问theme信息,验证扩展动态代理机制对变量同步的有效性 System.out.println("Query Updated Ticket Theme..."); System.out.println("Updated Ticket Theme: " + seller.getTicketTheme() + "\n"); } // 购票函数 protected static void buyTicket(TicketSeller seller, int ticketNumber, int money) { try { System.out.println("Transaction: Order " + ticketNumber + " ticket(s) with " + money + " RMB"); int charge = seller.buy(ticketNumber, money); System.out.println("Transaction: Succeed - Charge is " + charge + " RMB\n"); } catch (Exception e) { System.out.println("Transaction: Fail - " + e.getMessage() + "\n"); } } } |
最后,见演示程序的执行结果。
Ticket Seller Class: class com.demo.proxy.test.TicketSellerProxy0 Ticket Theme: World Expo 2010 Query Ticket Price... >>> Enter method: getTicketPrice <<< Exit method: getTicketPrice Ticket Price: 180 RMB Transaction: Order 1 ticket(s) with 200 RMB >>> Enter method: buy <<< Exit method: buy Transaction: Succeed - Charge is 20 RMB Transaction: Order 1 ticket(s) with 160 RMB >>> Enter method: buy <<< Exit method: buy Transaction: Fail - Money is not enough. Still needs 20 RMB. Transaction: Order 250 ticket(s) with 30000 RMB >>> Enter method: buy <<< Exit method: buy Transaction: Fail - There is no enough ticket available for sale, only 199 ticket(s) left Updating Ticket Theme... Query Updated Ticket Theme... >>> Enter method: getTicketTheme <<< Exit method: getTicketTheme Updated Ticket Theme: World Expo 2010 in Shanghai |
- “Java 动态代理机制分析及扩展,第 1 部分
”:阅读本系列的第一部分。
- “Dynamic Proxy Classes
”:查看 Java 动态代理的相关文档。
- “Introduction to Java Exception Handling
”:介绍了如何处理 Java 异常。
- “Java 理论与实践: 用动态代理进行修饰
”(developerWorks,2005 年 9 月):动态代理工具 是 java.lang.reflect 包的一部分,在 JDK 1.3 版本中添加到 JDK,它允许程序创建 代理对象。本文中,作者介绍了几个用于动态代理的应用程序。
- “利用动态代理的 Java 验证
”(developerWorks,2004 年 9 月):本文向您展示动态代理如何让核心应用程序代码独立于验证例程,而只关注业务逻辑。
- developerWorks Java 技术专区 :数百篇关于 Java 编程各个方面的文章。
王忠平,软件工程师,目前在 IBM 上海中国系统技术实验室任职。 |
何平,软件工程师,目前在 IBM 上海中国系统技术实验室任职。 |
发表评论
-
Java 8 配置Maven-javadoc-plugin
2016-12-09 13:56 5045Java 8 配置Maven-javadoc-plugin ... -
Compilation Overview
2014-12-22 11:03 631送src:http://openjdk.java.net/ ... -
RMI规范–第十章 RMI 通信协议
2014-07-03 15:32 1313RMI规范–第十章 RMI 通 ... -
优先级对列PriorityBlockingQueue
2013-09-29 17:31 1014优先级对列PriorityBlockin ... -
JAVA
2012-01-18 13:32 780BlockingQueue---DelayQueue 参考资 ... -
Java深度历险(十)——Java对象序列化与RMI(转)
2011-06-30 11:19 933原文地址: http://www.infoq.com/cn/ ... -
jconsole监控jboss/tomcat(转载)
2010-09-17 21:13 3477Jconsole 监控 JBOSS/TOMCAT ... -
转载 - Java 动态代理机制分析及扩展,第 1 部分
2010-09-14 22:28 806Java 动态代理机制分析及扩展,第 1 部分 原文地址:h ... -
熟练掌握JDK1.5枚举类型
2009-09-12 11:44 813Enum作为Sun全新引进的一 ...
相关推荐
动态代理,动态代理机制分析及扩展,动态代理机制分析及扩展,第,部分标记本文!Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地
Java动态代理机制是Java语言提供的一种强大的功能,它允许在运行时创建代理对象来实现特定接口,从而可以灵活地扩展或增强已有代码的功能。在Java中,动态代理主要通过两个类来实现:`java.lang.reflect.Proxy` 和 `...
Java 动态代理机制是Java反射机制的一部分,它允许开发者在运行时动态创建代理类和对象,以便在方法调用时插入自定义的行为。这种机制主要用于实现AOP(面向切面编程)和拦截器模式,提供了对方法调用的额外控制和...
`mysql-connector-java-5.1.27.jar`是这个驱动的一个特定版本,它允许Java开发者在他们的应用中无缝地访问和操作MySQL数据库。 MySQL连接器(JDBC驱动)是Java Database Connectivity (JDBC)的一部分,JDBC是Java ...
5.6.20 最新JDBC mysql-connector-java-5.1.32-bin.jar
mysql-connector-java-5.1.42-bin.jar 官网下载的驱动
mysql-connector-java-8.0.11.jar连接器,用于在hive和mysql的数据库连接
mysql驱动包 mysql-connector-java-5.1.13-bin.jar 方便快捷获取。。。
java视频开发ffmpeg-java.jar
JAVA连MySQL驱动mysql-connector-java-5.1.25.jar
使用C3P0额外依赖的一个jar包 :mchange-commons-java-0.2.3.4.jar
"mysql-connector-java-5.1.15-bin.jar"是这个驱动程序的一个特定版本,版本号为5.1.15。 在Java编程环境中,当你需要连接到MySQL数据库时,你需要在类路径中包含这个JAR文件。JDBC驱动程序主要分为四种类型:Type ...
在本文中,我们将深入探讨`mysql-connector-java-5.1.30`这个版本的特性、用途以及如何在Java项目中使用它。 ### MySQL Connector/J 5.1.30 特性 1. **兼容性**:此版本的驱动支持MySQL服务器版本5.0至5.7,这为...
官网下载 Begin Your Download To begin your download,...mysql-connector-java-5.1.23.zip MD5: 1a6cbbfccab7884134471992005fe0f9 Size: 3.7M Signature java编程eclipse和myeclipse链接mysql的jar包与相关文档
mysql-connector-java-5.1.30-bin.jar 最新的mysql jdbc
mysql 的jdbc 驱动。mysql-connector-java-5.1.38-bin.jar
MySQL Connector/J 8.0.27 是MySQL数据库与Java应用程序之间的重要桥梁,它是一个实现了JDBC(Java Database Connectivity)接口的驱动程序,允许Java开发者在应用中无缝地访问MySQL数据库。MySQL Connector/J使得...
连接MySQL所需要的最新jar包 mysql-connector-java-8.0.19.jar,喜欢最新版的可以下载使用
mysql数据库驱动包