OneCoder


  • 首页

  • 归档

  • 标签

  • 关于

  • 搜索

ZeroMQ 初学 Java Binding验证代码

发表于 2014-01-15
学习ZeroMQ使用,根据官方文档介绍,写了如下Java验证代码。仅供参考。需要依赖jzmq的jar包和本地库。
 
1、请求-响应模式
package com.coderli.zeromq.requestreplay;


import org.zeromq.ZMQ;
import com.coderli.zeromq.JZMQBase;


/**
 * ZeroMQ 请求响应模式验证代码 <br>
 * 此为服务端
 *
 * @author OneCoder
 * @date 2014年1月13日 下午11:28:47
 * @website http://www.coderli.com
 */
public class ReplayServer extends JZMQBase {


     public static void main(String[] args) {
           // 参数代表使用多少线程,大多数情况下,1个线程已经足够。
          ZMQ.Context context = ZMQ. context(1);
           // 指定模式为响应模式
          ZMQ.Socket socket = context.socket(ZMQ. REP);
          socket.bind( LOCAL_ADDRESS); // 绑定服务地址及端口
           for (;;) {
              System. out.println( "Server start.");
              socket.recv();
              String str = "Ok, I'm server";
              socket.send(str);
          }
     }
}
package com.coderli.zeromq.requestreplay;


import org.zeromq.ZMQ;
import com.coderli.zeromq.JZMQBase;


/**
 * ZeroMQ 请求响应模式验证代码 <br>
 * 此为客户端
 *
 * @author OneCoder
 * @date 2014年1月11日 上午10:34:18
 * @website http://www.coderli.com
 */
public class RequestClient extends JZMQBase {


     /**
      * @param args
      * @author OneCoder
      * @date 2014年1月11日 上午10:34:18
      */
     public static void main(String[] args) {


          ZMQ.Context context = ZMQ. context(1);
           // 指定模式为请求模式
          ZMQ.Socket socket = context.socket(ZMQ. REQ);
           // 创建链接
          socket.connect( LOCAL_ADDRESS);
           int count = 1;
           for (;;) {
               try {
                    long time = System. nanoTime();
                   socket.send( "Hello, currentTime: " + count);
                    byte[] recs = socket.recv();
                    long end = System. nanoTime();
                   System. out.println( new String(recs) + " Cost time: "
                             + (end - time));
                   count++;
                   Thread. sleep(1000);
              } catch (Exception e) {
                   e.printStackTrace();
              }
          }
     }
}
测试结果,单线程请求-相应一次的耗时大概在450us。
2、Publish-subscribe
package com.coderli.zeromq.pubsub;


import java.util.Random;


import org.zeromq.ZMQ;


import com.coderli.zeromq.JZMQBase;


/**
 * ZeroMQ 发布订阅模式Java验证代码 <br>
 * 此为发布者
 *
 * @author OneCoder
 * @date 2014年1月14日 上午11:16:03
 * @blog http://www.coderli.com
 */
public class Publisher extends JZMQBase {


     public static void main(String[] args) throws InterruptedException {
           // 参数代表使用多少线程,大多数情况下,1个线程已经足够。
          ZMQ.Context context = ZMQ. context(1);
           // 指定模式为发布者
          ZMQ.Socket socket = context.socket(ZMQ. PUB);
          socket.bind( LOCAL_ADDRESS); // 绑定服务地址及端口
           for (;;) {
               int i = ( int) ( new Random().nextDouble() * 2 + 1);
              String s = String. valueOf(i);
               long time = System. nanoTime();
              socket.send(s + String. valueOf(time));
              System. out.println( "发布了新消息,时间:" + time + " 类型:" + s);
              Thread. sleep(2000);
          }
     }
}
package com.coderli.zeromq.pubsub;


import org.zeromq.ZMQ;


import com.coderli.zeromq.JZMQBase;


/**
 * ZeroMQ 发布订阅模式Java验证代码 <br>
 * 此为订阅者1号
 *
 * @author OneCoder
 * @date 2014年1月14日 上午11:16:03
 * @blog http://www.coderli.com
 */
public class SubscriberOne extends JZMQBase {


     public static void main(String[] args) throws InterruptedException {
          ZMQ.Context context = ZMQ. context(1);
           // 指定模式为请求模式
          ZMQ.Socket socket = context.socket(ZMQ. SUB);
           // 创建订阅者,必须要过主题过滤器
           byte[] filter = "1".getBytes();
          socket.subscribe(filter);
          socket.connect( LOCAL_ADDRESS);
           for (;;) {
               byte[] recs = socket.recv();
               long receiveTime = System. nanoTime();
              String oriMsg = new String(recs);
              String msg = new String(recs,1,recs.length-1);
               long pubTime = Long. valueOf(msg);
               long costTime = receiveTime - pubTime;
              System. out.println( "Receive: " + oriMsg + " Cost time: " + costTime);
          }
     }
}
package com.coderli.zeromq.pubsub;


import org.zeromq.ZMQ;


