• 理解AOP

    说下我对AOP的理解:AOP是给程序添加统一功能的一种技术。在代码层面上来说,AOP就是在必要的业务代码中织入业务无关的、统一的代码的一种技术。在实现AOP的时候,通常努力争取的目标是对业务代码无侵入或是低侵入。 平时用得比较多的是Spring中的AOP实现,还有一些比如Spring中的shiro权限验证,@Cachable注解等在某种程度上也可以认为是AOP。在Spring AOP之外还有很多其他的实现方式。 在了解AOP实现方案之前有必要看一下java类编译加载的过程。 java类编译的过程如下: .java文件 –> 编译 –> .class文件 –> 载入虚拟机 在虚拟机中加载使用类的过程如下: 装载 –> 验证 –> 准备 –> 解析 –> 初始化 –> 使用 –>卸载 上面所述的过程中,通常可以在编译和装载这两个环节向原始代码中织入AOP代码。 编译环节织入代码往往需要定制的编译器,如使用AspectJ的时候,就可以通过AspectJ的编译器ajc来完成AOP代码的织入。这个环节织入的代码class文件中就有体现。 装载环节实现代码织入的方式比较多,大致有这么几种: 基于JVMTI Agent方式; 替换默认的系统类加载器; 使用自定义类加载器; 使用动态代理。 jvmtgi方式是一种侵入度很低的形式,只需要在启动时添加一些特定的启动参数,如javaagent。我曾经写过一个java方法运行时间统计程序:jspy-agent,就是用这种方式在在java方法中织入计时代码。这种方式需要对字节码文件进行操作,目前的字节码操作框架有asm、bcel、javassist等。 使用自定义类加载器替换默认系统类加载器也只需要添加一个启动参数:-Djava.system.class.loader=com.your.CustomClassLoader。前提是需要自定义一个类加载器。当然自定义类加载器也不只是有这一种使用方式。 关于动态代理,目前用的比较多的就是JDK动态代理和CGlib了,相关的资料一抓一大把,不说了。 大体上就是这些了。不过还有一个问题,想和大家一起想想:java设计模式中的代理模式、装饰模式是不是也算是AOP的一种实现呢? 参考文档: SpringAOP织入:https://blog.csdn.net/wuliusir/article/details/32916419 JVMTI Agent:https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/ #############

    [阅读更多...]
  • 使用ThreadLocal

    之前和同事讨论过一个日期格式化的问题,程序比较简单,大体是这样: 同事并不喜欢这个代码。原因是每调用一次format方法都会创建一个SimpleDateFormat对象。虽然我一再强调SimpleDateFormat对象是方法内的,即生即灭,不会导致明显的内存或性能上的问题。但是同事还是打算尝试一下。一段时间后我看到了下面的代码: 他使用了ThreadLocal。关于ThreadLocal,官方文档的说法如下: 该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。 文档的说法有些令人费解,所以这里把英文原文也贴出来: This class provides thread-local variables.  These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable.  ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). 简而言之,ThreadLocal的作用就是为每个线程提供一个线程内的局部变量来让该线程使用。这个局部变量的状态不应该受到其他线程的影响,从而在某种程度上实现了线程隔离。 也许我们需要换一个更简单直观的程序来看一下: 在这个ThreadLocalTest类中,我们创建了一个ThreadLocal变量,通过这个变量为每个线程创建了一个值为当前时间戳的字符串。在程序中启动了3个线程来测试每个是否线程都会得到一个唯一的字符串变量。另外,在每个线程中都会重复调用线程内部的字符串变量来测试该变量是否发生了变化。 执行结果如下图: 执行结果中的第一列是线程ID,第二列则是在不同时间输出的线程内部字符串变量。可以看到不同线程对应的字符串变量是不同的,一个线程在不同时间使用的内部字符串变量是相同的。 回到一开始讨论的日期工具类上。通过使用ThreadLocal我的同事避免了每次调用format方法都要创建一个SimpleDateFormat对象的问题,减少了对象创建回收的过程。同时因为每个线程内的SimpleDateFormat实例都是线程隔离的,也就是说这个线程内部的SimpleDateFormat实例每次只会被这一个线程所使用,所以虽然SimpleDateFormat类是非线程安全的,在使用中也不会出现问题。 了解了使用ThreadLocal的优势后,又想到了另一个问题:在使用ThreadLocal时每创建一个线程就会为这个线程创建一个内部实例,那么在无限次创建线程的时候,是否会因为ThreadLocal创建的内部实例过多导致内存泄漏?或者换个角度考虑:线程运行停止后,ThreadLocal是如何回收该线程的内部实例的。 这个问题我需要想一想。想明白了换篇文儿继续说。 #####

    [阅读更多...]
  • jar包中的jar

    前两天在整理一个工程的时候突发奇想,能不能把需要的jar都打到一个类似于war包的jar包里面去,但是不解开原始jar包,而是将之统一放到外面jar包中的一个lib目录下。 按这个思路试了好几次,都遇到了NoClassDefFoundError。一开始以为是manifest中配置有误,在几次尝试调整manifest文件仍然失败之后,觉得有必要查看一些类似的案例。 类似的案例比如SpringBoot工程打成的jar包,又比如jenkins的可以直接用jar命令调用的war包。找到相关的包分析了许久,仍然找不到端倪。 后来在Oracle的官方文档中找到了解释: To load classes in JAR files within a JAR file into the class path, you must write custom code to load those classes. For example, if MyJar.jar contains another JAR file called MyUtils.jar, you cannot use the Class-Path header in MyJar.jar’s manifest to load classes in MyUtils.jar into the class path. 懒得翻译了,大意就是如果要引用jar中的jar,就需要编写额外的代码,如自定义的类加载器。SpringBoot打成的包就是这样做的。 参考文档:https://docs.oracle.com/javase/tutorial/deployment/jar/downman.html #######

    [阅读更多...]
  • 从一段计时代码开始

    下面这段代码估计是我们见过(或写过)最多的代码了: 要统计某些逻辑块的执行时间,这段代码可以说是非常简单且有效。不过如果要统计的方法很多时,为那些方法都填上这段代码就有些让人头疼了。主要有这样几个问题: 太多的重复工作; 计时内容散乱不好整理; 在业务代码中插入了业务无关的内容。 前两个缺点忍一忍多费些力气也不是不能克服。关键是最后一个问题,一旦意识到以后,再这样写就总是觉得怪怪的。 这些问题该怎么解决呢。最初的思路是用模板方法模式或装饰器模式来解决,不过很快就否定了这个思路:代码中的业务逻辑通常是各种各样的,只为了解决计时这样一个边缘需求就强行引入一些方法模式很容易造成代码结构的混乱,而且这样做也可以说是一种更深的代码侵入。发现此路不通后很自然就转到AOP方案上了——AOP生来就是为了解决这种问题的。 具体该怎么使用AOP呢。之前在使用Spring框架的时候曾经写过一个基于spring-aspect完成的方法计时的程序: 代码中的StopWatch是一个自定义注解。这段代码会计算所有在包com.zhyea下的以StopWatch注解标记的方法的执行时间。 但是工作中很多应用是不依赖Spring的,这时就应该考虑一下AOP的实现了。简单来说,AOP的实现就是在某些方法执行前后运行特定的代码(类似于装饰器模式)。这些代码可以直接插入到类中,也可以在方法执行前后引用。至于插入AOP代码的时机,大体上有如下几个: 类编译时; 类加载前; 调用方法时。 对应三种插入代码的时机有着不同的AOP的实现方案。在类编译时插入AOP代码的如AspectJ,在使用时需要特别定制的编译器;调用方法时实现AOP,就如同前面的SpringAspect的例子,需要框架的支持,否则还是有一些代码的侵入;至于在类加载时实现AOP,Spring也有提供方案,是依赖cglib实现,不过这里不展开了。 对于我们要解决的方法计时的问题,我们可以考虑采用在类加载时插入计时代码来实现。这里我们会用到java.lang.instrument包和asm。 java.lang.instrument 是 Java SE 5 的新特性。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。 asm是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。asm可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。 下面我们就用asm和instrument来实现下计时统计的需求。如下是一个测试类: 在类中的operation方法中插入计时代码后: 我们的目标就是将测试类转换成这段代码的样子。代码中的TimerClerk是一个计时统计工具类,可以忽略。 首先,我们先使用asm来完成插入计时代码的功能: 在代码中使用了AdviceAdapter这个类。通过这个类可以很方便地在进入方法和退出方法时插入代码。现在我们可以使用TimerClassAdapter在任意其他类中插入计时代码了。有心的话可以写一个main方法来试一下,也可以用反编译工具来查看生成的类看看是否和预期一致。 关于asm的更多内容有需要请参考官方文档。(在csdn也有一份中文文档可以参考,点击此处下载)。 然后是在instrument中使用上面定义的TimerClassAdapter类: 通过instrument可以在类加载时将原始类的字节码装饰成我们需要的样子。并返回新的类到类加载器中。 将代码打包为agent.jar,可以使用如下的命令执行程序: 就这样,使用-javaagent可以在启动应用前调用我们的计时代码插入程序,这样在应用运行时就能同步计算每个方法的执行时间。 另:上面的代码部分摘自我的一个开源项目jspy中,项目目前代码量还比较小,如有兴趣可以参考下。 ########

    [阅读更多...]
  • 关于Zero Copy

    概述 很多web应用都会有大量的静态文件。我们通常是从硬盘读取这些静态文件,并将完全相同的文件数据写到response socket。这样的操作需要较少的CPU,但是效率有些低,它需要经过如下的过程:kernel从硬盘读取数据,越过kernel-user边界将数据传递给用户空间的web应用;用户空间的web应用再次越过kernel-user边界将完全相同的数据写回到kernel空间的socket。在将数据从硬盘传递到socket的过程中,用户空间web应用的角色相当于一个中介,并且有些低效。 数据每次经过kernel-user边界的时候,都需要被copy一次,这样会消耗CPU资源及内存带宽。幸运的是,我们可以使用一种被称为zero copy的技术来消除这些copy操作。使用zero copy技术的应用会请求kernel直接将数据从硬盘拷贝到socket,而无需再经过应用。zero copy极大地提升了应用的性能,并减少了内核态和用户态上下文切换的次数。 在Linux和UNIX系统上,Java类库通过java.nio.channels.FileChannel的transferTo()方法实现了对zero copy的支持。我们可以使用transferTo()方法将读取到的字节数组直接从被调用的channel传输到另一个可写的channel上,这个过程中数据流转不需要通过应用。 接下来我们会先讲解一下如何使用传统的多次copy的机制实现数据的传输,而后再演示下使用transferTo()方法实现的zero copy技术是如何提升性能的。 传统数据传输方案 思考一下如下的场景:从一个文件读取数据,通过网络将数据传递给另一个应用程序(这个场景描述了大部分服务器应用的行为,包括处理静态文件的WEB服务器,FTP服务器,Mail服务器等)。这个操作的核心步骤只有两步,我们看下代码: 我们的代码只有两行,看起来很简单,但是服务器完成这个过程却需要在用户态和内核态之间进行4次上下文切换,也就是说在这个操作完成之前数据需要被copy 4次。下面的图片展示了服务器是如何将数据从文件传输到socket的。 图一:传统模式下数据拷贝过程: 图二:传统模式下内核态和用户态之间的上下文切换 涉及到的步骤包括: 调用read()方法导致了用户态到内核态的切换(参看图二)。在系统内部是通过sys_read()(或类似的其他方法)从文件读取数据。第一次copy(参看图一)是通过直接内存访问(DMA)引擎实现的,这次copy从硬盘上读取了文件内容并将之保存在内核空间的缓冲区中。 第二次copy发生在数据从内核缓冲区被copy到用户缓冲区时,此时read()方法也返回了。read()方法的返回导致了从内核态到用户态一次切换。现在数据是保存在用户空间的缓冲区中。 socket调用send()方法再次引起了用户态到内核态的切换。第三次copy再次将数据放回到内核缓冲区。不过这次的内核缓冲区和上次的不同,这次的缓冲区和目标socket相关。 调用的send()方法返回时,产生了内核态到用户态的上下文切换。这次DMA引擎将数据从内核缓冲区发送到protocol引擎,也就是第四次copy,这是一个独立异步的操作。 使用内核缓冲区作为中间层(而不是直接将数据传送到用户缓冲区)可能看起来有些低效。但是最初将内核缓冲区作为中间层引入进程的目的就是提升性能。在读取数据的时候,作为中间层的内核缓冲区的角色相当于“预读取缓存”,也就是说如果应用请求的数据量比内核缓冲区空间小,就会将一部分数据预读取到作为中间层的内核缓冲区中以供下一次请求使用。很显然,在请求的数据量比内核缓冲区空间小时,这样做可以显著地提升应用性能。在写数据的时候,多个中间层有助于更好地实现异步写(先将数据写到中间缓存,中间层快满时再批量写出)。 不幸的是,在请求的数据量大过内核缓冲区很多时,这种方法本身也会成为性能瓶颈:因为数据会在硬盘、内核缓冲区和用户缓冲区之间多次拷贝。 zero copy可以排除这些多余的copy来提升性能。 zero copy方案 重新思考一下传统的数据传输方案,将会发现第二次和第三次的copy行为实际上是不必要的。在传统方案里,应用做的事情只不过是缓存数据并将之转发到socket缓冲区,我们可以考虑直接将数据从读缓存发送到socket缓冲区中。transferTo()方法能让我们实现这种操作。 transferTo()方法的定义如下: transferTo()方法可以将数据从FileChannel发送到指定的WritableByteChannel中。transferTo()方法需要依赖底层操作系统的支持才能实现zero copy。在UNIX系统和各种Linux系统中,支持zero copy的系统方法是sendfile(),这个方法可以将数据从一个文件描述符转发到另一个文件描述符中。 sendfile()方法定义: 在概述中,我们写过两行代码演示传统数据传输的方法,演示代码中的file.read()和socket.send()两个方法的调用可以替换为调用transferTo()方法,示例如下: 下图演示了调用transferTo()方法时数据传输的路径: 下图演示了调用transferTo()方法时用户态和内核态上下文切换的过程: 调用transferTo()方法涉及到的步骤为: 调用transferTo()方法产生了第一次copy:DMA引擎将文件内容copy到了读缓存中。 然后系统内核将数据copy到与输出socket相关的内核缓冲区中。 第三次copy发生在DMA引擎将数据从内核socket缓冲区发送到protocol引擎时。 看看效果: 将用户态-内核态上下文切换由四次减少到了两次; 将数据的copy由四次减少到了三次(其中只有一次涉及到CPU)。 不过这样子还没有达到使用zero copy的目标。如果底层网卡支持收集操作的话,我们还可以去掉由内核完成的copy(即第二次copy)。在Linux Kernel2.4及以后的版本中,socket缓冲区描述符已经被调整到满足这种需求了。这样这个方案不仅仅是减少了上下文切换的次数,也消除了copy过程中对CPU依赖的部分。尽管用户还是在用transferTo()方法,但是其底层行为已经发生了变化: 调用transferTo()方法时,DMA引擎将文件内容copy到内核缓冲区中; 不再将数据copy到socket缓冲区中,只是将数据描述符(包含地址信息和长度信息)追加到socket缓冲区。DMA引擎直接将数据从内核缓冲区传递到protocol引擎,从而消除了仅剩的CPU copy。 下图展示了使用transferTo()方法和收集操作时copy的详情: 构建文件服务器 现在我们练习使用一下zero copy,就演示一下文件在客户端和服务器之间的传递(示例代码下载地址见文末)。TraditionalClient.java以及TraditionalServer.java是基于传统方案的实现,和新方法是File.read()和Socket.send()。TraditionalServer.java是一个Server端程序,它监听着一个特定的端口以让Client连接,每次会从socket读取4KB数据。TraditionalClient.java连接到Server上,从一个文件中读取(使用File.read()方法)4KB数据并通过socket将数据发送(使用Socket.send()方法)给Server。 类似的,TransferToServer.java和TransferToClient.java实现了相同的功能,不过使用的是transferTo()方法(调用了系统的sendfile()方法),将文件数据从Server端发送到了Client端。 性能比较 我们在一台Linux Kernel版本2.6的机器上执行了示例代码,以毫秒级的时间尺度比较了传统方案和transferTo()方案传输不同大小的数据文件的速度。下表为测试结果: File size Normal file transfer (ms) transferTo (ms) 7MB 156 45 21MB 337 128 63MB 843 387 98MB 1320 617 200MB 2124 1150 350MB 3631 1762 700MB 13498 4422 1GB 18399 8537 可以看到,较之传统方案,transferTo() API降低了大约65%的时间消耗。对于需要在IO channel间进行大量数据copy和传输的应用(比如WebServer),transferTo()可以显著地提升性能。 总结 我们演示了使用transferTo()的性能优势,可以看到中间缓冲区copy(即使是发生在内核中)会有一定的性能损失。对于需要进行channel间大量数据copy的应用,zero copy技术可以显著地提升性能。 其他 本文译自:Efficient data transfer through zero copy 实例代码下载地址:原始地址   CSDN下载 ###################

    [阅读更多...]
  • SpringBoot集成Jersey的单元测试方案

    使用SpringBoot集成Jersey做单元测试时遇到了application.xml找不到的提示。详情如下: 测试使用的代码大致如下: 原因是SpringBoot的Context和Jersey的Context是不同的。要修复这个问题可以基于SpringBoot的Context来构建测试时Jersey的Context,但是这样做也会遇到一些问题。具体什么问题懒得说了。直接说解决方案:跳过Jersey的Context,不使用JerseyTest抽象类,直接自行创建WebTarget实例。 看一个测试超类代码示例好了: 就这样。 #############

    [阅读更多...]
  • 解决Gson 处理Map将整型处理为浮点型的问题

    gson一直是我用着非常得心应手的json处理工具。但是最近遇到了一个坑,就是在处理java.util.Map型json字符串的时候会把整型转为浮点型。 示例程序如下: 执行结果如下: 原因在于json的语法中关于数值只有一个number类型,而不会去判断这个number具体是整型是浮点型还是长整型。而Gson处理的时候也确实是偷懒了,统一将之视为浮点型。可以在Gson的ObjectTypeAdapter类中看到: 在case NUMBER这一行可以看到,数值型的数据都被处理为Double型的值。此外因为这里使用的Map的value类型是Object,在示例代码中就涉及到了Integer、Double和String。所以也不能使用TypeToken这样的解决方案。使用TypeToken的方式如下,适用于有明确的泛型说明的情况: 其它的解决方案也简单:1. 使用其他的json库,比如jackson和fastjson,亲测过,都没有这种问题;2. 添加自定义模块,修改这个问题。 详细说明下方案2。Gson支持添加自定义解析方案,可以使用GsonBuilder的registerTypeAdapter和registerTypeHierarchyAdapter。前者只针对设置的类进行序列化及反序列化,后者可以对设置的类及其子类进行序列化。可以添加的解析类的类型包括JsonSerializer、JsonDeserializer和TypeAdapter这三个接口的实现类。下面是一个使用自定义的JsonDeserializer方案: 这个方案也是有局限性的,只适用于Map的key和value都是数值型和字符串的情况(也可以添加对boolean的支持)。对于复杂的Map结构就有些无力了。 此外另一个方案是根据ObjectTypeAdapter自定义TypeAdapter。这个我也试过,功能支持还算可以,它解析json的方式是分层实现的。不过如果目标是具体类还可以,对于抽象类或接口的适用性就差一些。因为关键是json最外层的解析,如果是json对应的是对象的话,也就只能封装成一个目标类型对象或其超类对象。 先就这样。 #############

    [阅读更多...]
  • Hello Akka

    Akka是一个工具,用来在JVM上构建高并发、分布式、容错的事件驱动的应用。Akka支持java和scala两种语言。Akka最强大的一个特性是使用了并发Actor模型。 这一次本文会同时使用java和scala来进行说明。 示例代码 下面是一个非常简单使用akka的的例子。 java(做了折叠): scala(做了折叠): 在上面的例子中定义了一个Greetor  Actor,这个Actor可以获取最新的greeting消息,并对两种行为作出响应:设置一个新的greeting字符串;返回最新的greeting字符串。 接下来会详细解释下这个例子。 定义消息类 Actor并没有提供让开发者调用的公共API。它是通过Actor处理的消息来提供公共API的。消息可以是任意类型(java中指的是继承了Object的类,在scala中指的是继承了Any的类)的对象。也就是说我们可以使用包装类来发送直接类型的消息,也可以发送一些数据结构比如数组或者集合类的对象。然而因为消息是Actor的公共API,所以定义消息类的时候需要保证消息类的名称有一定的语义,或者在指定领域内有意义,尽管有时仅仅是将已有的类简单封装了下。不过这样可以构建简单易懂的Actor系统,在调试的时候也可以更容易一些。 在代码中我们定义了三个消息类: WhoToGreet,重新定义新的greeting消息; Greet,向Actor请求最新的消息; Greeting,返回最新的greeting消息。 在示例中这三个消息类定义在外部类HelloAkka中。这里要注意在消息对象中保存的消息应该是不可变的,否则的话就可能会出现两个不同的Actor共享状态的危险,这违反了Actor模型的设计原则。 虽然在这个示例中我们没有使用远程连接,但是在定义消息类时实现序列化是个好习惯——这样我们使用akka扩展到多个节点时就不需要再回过来修改代码。 消息类的定义: 下面是使用scala定义消息类的代码。scala的case类和case对象对Actor消息的支持非常好,因为它们本就是不可变的且支持模式匹配(这在对Actor接收的消息进行匹配时很有用)。使用case类的另一个好处就是其默认支持序列化。 定义Actor Actor是Akka的执行单元。Actor是面向对象的,因为它也封装了状态和行为。但是它要比Java或Scala中的普通对象有更强的隔离性。Actor模型禁止两个或多个Actor间共享状态的行为。一个Actor观察另一个Actor的状态的方式就是向其发送一条消息来请求状态。Actor是非常轻量级的——它只受到内存的限制。每个Actor只会消耗几百个字节的内存,这意味着在一个应用中可以轻松地创建数百万个并发的Actor。Actor模型强大的隔离原则以及事件驱动模型(稍后会说到)和位置透明等特性让我们可以用直观的方式轻松解决并发问题和伸缩性问题。 在java中创建Actor类需要继承抽象类UntypedActor并实现onReceive方法。使用scala也是差不多的方式,需要继承Actor trait并实现receive方法。在onReceive方法中定义了Actor的行为,在这个方法中Actor可以对它收到的不同的消息做出不同的反馈。一个Actor可以有或者说是通常都会有状态。访问或者是改变一个Actor的状态是完全线程安全的,因为这受到Actor模型的保护。 现在开始创建一个Greeter Actor,并使用一个变量greeting作为这个Actor的状态。greeting代表获取的最新定义的greeting消息。在Greeter的onReceive方法里面我们添加了一些行为用来对Greet和WhoToGreet两种消息分别作出反馈。先来看Java的实现: 代码中定义的Actor继承了抽象类UntypedActor,意味着其接收的消息是没有类型约束的,如代码中就是Object。此外也有类限制的Actor,不过目前暂时不关注这些。通常使用的Actor都是没有类型约束的。 暂时先不要考虑代码中定义的getSender()、tell(…)和getSelf()等API。稍后说到发送和响应消息时会详细解释这些API。 现在再看一下scala的实现。如代码中所示,scala case类的模式匹配的特性可以很大程度地简化Actor的receive方法。不过除此以外的内容还是还是和java版本很相似的: 有没有注意到scala版本的Greetor和java版本的一处不同:在scala版本的代码中并没有将未知类型的消息传递给unhandled方法。在scala中这不是必需的,因为scala将receive方法的行为解释为一个偏应用函数,也就是说匹配不上的语句会被默认为不处理,并由Akka自动将之传递给unhandled()方法。 另外一个不同就是Scala版本中继承的trait是Actor,而非是UntypedActor。因为这是scala的API,而不是Java的API,尽管二者本质上是同一种Actor。 创建Actor 到现在我们已经说过了如何创建Actor和消息。接下来我们会说一下如何创建Actor的实例。在Akka中创建Actor实例不能像平常一样直接使用new关键字,需要通过一个工厂来创建。这个工厂也不会直接返回一个目标Actor的实例,而是返回一个指向Actor实例的ActorRef对象。使用ActorRef看起来像是隔了一层,但是却增加了许多功能和灵活性。比如说位置透明:在相同语义的情况下,ActorRef表示的正在运行的实例既可以是在当前进程下也可以是在远端机器上。也就是说,位置并不重要。这也意味着,如果需要的话,可以在运行时改变Actor的位置或者调整应用的拓扑结构来优化系统。ActorRef这种间隔带来的另一个特性是使用“let it crash”模型来进行故障管理:系统可以主动crash故障的Actor并重启以实现自我治愈。 Akka中的这个工厂是ActorSystem。ActorSystem在某种程度上类似于Spring的BeanFactory,它也可以作为所有Actor的容器,执行管理这些Actor的生命周期等工作。可以通过一个名为actorOf的工厂方法创建Actor实例。这个方法需要一个Props的配置实例和一个名称。Actor(和ActorSystem)的名称在Akka中很重要,可以在查看Actor信息和在配置文件中添加配置时使用它们。因此完全有必要花些时间为Actor和ActorSystem起一个好名称。 下面是用java写的代码: scala的代码也没有太多不同: 现在我们已经创建了一个Greeter Actor的运行实例。接下来我们要看一下如何与之进行通信。 告诉Actor去做一些事情 和Actor的所有通信都是通过异步消息传递完成的。这也是如何使Actor做出反应以及事件驱动的方式。Actor不会主动做任何事情,除非它被通知做某事。开发者可以通过发送消息通知Actor做某些事。异步发送消息意味着发件方不会坚持等待接收方处理完消息。相反的,发件方只是将消息发送到接收方的收件箱里,然后就可以自由地去做一些比等待接收方处理消息更重要的事情了。接收方的收件箱本质上是一个队列,且是有序的。这保证了同一个Actor发送的多条消息的排序会被保留,不过却有可能会与另一个Actor发送的消息交治。 你可能会想知道当一个Actor在不处理任何消息时会做些什么事情。会做一些其他的具体的工作么?其实不会,此时Actor处于完全暂停的状态,它不会消耗除了内存以外的任何资源。就像牵线木偶一样。 我们可以通过传递消息到ActorRef的tell方法中告诉Actor去做一些事情。这个方法将消息放到Actor的收件箱以后就会立即退回。 java代码: scala代码: 使用scala还可以写得更简洁一些: 这里使用的“!”是一个绑定操作。 向Actor做出回复 发件方的自引用 在上面的代码里,没有等待Actor作出回复。有时候通信不是简单地单向通信模式,而是倾向于请求应答模式。这时一个直接的方式是添加一个对发件方的引用作为消息的一部分,以便接收方可以通过这个引用发送回复给发件方。这是一个常见的情况,它由Akka直接支持。对于发送的每一条消息,开发者都可以选择是否传递发件方的引用(Actor对应的ActorRef)。如果是从一个Actor内发送消息,那么就可以用过该Actor的自引用访问到这个Actor的ActorRef。然而请注意,不应该这样使用。在Java中可以通过getSelf()访问自引用,在scala中则可以通过self()方法。 java代码: scala的代码就会简单一些了。scala有一个隐式参数列表的特性,它允许自动透明地将参数传递给方法。我们在向另一个Actor发送消息时,可以利用这个特性自动传递对发件方的引用。 下面这段代码如果是在Actor A内部调用的,会在发送消息时将Actor A的ActorRef作为消息的发件方一起传递出去: 如果选择在tell方法中不传递对发件方的引用,或者说是忘记了,就会默认使用一个被称为“dead-letter”的Actor的引用。“dead-letter”是所有未处理的消息的结尾标识,可以使用Akka的事件总线( Event Bus )来订阅这种消息。 引用发件方 发件方的引用将会在接收方Actor处理消息时可用。因为每条消息都有一个与之唯一配对的发件方引用,也就是说接收方处理的每条消息的发件方引用是一直在变化的。因此,如果开发者出于某种原因想要在处理消息后继续使用某个特定的发件方引用,就需要保证持有它——可以考虑将之保存在一个成员变量或类似的结构中。要访问发件方引用,在java中可以使用getSender()方法,在scala中则可以直接使用sender: scala代码如下: 使用收件箱 当前大部分实际应用的Actor应用都会使用不止一个Actor。Actor模型的发明者,Carl Hewitt,最近在一个采访中说道:“One Actor is no Actor. Actors come in systems”。这是很重要且富有智慧的一个评论。要真正利用Actor模型,就应该使用大量的Actor。Actor编程中的每一个难题都可以通过添加更多的Actor来解决——通过将这个难题拆分成更细的子任务并将之委托给新的Actor。 为了简单起见,我们在这个示例中只使用了一个Actor。这意味着如果我们是从主程序与这唯一的一个Actor进行通信,我们就没有发件方,因为我们没有从另一个Actor的内部发送消息。幸运的是Akka提供了一个很好地解决方案:Inbox(收件箱)。 Inbox允许开发者创建一个“actor-in-a-box”。也就是说在Inbox中可以包含一个傀儡式的Actor,通过这个傀儡Actor可以向其他Actor发送消息并接收它们的回复。可以使用Inbox.create()方法创建一个Inbox实例,并使用inbox.send()方法从中发送消息。Inbox内置的傀儡Actor会把收到的所有消息放到一个队列里面,而后可以使用inbox.receive()方法将消息取出来。如果取消息的时候队列为空,那么调用的receive方法将会阻塞——直到有一条消息可以取出位置。很简单是吧。 开发者应该都知道:阻塞非常容易影响性能和扩展性,使用阻塞应当慎之又慎。我们在这实例中使用阻塞方法是因为它可以简化消息流,方便大家理解Actor模型。 现在我们将通过编写Greeter Actor的驱动程序代码来结束这篇文章。 java版本: scala版本: 就这样! 参考文档 http://www.lightbend.com/activator/template/hello-akka

    [阅读更多...]
  • 需要class,interface或enum

    引入github上的一个项目后在编译时收到了“需要class,interface或enum”这样的错误。就像下图: 图片是我随便找的,不过错误是一样的。 这样的错误通常是由编码问题导致的,只需要修改文件编码或项目编码就可以了,然而随之而来的另一个问题就是代码中的中文可能会丢失。后来找的解决方案是这样的,在pom中指定项目编码: 还有就是在编译时指定编码格式,具体怎么做也忘了,请自行百度或谷歌吧。

    [阅读更多...]
  • HttpClient多线程并发

    说明:以下的代码基于httpclient4.5.2实现。 我们要使用java的HttpClient实现get请求抓取网页是一件比较容易实现的工作: 要多线程执行get请求时上面的方法也堪用。不过这种多线程请求是基于在每次调用get方法时创建一个HttpClient实例实现的。每个HttpClient实例使用一次即被回收,也意味着每次调用这里的get方法都需要重新创建连接。这显然不是一种最优的实现。 HttpClient提供了多线程请求方案,可以查看官方文档的《Pooling connection manager》这一节。HttpCLient实现多线程请求是基于内置的连接池实现的,其中有一个关键的类即PoolingHttpClientConnectionManager,这个类负责管理HttpClient连接池。在PoolingHttpClientConnectionManager中提供了两个关键的方法:setMaxTotal和setDefaultMaxPerRoute。setMaxTotal设置连接池的最大连接数,setDefaultMaxPerRoute设置每个路由上的默认连接个数。此外还有一个方法setMaxPerRoute——单独为某个站点设置最大连接个数,像这样: 根据文档稍稍调整下我们的get请求实现: 这样就差不多了。不过对于我自己而言,我更喜欢httpclient的fluent实现,比如我们刚才实现的http get请求完全可以这样简单的实现: 我们要做的只是将以前的httpclient依赖替换为fluent-hc依赖: 并且这个fluent实现天然就是采用PoolingHttpClientConnectionManager完成的。它设置的maxTotal和defaultMaxPerRoute的值分别是200和100: 唯一一点让人不爽的就是Executor没有提供调整这两个值的方法。不过这也完全够用了,实在不行的话,还可以考虑重写Executor方法,然后直接使用Executor执行get请求: 就这样! ####

    [阅读更多...]