在Windows 7系统上编译运行体验Apache OFBiz-13.07.03

作为 Apache基金会的赞助项目, OFBiz(全称为" Open for Business")是一套功能齐全的企业自动化套件,其中包含企业资源规划(简称 ERP)、客户关系管理(简称 CRM)、电子商务、内容管理、计费与费 率管理、供应链管理、制造资源规划以及企业资产管理等方案。 OFBiz拥有丰富的说明文档及指导视频,其基于 Java语言因此能够运行在任意支持 Java SDK的系统当中,包括 WindowsOS XLinux以及 Unix

1.去 OFBiz的官网http://ofbiz.apache.org/下载目前最新版本的13.07.03。下载完成后,是个 zip格式的压缩包。解压压缩包到任意目录。

2.安装最新的 JDK(要求最低是 1.7,目前的 1.8版本是可以正常使用的)。

完成后,直接访问http://localhost:8080/ecommerce即可看到如下页面:
ofbiz_index
其他的参考代码根目录下面的 README.

参考链接


IntelliJ IDEA 2016.2使用Spring 4.3.1.RELEASE,sockjs-1.1.1,stomp-1.2搭建基于Tomcat-7.0.68的WebSocket应用

接着上文IntelliJ IDEA 2016.2使用Spring 4.3.1.RELEASE搭建基于Tomcat-7.0.68的WebSocket应用

上文的最后我们说到, WebSocket是需要定时心跳的,否则会在一段时间后自动断开连接,而更重要的是,不是所有的浏览器都支持 WebSocket,早期的 IE 10之前的版本就是不支持的,而这一部分的设备其实是不算少的,而 sockjs的出现,恰恰好来解决了这个问题。对于不支持 WebSocket的浏览器, sockjs使用了多种方式来兼容这种情况,包括使用长轮询等方式, Spring更是内建支持这种方式。

下面我们看如何在上篇文章的基础上,增加对于 sockjs的支持。

首先是 STOMP的文档官网地址 http://stomp.github.io/
代码的地址为https://github.com/jmesnil/stomp-websocket,项目下面的 lib/stomp.js就是我们想要的文件。也可以本站下载stomp.js.zip

接下来 sockjs的代码地址https://github.com/sockjs/sockjs-client,项目下面的 dist/sockjs-1.1.1.js就是我们想要的文件。也可以本站下载sockjs-1.1.1.js.zip

接下来我们把下载到的文件放到我们工程目录下面的 web-> resources-> javascript目录下面,如下图:

stomp-websockjs-resources

接下来,添加我们需要的 com.fasterxml.jackson.core:jackson-annotations:2.8.1, com.fasterxml.jackson.core:jackson-core:2.8.1, com.fasterxml.jackson.core:jackson-databind:2.8.1这三个 jar包,增加的方式参照上一篇中对于 javax.servlet:javax.servlet-api:3.1.0的操作方法。与上一篇的操作不同的是,这次添加的三个 jar包,都要放到编译完成后的 War包中。最后的结果如下图:
ToolsJacksonMaven

ToolsJacksonMavenWar

下面,我们开始进行代码的操作,我们在上篇文章中的 src-> Tools-> WebSocket中新增两个源代码文件 SockJsController.java, WebJsSocketConfig.java.如下图:

NewJavaSourcesForSockjs

其中的代码如下:
SockJsController.java

WebJsSocketConfig.java

然后修改 WebSocket.jsp

最后,我们修改 web-> WEB-INF-> web.xml,在其中增加

修改后的最终结果如下:

参考链接


 

Ubuntu 16.04下Tomcat 7.0.68启动服务时候报告“java.lang.NoSuchMethodError: java.util.concurrent.ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;”

在使用Ubuntu16.04 安装openjdk-7-jdk介绍的方式切换 Java1.7版本后,在进行代码调试的时候, Tomcat 7.0.68报告如下错误:

这说明 Ubuntu 16.04下的 Tomcat是在 Java 8环境下面编译的,尽管我们已经切换到 Java 7下面了,但是 Tomcat并不能很好的执行我们的 Java 7代码。这个时候的解决方法就是,编译代码的时候指定 Java 7,但是在 Tomcat执行的时候,指定使用 Java 8来运行。

