代理模式及手写实现动态代理

  • 一、代理模式
    • 1. 定义
    • 2. 示例
      • (1)静态代理
      • (2)动态代理
    • 3. 通用类图
    • 4. 代理模式的优点
  • 二、jdk动态代理实现原理
    • 1. jdk动态代理源码分析(通过该示例学会阅读源码的方法)
    • 2.jdk动态代理生成的代理类的源码
    • 3.总结
  • 三、手写实现jdk动态代理

一、代理模式

熟悉代理模式的可以直接点击目录第二章,jdk动态代理实现原理,本文的精髓所在,通过这个例子,教大家如何去学习源码。

1. 定义

代理模式(Proxy Pattern) 是一个使用频率非常高的设计模式,其定义如下:

Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问。)

2. 示例

(1)静态代理

  • 游戏者接口

    /*** 游戏者接口*/
    public interface IGamePlayer {/*** 登录游戏** @param user     用户名* @param password 用户密码*/void login(String user, String password);/*** 玩游戏*/void play();
    }
    
  • 游戏者

    public class GamePlayer implements IGamePlayer {private String name = "";public GamePlayer(String name) {this.name = name;}/*** 登录*/@Overridepublic void login(String user, String password) {System.out.println("登录名为" + user + "的用户" + this.name + "登录成功!");}/*** 玩游戏*/@Overridepublic void play() {System.out.println(this.name + "的账号正在进行游戏");}
    }
  • 代练者

    /**
    * 代练类,负责帮助玩家代练,按时计费
    */
    public class GamePlayerProxy implements IGamePlayer {/*** 代练对象*/private IGamePlayer gamePlayer = null;/*** 代练一小时价格*/private int PER_HUOR_COST = 5;/*** 通过构造方法传入需要代练服务的对象*/public GamePlayerProxy(IGamePlayer gamePlayer) {this.gamePlayer = gamePlayer;}@Overridepublic void login(String user, String password) {System.out.println("代练开始登录账号");this.gamePlayer.login(user, password);}@Overridepublic void play() {long strTime = System.currentTimeMillis();Date date = new Date(strTime);Calendar calendar = Calendar.getInstance();calendar.setTime(date);System.out.println("代练开始时间是" + date);this.gamePlayer.play();long endTime = System.currentTimeMillis();int costTime = (int) (endTime - strTime);//使用毫秒模拟小时,给开始时间增加消耗的毫秒数个小时calendar.add(Calendar.HOUR, costTime);System.out.println("代练结束时间是" + calendar.getTime());System.out.println("共计代练" + costTime + "小时,收费" + costTime * PER_HUOR_COST + "元。");}
    }
    
  • 场景类

    public class Client {public static void main(String[] args) {//定义一个痴迷的玩家IGamePlayer player = new GamePlayer("张三");//然后再定义一个代练者IGamePlayer proxy = new GamePlayerProxy(player);//登陆账号proxy.login("zhangSan", "password");//开始代练proxy.play();}
    }
    
  • 测试结果

(2)动态代理

动态代理步骤:
1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
2.创建被代理的类以及接口
3.通过Proxy的静态方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
4.通过代理调用方法

  • Person接口

    public interface Person {void hello();
    }
    
  • ZhangSan实现类

    public class ZhangSan implements Person {@Overridepublic void hello() {System.out.println("我是张三,大家好");}
    }
    
  • MyInvocationHandler

    public class MyInvocationHandler implements InvocationHandler {private ZhangSan zhangSan;public MyInvocationHandler(ZhangSan zhangSan) {this.zhangSan = zhangSan;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {before();Object o = method.invoke(zhangSan, args);after();return o;}public void before() {System.out.println("动态代理前置处理");}public void after() {System.out.println("动态代理后置处理");}}
    
  • Test类

    public class Test {public static void main(String[] args) {try {Person person = (Person) Proxy.newProxyInstance(Test.class.getClassLoader(),ZhangSan.class.getInterfaces(), new MyInvocationHandler(new ZhangSan()));person.hello();} catch (Exception e) {e.printStackTrace();}}
    }
    
  • 测试结果

3. 通用类图

代理模式的通用类如图所示,如果不能理解也可以先看下面的示例,再返回来重新查看通用模型

代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策
略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理 模式可以提供非常好的访问控制。在一些著名开源软件中也经常见到它的身影,如Spring框架中的aop就是基于代理模式(更确切的说是动态代理)实现的。我们先看一下类图中三个组件的解释:

- Subject抽象主题角色

抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。

/*** 业务接口*/
public interface ISubject {/*** 定义一个方法*/void request();
}

- RealSubject具体主题角色

也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者,这个往往是我们需要关注的重点。

/*** 实际业务对象*/
public class RealISubject implements ISubject {/*** 业务实现方法*/@Overridepublic void request() {//业务逻辑处理}
}

- Proxy代理主题角色

也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制
委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。

public class Proxy implements ISubject {/*** 被代理对象的引用*/private ISubject subject = null;/*** 默认被代理对象*/public Proxy() {this.subject = new Proxy();}/*** 传入被代理对象*/public Proxy(ISubject subject) {this.subject = subject;}/*** 代理对象控制被代理对象行为,添加预处理和后置处理,如可以添加日志打印,权限控制,事务管理*/@Overridepublic void request() {before();this.subject.request();after();}public void before() {//预处理}public void after() {//后置处理}
}

4. 代理模式的优点

  • 职责清晰
    主题角色(被代理对象)就是实现实际的业务逻辑,不用关心其他非本职责的逻辑处理,如日志,事务的开启、关闭、回滚等,就可以通过后期代理来实现。在spring中常说的AOP(面向切面编程)的思想就是将代码进行横向切分,通过预编译方式和运行期间动态代理实现程序功能的统一维护。它可以将模块划分的更加细致,减少各个模块和公用模块之间的耦合,让我们将关注点转移到业务本身。
  • 高扩展性
    具体主题角色(被代理对象)是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。在这种情况下,我们的代理类只需直接调用真实对象的业务方法即可,我们只需要关心流程控制,和一些其他的逻辑。如我们在使用业务代码前,进行权限验证,如进入业务代码前,记录此次调用,将调用记录(如时间,调用方法,调用地点)写入数据库来方便后台监控用户的行为。同样的,我们在主题角色(被代理对象)中只需专注于业务逻辑变更即可。

二、jdk动态代理实现原理

1. jdk动态代理源码分析(通过该示例学会阅读源码的方法)

以之前的动态代理 代码为例,我们阅读jdk动态代理源码分析其实现原理。在阅读源码时我们最重要的是找对切入点,在这段代码中,没有复杂的逻辑,很明显,我们以newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)这个方法为切入点。我们在代码中着重观察我们的代理类生成的路径,f3进入这个方法。这里如果不懂或者暂时不感兴趣可直接查看下一部分和总结部分查看结论。推荐大家最好可以跟着自己走一遍。大家也可以查看我的另一篇文章ArrayList核心源码分析-扩容机制(jdk1.8)来练习源码阅读能力。

  1. newProxyInstance:Proxy.java(java.lang.reflect)
    在这个方法中,找到关键代码,这一行代码返回代理类的class文件,下面再通过反射创造实例返回。(我这里因为篇幅原因省去了这些类的具体代码,如果看起来有点晕,强烈建议自己动手试试,或者直接查看结论)

    /*
    * Look up or generate the designated proxy class.
    * 查找或生成指定的代理类。
    */
    Class<?> cl = getProxyClass0(loader, intfs);
    

    再次点击f3进入getProxyClass0方法

  2. getProxyClass0:Proxy (java.lang.reflect)
    这个方法非常简单,我们观察它的返回值

    // 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
    //如果存在由实现了给定接口的给定加载器定义的代理类,则将仅返回缓存的副本;
    //否则,它将通过ProxyClassFactory创建代理类。在java中是采用了享元模式存储了已经生成的代理类的class文件
    return proxyClassCache.get(loader, interfaces);
    

    再次f3进入get方法

  3. get:WeakCache (java.lang.reflect)
    我们可以观察到最终总是返回value

      while (true) {if (supplier != null) {// supplier might be a Factory or a CacheValue<V> instanceV value = supplier.get();if (value != null) {return value;}}...
    

    f3进入get方法

    public interface Supplier<T> {/*** Gets a result.** @return a result*/T get();
    }
    

    我们走到这里无法确定到底调用的是那个实现类,此时可以在上一层V value = supplier.get();处打上断点。然后debug运行Test类,可以看到停到断点处。

    我们可以观察到调用的是WeakCache$Factory类的方法。找到该类的这个方法

  4. get:WeakCache$Factory (java.lang.reflect)即WeakCache的内部类Factory
    观察该方法,我们发现最终return value,再细看有一行create value,发现这一行关键代码,==在这行打上断点。==我们每次进行类的跳转如果不是很清楚就可以在每次跳转前打上断点。

    value = Objects.requireNonNull(valueFactory.apply(key, parameter));
    

    这段代码requireNonNull()做一个非空检查,核心是valueFactory.apply(key, parameter),再次f3进入该方法
    可以看到这是一个接口,我们无法判断是那个实现类,我们继续使用上次的方法,查看到底调用的是哪个实现类,在debug中f6,走到我们上一步中打下的断点。

    我们可以看到代理类的class文件从这个方法中生成,我们观察debug窗口变量监控

    可以看到发现我们调用的是Proxy$ProxyClassFactory的apply()方法。找到该类的该方法

  5. apply:Proxy$ProxyClassFactory (java.lang.reflect)

    我们观察到该方法中有一行代码,生成指定的proxy class,这就是我们要找的东西啦。

    我们可以看到该方法生成class文件的byte流,我们再进入方法,可以观察到其实是在使用文件写入的方式,动态写入java文件,然后编译java文件生成class文件,最后将其转换为byte流返回。其实动态代理和静态代理的区别就是:静态是在运行程序时已经生成了class文件并且加载进了jvm。我们看到下一行defineClass0()其实就是将该class文件动态的加载到jvm中,显然动态代理就是运行时加载代理类的class。我们会在第三部分会自己手写实现这个gennerate方法,所以不在重复。可以从第三部分中获得更多的细节。

2.jdk动态代理生成的代理类的源码

改变之前的Test测试类代码如下

public class Test {public static void main(String[] args) {try {Person person = (Person) Proxy.newProxyInstance(Test.class.getClassLoader(),ZhangSan.class.getInterfaces(), new MyInvocationHandler(new ZhangSan()));//之前源码分析中的关键方法byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});//在哪里存储生成的class文件,因为idea中有反编译插件,所以我们可以看到该java文件源码FileOutputStream f = new FileOutputStream("src/main/java/com/jdk_proxy/$Proxy0.class");f.write(bytes);person.hello();System.out.println(person.getClass());} catch (Exception e) {e.printStackTrace();}}
}

生成的class文件如下,注意看注释行

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//import com.jdk_proxy.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
//可以看到该类实现了newProxyInstance:Proxy.java(java.lang.reflect)这个方法传入的接口参数,继承了Proxy类
public final class $Proxy0 extends Proxy implements Person {private static Method m1;private static Method m3;private static Method m2;private static Method m0;//我们可以看到它会传递InvocationHandler至父类Proxypublic $Proxy0(InvocationHandler var1) throws  {super(var1);}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);}}//这里就是我们的代理类重写接口中的方法,也是我们最终在调用代理类中的方法时所真正调用的方法public final void hello() throws  {try {//注意这里的三个参数,我们可以看到他将this和在staic块中反射生成的接口中的方法,和调用代理传入的参数super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | 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);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}//在加载时通过反射从实现的接口中获得接口中的Method对象。static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m3 = Class.forName("com.jdk_proxy.Person").getMethod("hello");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());}}
}

Proxy类

public class Proxy implements java.io.Serializable {.../*** the invocation handler for this proxy instance.* @serial*/protected InvocationHandler h;protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;}...

InvocationHandler类

public interface InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}

3.总结

  • 动态代理实现过程(结合上面的class文件和我们示例程序)
  1. 通过实现 InvocationHandler 接口,自定义调用处理器;
  2. 通过调用 newProxyInstance:Proxy.java(java.lang.reflect) 指定ClassLoader对象和一组 interface 来创建动态代理类;
  3. 通过newProxyInstance传入的接口来动态的生成代理类java文件(下一部分有自己实现代码,可以详细了解原理),然后进行编译,最后加载到jvm中。代理类中的所有方法都调用了newProxyInstance:Proxy.java(java.lang.reflect) 中传入的InvocationHandler的invoke方法。也就是说我们的代理类重写了接口中的所有方法,然后再这些方法中只做了一件事,调用invoke:InvocationHandler
  4. 通过反射创建代理类对象返回。
  • 源码分析总结
    我们将断点打ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});这个方法上,debug运行

    生成Java文件,编译成class文件,然后加载到jvm中,整个调用链如下。在学习时如果上面源码分析部分有点晕可以利用这种方法,快速定位代码。

三、手写实现jdk动态代理

  • Generator

    public class Generator {static String rt = "\r\n";public static Class<?> generator(Class<?>[] interfaces, MyClassLoader loader, MyInvocationHandler h) throws Exception {try {// 1.创建代理类java源码文件,写入到硬盘中..Method[] methods = interfaces[0].getMethods();String name = interfaces[0].getName();String packageName = name.substring(0, name.lastIndexOf("."));StringBuilder stringBuilder = new StringBuilder();//包名stringBuilder.append("package ");stringBuilder.append(packageName);stringBuilder.append(";");stringBuilder.append(rt);//导入的类stringBuilder.append("import java.lang.reflect.Method;");stringBuilder.append(rt);stringBuilder.append("import com.proxy_design.*;");stringBuilder.append(rt);//类的声明stringBuilder.append("public class Proxy0 extends MyProxy implements ");stringBuilder.append(interfaces[0].getName());stringBuilder.append("{");stringBuilder.append(rt);//构造方法stringBuilder.append("\tpublic Proxy0(MyInvocationHandler h){");stringBuilder.append(rt);stringBuilder.append("\t\tsuper(h);");stringBuilder.append(rt);stringBuilder.append("\t}");stringBuilder.append(rt);//添加重写后的方法,在所有方法中调用super.h.invoke方法即可stringBuilder.append(getMethodString(methods, interfaces[0]));stringBuilder.append(rt);stringBuilder.append("}");stringBuilder.append(rt);String proxyClass = stringBuilder.toString();// 2. 将代理类源码文件写入硬盘中,根据自己的目录输入String filename = "src/main/java/"+ packageName.replace(".", "/") + "/Proxy0.java";File f = new File(filename);FileWriter fw = new FileWriter(f);fw.write(proxyClass);fw.flush();fw.close();// 3.使用JavaJavaCompiler 编译该Proxy0源代码 获取class文件JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);Iterable units = fileMgr.getJavaFileObjects(filename);JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);t.call();fileMgr.close();return loader.findClass("Proxy0");} catch (Exception e) {e.printStackTrace();}throw new ClassNotFoundException();}public static String getMethodString(Method[] methods, Class intf) {StringBuilder proxyMe = new StringBuilder();for (int i = 0; i < methods.length; i++) {proxyMe.append("\tprivate static Method m").append(i).append(";").append(rt);}for (int i = 0; i < methods.length; i++) {proxyMe.append("\tpublic void ");proxyMe.append(methods[i].getName());proxyMe.append("(){");proxyMe.append(rt);proxyMe.append("\t\ttry {");proxyMe.append(rt);proxyMe.append("\t\t\tsuper.h.invoke(this,m");proxyMe.append(i);proxyMe.append(",null);");proxyMe.append(rt);proxyMe.append("\t\t} catch (Throwable throwable) {");proxyMe.append(rt);proxyMe.append("\t\t\tthrowable.printStackTrace();");proxyMe.append(rt);proxyMe.append("\t\t}");proxyMe.append(rt);proxyMe.append("\t}");proxyMe.append(rt);}//从接口中反射获得所有方法proxyMe.append("\tstatic {");proxyMe.append(rt);proxyMe.append("\t\ttry{");proxyMe.append(rt);for (int i = 0; i < methods.length; i++) {proxyMe.append("\t\t\tm");proxyMe.append(i);proxyMe.append("=");proxyMe.append(intf.getName());proxyMe.append(".class.getMethod(\"");proxyMe.append(methods[i].getName());proxyMe.append("\",new Class[]{});");proxyMe.append(rt);}proxyMe.append("\t\t} catch (NoSuchMethodException var2) {");proxyMe.append(rt);proxyMe.append("\t\t\tthrow new NoSuchMethodError(var2.getMessage());");proxyMe.append(rt);proxyMe.append("\t\t}");proxyMe.append(rt);proxyMe.append("\t}");return proxyMe.toString();}
    }
  • MyClassLoader

    public class MyClassLoader extends ClassLoader {@Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {//class文件将生成的位置File file = new File("src/main/java/com/proxy_design/Proxy0.class");if (file.exists()) {try {//将文件转换为byte流FileInputStream fis = new FileInputStream(file);ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);byte[] b = new byte[1000];int n;while ((n = fis.read(b)) != -1) {bos.write(b, 0, n);}fis.close();bos.close();byte[] buffer = bos.toByteArray();//加载类返回类,此时静态块中会被调用return defineClass("com.proxy_design." + name, buffer, 0, buffer.length);} catch (IOException e) {e.printStackTrace();}}return super.findClass(name);
    }
    

}
```

  • MyInvocationHandler

    public interface MyInvocationHandler {public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;}
    
  • MyInvocationHandlerImp

    public class MyInvocationHandlerImp implements MyInvocationHandler {private ZhangSan zhangSan;public MyInvocationHandlerImp(ZhangSan zhangSan) {this.zhangSan = zhangSan;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("动态代理前置处理");//调用被代理对象的真实方法Object o = method.invoke(zhangSan, args);System.out.println("动态代理后置处理");return o;}
    }
  • MyProxy

    public class MyProxy {/*** 在使用动态代理生成的proxy类中,他会继承本类,然后在构造方法中传入h*/protected MyInvocationHandler h;public MyProxy(MyInvocationHandler h) {this.h = h;}public static Object newProxyInstance(MyClassLoader loader,Class<?>[] interfaces,MyInvocationHandler h) throws Exception {Class<?> proxy = Generator.generator(interfaces, loader, h);Constructor<?> proxyConstructor = proxy.getConstructor(MyInvocationHandler.class);return proxyConstructor.newInstance(h);}
    }
    
  • Test

    public class Test {public static void main(String[] args) {try {Person proxyMapper = (Person) MyProxy.newProxyInstance(new MyClassLoader(),ZhangSan.class.getInterfaces(), new MyInvocationHandlerImp(new ZhangSan()));proxyMapper.hello();} catch (Exception e) {e.printStackTrace();}}
    }
    
  • Person、ZhangSan和上面一样。(动态代理示例)

  • 生成的Proxy类源码

    public class Proxy0 extends MyProxy implements com.proxy_design.Person{public Proxy0(MyInvocationHandler h){super(h);}private static Method m0;//实际生效方法public void hello(){try {super.h.invoke(this,m0,null);} catch (Throwable throwable) {throwable.printStackTrace();}}static {try{m0=com.proxy_design.Person.class.getMethod("hello",new Class[]{});} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());}}
    }
    

两万字吐血总结,代理模式及手写实现动态代理(aop原理,基于jdk动态代理)相关推荐

  1. Spring AOP之---基于JDK动态代理和CGLib动态代理的AOP实现

    AOP(面向切面编程)是OOP的有益补充,它只适合那些具有横切逻辑的应用场合,如性能监测,访问控制,事物管理,日志记录等.至于怎么理解横切逻辑,敲完实例代码也就明白了. 为什么要使用AOP,举个栗子: ...

  2. spring中aop默认使用jdk动态代理,springboot2以后默认使用cglib来实现动态代理详解

    Spring5 AOP 默认使用 Cglib 了?我第一次听到这个说法是在一个微信群里: 真的假的?查阅文档 刚看到这个说法的时候,我是保持怀疑态度的. 大家都知道 Spring5 之前的版本 AOP ...

  3. 手写一个动态代理实现,手写Proxy,手写ClassLoader,手写InvocationHandler

    整个过程中用到了手写类加载器, 手写动态生成java代码 手写编译代码 最后实现动态代理 手写Proxy示例代码: package com.example.demo.proxy.custom;impo ...

  4. java基础巩固-宇宙第一AiYWM:为了维持生计,手写RPC~Version07(RPC原理、序列化框架们、网络协议框架们 、RPC 能帮助我们做什么呢、RPC异常排查:ctrl+F搜超时)整起

    上次Version06说到了咱们手写迷你版RPC的大体流程, 对咱们的迷你版RPC的大体流程再做几点补充: 为什么要封装网络协议,别人说封装好咱们就要封装?Java有这个特性那咱就要用?好像是这样.看 ...

  5. Spring AOP底层实现- JDK动态代理和CGLIB动态代理

    Spring AOP是运行时织入的,那么运行时织入到底是怎么实现的呢?答案就是代理对象. 代理又可以分为静态代理和动态代理. 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前, ...

  6. 手写简易版链表及原理分析

    好多人都觉得为什么要自己写这样的数据结构,变成里面不是有吗?为什么要去写,有这个疑问,其实这个疑问这我的脑海中也存在了很长一段时间,本人是学习java编程的,直接看java的集合框架不行吗?这个时候如 ...

  7. AOP 拦截器 JDK动态代理机制 struts2 mybatis spring-aop

    struts2拦截器兑现原理(转) struts2拦截器实现原理(转) 第一步:创建目标对象的接口 /** * 目标对象的接口 JDK的动态代理要求目标对象必须实现一个接口 *  * @author ...

  8. python手写数字识别教学_python实现基于SVM手写数字识别功能

    本文实例为大家分享了SVM手写数字识别功能的具体代码,供大家参考,具体内容如下 1.SVM手写数字识别 识别步骤: (1)样本图像的准备. (2)图像尺寸标准化:将图像大小都标准化为8*8大小. (3 ...

  9. JAVA工厂模式(手写Spring IOC案例)

    通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类. 源码地址:https://gitee.com/yang-kairui/design-pattern 通过简单模拟sprin ...

最新文章

  1. 百度WordPress结构化数据插件上线
  2. 适配器模式源码解析(jdk+spring+springjpa+springmvc)
  3. 汽车电子专业知识篇(四)-一文详解无人驾驶中的各种感知传感器
  4. 两种实现简单cp的方法
  5. Hive数据倾斜优化
  6. python 实现对地图的点击_python实现Pyecharts实现动态地图(Map、Geo)
  7. Real Vision CEO预测以太坊币价将在今年年初达到2万美元
  8. Embedding 技术在推荐系统中的应用实践
  9. 为什么html中li浮动,相对定位下的绝对定位下的li为什么不能浮动??
  10. BIND 服务器修复多个高危漏洞
  11. 面向对象(二) 继承/里氏替换
  12. 构造模式(Builder Pattern)
  13. 使用 Spring 3 MVC HttpMessageConverter 功能构建 RESTful web 服务
  14. Gambit中,interface和interior的区别
  15. java游戏下载怎么玩_jar的手机游戏怎么玩?java手机游戏的玩法
  16. html悬浮音乐符号,简谱中常用的音乐记号
  17. excel表格打印每页都有表头_如何在Excel打印时每页都有表头?你知道么
  18. 星空云协同开发入门(一)
  19. 惠普HP LaserJet M42523n 打印机驱动
  20. Jqgird 如何使用自带的search模块进行数据查询

热门文章

  1. android 停止app 指令,使用adb命令停止APP后台进程的方法
  2. 【关于分布式系统开发和微服务架构自我心得小记
  3. echarts星状图节点图片自定义 总结图片引入的方法
  4. 计算机组装部件推荐,主流性价比装机 AMD锐龙R5-3600独显近六千元电脑组装机配置推荐...
  5. 未能加载文件或程序集.HRESULT:0x80131515解决方法
  6. 模型融合的一些思路集锦
  7. web端通过novnc方式远程windows server环境搭建详解
  8. Mybatis 实现共通的增删改查
  9. 龙珠激斗服务器一直维护,龙珠激斗初始角色哪个好 六大新手角色评析
  10. 计算机基础win7桌面操作,计算机基础1实验报告win7基本操作.doc