import com.coderli.zeromq.JZMQBase;


/**
 * ZeroMQ 发布订阅模式Java验证代码 <br>
 * 此为订阅者2号
 *
 * @author OneCoder
 * @date 2014年1月14日 上午11:16:03
 * @blog http://www.coderli.com
 */
public class SubscriberTwo extends JZMQBase{


     public static void main(String[] args) throws InterruptedException {
          ZMQ.Context context = ZMQ. context(1);
           // 指定模式为请求模式
          ZMQ.Socket socket = context.socket(ZMQ. SUB);
           // 创建订阅者,必须要过主题过滤器
           byte[] filter = "2".getBytes();
          socket.subscribe(filter);
          socket.connect( LOCAL_ADDRESS);
           for (;;) {
               byte[] recs = socket.recv();
               long receiveTime = System. nanoTime();
              String oriMsg = new String(recs);
              String msg = new String(recs,1,recs.length-1);
               long pubTime = Long. valueOf(msg);
               long costTime = receiveTime - pubTime;
              System. out.println( "Receive: " + oriMsg + " Cost time: " + costTime);
          }
     }
}
   * 发布者中随机发布开头为1或者2的消息。
   * 订阅者中必须有过滤器,从前向后匹配发布者发送的消息,完全匹配则接收消息。这里1/2号订阅者分别过滤消息开头是1/2的数据。
   * 如果没有发布者,则订阅者阻塞,直到有发布者发送消息。如果订阅者掉线,消息会丢失。
   * 如果要订阅多个filter只需多次调用subscribe方法即可。
   * 从发布到订阅收到消息,大约耗时300us。
 
3、PipeLine模式
 
想象一下这样的场景,如果需要统计各个机器的日志,我们需要将统计任务分发到各个节点机器上,最后收集统计结果,做一个汇总。PipeLine比较适合于这种场景,他的结构图,如图3所示。
package com.coderli.zeromq.pipeline;

import org.zeromq.ZMQ;
import com.coderli.zeromq.JZMQBase;


/**
 * ZeroMQ Pipeline模式Java验证代码 <br>
 * 此为主Pusher
 *
 * @author OneCoder
 * @date 2014年1月14日 上午11:16:03
 * @blog http://www.coderli.com
 */
public class MainPusher extends JZMQBase {


     public static void main(String[] args) throws InterruptedException {
           // 参数代表使用多少线程,大多数情况下,1个线程已经足够。
          ZMQ.Context context = ZMQ. context(1);
           // 指定模式为Pusher
          ZMQ.Socket socket = context.socket(ZMQ. PUSH);
          socket.bind( LOCAL_ADDRESS); // 绑定服务地址及端口
           for (;;) {
               long time = System. nanoTime();
              socket.send(String. valueOf(time));
              System. out.println( "发布了新消息,时间:" + time);
              Thread. sleep(2000);
          }
     }


}
package com.coderli.zeromq.pipeline;

import org.zeromq.ZMQ;
import com.coderli.zeromq.JZMQBase;


/**
 * ZeroMQ Pipeline模式Java验证代码 <br>
 * 此为中转worker
 *
 * @author OneCoder
 * @date 2014年1月14日 上午11:16:03
 * @blog http://www.coderli.com
 */
public class WorkerOne extends JZMQBase {


     public static void main(String[] args) {
           // 指定模式为pull模式
          ZMQ.Socket receiver = ZMQ.context(1).socket(ZMQ.PULL);
          receiver.connect( LOCAL_ADDRESS);
           // 指定模式为push模式
          ZMQ.Socket sender = ZMQ.context(1).socket(ZMQ.PUSH);
          sender.connect( LOCAL_ADDRESS_PUSHER);
           for (;;) {
               byte[] recs = receiver.recv();
               long receiveTime = System. nanoTime();
              String oriMsg = new String(recs);
               long pubTime = Long. valueOf(oriMsg);
               long costTime = receiveTime - pubTime;
              System. out.println( "Receive: " + oriMsg + " Cost time: " + costTime);
              sender.send( "1" + oriMsg);
              System. out.println( "Send to sinker.");
          }
     }
}
package com.coderli.zeromq.pipeline;

import org.zeromq.ZMQ;
import com.coderli.zeromq.JZMQBase;


/**
 * ZeroMQ Pipeline模式Java验证代码 <br>
 * 此为中转worker
 *
 * @author OneCoder
 * @date 2014年1月14日 上午11:16:03
 * @blog http://www.coderli.com
 */
public class WorkerTwo extends JZMQBase {