参考链接


Java error java.util.concurrent.ConcurrentHashMap.keySet

IntelliJ IDEA 2016.2使用Spring 4.3.1.RELEASE搭建基于Tomcat-7.0.68的WebSocket应用

先参照 IntelliJ IDEA 2016.1建立Strut2工程并使用Tomcat调试建立新的工程,一步一步操作,包括最后引用 Spring框架部分。

经过上面的操作, Spring-WebSocket的包应该已经被默认引入了,如下图所示:

spring-websocket-release-lib

这就意味着我们已经不需要再进行过多的额外配置了。

接下来,我们在 src-> Tools下面新建一个 WebSocket的目录,里面创建三个 Java文件。如下图:
WebSocketJavaSourceList

每个文件中的代码如下:
SystemWebSocketHandler.java

WebSocketConfig.java

WebSocketHandshakeInterceptor.java

由于我们使用了 Struts2导致我们的网络请求优先被 Struts2的拦截器拦截,而 Struts2又处理不了 websocket请求,结果直接返回了 404错误。因此我们需要替换掉默认的在 web.xml中定义的 Struts2的拦截器,要求 Struts2不处理 websocket请求。
我们在 src-> Tools下面新建一个 Filter的目录,下面创建一个名为 StrutsPrepareAndExecuteFilterEx.java的源代码文件,如下图:

StrutsPrepareAndExecuteFilterEx
具体的代码如下:
StrutsPrepareAndExecuteFilterEx.java

这时候的代码是无法编译通过的,原因是依赖的 javaxJar包不存在。此时,我们需要手工引入 javax.servlet:javax.servlet-api:3.1.0这个 Jar包。如下图:

ToolWebSocketProjectStructure

ToolWebSocketProjectStructureAddJars

ToolWebSocketProjectStructureAddJarsFromMaven

ToolWebSocketProjectStructureAddJarsFromMavenJavaxServerletApi3.1.0

ToolWebSocketProjectStructureAddJarsFromMavenJavaxServerletApi3.1.0Add

ToolWebSocketProjectStructureAddJarsFromMavenJavaxServerletApi3.1.0AddApply

还要注意,刚刚添加进入的 javax.servlet:javax.servlet-api:3.1.0这个 Jar包,我们只在编译期间使用,在运行时候,使用 Tomcat自己实现的那个同名 Jar包。也就是这个包是个 Provided,而不是 Compile关系,具体如下图:

ToolWebSocketProjectStructureKeepOutsideWar

接下来,修改 web.xml指定 Struts2的拦截器为我们定义的拦截器

修改为

经过上面的修改后,依旧是没办法进行网络访问的,原因是 web.xml中的 Spring拦截器并没有拦截 WebSocket的数据请求。

修改为

注意增加的

下一步,配置 Spring的配置文件 web-> WEB-INF-> dispatcher-servlet.xml增加配置信息类的扫描目录包含我们刚刚创建的 src-> Tools-> WebSocket的目录(缺少这一步会导致我们通过注解实现的配置信息类没有被自动加载,导致无法访问),修改后的内容如下:

最后,调用的页面的代码
WebSocket.jsp

最后的客户端显示效果如下图所示:
WebSocketJsp

注意:如此创建的 WebSocket是会在两三分钟后主动断开连接的,原因在于 WebSocket需要周期性的发送心跳报文来维持连接。后续我们会尝试使用s ockjs来实现自动发送心跳的逻辑。

具体的sockjs的接入方法,参考IntelliJ IDEA 2016.2使用Spring 4.3.1.RELEASE,sockjs-1.1.1,stomp-1.2搭建基于Tomcat-7.0.68的WebSocket应用

Java 读写Properties配置文件

1. Properties类与 Properties配置文件

Properties类继承自 Hashtable类并且实现了 Map接口,也是使用一种键值对的形式来保存属性集。不过 Properties有特殊的地方,就是它的键和值都是字符串类型。

2. Properties中的主要方法

(1) load(InputStream inStream)

   这个方法可以从 .properties属性文件对应的文件输入流中,加载属性列表到 Properties类对象如下面的代码:

(2) store(OutputStream out, String comments)

这个方法将 Properties类对象的属性列表保存到输出流中如下面的代码:

如果 comments不为空,保存后的属性文件第一行会是 #comments,表示注释信息;如果为空则没有注释信息。

注释信息后面是属性文件的当前保存时间信息。

(3) getProperty/setProperty

这两个方法是分别是获取和设置属性信息。

3.代码实例

属性文件 a.properties如下:

读取 a.properties属性列表,与生成属性文件 b.properties。代码如下:

参考链接


Java 读写Properties配置文件

在JNI编程中避免内存泄漏

JAVA中的内存泄漏

JAVA编程中的内存泄漏,从泄漏的内存位置角度可以分为两种: JVMJava Heap的内存泄漏; JVM内存中 native memory的内存泄漏。

Java Heap的内存泄漏

Java对象存储在 JVM进程空间中的 Java Heap中, Java Heap可以在 JVM运行过程中动态变化。如果 Java对象越来越多,占据 Java Heap的空间也越来越大, JVM会在运行时扩充 Java Heap的容量。如果 Java Heap容量扩充到上限,并且在 GC后仍然没有足够空间分配新的 Java对象,便会抛出 out of memory异常,导致 JVM进程崩溃。

Java Heapout of memory异常的出现有两种原因——①程序过于庞大,致使过多 Java对象的同时存在;②程序编写的错误导致 Java Heap内存泄漏。

多种原因可能导致 Java Heap内存泄漏。 JNI编程错误也可能导致 Java Heap的内存泄漏。

JVMnative memory的内存泄漏

从操作系统角度看, JVM在运行时和其它进程没有本质区别。在系统级别上,它们具有同样的调度机制,同样的内存分配方式,同样的内存格局。

JVM进程空间中, Java Heap以外的内存空间称为 JVMnative memory。进程的很多资源都是存储在 JVMnative memory中,例如载入的代码映像,线程的堆栈,线程的管理控制块, JVM的静态数据、全局数据等等。也包括 JNI程序中 native code分配到的资源。

JVM运行中,多数进程资源从 native memory中动态分配。当越来越多的资源在 native memory中分配,占据越来越多 native memory空间并且达到 native memory上限时, JVM会抛出异常,使 JVM进程异常退出。而此时 Java Heap往往还没有达到上限。

多种原因可能导致 JVMnative memory内存泄漏。例如 JVM在运行中过多的线程被创建,并且在同时运行。 JVM为线程分配的资源就可能耗尽 native memory的容量。

JNI编程错误也可能导致 native memory的内存泄漏。对这个话题的讨论是本文的重点。


JNI编程中明显的内存泄漏

JNI编程实现了 native codeJava程序的交互,因此 JNI代码编程既遵循 native code编程语言的编程规则,同时也遵守 JNI编程的文档规范。在内存管理方面, native code编程语言本身的内存管理机制依然要遵循,同时也要考虑 JNI编程的内存管理。

本章简单概括 JNI编程中显而易见的内存泄漏。从 native code编程语言自身的内存管理,和 JNI规范附加的内存管理两方面进行阐述。

Native Code本身的内存泄漏

JNI编程首先是一门具体的编程语言,或者 C语言,或者 C++,或者汇编,或者其它 native的编程语言。每门编程语言环境都实现了自身的内存管理机制。因此, JNI程序开发者要遵循 native语言本身的内存管理机制,避免造成内存泄漏。以 C语言为例,当用 malloc()在进程堆中动态分配内存时, JNI程序在使用完后,应当调用 free()将内存释放。总之,所有在 native语言编程中应当注意的内存泄漏规则,在 JNI编程中依然适应。

Native语言本身引入的内存泄漏会造成 native memory的内存,严重情况下会造成 native memoryout of memory

Global Reference引入的内存泄漏

JNI编程还要同时遵循 JNI的规范标准, JVM附加了 JNI编程特有的内存管理机制。

JNI中的 Local Reference只在 native method执行时存在,当 native method执行完后自动失效。这种自动失效,使得对 Local Reference的使用相对简单, native method执行完后,它们所引用的 Java对象的 reference count会相应减 1。不会造成 Java HeapJava对象的内存泄漏。

