代理模式精讲(手写JDK动态代理)
2018-10-10 08:39:41来源:博客园 阅读 ()
代理模式是一种架构型模式,表现出来就是一个类代表另一个类的功能,一般用在想对访问一个类的时候做一些控制,同时又不想影响正常的业务,这种代理模式在现实的生活中应用的也非常的广泛,我用穷举法给举几个好理解的例子:
1.像我们在大城市里的中介找房子的网站,房东、租户、中间商的三角关系,就是典型的代理模式。
2.还有就是帮助人找媳妇的媒婆,也是代理模式。
3.还有黄牛党等等还有很多。
正向代理和反向代理的区别:
1.正向代理代理的是用户,用户->代理服务器->谷歌的原网站模式,通过代理服务器访问谷歌。
2.反向代理代理的是服务器,比如我们访问百度的时候,其实感觉像访问一台电脑,其实在后面有成千上万台的服务器,比如通过代理服务器代理后台的服务器进行分发请求。
下面咱们通过代码进行一步一步的进行演进:
静态代理
父亲给儿子找对象,从代码不灵活进行演变。
package pattern.proxy; /** * 父亲给儿子找对象,父亲有儿子的引用 * 才可以给儿子找对象 */ public class Father { private Son person; public Father(Son person){ this.person=person; } public void findLove(){ System.out.println("根据你的要求物色"); this.person.findLove(); System.out.println("双色父母是不是同意"); } }
package pattern.proxy; public class Son { public void findLove(){ System.out.println("找对象,腿长的,漂亮的"); } }
package pattern.proxy; public class ProxyTest { public static void main(String[] args) { //父亲给儿子找对象 Father father=new Father(new Son()); father.findLove(); } }
下面使用接口进行改造:
package pattern.proxy; public interface Person { public void findLove(); public void findHouse(); }
package pattern.proxy; /** * 父亲给儿子找对象,父亲有儿子的引用 * 才可以给儿子找对象 */ public class Father implements Person { private Person person; public Father(Person person){ this.person=person; } public void findLove(){ System.out.println("根据你的要求物色"); person.findLove(); System.out.println("双色父母是不是同意"); } @Override public void findHouse() { person.findHouse(); } }
package pattern.proxy; public class Son implements Person { public void findLove(){ System.out.println("找对象,腿长的,漂亮的"); } @Override public void findHouse() { System.out.println("找房子了"); } }
package pattern.proxy; public class ProxyTest { public static void main(String[] args) { //父亲给儿子找对象 Person father=new Father(new Son()); father.findLove(); } }
其实如果接口里面代理的方法太多,是比较麻烦的,下面使用动态代理进行改造。
JDK动态代理
package pattern.proxy.jdk; public interface Person { public void findLove(); }
package pattern.proxy.jdk; public class XieMu implements Person { public void findLove(){ System.out.println("xiemu要找对象了,要求比较高,漂亮,美"); } }
package pattern.proxy.jdk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JdkMeiPo implements InvocationHandler { //被代理的对象 private Person target; public Object getInstance(XieMu target){ this.target=target; Class<?> clazz=target.getClass(); //生成一个新的对象,通过字节码重组 return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是媒婆,我要给你找对象,现在已经拿到你的需求"); System.out.println("开始物色"); method.invoke(target,args); System.out.println("如果合适,那就办事了"); return null; } }
package pattern.proxy.jdk; public class JdkMeiPoTest { public static void main(String[] args) { Person person=(Person) new JdkMeiPo().getInstance(new XieMu()); person.findLove(); } }
这里面的JdkMeiPo持有被代理人的对象,同时实现InvocationHandler接口,还有一个Proxy.newProxyInstance()的方法用来生成一个新的对象,通过字节码重组。
cglib动态代理
cglib是面向方法就可以进行代理,但是是通过字节码重组动态生成的接口。
package pattern.proxy.cglib; public class ZhangSan { public void findLove(){ System.out.println("漂亮的美美"); } }
package pattern.proxy.cglib; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CglibMeiPo implements MethodInterceptor{ public Object getInstance(Class<?> clazz) throws Exception{ Enhancer enhancer=new Enhancer(); //要把哪个设置为即将生成的新类父类 enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //业务的增强 System.out.println("我是媒婆,我要给你找对象,现在已经拿到你的需求"); System.out.println("开始物色"); methodProxy.invokeSuper(o,objects); System.out.println("如果合适,那就办事了"); return null; } }
package pattern.proxy.cglib; public class CglibTest { public static void main(String[] args) throws Exception { ZhangSan obj=(ZhangSan) new CglibMeiPo().getInstance(ZhangSan.class); obj.findLove(); } }
手写JDK动态代理
把动态代理生成的代理类保存到文件里:
package pattern.proxy.jdk; import sun.misc.ProxyGenerator; import java.io.FileOutputStream; public class JdkMeiPoTest { public static void main(String[] args) throws Exception {//原理: //1、拿到被代理对象的引用,并且获取到它的所有的接口,反射获取 //2、JDK Proxy类重新生成一个新的类、同时新的类要实现被代理类所有实现的所有的接口 //3、动态生成Java代码,把新加的业务逻辑方法由一定的逻辑代码去调用(在代码中体现) //4、编译新生成的Java代码.class //5、再重新加载到JVM中运行 //以上这个过程就叫字节码重组 //JDK中有个规范,只要要是$开头的一般都是自动生成的 byte[] bytes=ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Person.class}); FileOutputStream fos=new FileOutputStream("D://$proxy0.class"); fos.write(bytes); fos.flush(); fos.close(); } }
生成的$Proxy0文件:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import pattern.proxy.jdk.Person; public final class $Proxy0 extends Proxy implements Person { private static Method m1;
//m3调用的是findLove(),从下面的静态快可以看出来 private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); }
//Object对象的 public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } }
//代理类实现了Person类,重写了findLove的方法 public final void findLove() throws { try {
//$Proxy0继承了Proxy类,就可以用Proxy里的InvocationHandler里的h,这个其实就是JdkMeiPo实现的接口InvocationHander.
//调用JdkMeiPo的invoke方法,传入当先的对象和invoke中执行的findLove方法,参数为空 super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
//Object对象的 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); } }
//Object对象的 public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("pattern.proxy.jdk.Person").getMethod("findLove"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
package java.lang.reflect; import java.lang.ref.WeakReference; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.IdentityHashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiFunction; import sun.misc.ProxyGenerator; import sun.misc.VM; import sun.reflect.CallerSensitive; import sun.reflect.Reflection; import sun.reflect.misc.ReflectUtil; import sun.security.util.SecurityConstants; public class Proxy implements java.io.Serializable { private static final long serialVersionUID = -2222568056686623797L; /** parameter types of a proxy class constructor */ private static final Class<?>[] constructorParams = { InvocationHandler.class }; /** * a cache of proxy classes */ private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); /** * the invocation handler for this proxy instance. * @serial */ protected InvocationHandler h; /** * Prohibits instantiation. */ private Proxy() { } protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } @CallerSensitive public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException { final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } return getProxyClass0(loader, intfs); } private static void checkProxyAccess(Class<?> caller, ClassLoader loader, Class<?>... interfaces) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader ccl = caller.getClassLoader(); if (VM.isSystemDomainLoader(loader) && !VM.isSystemDomainLoader(ccl)) { sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); } ReflectUtil.checkProxyPackageAccess(ccl, interfaces); } } /** * Generate a proxy class. Must call the checkProxyAccess method * to perform permission checks before calling this. */ private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); } /* * a key used for proxy class with 0 implemented interfaces */ private static final Object key0 = new Object(); /* * Key1 and Key2 are optimized for the common use of dynamic proxies * that implement 1 or 2 interfaces. */ /* * a key used for proxy class with 1 implemented interface */ private static final class Key1 extends WeakReference<Class<?>> { private final int hash; Key1(Class<?> intf) { super(intf); this.hash = intf.hashCode(); } @Override public int hashCode() { return hash; } @Override public boolean equals(Object obj) { Class<?> intf; return this == obj || obj != null && obj.getClass() == Key1.class && (intf = get()) != null && intf == ((Key1) obj).get(); } } /* * a key used for proxy class with 2 implemented interfaces */ private static final class Key2 extends WeakReference<Class<?>> { private final int hash; private final WeakReference<Class<?>> ref2; Key2(Class<?> intf1, Class<?> intf2) { super(intf1); hash = 31 * intf1.hashCode() + intf2.hashCode(); ref2 = new WeakReference<Class<?>>(intf2); } @Override public int hashCode() { return hash; } @Override public boolean equals(Object obj) { Class<?> intf1, intf2; return this == obj || obj != null && obj.getClass() == Key2.class && (intf1 = get()) != null && intf1 == ((Key2) obj).get() && (intf2 = ref2.get()) != null && intf2 == ((Key2) obj).ref2.get(); } } /* * a key used for proxy class with any number of implemented interfaces * (used here for 3 or more only) */ private static final class KeyX { private final int hash; private final WeakReference<Class<?>>[] refs; @SuppressWarnings("unchecked") KeyX(Class<?>[] interfaces) { hash = Arrays.hashCode(interfaces); refs = (WeakReference<Class<?>>[])new WeakReference<?>[interfaces.length]; for (int i = 0; i < interfaces.length; i++) { refs[i] = new WeakReference<>(interfaces[i]); } } @Override public int hashCode() { return hash; } @Override public boolean equals(Object obj) { return this == obj || obj != null && obj.getClass() == KeyX.class && equals(refs, ((KeyX) obj).refs); } private static boolean equals(WeakReference<Class<?>>[] refs1, WeakReference<Class<?>>[] refs2) { if (refs1.length != refs2.length) { return false; } for (int i = 0; i < refs1.length; i++) { Class<?> intf = refs1[i].get(); if (intf == null || intf != refs2[i].get()) { return false; } } return true; } } /** * A function that maps an array of interfaces to an optimal key where * Class objects representing interfaces are weakly referenced. */ private static final class KeyFactory implements BiFunction<ClassLoader, Class<?>[], Object> { @Override public Object apply(ClassLoader classLoader, Class<?>[] interfaces) { switch (interfaces.length) { case 1: return new Key1(interfaces[0]); // the most frequent case 2: return new Key2(interfaces[0], interfaces[1]); case 0: return key0; default: return new KeyX(interfaces); } } } /** * A factory function that generates, defines and returns the proxy class given * the ClassLoader and array of interfaces. */ private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { private static final String proxyClassNamePrefix = "$Proxy"; private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { 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 + "."; } long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } } }
//loalder类加载器
//实现的接口数组
//h所有动态代理类的方法调用,都会交由InvocationHandler接口实现类里的invoke()方法去处理。这是动态代理的关键所在 @CallerSensitive 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<?> 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); } } private static void checkNewProxyPermission(Class<?> caller, Class<?> proxyClass) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { if (ReflectUtil.isNonPublicProxyClass(proxyClass)) { ClassLoader ccl = caller.getClassLoader(); ClassLoader pcl = proxyClass.getClassLoader(); // do permission check if the caller is in a different runtime package // of the proxy class int n = proxyClass.getName().lastIndexOf('.'); String pkg = (n == -1) ? "" : proxyClass.getName().substring(0, n); n = caller.getName().lastIndexOf('.'); String callerPkg = (n == -1) ? "" : caller.getName().substring(0, n); if (pcl != ccl || !pkg.equals(callerPkg)) { sm.checkPermission(new ReflectPermission("newProxyInPackage." + pkg)); } } } } public static boolean isProxyClass(Class<?> cl) { return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl); } @CallerSensitive public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException { if (!isProxyClass(proxy.getClass())) { throw new IllegalArgumentException("not a proxy instance"); } final Proxy p = (Proxy) proxy; final InvocationHandler ih = p.h; if (System.getSecurityManager() != null) { Class<?> ihClass = ih.getClass(); Class<?> caller = Reflection.getCallerClass(); if (ReflectUtil.needsPackageAccessCheck(caller.getClassLoader(), ihClass.getClassLoader())) { ReflectUtil.checkPackageAccess(ihClass); } } return ih; } private static native Class<?> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len); }
下面实现一个自己的动态代理:
package pattern.proxy.custom; public interface Person { public void findLove(); }
package pattern.proxy.custom; public class XieMu implements Person { public void findLove(){ System.out.println("xiemu要找对象了,要求比较高,漂亮,美"); } }
package pattern.proxy.custom; import java.lang.reflect.Method; public class CustomMeiPo implements JavaInvocationHandler { private Person target; public Object getInstance(Person target){ this.target=target; Class<?> clazz=target.getClass(); return JavaProxy.newProxyInstance(new JavaClassLoader(),clazz.getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是媒婆,我要给你找对象,现在已经拿到你的需求"); System.out.println("开始物色"); method.invoke(target,args); System.out.println("如果合适,那就办事了"); return null; } }
根据kdk里面的主要java类创建自定义的类:
package pattern.proxy.custom; import javax.tools.JavaCompiler; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Iterator; public class JavaProxy { public static final String Ln="\r\n"; public static Object newProxyInstance(JavaClassLoader classLoader, Class<?>[] interfaces, JavaInvocationHandler h){ try { //1.动态生成源代码.java文件 String src=generateSrc(interfaces); //2.java文件输出到磁盘 String filePath=JavaProxy.class.getResource("").getPath(); System.out.println(filePath); File file=new File(filePath+"$Proxy0.java"); FileWriter fw=new FileWriter(file); fw.write(src); fw.flush(); fw.close(); //3.把生成的.java文件编译成.class文件 JavaCompiler compiler= ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager manage=compiler.getStandardFileManager(null,null,null); Iterable iterable=manage.getJavaFileObjects(file); JavaCompiler.CompilationTask task=compiler.getTask(null,null,null,null,null,null); task.call(); manage.close(); //4.编译生成的.class文件加载到JVM中来 Class proxyClass=classLoader.findClass("$Proxy0"); Constructor c=proxyClass.getConstructor(JavaInvocationHandler.class);
file.delete(); //5.返回字节码重组后新的对象 return c.newInstance(h); }catch (Exception e){ e.printStackTrace(); } return null; } public static String generateSrc(Class<?>[] interfaces){ StringBuffer sb=new StringBuffer(); sb.append("package pattern.proxy.custom;" + Ln); sb.append("import java.lang.reflect.Method;"+ Ln); sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + Ln); sb.append("public JavaInvocationHandler h;" + Ln); sb.append("public $Proxy0(JavaInvocationHandler h) { "+Ln); sb.append("this.h=h;"+Ln); sb.append("}"+Ln); for (Method m:interfaces[0].getMethods()){ sb.append("public "+ m.getReturnType().getName() + " " +m.getName() + "(){"+ Ln); sb.append("try{"+Ln); sb.append("Method m="+interfaces[0].getName()+".class.getMethod(\"" + m.getName() + "\",new Class[]{});" + Ln); sb.append("this.h.invoke(this,m,null);"+Ln); sb.append("}catch(Throwable e){" + Ln); sb.append("e.printStackTrace();" + Ln); sb.append("}" + Ln); sb.append("}" + Ln); } sb.append("}" + Ln); return sb.toString(); } }
package pattern.proxy.custom; import java.lang.reflect.Method; public interface JavaInvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
package pattern.proxy.custom; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class JavaClassLoader extends ClassLoader { private File classPathFile; public JavaClassLoader(){ String classPath=JavaClassLoader.class.getResource("").getPath(); this.classPathFile=new File(classPath); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String className=JavaClassLoader.class.getPackage().getName()+"."+name; if(classPathFile!=null){ File classFile=new File(classPathFile,name.replaceAll("\\.","/")+".class"); if(classFile.exists()){ FileInputStream in=null; ByteArrayOutputStream out=null; try { in=new FileInputStream(classFile); out=new ByteArrayOutputStream(); byte[] buff=new byte[1024]; int len; while ((len=in.read(buff))!=-1){ out.write(buff,0,len); } return defineClass(className,out.toByteArray(),0,out.size()); }catch (Exception e){ e.printStackTrace(); }finally { if(in!=null){ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if(out!=null){ try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } } return null; } }
测试类:
package pattern.proxy.custom; public class CustomProxyTest { public static void main(String[] args) { Person person=(Person) new CustomMeiPo().getInstance(new XieMu()); person.findLove(); } }
效果:
我是媒婆,我要给你找对象,现在已经拿到你的需求
开始物色
xiemu要找对象了,要求比较高,漂亮,美
如果合适,那就办事了
喜欢和喜欢学习的一起交流技术!
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:小白学线程入门(易懂)
下一篇:Oracle数据库的创建表全
- 代理项 Surrogate 2020-06-11
- 设计模式-委派/策略模式 2020-06-09
- 深入理解:设计模式中的七大设计原则 2020-06-07
- 设计模式---类之间的关系知多少 2020-06-07
- 你与面试官所了解的单例模式并不一样! 2020-06-06
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash