原文链接:AOP与JAVA动态代理

1、AOP的各种实现

AOP就是面向切面编程,我们可以从以下几个层面来实现AOP

  • 在编译期修改源代码
  • 在运行期字节码加载前修改字节码
  • 在运行期字节码加载后动态创建代理类的字节码

2、AOP各种实现机制的比较

以下是各种实现机制的比较:

类别 机制 原理 优点 缺点
静态AOP 静态织入 在编译期,切面直接以字节码的形式编译到目标字节码文件中 对系统无性能影响 灵活性不够
动态AOP 动态代理 在运行期,目标类加载后,为接口动态生成代理类,将切面织入到代理类中 相对于静态AOP更加灵活

切入的关注点需要实现接口。

对系统有一点性能影响

动态字节码生成 CGLIB 在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中 没有接口也可以织入 扩展类的实例方法为final时,则无法进行织入
自定义类加载器   在运行期,目标加载前,将切面逻辑加到目标字节码里 可以对绝大部分类进行织入 代码中如果使用了其他类加载器,则这些类将不会被织入
字节码转换   在运行期,所有类加载器加载字节码前进行拦截 可以对所有类进行织入  

3、AOP里的公民

  • Joinpoint:拦截点,如某个业务方法
  • Pointcut:Joinpoint的表达式,表示拦截哪些方法。一个Pointcut对应多个Joinpoint
  • Advice:要切入的逻辑
  • Before Advice:在方法前切入
  • After Advice:在方法后切入,抛出异常则不会切入
  • After Returning Advice:在方法返回后切入,抛出异常则不会切入
  • After Throwing Advice:在方法抛出异常时切入
  • Around Advice:在方法执行前后切入,可以中断或忽略原有流程的执行
  • 公民之间的关系

织入器通过在切面中定义pointcout来搜索目标(被代理类)的JoinPoint(切入点),然后把要切入的逻辑(Advice)织入到目标对象里,生成代理类

4、AOP的实现机制

  • 动态代理
  • 动态字节码生成
  • 自定义类加载器
  • 字节码转换
4.1 动态代理

静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了

动态代理:即在运行期动态创建代理类,使用动态代理实现AOP需要4个角色:

  • 被代理的类:即AOP里所说的目标对象
  • 被代理类的接口
  • 织入器:使用接口反射机制生成一个代理类,在这个代理类中织入代码
  • InvocationHandler切面:切面,包含了Advice和Pointcut

4.1.1 动态代理的演示

例子演示的是在方法执行前织入一段记录日志的代码,其中

  • Business是代理类
  • LogInvocationHandler是记录日志的切面
  • IBusiness、IBusiness2是代理类的接口
  • Proxy.newProxyInstance是织入器
  1. public interface IBusiness {
  2. void doSomeThing();
  3. }
  4.  
  5. public interface IBusiness2 {
  6. void doSomeThing2();
  7. }
  8.  
  9. public class Business implements IBusiness, IBusiness2 {
  10. @Override
  11. public void doSomeThing() {
  12. System.out.println("执行业务逻辑");
  13. }
  14.  
  15. @Override
  16. public void doSomeThing2() {
  17. System.out.println("执行业务逻辑2");
  18. }
  19. }
  1. package aop;
  2.  
  3. import java.lang.reflect.InvocationHandler;
  4. import java.lang.reflect.Method;
  5.  
  6. /**
  7. * 打印日志的切面
  8. */
  9. public class LogInvocationHandler implements InvocationHandler {
  10.  
  11. private Object target;//目标对象
  12.  
  13. public LogInvocationHandler(Object target) {
  14. this.target = target;
  15. }
  16.  
  17. @Override
  18. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  19. //执行织入的日志,你可以控制哪些方法执行切入逻辑
  20. if (method.getName().equals("doSomeThing2")) {
  21. System.out.println("记录日志");
  22. }
  23. //执行原有逻辑
  24. Object recv = method.invoke(target, args);
  25. return recv;
  26. }
  27. }
  1. package aop;
  2.  
  3. import java.lang.reflect.Proxy;
  4.  
  5. public class Main {
  6. public static void main(String[] args) {
  7. //需要代理的类接口,被代理类实现的多个接口都必须在这这里定义
  8. Class[] proxyInterface = new Class[] {IBusiness.class, IBusiness2.class};
  9. //构建AOP的Advice,这里需要传入业务类的实例
  10. LogInvocationHandler handler = new LogInvocationHandler(new Business());
  11. //生成代理类的字节码加载器
  12. ClassLoader classLoader = Business.class.getClassLoader();
  13. //织入器,织入代码并生成代理类
  14. IBusiness2 proxyBusiness = (IBusiness2) Proxy.newProxyInstance(classLoader, proxyInterface, handler);
  15. proxyBusiness.doSomeThing2();
  16. ((IBusiness)proxyBusiness).doSomeThing();
  17. }
  18. }