Global ReferenceJava对象的引用一直有效,因此它们引用的 Java对象会一直存在 Java Heap中。程序员在使用 Global Reference时,需要仔细维护对 Global Reference的使用。如果一定要使用 Global Reference,务必确保在不用的时候删除。就像在 C语言中,调用 malloc()动态分配一块内存之后,调用 free()释放一样。否则, Global Reference引用的 Java对象将永远停留在 Java Heap中,造成 Java Heap的内存泄漏。


JNI编程中潜在的内存泄漏——对 LocalReference的深入理解

Local Referencenative method执行完成后,会自动被释放,似乎不会造成任何的内存泄漏。但这是错误的。对 Local Reference的理解不够,会造成潜在的内存泄漏。

本章重点阐述 Local Reference使用不当可能引发的内存泄漏。引入两个错误实例,也是 JNI程序员容易忽视的错误;在此基础上介绍 Local Reference表,对比 native method中的局部变量和 JNI Local Reference的不同,使读者深入理解 JNI Local Reference的实质;最后为 JNI程序员提出应该如何正确合理使用 JNI Local Reference,以避免内存泄漏。

错误实例 1

在某些情况下,我们可能需要在 native method里面创建大量的 JNI Local Reference。这样可能导致 native memory的内存泄漏,如果在 native method返回之前 native memory已经被用光,就会导致 native memoryout of memory

在代码清单 1 里,我们循环执行 count次,JNI function NewStringUTF()在每次循环中从 Java Heap中创建一个 String对象, strJava Heap传给 JNI native methodLocal Reference,每次循环中新创建的 String对象覆盖上次循环中 str的内容。 str似乎一直在引用到一个 String对象。整个运行过程中,我们看似只创建一个 Local Reference

执行代码清单 1的程序,第一部分为 Java代码, nativeMethod(int i)中,输入参数设定循环的次数。第二部分为 JNI代码,用 C语言实现了 nativeMethod(int i)

清单 1. Local Reference引发内存泄漏

运行结果证明, JVM运行异常终止,原因是创建了过多的 Local Reference,从而导致 out of memory。实际上, nativeMethod在运行中创建了越来越多的 JNI Local Reference,而不是看似的始终只有一个。过多的 Local Reference,导致了 JNI内部的 JNI Local Reference表内存溢出。

错误实例 2

实例 2 是实例 1 的变种, Java代码未作修改,但是 nativeMethod(int i)C语言实现稍作修改。在 JNInative method中实现的 utility函数中创建 JavaString对象。 utility函数只建立一个 String对象,返回给调用函数,但是 utility函数对调用者的使用情况是未知的,每个函数都可能调用它,并且同一函数可能调用它多次。在实例 2 中, nativeMethod在循环中调用 count次, utility函数在创建一个 String对象后即返回,并且会有一个退栈过程,似乎所创建的 Local Reference会在退栈时被删除掉,所以应该不会有很多 Local Reference被创建。实际运行结果并非如此。

清单 2. Local Reference引发内存泄漏

运行结果证明,实例 2 的结果与实例 1 的完全相同。过多的 Local Reference被创建,仍然导致了 JNI内部的 JNI Local Reference表内存溢出。实际上,在 utility函数 CreateStringUTF(JNIEnv * env)

执行完成后的退栈过程中,创建的 Local Reference并没有像 native code中的局部变量那样被删除,而是继续在 Local Reference表中存在,并且有效。 Local Reference 和局部变量有着本质的区别。

Local Reference深层解析

Java JNI的文档规范只描述了 JNI Local Reference是什么(存在的目的),以及应该怎么使用 Local Reference(开放的接口规范)。但是对 Java虚拟机中 JNI Local Reference的实现并没有约束,不同的 Java虚拟机有不同的实现机制。这样的好处是,不依赖于具体的 JVM实现,有好的可移植性;并且开发简单,规定了“应该怎么做、怎么用”。但是弊端是初级开发者往往看不到本质,“不知道为什么这样做”。对 Local Reference没有深层的理解,就会在编程过程中无意识的犯错。

Local ReferenceLocal Reference

理解 Local Reference表的存在是理解 JNI Local Reference的关键。

JNI Local Reference的生命期是在 native method的执行期(从 Java程序切换到 native code环境时开始创建,或者在 native method执行时调用 JNI function创建),在 native method执行完毕切换回 Java程序时,所有 JNI Local Reference被删除,生命期结束(调用 JNI function可以提前结束其生命期)。