     public static void main(String[] args) {
           // 指定模式为pull模式
          ZMQ.Socket receiver = ZMQ.context(1).socket(ZMQ.PULL);
          receiver. connect(LOCAL_ADDRESS);
           // 指定模式为push模式
          ZMQ.Socket sender = ZMQ.context(1).socket(ZMQ.PUSH);
          sender. connect(LOCAL_ADDRESS_PUSHER);
           for (;;) {
               byte[] recs = receiver.recv();
               long receiveTime = System. nanoTime();
              String oriMsg = new String(recs);
               long pubTime = Long. valueOf(oriMsg);
               long costTime = receiveTime - pubTime;
              System. out
                        .println( "Receive: " + oriMsg + " Cost time: " + costTime);
              sender.send( "2" + oriMsg);
              System. out.println( "Send to sinker.");
          }
     }
}
package com.coderli.zeromq.pipeline;

import org.zeromq.ZMQ;
import com.coderli.zeromq.JZMQBase;


/**
 * ZeroMQ Pipeline模式Java验证代码 <br>
 * 此为最终sinker
 *
 * @author OneCoder
 * @date 2014年1月14日 上午11:16:03
 * @blog http://www.coderli.com
 */
public class Sinker extends JZMQBase {


     public static void main(String[] args) {
          ZMQ.Context context = ZMQ. context(1);
           // 指定模式为pull模式
          ZMQ.Socket receiver = context.socket(ZMQ. PULL);
          receiver. bind(LOCAL_ADDRESS_PUSHER);
           for (;;) {
               byte[] recs = receiver.recv();
               long receiveTime = System. nanoTime();
              String oriMsg = new String(recs);
              String msg = new String(recs,1,recs.length-1);
               long pubTime = Long. valueOf(msg);
               long costTime = receiveTime - pubTime;
              System. out.println( "Receive: " + oriMsg + " Cost time: " + costTime);
          }
     }
}
以上只是一些初级结构的初步使用,对于我来说重点还是研究router模式,实现N对M集群的定向通信。随后会公布研究代码
阅读全文 »

Windows7 x64下编译ZeroMQ和JZMQ Java Binding

发表于 2014-01-13

首先,先说明的是,OneCoder采用的是使用vs2010的编译方式,zeromq的版本是3.2.4。

先编译zeromq3.2.4的源码。双击builds/msvc/ 目录下的msvc.sln导入到VS2010。选择x64位编译器,生成解决方案。

默认的生成目录是在zeromq-3.2.4\builds\msvc\Release\ 下。

然后编译Jzmq,从github上下载源码。
git clone https://github.com/zeromq/jzmq.git

默认master分支的代码在windows好像目录结构不对,所以这里选择的是切换到tag2.2.2的分支。
同样,打开jzmq目录下,builds\msvs\msvc.sln文件,导入到VS2010中。
编辑工程属性。选择x64编译环境。编辑VC++工程目录

在包含目录中,添加JDK的include目录,JDK include目录中的win32目录以及刚才zeromq目录中的include目录。

在库目录中,添加刚才编译zeromq生成的release目录。

然后生成解决方案即可。

与4.0.3版本编译,会出现异常:

error C2371: “int8_t”: 重定义;不同的基类型

可能兼容性还有问题。

运行期,只需要依赖编译生成的jzmq.dll和libzmq.dll文件即可。

阅读全文 »

MacOS10.9 下 ZeroMQ4.0.3和Java Binding安装部署

发表于 2014-01-10

ZeroMQ是什么可以自己去官网了解。
http://zeromq.org/

Mac下,对于安装了brew的朋友,很简单了。
首先安装zeromq

brew install zeromq

如果报错,很可能是因为没有安装命令行编译工具。可以通过xcode命令安装

xcode-select --install

安装成功后,即可正常编译zeromq了。

对于自己手动编译的朋友,也不麻烦。首先还是要保证安装了命令行编译工具,同上通过xcode-select安装。然后解压zeromq。然后进入目录,执行:

./configure
make
make install

然后安装jzmq,java binding
通过github下载源码
git clone https://github.com/zeromq/jzmq.git
然后依次执行

./autogen.sh
./configure
make
make install

即可在/usr/local/share/java 目录下编译出zmq.jar。

注:
如果报错:
checking whether the C compiler works… no
可以执行:

export CC=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc
CPP='/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc -E'

 

阅读全文 »

Restlet2.1.6发布,修正ObjectRepresentation的构造函数问题

发表于 2013-12-16

OneCoder在Restlet 2.1.4中 匪夷所思的ObjectRepresentation的构造函数中,提到过在使用2.1.4的时候遇到的异常

Exception in thread "main" java.lang.IllegalArgumentException : The serialized representation must have this media type: application/x-java-serialized-object or this one: application/x-java-serialized-object+xml
          at org.restlet.representation.ObjectRepresentation.<init>(ObjectRepresentation.java:203)
     at org.restlet.representation.ObjectRepresentation.<init>(ObjectRepresentation.java:114)

当时结果阅读代码,认为是Restlet的一个bug,并提交给Restlet。得到回复确认,称将在2.1.6版本中修复:

Hello,

thansk a lot for reporting this issue which is clearly a regression. I've added a ticket for that point: https://github.com/restlet/restlet-framework-java/issues/809
The fix will be part of the 2.1.6 release, available in a few minutes.

Best regards,
Thierry Boileau
 

今天想起,登录了一下Restlet的官方,发现最新版已经是2.1.6了。查看了一些change log,发现该问题确实已经解决。

===========
Changes log  
===========

- 2.1.6 (12/05/2013)
    - Bug fixed
       - Fixed issue #809 regression introduced when handling issues #774 and #778.

不过OneCoder还没有升级测试。大家可以测试一下。

 

阅读全文 »

Restlet 客户端连接超时问题解决

发表于 2013-12-16

使用Restlet进行同步请求,有时可能处理的时间会很长所以需要客户端进行较长时间的等待。从API中查得客户端的设置方式如下:

ClientResource client = new ClientResource(new Context(), uri);
client.setRetryAttempts(0);
client.setProtocol(protocol);
client.getContext().getParameters().add("socketTimeout", "60000");

不过,设置后,OneCoder经测试却发现无效。无论socketTimeout设置为多少。均会在1分钟左右超时。

这好像是由于Restlet默认使用的是一个简单的http服务,而在现在版本中,Restlet提供了很多增强的扩展。其中一个是ext.jetty扩展。即以jetty服务器做服务,启动Rest服务。使用方式很简单,无需修改原有代码,只需增加ext.jetty的依赖即可。

 <dependency>
  <groupId>org.restlet.jse</groupId>
  <artifactId>org.restlet.ext.jetty</artifactId>
  <version>2.1.2</version>
</dependency>

再次测试,有效。

值得一提的是,Restlet提供了很多扩展包。会对默认的Restlet服务进行很多增强。

阅读全文 »

Spring Framework 4.0 迁移指南 (官方文档翻译)

发表于 2013-12-14

看到Spring Framework4.0发布的消息,看了下new future,OneCoder很喜欢spring这种追“时髦”的风格,groovy脚本配置和Java8都支持了。顺便就翻译了一下官方的迁移指南。对一般使用来说,迁移没什么难度。替换依赖基本就可以了。

如果想要了解Sping Framework4.0.0的新特性,可以参考官方文档中的:New Features and Enhancements in Spring Framework 4.0

环境依赖要求:

Spring Framework4.0 需要Java SE 6 或以上的版本。(特别强调,最低版本实际为2008年发布的JDK6 update 10)。如果你从老版本的Java环境中迁移,你至少需要升级到最近的JDK6版本。推荐使用Java7和8,Java8 的稳定开发者预览状态会一致持续到2014年3月,OpenJDK8 进入最终版为止。

如果你在 Java EE 服务器部署Spring应用,你需要确认你的应用支持Java EE 6及以上的版本。这其中,特别需要注意的是满足JPA2.0和Servlet3.0规范。这个意思其实是说,你仍然可以把你的Spring Framework4.0的应用部署在只支持Servlet2.5规范的容器中。(如。Google App Engine, WebSphere 7, WebLogic 10.3),只是Spring4中一些基于Servlet3.0的特性将会无效。

依赖升级

Spring Framework4.0 声明了下列(可选)依赖的最低版本:

规范

  • Servlet 3.0 (2.5 支持部署)
  • JPA 2.0
  • Bean Validation 1.0
  • JSF 2.0
  • JCache 1.0 PFD
  • JDO 3.0

容器

  • Tomcat 6.0.30
  • Jetty 7.3
  • JBoss AS 6.0
  • GlassFish 3.1
  • Oracle WebLogic 10.3.4 (with JPA 2.0 patch applied)
  • IBM WebSphere 7.0.0.9 (with JPA 2.0 feature pack installed)

库

  • Hibernate Validator 4.3
  • Hibernate 3.6 (推荐4.2 )
  • EhCache 2.1 (推荐2.5)
  • <Quartz 1.8 (推荐2.2 )
  • Jackson 1.8 (推荐2.2 )
  • Groovy 1.8 (推荐2.2)
  • Joda-Time 2.0 (推荐2.3)</span>
  • Hessian 4.0
  • XStream 1.4
  • Apache POI 3.5

废弃的代码

下列的类和方法在Spring Framework4.0中被废弃。这些代码未来将会被移除,所以请检查javadoc并迁移至推荐的写法:

Jackson v1

所有Jackson v1支持的被废弃,以支持Jacksonv2:

  • MappingJacksonMessageConverter
  • JacksonObjectMapperFactoryBean
  • MappingJacksonHttpMessageConverter

泛型:

GenericTypeResolver中的许多方法都被废弃了。新的ResolvableType类提供了对GeneriTypeResolver和GenericCollectionTypeResolver类中废弃方法的替换:

  • GenericTypeResolver.getTargetType(MethodParameter methodParam)
  • GenericTypeResolver.resolveType(Type genericType, Map<TypeVariable, Type> map)
  • GenericTypeResolver.getTypeVariableMap(Class<?> clazz)

Burlap

Burlap不再在开发包下,并且将在以后完全不再提供支持。

  • BurlapClientInterceptor
  • BurlapExporter
  • BurlapProxyFactoryBean
  • BurlapServiceExporter
  • SimpleBurlapServiceExporter

过时的JBoss类

下面的类由于不在当前JBoss释放版中而被废弃:</span></p>

  • JBossWorkManagerTaskExecutor
  • JBossWorkManagerUtils

其他废弃

  • AbstractJaxWsServiceExporter.setWebServiceFeatures(Object[] webServiceFeatures)
  • JaxWsPortClientInterceptor.setWebServiceFeatures(Object[] webServiceFeatures)</span>
  • DefaultKeyGenerator

默认的缓存key生成器</span>

Spring使用的默认的KeyGenerator实现,由原来的DefaultKeyGenerator变为SimpleKeyGenerator。新的生成器不会再有key冲突并且基本不太可能使一个缓存的方法返回错误的结果。如果仍想使用之前的key策略,你需要配置使用废弃的DefaultKeyGenerator或者创建一个自定义的KeyGenerator实现。

MVC 命名空间

Spring MVC的命名空间XSD已经升级,以正确使用一对属性。当升级到spring-mvc-4.0.xsd后,你应该分别用 enable-matrix-variables 和ignore-default-model-on-redirect respectively 来替换原来的enableMatrixVariables 和ignoreDefaultModelOnRedirect 属性。

阅读全文 »

log4j2 与 log4j使用时的几点小区别 - log4j2上手说明

发表于 2013-12-08

虽然log4j2 目前还是beta版,不过OneCoder已经忍不住要尝试一下。跟使用log4j 比起来,上手上主要的区别有。
1、依赖的jar包。使用slf4j+log4j2 时,依赖的jar包如下:(gradle配置,Maven对照修改即可)

dependencies{
compile(
"org.apache.logging.log4j:log4j-api:$log4j_version",
"org.apache.logging.log4j:log4j-core:$log4j_version",
"org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
)

}

其中,log4j_version=2.0-beta9


2、默认配置文件名字
默认搜索的配置文件名字变为log4j2或log4j-test开头的配置文件,这个变化,让OneCoder吃了些苦头。没注意观察,还自以为配置文件还是log4j.xml,结果怎么都不管用。后来仔细阅读官方文档才发现问题。log4j2中,支持json和xml两个格式的配置文件,配置文件的搜索顺序为:

  1. Log4j will inspect the "log4j.configurationFile" system property and, if set, will attempt to load the configuration using the ConfigurationFactory that matches the file extension.
  2. If no system property is set the JSON ConfigurationFactory will look for log4j2-test.json or log4j2-test.jsn in the classpath.
  3. If no such file is found the XML ConfigurationFactory will look for log4j2-test.xml in the classpath.
  4. If a test file cannot be located the JSON ConfigurationFactory will look for log4j2.json or log4j2.jsn on the classpath.
  5. If a JSON file cannot be located the XML ConfigurationFactory will try to locate log4j2.xml on the classpath.
  6. If no configuration file could be located the DefaultConfiguration will be used. This will cause logging output to go to the console.

环境变量log4j.configurationFile指定的文件->log4j2-test.json/log4j2-test.jsn -> log4j2-test.xml -> log4j2.json/log4j2.jsn -> log4j2.xml - > 默认配置DefaultConfiguration

3、配置文件格式

配置文件样例,跟1比起来有所简化。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
     <Appenders>
          <Console name="Console" target="SYSTEM_OUT">
               <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %l - %msg%n" />
          </Console>
     </Appenders>
     <Loggers>
          <Root level="debug">
               <AppenderRef ref="Console" />
          </Root>
     </Loggers>
</Configuration>

其他的变化和优势,您可以在使用中慢慢体会了。

阅读全文 »

Restlet流式读取远端文件内容 InputRepresentation

发表于 2013-12-04

OneCoder验证用Restlet做服务,读取远端文件内容功能,编写验证代码。目前测试通过,主要是利用restlet内部提供的InputRepresentation对象,通过ReadableByteChannel,按字节流的方式读取文件内容。代码如下,省略注册服务的部分,只给出服务端和客户端关键代码:
服务端:

package com.coderli.restlet.file;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;


import org.restlet.representation.InputRepresentation;
import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;


/**
* 文件读取服务端
*
* @author lihzh(OneCoder)
* @date 2013年12月4日 下午4:35:22
* @website http://www.coderli.com
*/
public class MacFile extends ServerResource {


     @Get
     public InputRepresentation readFile() throws FileNotFoundException {
          System. out.println("开始读取文件" );
          File file = new File("/Users/apple/Documents/stockdata/SH600177.TXT" );
          InputStream inputStream = new FileInputStream(file);
          InputRepresentation inputRepresentation = new InputRepresentation(
                   inputStream);
           return inputRepresentation;
     }
}

客户端:

package com.coderli.restlet.file;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;


import org.restlet.data.MediaType;
import org.restlet.representation.ReadableRepresentation;
import org.restlet.representation.Representation;
import org.restlet.resource.ClientResource;