执行结果:

记录日志
执行业务逻辑2
执行业务逻辑

4.1.2 动态代理的原理

本节将结合动态代理的源代码讲解其实现原理

动态代理的核心其实就是代理对象的生成,即Proxy.newProxyInstance(classLoader, proxyInterface, handler)

让我们进入newProxyInstance方法观摩下,核心代码就三行:

  1. //获取代理类
  2. Class cl = getProxyClass(loader, interfaces);
  3. //获取带有InvocationHandler参数的构造方法
  4. Constructor cons = cl.getConstructor(constructorParams);
  5. //把handler传入构造方法生成实例
  6. return (Object) cons.newInstance(new Object[] { h });  

getProxyClass(loader, interfaces)方法用于获取代理类,它主要做了三件事情:

  • 在当前类加载器的缓存里搜索是否有代理类
  • 没有则生成代理
  • 并缓存在本地JVM里

查找代理类getProxyClass(loader, interfaces)方法:

  1. 1 // 缓存的key使用接口名称生成的List
  2. 2 Object key = Arrays.asList(interfaceNames);
  3. 3 synchronized (cache) {
  4. 4 do {
  5. 5 Object value = cache.get(key);
  6. 6 // 缓存里保存了代理类的引用
  7. 7 if (value instanceof Reference) {
  8. 8 proxyClass = (Class) ((Reference) value).get();
  9. 9 }
  10. 10 if (proxyClass != null) {
  11. 11 // 代理类已经存在则返回
  12. 12 return proxyClass;
  13. 13 } else if (value == pendingGenerationMarker) {
  14. 14 // 如果代理类正在产生,则等待
  15. 15 try {
  16. 16 cache.wait();
  17. 17 } catch (InterruptedException e) {
  18. 18 }
  19. 19 continue;
  20. 20 } else {
  21. 21 //没有代理类,则标记代理准备生成
  22. 22 cache.put(key, pendingGenerationMarker);
  23. 23 break;
  24. 24 }
  25. 25 } while (true);
  26. 26 }

生成加载代理类:

  1. //生成代理类的字节码文件并保存到硬盘中(默认不保存到硬盘)
  2. proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
  3. //使用类加载器将字节码加载到内存中
  4. proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);

代理类生成过程ProxyGenerator.generateProxyClass()方法的核心代码分析:

  1. //添加接口中定义的方法,此时方法体为空
  2. for (int i = 0; i < this.interfaces.length; i++) {
  3. localObject1 = this.interfaces[i].getMethods();
  4. for (int k = 0; k < localObject1.length; k++) {
  5. addProxyMethod(localObject1[k], this.interfaces[i]);
  6. }
  7. }
  8.  
  9. //添加一个带有InvocationHandler的构造方法
  10. MethodInfo localMethodInfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);
  11.  
  12. //循环生成方法体代码(省略)
  13. //方法体里生成调用InvocationHandler的invoke方法代码。(此处有所省略)
  14. this.cp.getInterfaceMethodRef("InvocationHandler", "invoke", "Object; Method; Object;")
  15.  
  16. //将生成的字节码,写入硬盘,前面有个if判断,默认情况下不保存到硬盘。
  17. localFileOutputStream = new FileOutputStream(ProxyGenerator.access$000(this.val$name) + ".class");
  18. localFileOutputStream.write(this.val$classFile);

通过以上分析,我们可以推出动态代理为我们生产了一个这样的代理类。把方法soSomeThing的方法体修改为调用LogInvocationHandler的invoke方法