实际上,每当线程从 Java环境切换到 native code上下文时( J2N), JVM会分配一块内存,创建一个 Local Reference表,这个表用来存放本次 native method执行中创建的所有的 Local Reference。每当在 native code中引用到一个 Java对象时, JVM就会在这个表中创建一个 Local Reference。比如,实例 1 中我们调用 NewStringUTF()Java Heap中创建一个 String对象后,在 Local Reference表中就会相应新增一个 Local Reference

图 1. Local Reference表、 Local ReferenceJava对象的关系

image003

图 1 中:

⑴运行 native method的线程的堆栈记录着 Local Reference表的内存位置(指针 p)。

Local Reference表中存放 JNI Local Reference,实现 Local ReferenceJava对象的映射。

native method代码间接访问 Java对象(java obj1,java obj2)。通过指针 p定位相应的 Local Reference的位置,然后通过相应的 Local Reference映射到 Java对象。

⑷ 当 native method引用一个 Java对象时,会在 Local Reference表中创建一个新 Local Reference。在 Local Reference结构中写入内容,实现 Local ReferenceJava对象的映射。

native method调用 DeleteLocalRef()释放某个 JNI Local Reference时,首先通过指针 p定位相应的 Local ReferenceLocal Ref表中的位置,然后从 Local Ref表中删除该 Local Reference,也就取消了对相应 Java对象的引用( Ref count1)。

⑹当越来越多的 Local Reference被创建,这些 Local Reference会在 Local Ref表中占据越来越多内存。当 Local Reference太多以至于 Local Ref表的空间被用光, JVM会抛出异常,从而导致 JVM的崩溃。

Local Ref不是 native code的局部变量

很多人会误将 JNI中的 Local Reference理解为 Native Code的局部变量。这是错误的。

Native Code的局部变量和 Local Reference是完全不同的,区别可以总结为:

⑴局部变量存储在线程堆栈中,而 Local Reference存储在 Local Ref表中。

⑵局部变量在函数退栈后被删除,而 Local Reference在调用 DeleteLocalRef()后才会从 Local Ref表中删除,并且失效,或者在整个 Native Method执行结束后被删除。

⑶ 可以在代码中直接访问局部变量,而 Local Reference的内容无法在代码中直接访问,必须通过 JNI function间接访问。 JNI function实现了对 Local Reference的间接访问, JNI function的内部实现依赖于具体 JVM

代码清单 1 中 str = (*env)->NewStringUTF(env, "0");

strjstring类型的局部变量。 Local Ref表中会新创建一个 Local Reference,引用到 NewStringUTF(env, "0")Java Heap中新建的 String对象。如图 2 所示:

图 2. str 间接引用 string 对象image005

图 2 中,str 是局部变量,在 native method 堆栈中。Local Ref3 是新创建的 Local Reference,在 Local Ref 表中,引用新创建的 String 对象。JNI 通过 str 和指针 p 间接定位 Local Ref3,但 p 和 Local Ref3 对 JNI 程序员不可见。

Local Reference导致内存泄漏

在以上论述基础上,我们通过分析错误实例 1 和实例 2,来分析 Local Reference 可能导致的内存泄漏,加深对 Local Reference 的深层理解。

分析错误实例 1:

局部变量 str在每次循环中都被重新赋值,间接指向最新创建的 Local Reference,前面创建的 Local Reference 一直保留在 Local Ref 表中。

在实例 1 执行完第 i次循环后,内存布局如图 3:

图 3. 执行 i次循环后的内存布局

image007

继续执行完第 i+1次循环后,内存布局发生变化,如图 4:

图 4. 执行 i+1次循环后的内存布局

image009

图 4 中,局部变量 str 被赋新值,间接指向了 Local Ref i+1。在 native method运行过程中,我们已经无法释放 Local Ref i占用的内存,以及 Local Ref i所引用的第 istring对象所占据的 Java Heap内存。所以, native memoryLocal Ref i被泄漏, Java Heap中创建的第 istring对象被泄漏了。

也就是说在循环中,前面创建的所有 iLocal Reference都泄漏了 native memory的内存,创建的所有 i 个 string对象都泄漏了 Java Heap的内存。