/**
* 文件读取,客户端
*
* @author lihzh(OneCoder)
* @date 2013年12月4日 下午4:36:00
*/
public class MacFileClient {


     public static void main(String[] args) throws IOException {
          ClientResource clientResource = new ClientResource(
                    "http://localhost:8182/macFile" );
          Representation rp = clientResource.get(MediaType.ALL );
          ReadableRepresentation rRepresentation = (ReadableRepresentation) rp;
          ReadableByteChannel rbc = rRepresentation.getChannel();
          ByteBuffer bb = ByteBuffer. allocate(1024);
           int index = -1;
           do {
              index = rbc.read(bb);
               if (index <= 0) {
                    break;
              }
              bb.position(0);
               byte[] bytes = new byte[index];
              bb.get(bytes);
              System. out.print(new String(bytes, "gbk"));
              bb.clear();
          } while (index > 0);
     }
}

需要注意的是,这里客户端实现中的System.out.print部分是由缺陷的,在有汉字的时候,会因为不正确的截断字节数组造成乱码。这里是因为我验证的时候文件只有英文和数组,所以简单的采用此种方式。

阅读全文 »

Restlet 2.1.4中 匪夷所思的ObjectRepresentation的构造函数

发表于 2013-12-03

OneCoder使用Restlet最新版2.1.4开发样例,却一直抛出异常:

Exception in thread “main” java.lang.IllegalArgumentException : The serialized representation must have this media type: application/x-java-serialized-object or this one: application/x-java-serialized-object+xml
          at org.restlet.representation.ObjectRepresentation.(ObjectRepresentation.java:203)
     at org.restlet.representation.Objec tRepresentation.(ObjectRepresentation.java:114)

无论怎么设置MediaType都无效,无奈只能查看次构造函数的源码:

public ObjectRepresentation(Representation serializedRepresentation,
            final ClassLoader classLoader) throws IOException,
            ClassNotFoundException, IllegalArgumentException {
        super(MediaType.APPLICATION_JAVA_OBJECT);


        if (serializedRepresentation.getMediaType().equals(
                MediaType. APPLICATION_JAVA_OBJECT)) {
            if (!VARIANT_OBJECT_BINARY_SUPPORTED ) {
                throw new IllegalArgumentException(
                        "SECURITY WARNING: The usage of ObjectInputStream when "
                                + "deserializing binary presentations from unstrusted "
                                + "sources can lead to malicious attacks. As pointed "
                                + "here (https://github.com/restlet/restlet-framework-java/issues/778), "
                                + "the ObjectInputStream class is able to force the JVM to execute unwanted "
                                + "Java code. Thus, the support of such format has been disactivated "
                                + "by default. You can activate this support by turning on the following system property: "
                                + "org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED." );
            }
            setMediaType(MediaType.APPLICATION_JAVA_OBJECT );
            InputStream is = serializedRepresentation.getStream();
            ObjectInputStream ois = null;
            if (classLoader != null) {
                ois = new ObjectInputStream(is) {
                    @Override
                    protected Class<?> resolveClass(
                            java.io.ObjectStreamClass desc)
                            throws java.io.IOException,
                            java.lang.ClassNotFoundException {
                        return Class
                                . forName(desc.getName(), false, classLoader);
                    }
                };
            } else {
                ois = new ObjectInputStream(is);
            }


            this.object = (T) ois.readObject();


            if (is.read() != -1) {
                throw new IOException(
                        "The input stream has not been fully read.");
            }


            ois.close();
        } else if (VARIANT_OBJECT_XML_SUPPORTED
                && serializedRepresentation.getMediaType().equals(
                        MediaType.APPLICATION_JAVA_OBJECT_XML )) {
            if (!VARIANT_OBJECT_XML_SUPPORTED ) {
                throw new IllegalArgumentException(
                        "SECURITY WARNING: The usage of XMLDecoder when "
                                + "deserializing XML presentations from unstrusted "
                                + "sources can lead to malicious attacks. As pointed "
                                + "here (http://blog.diniscruz.com/2013/08/using-xmldecoder-to-execute-server-side.html), "
                                + "the XMLDecoder class is able to force the JVM to "
                                + "execute unwanted Java code described inside the XML "
                                + "file. Thus, the support of such format has been "
                                + "disactivated by default. You can activate this "
                                + "support by turning on the following system property: "
                                + "org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED." );
            }
            setMediaType(MediaType.APPLICATION_JAVA_OBJECT_XML );
            InputStream is = serializedRepresentation.getStream();
            java.beans.XMLDecoder decoder = new java.beans.XMLDecoder(is);
            this.object = (T) decoder.readObject();


            if (is.read() != -1) {
                throw new IOException(
                        "The input stream has not been fully read.");
            }


            decoder.close();
        }
        throw new IllegalArgumentException(
                "The serialized representation must have this media type: "
                        + MediaType.APPLICATION_JAVA_OBJECT .toString()
                        + " or this one: "
                        + MediaType.APPLICATION_JAVA_OBJECT_XML .toString());
    }