代码如下:

  1. public class ProxyBusiness implements IBusiness, IBusiness2 {
  2.  
  3. private LogInvocationHandler h;
  4.  
  5. @Override
  6. public void doSomeThing2() {
  7. try {
  8. Method m = (h.target).getClass().getMethod("doSomeThing", null);
  9. h.invoke(this, m, null);
  10. } catch (Throwable e) {
  11. // 异常处理(略)
  12. }
  13. }
  14.  
  15. @Override
  16. public boolean doSomeThing() {
  17. try {
  18. Method m = (h.target).getClass().getMethod("doSomeThing2", null);
  19. return (Boolean) h.invoke(this, m, null);
  20. } catch (Throwable e) {
  21. // 异常处理(略)
  22. }
  23. return false;
  24. }
  25.  
  26. public ProxyBusiness(LogInvocationHandler h) {
  27. this.h = h;
  28. }
  29.  
  30. //测试用
  31. public static void main(String[] args) {
  32. //构建AOP的Advice
  33. LogInvocationHandler handler = new LogInvocationHandler(new Business());
  34. new ProxyBusiness(handler).doSomeThing();
  35. new ProxyBusiness(handler).doSomeThing2();
  36. }
  37. }  
4.1.3 小结

从前两节的分析我们可以看出,动态代理在运行期通过接口动态生成代理类,这为其带来了一定的灵活性,但这个灵活性却带来了两个问题:

  • 第一,代理类必须实现一个接口,如果没实现接口会抛出一个异常
  • 第二,性能影响,因为动态代理是使用反射机制实现的,首先反射肯定比直接调用要慢,其次使用反射大量生成类文件可能引起full gc,因为字节码文件加载后会存放在JVM运行时方法区(或者叫永久代、元空间)中,当方法区满时会引起full gc,所以当你大量使用动态代理时,可以将永久代设置大一些,减少full gc的次数
4.2 CGLIB动态字节码生成

使用动态字节码生成技术实现AOP原理是在运行期间目标字节码加载后,生成目标类的子类,将切面逻辑加入到子类中,所以cglib实现AOP不需要基于接口

本节介绍如何使用cglib来实现动态字节码技术。

cglib是一个强大的、高性能的Code生成类库,它可以在运行期间扩展Java类和实现Java接口,它封装了Asm,所以使用cglib前需要引入Asm的jar

4.2.1 使用cglib实现AOP
  1. 1 package cglib;
  2. 2
  3. 3 /**
  4. 4 * 这个是没有实现接口的实现类
  5. 5 */
  6. 6 public class BookFacadeImpl {
  7. 7 public void addBook() {
  8. 8 System.out.println("增加图书的普通方法。。。");
  9. 9 }
  10. 10
  11. 11 public void deleteBook() {
  12. 12 System.out.println("删除图书的普通方法。。。");
  13. 13 }
  14. 14 }
  1. 1 package cglib;
  2. 2
  3. 3 import net.sf.cglib.proxy.Enhancer;
  4. 4 import net.sf.cglib.proxy.MethodInterceptor;
  5. 5 import net.sf.cglib.proxy.MethodProxy;
  6. 6
  7. 7 import java.lang.reflect.Method;
  8. 8
  9. 9 /**
  10. 10 * 使用cglib动态代理
  11. 11 */
  12. 12 public class BookFacadeCglib implements MethodInterceptor {
  13. 13
  14. 14 private Object target;
  15. 15
  16. 16 /**
  17. 17 * 创建代理对象
  18. 18 *
  19. 19 * @param target
  20. 20 * @return
  21. 21 */
  22. 22 public Object getInstance(Object target) {
  23. 23 this.target = target;
  24. 24 Enhancer enhancer = new Enhancer();
  25. 25 enhancer.setSuperclass(this.target.getClass());
  26. 26 //回调方法
  27. 27 enhancer.setCallback(this);
  28. 28 //创建代理
  29. 29 return enhancer.create();
  30. 30 }
  31. 31
  32. 32 //回调方法
  33. 33 @Override
  34. 34 public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  35. 35 if (method.getName().equals("addBook")) {
  36. 36 System.out.println("记录增加图书的日志");
  37. 37 }
  38. 38 methodProxy.invokeSuper(obj, args);
  39. 39 return null;
  40. 40 }
  41. 41 }
  1. package cglib;
  2.  
  3. /**
  4. * 测试cglib字节码代理
  5. */
  6. public class TestCglib {
  7. public static void main(String[] args) {
  8. BookFacadeCglib cglib = new BookFacadeCglib();
  9. BookFacadeImpl bookFacade = (BookFacadeImpl) cglib.getInstance(new BookFacadeImpl());
  10. bookFacade.addBook();
  11. bookFacade.deleteBook();
  12. }
  13. }