直到 native memory执行完毕,返回到 Java程序时( N2J),这些泄漏的内存才会被释放,但是 Local Reference 表所分配到的内存往往很小,在很多情况下 N2J之前可能已经引发严重内存泄漏,导致 Local Reference表的内存耗尽,使 JVM崩溃,例如错误实例 1。

分析错误实例 2:

实例 2 与实例 1 相似,虽然每次循环中调用工具函数 CreateStringUTF(env)来创建对象,但是在 CreateStringUTF(env)返回退栈过程中,只是局部变量被删除,而每次调用创建的 Local Reference仍然存在 Local Ref表中,并且有效引用到每个新创建的 string对象。 str局部变量在每次循环中被赋新值。

这样的内存泄漏是潜在的,但是这样的错误 在 JNI程序员编程过程中却经常出现。通常情况,在触发 out of memory之前, native method已经执行完毕,切换回 Java 环境,所有 Local Reference 被删除,问题也就没有显露出来。但是某些情况下就会引发 out of memory,导致实例 1 和实例 2 中的 JVM崩溃。

控制 Local Reference生命期

因此,在 JNI编程时,正确控制 JNI Local Reference的生命期。如果需要创建过多的 Local Reference,那么在对被引用的 Java对象操作结束后,需要调用 JNI function(如 DeleteLocalRef()),及时将 JNI Local ReferenceLocal Ref表中删除,以避免潜在的内存泄漏。

总结


本文阐述了 JNI编程可能引发的内存泄漏, JNI编程既可能引发 Java Heap的内存泄漏,也可能引发 native memory的内存泄漏,严重的情况可能使 JVM运行异常终止。 JNI软件开发人员在编程中,应当考虑以下几点,避免内存泄漏:

  • native code本身的内存管理机制依然要遵循。
  • 使用 Global reference时,当 native code不再需要访问 Global reference时,应当调用 JNI函数 DeleteGlobalRef()删除 Global reference和它引用的 Java对象。 Global reference管理不当会导致 Java Heap的内存泄漏。
  • 透彻理解 Local reference,区分 Local referencenative code的局部变量,避免混淆两者所引起的 native memory的内存泄漏。
  • 使用 Local reference时,如果 Local reference引用了大的 Java对象,当不再需要访问 Local reference时,应当调用 JNI函数 DeleteLocalRef()删除 Local reference,从而也断开对 Java对象的引用。这样可以避免 Java Heapout of memory
  • 使用 Local reference时,如果在 native method执行期间会创建大量的 Local reference,当不再需要访问 Local reference时,应当调用 JNI函数 DeleteLocalRef()删除 Local referenceLocal reference表空间有限,这样可以避免 Local reference表的内存溢出,避免 native memoryout of memory
  • 严格遵循 Java JNI规范书中的使用规则。

参考链接


在 JNI 编程中避免内存泄漏

Java泛型:类型擦除

类型擦除


显然在平时使用中,ArrayList<Integer>()new ArrayList<String>()是完全不同的类型,但是在这里,程序却的的确确会输出 true

这就是Java泛型的类型擦除造成的,因为不管是ArrayList<Integer>()还是new ArrayList<String>(),都在编译器被编译器擦除成了 ArrayList。 那编译器为什么要做这件事?原因也和大多数的Java让人不爽的点一样——兼容性。由于泛型并不是从Java诞生就存在的一个特性,而是等到SE5才被加 入的,所以为了兼容之前并未使用泛型的类库和代码,不得不让编译器擦除掉代码中有关于泛型类型信息的部分,这样最后生成出来的代码其实是『泛型无关』的, 我们使用别人的代码或者类库时也就不需要关心对方代码是否已经『泛化』,反之亦然。

在编译器层面做的这件事(擦除具体的类型信息),使得Java的泛型先天都存在一个让人非常难受的缺点:

在泛型代码内部,无法获得任何有关泛型参数类型的信息。

关于 getTypeParameters()的解释:

Returns an array of TypeVariable objects that represent the type variables declared by the generic declaration represented by this GenericDeclaration object, in declaration order. Returns an array of length 0 if the underlying generic declaration declares no type variables.