惊呆的发现,最后的throw new IllegalArgumentException 逻辑被赤裸裸的暴露在外面,也就是不论上面走的是if还是else,最终都会走到这里抛出异常结束。这不免让我一头雾水,回头查看2.1.2版本的源码,发现抛出异常的代码是写在最后的else块里的,这可就大不相同了。

我只能以我目前粗浅的了解,怀疑这是restlet的一个粗心的bug,我已经给restlet发了邮件咨询了该问题,等待回复中。目前,我也只能降到2.1.2版本的restlet进行开发,在2.1.2版上无此问题。

注:已确认是bug。

阅读全文 »

log4j日志封装说明—slf4j对于log4j的日志封装-正确获取调用堆栈

发表于 2013-11-27

日志是项目中必用的东西,日志产品里最普及应该就是log4j了。(logback这里暂不讨论。) 先看一下常用的log4j的用法,一般来说log4j都会配合slf4j或者common-logging使用,这里已slf4j为例。添加gradle依赖:

dependencies {
compile('log4j:log4j:1.2.17',
'org.slf4j:slf4j-api:1.7.5',
'org.slf4j:slf4j-log4j12:1.7.5')
}

最直接的用法就是在每个需要记录日志的类里,构造一个属于自己类的log实例,实际上很多著名的开源项目也是这么做的。如spring。

private static final Log logger = LogFactory.getLog(BeanUtils.class);

那么我们也先从这种用法开始,先配置好最基本的log4j.xml配置文件。

<?xml version= "1.0" encoding ="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "http://log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" >

     <appender name="Console" class="org.apache.log4j.ConsoleAppender" >
           <layout class= "org.apache.log4j.PatternLayout" >
               <param name= "ConversionPattern" value ="%d >> %-5p >> %t >> %l >> %m%n" />
           </layout>
     </appender >
     <root >
           <level value= "info" />
           <appender-ref ref= "Console" />
     </root >

</log4j:configuration>

参数说明:

%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
%r 输出自应用启动到输出该log信息耗费的毫秒数
%c 输出所属的类目,通常就是所在类的全名
%t 输出产生该日志事件的线程名
%n 输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n”
%d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
%l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。

日志测试类:

package com.coderli.log4jpackage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* log4j封装实验室中的用户模拟类
*
* @author OneCoder
* @date 2013年11月25日 下午4:39:19
*/
public class UserClass {

     private static final Logger logger = LoggerFactory
              . getLogger(UserClass.class);
     public static void main(String[] args) {
           logger.info("这是一个Info级别的log4j日志。" );
     }
}

输出日志:

2013-11-26 11:09:21,305 >> INFO  >> main >> com.coderli.log4jpackage.UserClass.main(UserClass.java:18) >> 这是一个Info级别的log4j日志。

这里包含的日志发生时的类、线程、行号等信息。很完整。本身这么做没什么问题,只是可能有的项目考虑如果每个类里都写这样一个开头,有点麻烦,同时,如果每个类一个独立的声明,log4j内存会缓存很多的实例,占用内存,可能有时候也不便于统一配置管理。所以,有些项目里考虑了对log进行封装,提供统一的一个静态方法调用:

package com.coderli.log4jpackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author lihzh(OneCoder)
* @date 2013年11月25日 下午4:39:54
* @blog http://www.coderli.com
*/
public class MyLog4j {
     private static final Logger logger = LoggerFactory.getLogger("MyLog4j");

     public static void info(String msg) {
           logger.info(msg);
     }
}

记录日志代码变为:

/**
 * log4j封装实验室中的用户模拟类
 *
 * @author OneCoder
 * @date 2013年11月25日 下午4:39:19
 */
public class UserClass {

     public static void main(String[] args) {
          MyLog4j. info("这是一个Info级别的log4j日志。" );
     }
}

日志输出:

2013-11-26 11:23:29,936 >> INFO  >> main >> com.coderli.log4jpackage.MyLog4j.info(MyLog4j.java:16 ) >> 这是一个Info级别的log4j日志。

咋一看没什么问题,仔细分析就发现,对我们调试很有帮助的日志发生时的类名、行号都编程了封装类里面的类和行,这对于依靠日志进行错误调试来说是悲剧的。这种封装虽然解决了实例过多的问题,但是也是失败的,甚至是灾难性的。

也有人把发生日志的Class信息也传递进来一起打印。如:

public static void info(String className, String msg) {
           logger.info(className + ">> " + msg);
     }

这种方式显然是治标不治本的。于是很多人想到了另外的封装方式,即提供一个统一获取logger实例的位置,然后在自己的类里进行嗲用:

public static Logger getLogger() {
           return logger;
     }

调用代码:

public static void main(String[] args) {
          MyLog4j. getLogger().info("这是一个Info级别的log4j日志。" );
     }

这种方法,日志虽然恢复了正常,但是对于开发者实际又增加了麻烦,打印一个日志需要两部操作了。当然你可以使用import static。或者每个类里还是全局声明一个logger实例。