执行结果:

记录增加图书的日志
增加图书的普通方法。。。
删除图书的普通方法。。。

4.3 自定义类加载器

如果我们实现了一个自定义类加载器,在类加载到JVM之前直接修改某些类的方法,并将切入逻辑织入到这个方法里,然后将修改后的字节码文件交给虚拟机运行,那岂不是更直接

Javassist是一个编辑字节码的框架,可以让你很简单地操作字节码。它可以在运行期定义或修改Class。使用Javassist实现AOP的原理是在字节码加载前直接修改需要切入的方法

这比使用cglib实现AOP更加高效,并且没有太多限制,实现原理如下图:

我们使用类加载器启动我们自定义的类加载器,在这个类加载器里加一个类加载监听器,监听器发现目标类被加载时就织入切入逻辑

4.3.1 Javassist实现AOP的代码

清单1:启动自定义的类加载器

  1. //获取存放CtClass的容器ClassPool
  2. ClassPool cp = ClassPool.getDefault();
  3. //创建一个类加载器
  4. Loader cl = new Loader();
  5. //增加一个转换器
  6. cl.addTranslator(cp, new MyTranslator());
  7. //启动MyTranslator的main函数
  8. cl.run("jsvassist.JavassistAopDemo$MyTranslator", args);

清单2:类加载监听器

  1. public static class MyTranslator implements Translator {
  2.  
  3. public void start(ClassPool pool) throws NotFoundException, CannotCompileException {
  4. }
  5.  
  6. /* *
  7. * 类装载到JVM前进行代码织入
  8. */
  9. public void onLoad(ClassPool pool, String classname) {
  10. if (!"model$Business".equals(classname)) {
  11. return;
  12. }
  13. //通过获取类文件
  14. try {
  15. CtClass cc = pool.get(classname);
  16. //获得指定方法名的方法
  17. CtMethod m = cc.getDeclaredMethod("doSomeThing");
  18. //在方法执行前插入代码
  19. m.insertBefore("{ System.out.println(\"记录日志\"); }");
  20. } catch (NotFoundException e) {
  21. } catch (CannotCompileException e) {
  22. }
  23. }
  24.  
  25. public static void main(String[] args) {
  26. Business b = new Business();
  27. b.doSomeThing2();
  28. b.doSomeThing();
  29. }
  30. }

输出:

执行业务逻辑2

记录日志

执行业务逻辑

4.3.2 小结

从本节中可知,使用自定义的类加载器实现AOP在性能上有优于动态代理和cglib,因为它不会产生新类,但是它仍人存在一个问题,就是如果其他的类加载器来加载类的话,这些类就不会被拦截

4.4 字节码转换

自定义类加载器实现AOP只能拦截自己加载的字节码,那么有一种方式能够监控所有类加载器加载的字节码吗?

有,使用Instrumentation,它是Java5的新特性,使用Instrument,开发者可以构建一个字节码转换器,在字节码加载前进行转换

本节使用Instrumentation和javassist来实现AOP

4.4.1 构建字节码转换器

首先需要创建字节码转换器,该转换器负责拦截Business类,并在Business类的doSomeThing方法前使用javassist加入记录日志的代码

  1. 1 public class MyClassFileTransformer implements ClassFileTransformer {
  2. 2
  3. 3 /**
  4. 4 * 字节码加载到虚拟机前会进入这个方法
  5. 5 */
  6. 6 @Override
  7. 7 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
  8. 8 ProtectionDomain protectionDomain, byte[] classfileBuffer)
  9. 9 throws IllegalClassFormatException {
  10. 10 System.out.println(className);
  11. 11 //如果加载Business类才拦截
  12. 12 if (!"model/Business".equals(className)) {
  13. 13 return null;
  14. 14 }
  15. 15
  16. 16 //javassist的包名是用点分割的,需要转换下
  17. 17 if (className.indexOf("/") != -1) {
  18. 18 className = className.replaceAll("/", ".");
  19. 19 }
  20. 20 try {
  21. 21 //通过包名获取类文件
  22. 22 CtClass cc = ClassPool.getDefault().get(className);
  23. 23 //获得指定方法名的方法
  24. 24 CtMethod m = cc.getDeclaredMethod("doSomeThing");
  25. 25 //在方法执行前插入代码
  26. 26 m.insertBefore("{ System.out.println(\"记录日志\"); }");
  27. 27 return cc.toBytecode();
  28. 28 } catch (NotFoundException e) {
  29. 29 } catch (CannotCompileException e) {
  30. 30 } catch (IOException e) {
  31. 31 //忽略异常处理
  32. 32 }
  33. 33 return null;
  34. 34 }