我们期待的是得到泛型参数的类型,但是实际上我们只得到了一堆占位符。

我们无法在泛型内部创建一个 T类型的数组,原因也和之前一样, T仅仅是个占位符,并没有真实的类型信息,实际上,除了 new表达式之外, instanceof操作和转型(会收到警告)在泛型内部都是无法使用的,而造成这个的原因就是之前讲过的编译器对类型信息进行了擦除。

同时,面对泛型内部形如 T var;的代码时,记得多念几遍:它只是个Object,它只是个Object……

虽然有类型擦除的存在,使得编译器在泛型内部其实完全无法知道有关 T的任何信息,但是编译器可以保证重要的一点:内部一致性,也是我们放进去的是什么类型的对象,取出来还是相同类型的对象,这一点让Java的泛型起码还是有用武之地的。

代码片段四展现就是编译器确保了我们放在 t上的类型的确是 T(即便它并不知道有关 T的任何类型信息)。这种确保其实做了两步工作:

  • set()处的类型检验
  • get()处的类型转换

这两步工作也成为边界动作

代码片段五同样展示的是泛型的内部一致性。

擦除的补偿


如上看到的,但凡是涉及到确切类型信息的操作,在泛型内部都是无法共工作的。那是否有办法绕过这个问题来编程,答案就是显示地传递类型标签。

代码片段六展示了一种用类型标签生成新对象的方法,但是这个办法很脆弱,因为这种办法要求对应的类型必须有默认构造函数,遇到 Integer类型的时候就失败了,而且这个错误还不能在编译器捕获。

进阶的方法可以用限制类型的显示工厂和模板方法设计模式来改进这个问题,具体可以参见《Java编程思想 (第4版)》P382。

代码片段七展示了对泛型数组的擦除补偿,本质方法还是通过显示地传递类型标签,通过 Array.newInstance(type, size)来生成数组,同时也是最为推荐的在泛型内部生成数组的方法。

参考链接


Java泛型:类型擦除
java泛型(二)、泛型的内部原理:类型擦除以及类型擦除带来的问题

JNI DETECTED ERROR IN APPLICATION: input is not valid Modified UTF-8: illegal start byte 0xfe

在使用Jni的JNIEnv->NewStringUTF的时候抛出了异常" JNI DETECTED ERROR IN APPLICATION: input is not valid Modified UTF-8: illegal start byte 0xfe"。网上搜索了一下,这个异常是由于Java虚拟机内部的 dalvik/vm/CheckJni.c中的 checkUtfString函数抛出的,并且JVM的这个接口明确是不支持四个字节的UTF8字符。因此需要在调用函数之前,对接口传入的字符串进行过滤,过滤函数如下:

参考链接 android jni中 utf-8的check

使用 Mockito 单元测试

目录


1. 需求知识

2. 使用 存根(Stub) 和 模拟对象(Mock Object) 进行测试

2.1. 为什么需要模拟?

2.2. 存根(Stub) vs. 模拟对象 (Mock)

2.3. 行为测试 vs. 状态测试

2.4. 生成模拟对象

3. 模拟框架( Mock Framework)

4. Mockito

4.1. 使用 Mockito 模拟对象

4.2. 使用 Mockito

4.3. Mockito的限制

4.4. 模拟对象的配置

4.5. 验证模拟对象的行为

4.6. Spy

5. Mockito 在 Android 平台测试

5.1. 在 Android 使用 Mockito

5.2. 安装

6. 链接和参考

1.需求知识

该教程需要理解单元测试和熟悉JUnit框架的使用。

如果您不熟悉JUnit,请阅读JUnit教程。

2. 使用 存根(Stub) 和 模拟对象(Mock Object) 进行测试

2.1. 为什么需要模拟?

一个单元测试需要在隔离的环境下执行。如果可以的话需要消除其他依赖的服务影响。但实际上,软件中是充满依赖关系的.我们会基于service类写操作类,而service类又是基于数据访问类(DAOs)的,依次下去.

为了解决这个问题, 可以使用 存根 (Stub) 或者 模拟 (Mock) 对象的方法进行测试。

2.2. 存根(Stub) vs. 模拟对象 (Mock)