那么有没有想过,slf4j内部是怎么对log4j封装的呢?我们通过slf4j调用为什么就可以之间获得你实际打印日志的行号,同时又不会把自己类给暴露出来呢?你可能还没明白我在说什么,细说一下,如果我们直接使用log4j的logger,打印出来的是我们调用类的行号这没什么问题,但是这里我们获得的是slf4j的logger实例,它底层调用的是log4j的logger实例,那么为什么不会打印出slf4j内部调用类的行号呢?这就是我关心的问题。知道了这个,也许我们就可以封装出更好用的全局log组件了。

其实,这个问题的关键就集中在log4j是如何获取你调用日志的代码的类和行号的,在Java中可以通过Throwable来获取调用堆栈, 例如我们将如下代码,放在MyLog4j类的info方法中:

   public static void info(String msg) {
      Throwable throwable = new Throwable();
      StackTraceElement[] ste = throwable.getStackTrace();
      for (StackTraceElement stackTraceElement : ste) {
         System.out
               .println("ClassName: " + stackTraceElement.getClassName());
         System.out.println("Method Name: "
               + stackTraceElement.getMethodName());
         System.out.println("Line number: "
               + stackTraceElement.getLineNumber());
      }
      logger.info(msg);
   }

再次通过UserClass调用,就可获得如下输出:

ClassName: com.coderli.log4jpackage.MyLog4j
Method Name: info
Line number: 28
ClassName: com.coderli.log4jpackage.UserClass
Method Name: main
Line number: 12

由此可见只要在调用堆栈里找到用户的类,就可以获得所有我们需要的信息。有了这个基础,我们再来看看slf4j和log4j是怎么做的。

在log4j的Logger中,实际对外提供了用于封装的统一的log方法。

 /**

     This is the most generic printing method. It is intended to be
     invoked by <b>wrapper</b> classes.

     @param callerFQCN The wrapper class' fully qualified class name.
     @param level The level of the logging request.
     @param message The message of the logging request.
     @param t The throwable of the logging request, may be null.  */
  public
  void log(String callerFQCN, Priority level, Object message, Throwable t)

而第一个参数callerFQCN,就是关键的决定调用者位置的参数。在LocationInfo中,可看到对该参数的使用方式为:

public LocationInfo(Throwable t, String fqnOfCallingClass) {
      if(t == null || fqnOfCallingClass == null)
return;
      if (getLineNumberMethod != null) {
          try {
              Object[] noArgs = null;
              Object[] elements =  (Object[]) getStackTraceMethod.invoke(t, noArgs);
              String prevClass = NA;
              for(int i = elements.length - 1; i >= 0; i--) {
                  String thisClass = (String) getClassNameMethod.invoke(elements[i], noArgs);
                  if(fqnOfCallingClass.equals(thisClass)) {
                      int caller = i + 1;
                      if (caller < elements.length) {
                          className = prevClass;
                          methodName = (String) getMethodNameMethod.invoke(elements[caller], noArgs);
                          fileName = (String) getFileNameMethod.invoke(elements[caller], noArgs);
                          if (fileName == null) {
                              fileName = NA;
                          }
                          int line = ((Integer) getLineNumberMethod.invoke(elements[caller], noArgs)).intValue();
                          if (line < 0) {
                              lineNumber = NA;
                          } else {
                              lineNumber = String.valueOf(line);
                          }
                          StringBuffer buf = new StringBuffer();
                          buf.append(className);
                          buf.append(".");
                          buf.append(methodName);
                          buf.append("(");
                          buf.append(fileName);
                          buf.append(":");
                          buf.append(lineNumber);
                          buf.append(")");
                          this.fullInfo = buf.toString();
                      }
                      return;
                  }
                  prevClass = thisClass;
              }
              return;
…….//省略若干

可见,log4j把传递进来的callerFQCN在堆栈中一一比较,相等后,再往上一层即认为是用户的调用类。所以,在slf4j封装的logger中是这样封装的:

final static String FQCN = Log4jLoggerAdapter.class .getName();
public void info(String msg) {
    logger.log(FQCN, Level. INFO, msg, null );
  }

用户的代码调用的正是调用的这个info,所以就会正常的显示用户代码的行号了。那么对于封装来说,你只需要注意调用log4j的log方法,并传递进去正确的FQCN即可。

final static String FQCN = Log4jLoggerAdapter.class .getName();
public void info(String msg) {
    logger.log(FQCN, Level. INFO, msg, null );
  }
阅读全文 »
1 … 20 21 22 … 36
LiHongZhe

LiHongZhe

onecoder's blog.

352 日志
8 分类
RSS
Creative Commons
Links
  • 酷壳
  • 煮酒品茶
  • 小弟子的网络之路
  • 图表秀-在线图表制作
© 2012 - 2022 LiHongZhe
由 Jekyll 强力驱动
主题 - NexT.Muse
本站访客数 人次 本站总访问量 次