4.4.2 注册转换器

使用premain函数注册字节码转换器,该方法在main函数之前执行

  1. public class MyClassFileTransformer implements ClassFileTransformer {
  2. public static void premain(String options, Instrumentation ins) {
  3. //注册我自己的字节码转换器
  4. ins.addTransformer(new MyClassFileTransformer());
  5. }
  6. }
4.4.3 配置和执行

需要告诉JVM在启动main函数之前,需要先执行premain函数。

首先,需要将premain函数所在的类打成jar包,并修改jar包里的META-INF\MANIFEST.MF文件

  1. 1 Manifest-Version: 1.0
  2. 2 Premain-Class: bci. MyClassFileTransformer

其次,在JVM的启动参数里加上-javaagent:D:\java\projects\opencometProject\Aop\lib\aop.jar

4.4.4 输出

执行main函数,你会发现切入的代码无侵入性的织入进去了

  1. 1 public static void main(String[] args) {
  2. 2 new Business().doSomeThing();
  3. 3 new Business().doSomeThing2();
  4. 4 }
  5. 5

输出:

  1. 1 model/Business
  2. 2 sun/misc/Cleaner
  3. 3 java/lang/Enum
  4. 4 model/IBusiness
  5. 5 model/IBusiness2
  6. 6 记录日志
  7. 7 执行业务逻辑
  8. 8 执行业务逻辑2
  9. 9 java/lang/Shutdown
  10. 10 java/lang/Shutdown$Lock

从输出中可以看到系统类加载器加载的类也经过了这里

5、AOP实战

5.1 AOP功能
  • 性能监控:在方法调用前后记录调用时间,方法执行太长或超时报警
  • 缓存代理:缓存某方法的返回值,下次执行该方法时,直接从缓存里获取
  • 软件破解:使用AOP修改软件的验证类的判断逻辑
  • 记录日志:在方法执行前后记录系统日志
  • 工作流系统:工作流系统需要将业务代码和流程引擎代码混合在一起执行,那么我们可以使用AOP将其分离,并动态挂接业务
  • 权限验证:方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,由业务代码捕捉
5.2 Spring的AOP

Spring默认采取动态代理机制实现AOP,当动态代理不可用时(代理类无接口)会使用cglib机制