存根(Stub)类是实现了一个接口或者抽象类的类,可以在测试过程中使用该类,例如:

一个模拟对象(mock object)是一个接口或者抽象类的虚拟实现。例如:

存根和模拟对象都可以传递给其他的对象进行测试。你的一些单元测试可以测这些类的正确性等。利用存根对象或者模拟对象可以保证测试过程中不受到其他的影响。

存根对象需要自定义实现方法;

模拟对象只需要更少的代码和简单的配置。

以下的内容将详细介绍模拟对象的使用方法。

2.3. 行为测试 vs. 状态测试

Mock 对象允许你对行为进行测试。有一些测试不需要验证结果,但是需要检查某些方法是否被正确的参数调用过。这种测试为行为测试。

状态测试只是关注与结果是否正确,而行为测试能够判断一个应用调用结构以及层次。

2.4. 生成模拟对象

你们可以使用Mock 框架来生成模拟对象。Mock 框架允许你在运行期间创建对象,并且定义它的一些行为。

一个典型的例子就是使用模拟对象来模拟数据库DAO层。在生产环境上是使用运行的数据库,但是在单元测试环境中完全可以用模拟对象来模拟数据,确保单元测试的正确条件。这样就不需要依赖于外部的数据。

3. 模拟框架( Mock Framework)

比较流行的模拟框架有 EasyMock、jMock 和 Mockito。下面的列表是这些框架的链接。
# jMock
http://jmock.org/
# EasyMock
http://easymock.org/
# Mockito
http://mockito.org/

4. Mockito

4.1. 使用 Mockito 模拟对象

Mockito 是比较流行的模拟框架,可以与JUnit 联合起来测试。它允许你进行创建和配置模拟对象。

Mockito的官方网站: Mockito 主页.

4.2. 使用 Mockito

Mockito 支持使用 mock() 静态方法创建模拟对象。

同样也支持 @Mock注解方式,如果使用注解的方式,需要使用在初始化方法调用 MockitoAnnotation.InitMock( this ) 方法

例如,下面的例子就是使用 Mockito 进行对类 ClassToTest 的单元测试。

提示

可以使用静态导入方法调用方法 mock()

4.3. Mockito的限制

Mockito 以下的类型不能进行构造:

  • 终态类(final classes)
  • 匿名类(anonymous classes)
  • 基本数据类型(primitive types)

4.4. 模拟对象的配置

Mockito 可以使用 verify() 方法来确认某些方法是否被调用过.

when(....).thenReturn(....) 结构可以为某些条件给定一个预期的返回值.

同样可以使用doReturn(object).when(kdskfsk).methodCall 结构

4.5. 验证模拟对象的行为

Mockito 跟踪了所有的方法调用和参数的调用情况。verify()可以验证方法的行为。

查看下面的例子:

4.6. Spy

@Spy 或者方法 spy() 可以包含一个真实的对象. 每次调用,除非特出指定,委托给改真实对象的调用.

5. Mockito 在 Android 平台测试

5.1. 在 Android 使用 Mockito

Mockito 同样也可以在安卓平台上进行测试。

5.2. 安装

在 Android 测试项目中使用 Mockito。添加下面的包到Android 测试项目的 libs 目录
https://mockito.googlecode.com/files/mockito-all-1.9.5.jar
http://dexmaker.googlecode.com/files/dexmaker-1.0.jar
http://dexmaker.googlecode.com/files/dexmaker-mockito-1.0.jar
接下来可以在你的测试项目中使用 Mockito 。

6. 链接和参考

Mockito 项目主页
Mockito 的依赖注入功能
Unit tests with Mockito - Tutorial
使用 Mockito 单元测试 – 教程

GSON序列化时排除字段的几种方式


使用transient


这个方法最简单,给字段加上 transient 修饰符就可以了,如下所示:

单元测试用例:

使用Modifier指定


这个方法需要用GsonBuilder定制一个GSON实例,如下所示:

单元测试用例:

使用@Expose注解


注意,没有被 @Expose 标注的字段会被排除,如下所示:

单元测试用例:

使用ExclusionStrategy定制排除策略


这种方式最灵活,下面的例子把所有以下划线开头的字段全部都排除掉:

单元测试用例:

参考链接


GSON序列化时排除字段的几种方式