但Spring的AOP有一定的缺点:

  • 第一,只能对方法进行切入,不能对接口、字段、静态代码块进行切入(切入接口的某个方法,则该接口下所有实现类的该方法都将被切入)
  • 第二,同类中的互相调用方法将不会使用代理类。因为要使用代理类必须从Spring容器中获取Bean
  • 第三,性能不是最好的。从前面几节得知,我们自定义的类加载器,性能优于动态代理和cglib
  1. public IMsgFilterService getThis() {
  2.   return (IMsgFilterService) AopContext.currentProxy();
  3. }
  4.  
  5. public boolean evaluateMsg () {
  6.   // 执行此方法将织入切入逻辑
  7.   return getThis().evaluateMsg(String message);
  8. }
  9.  
  10. @MethodInvokeTimesMonitor("KEY_FILTER_NUM")
  11. public boolean evaluateMsg(String message) {
  1. public boolean evaluateMsg () {
  2. // 执行此方法将不会织入切入逻辑
  3.   return evaluateMsg(String message);
  4. }
  5.  
  6. @MethodInvokeTimesMonitor("KEY_FILTER_NUM")
  7. public boolean evaluateMsg(String message) {

6、参考资料

http://www.iteye.com/topic/1116696

转:AOP与JAVA动态代理的更多相关文章

  1. AOP与JAVA动态代理

    1.AOP的各种实现 AOP就是面向切面编程,我们可以从以下几个层面来实现AOP 在编译期修改源代码 在运行期字节码加载前修改字节码 在运行期字节码加载后动态创建代理类的字节码 2.AOP各种实现机制 ...

  2. java动态代理基本原理及proxy源码分析一

    本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的态度,于是对java动态代理的本质原理做了一些研究,于是便有了这个系列的文章 为了尽快进入正题,这里 ...

  3. Java动态代理学习【Spring AOP基础之一】

    Spring AOP使用的其中一个底层技术就是Java的动态代理技术.Java的动态代理技术主要围绕两个类进行的 java.lang.reflect.InvocationHandler java.la ...

  4. java动态代理实现与原理详细分析(代码层面解释了AOP的实现)

    关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 一.代理模式    代理模式是常用的java设计模式, ...

  5. AOP面向切面编程JAVA动态代理实现用户权限管理(实现篇)

    java动态代理机制的功能十分强大,使用动态代理技术能够有效的降低应用中各个对象之间的耦合紧密程度,提高开发的效率以及程序的可维护性,事实上Spring AOP就是建立在Java动态代理的基础之上.其 ...

  6. Java动态代理-->Spring AOP

    引述要学习Spring框架的技术内幕,必须事先掌握一些基本的Java知识,正所谓“登高必自卑,涉远必自迩”.以下几项Java知识和Spring框架息息相关,不可不学(我将通过一个系列分别介绍这些Jav ...

  7. Java 动态代理机制详解

    在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...

  8. Java 动态代理作用是什么?

    Java 动态代理作用是什么?   1 条评论 分享   默认排序按时间排序 19 个回答 133赞同反对,不会显示你的姓名 Intopass 程序员,近期沉迷于动漫ING 133 人赞同 ① 首先你 ...

  9. java动态代理浅析

    最近在公司看到了mybatis与spring整合中MapperScannerConfigurer的使用,该类通过反向代理自动生成基于接口的动态代理类. 于是想起了java的动态代理,然后就有了这篇文章 ...

随机推荐

  1. json简介及JsonCpp用法

    [时间:2017-04] [状态:Open] [关键词:数据交换格式,json,jsoncpp,c++,json解析,OpenSource] json简介 本文仅仅是添加我个人对json格式的理解,更 ...

  2. Selenium Web 自动化 - 项目实战(一)

    Selenium Web 自动化 - 测试框架(一) 2016-08-05 目录 1 框架结构雏形2 把Java项目转变成Maven项目3 加入TestNG配置文件4 Eclipse编码修改5 编写代 ...

  3. Python3用gevent写个文件字符串查找器

    [本文出自天外归云的博客园] 1.递归遍历目录下所有文件并通过finder函数定位指定格式字符串 2.用来查找字符串的finder函数是自己定义的,这里定义了一个ip_port_finder通过正则表 ...

  4. Caffe、TensorFlow、MXnet三个开源库对比+主流分类模型对比

    库名称 开发语言 支持接口 安装难度(ubuntu) 文档风格 示例 支持模型 上手难易 Caffe c++/cuda c++/python/matlab *** * *** CNN ** MXNet ...

  5. C语言之单元测试

    在ITOO高校云平台项目实践中,我们模板的模块因为在调别人的接口时出现了问题,为了弄明白是不是接口出了问题,就必须学会单元测试. WHAT? 单元测试(unit testing),是指对软件中的最小可 ...

  6. Ubuntu 设置NAT共享网络(命令行方法)

    本文介绍如何使用iptables来实现NAT转发,事实上就是将一台机器作为网关(gateway)来使用.我们假设充当网关的机器至少有网卡eth0和eth1,使用eth0表示连接到外网的网卡,使用eth ...

  7. Linux下MySQL5.7.18二进制包安装(无默认配置文件my_default.cnf)

    最新在学习MySQL,纯新手,对Linux了解的也不多,因为是下载的最新版的MySQL(MySQL5.7.18)二进制包,CentOS7.2下测试安装,方便以后折腾.大概步骤如下,安装删除反复折腾了几 ...

  8. spring扩展的常用接口

    一:ApplicationContextAware接口 实现ApplicationContextAware接口,重写setApplicationContext方法,可以将spring容器上下文对象注入 ...

  9. java 多线程并发 synchronized 同步机制及方式

    2. 锁机制 3. 并发 Excutor框架 4. 并发性与多线程介绍 1. synchronized  参考1. synchronized 分两种方式进行线程的同步:同步块.同步方法 1. 方法同步 ...

  10. 4G通信模块在ARM平台下的应用

    4G模块是连接物与物的重要载体,是终端设备接入物联网的核心部件之一.随着4G的普及,许多新兴市场对4G通信模块的需求都在日益扩大,那么在ARM平台的嵌入式设备上如何快速的应用4G模块呢? 4G通信模块 ...