OneCoder


  • 首页

  • 归档

  • 标签

  • 关于

  • 搜索

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 );
  }
阅读全文 »

log4j 和 java.lang.OutOfMemoryError PermGen space

发表于 2013-11-24

还是OneCoder在项目中沙箱的问题,用classloader隔离做的沙箱,反复运行用户的任务,出现永生区内存溢出:
java.lang.OutOfMemoryError: PermGen space

这个问题在tomcat重复热部署的时候其实比较常见。其道理也和我们沙箱的道理基本一致,就是每次任务运行的类没有卸载掉。而永生区正式存储加载入classloader中的类,反射的方法等的地方。如此只增不减,自然会产生溢出。

那么,什么情况下会产生沙箱中loader进来的类不会被回收的情况呢?简单说,就是当外部loader里加载的类,持有了沙箱loader中的加载的类的实例时。道理简单,但是实际项目中,寻找这种可能情况就复杂的多了。为了说明这个道理,我们做个简单的试验。

ClassLoader加载试验

试验思想,基本思想就是循环创建Classloader手动加载包,并通过反射调用包中的代码,考察在外部loader有无引用的情况下,PermGen区的变化情况。监控工具,即为JDK自带的Java VisualVM。

场景一、无引用

package com.coderli.permgen.leak;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Classloader导致Permleak测试类
 * 
 * @author <a href="http://www.coderli.com">OneCoder</a>
 * @date 2013年11月21日 上午10:07:02
 * @website http://www.coderli.com
 */
public class ClassloaderPermGenLeakTest {

public static void main(String[] args) throws InterruptedException,
MalformedURLException, ClassNotFoundException {

     ExecutorService es = new ThreadPoolExecutor(1, 10, 5L,
     TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
     ClassloaderPermGenLeakTest clpg = new ClassloaderPermGenLeakTest();

     for (int i = 0; i < 1; i++) {
          es.execute(clpg.new ExecutroRun());
     }
     Thread.sleep(2000000);
}

private class ExecutroRun implements Runnable {

@Override
public void run() {
     try {
          permLeak();
     } catch (Exception e) {
          e.printStackTrace();
     }
}

private void permLeak() throws Exception {
     Thread.sleep(5000);
     List<Object> insList = new ArrayList<Object>();
     ClassLoader _temp = Thread.currentThread().getContextClassLoader();
     for (int i = 0; i < 100; i++) {
          URL[] urls = getURLS();
          URLClassLoader urlClassloader = new URLClassLoader(urls, null);
          Class<?> logfClass = Class.forName("org.apache.commons.logging.LogFactory", true,
                urlClassloader);
          Method getLog = logfClass.getMethod("getLog", String.class);
          getLog.invoke(logfClass, "logName&rdquo;);        (100);
          System.out.println("print: " + i);
     }
}

private URL[] getURLS() throws MalformedURLException {
     File libDir = new File("src/main/java/com/coderli/permgen/leak/lib");
     File[] subFiles = libDir.listFiles();
     int count = subFiles.length;
     URL[] urls = new URL[count];
     for (int i = 0; i < count; i++) {
          urls[i] = subFiles[i].toURI().toURL();
     }
     return urls;
     }
}

从代码可见,在自定义的100个Classloader里,我们只是通过反射调用了LogFactory里的getLog方法,对于该方法返回的实例,并没有保存。所以没有引用,可正常回收。

通过内存dump查看,可以发现,内存中没有commmon logging相关的类,说明PermGen区正常回收了。

场景二、持有引用

修改代码如下:

package com.coderli.permgen.leak;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Classloader导致Permleak测试类
 * 
 * @author lihzh
 * @date 2013年11月21日 上午10:07:02
 */
public class ClassloaderPermGenLeakTest {


private List<Object> insList = new ArrayList<Object>();

public static void main(String[] args) throws InterruptedException,
MalformedURLException, ClassNotFoundException {

     ExecutorService es = new ThreadPoolExecutor(1, 10, 5L,
     TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
     ClassloaderPermGenLeakTest clpg = new ClassloaderPermGenLeakTest();

     for (int i = 0; i < 1; i++) {
          es.execute(clpg.new ExecutroRun());
     }
     Thread.sleep(2000000);
     System.out.println(clpg.insList.size());
}

private class ExecutroRun implements Runnable {

     @Override
     public void run() {
          try {
               permLeak();
          } catch (Exception e) {
               e.printStackTrace();
          }
     }

private void permLeak() throws Exception {
     Thread.sleep(5000);
     for (int i = 0; i < 100; i++) {
          URL[] urls = getURLS();
          URLClassLoader urlClassloader = new URLClassLoader(urls, null);
          Class<?> logfClass = Class.forName(
               "org.apache.commons.logging.LogFactory", true,
               urlClassloader);
          Method getLog = logfClass.getMethod("getLog", String.class);
          Object result = getLog.invoke(logfClass, "logName");
          insList.add(result);
          System.out.println("print: " + i);
     }
}

private URL[] getURLS() throws MalformedURLException {
     File libDir = new File("src/main/java/com/coderli/permgen/leak/lib");
     File[] subFiles = libDir.listFiles();
     int count = subFiles.length;
     URL[] urls = new URL[count];
     for (int i = 0; i < count; i++) {
          urls[i] = subFiles[i].toURI().toURL();
     }
     return urls;
     }
}

在外部增加一个list存放内部反射出来的对象,并保证list对象被引用。

这次可看到PermGen不会被释放,观察dump内存


有大量的commons logging类没有释放。即出现内存泄漏。

这是最简单,最直观的PermGen Leak的场景。更多的时候,这种内存泄漏并不是那么的好察觉。 比如,我们经历的沙盒中的问题就是一个比较典型的问题,沙盒中使用log4j。

log4j导致永生区溢出的问题其实并不罕见,网上也有不少人讨论过这个问题。下面摘录的一段博文内容,给了我启示。

More sneaky problems

I don't blame you if you didn't see the problem with the Level class: it's sneaky. Last year we had some undeployment problems in our application server. My team, in particular Edward Chou, spent some time to track them all down. Next to the problem withLevel, here are some other problems Edward and I encountered. For instance, if you happen to use some of the Apache Commons BeanHelper's code: there's a static cache in that code that refers to Method objects. The Method object holds a reference to the class the Method points to. Not a problem if the Apache Commons code is loaded in your application's classloader. However, you do have a problem if this code is also present in the classpath of the application server because those classes take precedence. As a result now you have references to classes in your application from the application server's classloader... a classloader leak!

I did not mentiond yet the simplest recipe for disaster: a thread started by the application while the thread does not exit after the application is underplayed.

在使用log4j的时候,不论你是直接使用log4j还是通过slf4j和commons-longging间接使用,当你通过LoggerFactory(LogFactory)获取一个实例的时候,log4j都会将该实例缓存起来。这就会导致你外部持有了一个沙箱内部的log对象,内部又持有了一个外部的对象,最终导致所有加载的log4j的相关类都无法释放。产生溢出。

而在我们的项目还不仅如此,我们还是使用了log4j的MDC类,log4j提供了线程相关的MDC类用于保存当前线程的日志上线文信息。如果内外loader的类公用一个线程,那么在线程没有终止的情况下,MDC信息会绑定在当前线程的ThreadLocal上,这也会造成类无法释放,最终产生溢出。

那么解决上述问题的比较常见的办法就是全局使用一份log4j,即将log4j置于更高层次的loader中进行加载,避免每个loader重复加载。而在我们的项目中,也采用了类似的做法,即在沙箱的一个公用的父类loader中加载了log4j类。从而避免溢出。

最后,推荐两篇相关博文,里面有关于永生区(PermGen)较为细致的分析和讲解

http://frankkieviet.blogspot.com/2006/10/classloader-leaks-dreaded-permgen-space.html

https://blogs.oracle.com/jonthecollector/entry/presenting_the_permanent_generation

阅读全文 »

log4j 多classloader重复加载配置问题解决

发表于 2013-11-20

最近OneCoder在开发隔离任务运行的沙箱,用于隔离用户不同任务间以及任务和框架本身运行代码的隔离和解决潜在的jar包冲突问题。
运行发现,隔离的任务正常运行,但是却没有任何日志记录。从控制台可看到如下错误信息:

log4j:ERROR A "org.apache.log4j.xml.DOMConfigurator" object is not assignable to a "org.apache.log4j.spi.Configurator" variable.
log4j:ERROR The class "org.apache.log4j.spi.Configurator" was loaded by
log4j:ERROR [java.net.URLClassLoader@5b0a69d3] whereas object of type
log4j:ERROR "org.apache.log4j.xml.DOMConfigurator" was loaded by [WebappClassLoader
  context:
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@4d405ef7
].
log4j:ERROR Could not instantiate configurator [org.apache.log4j.xml.DOMConfigurator].
log4j:WARN No appenders could be found for logger (com.xxx.xxx.xxx).
log4j:WARN Please initialize the log4j system properly.

错误信息很明显,log4j被两个不同的classloader初始化了两遍结果包错。这个问题不难解决,主要有两种手段:
第一、增加配置,让log4j忽略由不同的classloader的初始化这个问题。在log4j的配置文件中增加

log4j.ignoreTCL=true

即可。在某些版本的log4j的错误信息中,还会提到下面的网址:
http://logging.apache.org/log4j/1.2/faq.html,里面有关注上面属性的介绍。
第二、修改初始化log4j线程的classloader。由于log4j会用当前线程上下文中的classloader去初始化配置,所以在我的沙箱应用中,虽然log4j的jar是由URLClassLoader加载进来的,但是加载的线程还是在WebApp中的,还是WebApp的ClassLoader,所以会出现上面的提示信息。因此,我们至要通过下面代码:

Thread.currentThread().setContextClassLoader(urlclassLoader);

将当前线程的上下文loader改为沙箱的ClassLoader即可。

两种解决方式,你可以按需选择了。

阅读全文 »

log4j 清除MDC上下文 MDC分类日志

发表于 2013-11-19

在项目里需要分类收集处理日志信息,使用log4j的MDC在线程中添加分类信息。不过最近却出现日志信息记录错误的情况,具体来说,就是会出现本来是属于下一个分类的一部分信息莫名的记录到上一个分类的日志文件中了。这很显然是MDC信息错误造成的。其实,最开始暴露给我们的现象是,在日志清空后,第一次运行任务,会出现两条日志。显然,这个现象更具有迷惑性,让OneCoder一度以为是任务运行了两次。

那么,MDC为什么会出现这个问题呢?简单说,MDC需要在用完后被重置,或者说清空。因为MDC是绑定到线程的,所以,在大多数简单的使用的情况下,例如,在服务端接受请求的入口处,设置一个MDC信息,如果服务端这个处理任务的线程,结束后被丢弃了,那么,也不会出现什么问题。但是如果是一个线程池,池中的线程会被重复利用的情况下,如果你没有结束后清除MDC的信息,那么在下次设置之前,上次设置之后的这段日志,就会出现分类错乱的情况。

解决方法也很简单,在log4j1.2.16以后的版本里,MDC直接提供了clear()方法。即:

MDC.clear()

如果是之前的版本,如果你使用的是slf4j作为接口,那么slf4j也对MDC进行了封装,可以调用slf4j的MDC.clear()方法。

再不然,你也可以自己手动清理MDC。

MDC.getContext().clear();
//当然,需要校验一下MDC.getContext()不为null。
阅读全文 »

Java JNI Windows64位系统下 使用32位的dll

发表于 2013-11-19

今天遇到在处理一个多classloader调用本地native方法报错的问题的时候,想要通过调用本地的一个dll进行测试。该dll是在32位环境下编译的。而OneCoder的调试机器是64位的win7。自然调用会报如下错误:

Can’t load IA 32-bit .dll on a AMD 64-bit

错误信息很明显,于是替换了一个32位的JDK,重新测试,结果又报找不到dll的异常。加载dll的代码很简单:

System.loadLibrary("hello”);//dll的文件名为hello.dll

此时,我已经按照经验将dll扔到C:\windows\System32目录下了,感觉应该万无一失的,结果却不行。以前在做windows下libvirt接口开发的时候,就是这么做的,当时运行正常。这是为什么?

仔细回忆,想起当时的环境是win7 32位,而现在是64位的,难道windows的目录有变化?马上查看,果然在windows目录下又发现了一个SysWow64的文件夹,里面的文件几乎和System32内的文件一致。立马把hello.dll扔到里面再次运行。成功!

果然64位的win7的目录有变化,后来笔者也在网上搜索了一番,有几篇介绍windows32位和64位系统dll文件夹命名规则的文章,如果大家有兴趣可以去搜索一下。

由该问题,笔者也想起之前对于windows下libvirt开发的文章中介绍的操作方式可能不够严谨,当时也有很多朋友向OneCoder询问为什么他们按照我介绍的扔到System32目录下,仍然不好用,但是我没能给出解答,如此看来,很可能是那些朋友的开发环境是64位的系统,如果是这样,那他们放到C:\Windows\SysWoW64下,应该会好用了。 

阅读全文 »

Java NIO框架Netty教程(十七) - Netty4 Hello world

发表于 2013-11-17

最近很多人问我有没有Netty4的Hello World样例,很早之前知道Netty要出4,当时只知道4的包名完全边了,因为Netty从JBoss中独立出来了,并采用了新的netty.io的域名,但是没想到代码也有这么大的调整。

既然答应了别人,就抽时间看一下Netty4,也顺便补充一下自己的知识。还是先从最简单的Hello world开始吧。下面代码基于最近版的Netty4,netty4.0.12-Final。由于Netty4最代码进行了分包,分了很多工程,有可能会对你的开发造成困扰,不过Netty4也提供了一个netty4-all的jar包,里面包含了所有的代码,方便你进行依赖开发。这里OneCoder用的就是该jar包。

/**
 * Netty4 服务端代码
 *
 * @author lihzh
 * @date 2013年11月15日 下午1:10:06
 * @website http://www.coderli.com
 */
public class HelloWorldServer {

      public static void main(String[] args) {
           // EventLoop 代替原来的 ChannelFactory
          EventLoopGroup bossGroup = new NioEventLoopGroup();
          EventLoopGroup workerGroup = new NioEventLoopGroup();
           try {
               ServerBootstrap serverBootstrap = new ServerBootstrap();
               // server端采用简洁的连写方式,client端才用分段普通写法。
              serverBootstrap.group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel. class )
                        .childHandler( new ChannelInitializer&lt;SocketChannel&gt;() {
                              @Override
                              public void initChannel(SocketChannel ch)
                                       throws Exception {
                                  ch.pipeline().addLast( new HelloServerHandler());
                             }
                        }).option(ChannelOption. SO_KEEPALIVE , true );

              ChannelFuture f = serverBootstrap.bind(8000).sync();
              f.channel().closeFuture().sync();
          } catch (InterruptedException e) {
          } finally {
              workerGroup.shutdownGracefully();
              bossGroup.shutdownGracefully();
          }
     }

      private static class HelloServerHandler extends
              ChannelInboundHandlerAdapter {

           /**
           * 当绑定到服务端的时候触发,打印"Hello world, I'm client."
           *
           * @alia OneCoder
           * @author lihzh
           * @date 2013年11月16日 上午12:50:47
           */
           @Override
           public void channelActive(ChannelHandlerContext ctx) throws Exception {
              System. out .println("Hello world, I'm server.");
          }
     }

}
/**
 * Netty4 客户端代码
 * @author OneCoder
 * @date 2013年11月15日 下午1:28:21
 * @website http://www.coderli.com
 */
public class HelloWorldClient {

      public static void main(String args[]) {
           // Client服务启动器 3.x的ClientBootstrap 改为Bootstrap,且构造函数变化很大,这里用无参构造。
          Bootstrap bootstrap = new Bootstrap();
           // 指定channel类型
          bootstrap.channel(NioSocketChannel. class );
           // 指定Handler
          bootstrap.handler( new HelloClientHandler());
           // 指定EventLoopGroup
          bootstrap.group( new NioEventLoopGroup());
           // 连接到本地的8000端口的服务端
          bootstrap.connect( new InetSocketAddress("127.0.0.1" , 8000));
     }

      private static class HelloClientHandler extends
              ChannelInboundHandlerAdapter {

           /**
           * 当绑定到服务端的时候触发,打印"Hello world, I'm client."
           *
           * @alia OneCoder
           * @author lihzh
           * @date 2013年11月16日 上午12:50:47
           */
           @Override
           public void channelActive(ChannelHandlerContext ctx) throws Exception {
              System. out .println("Hello world, I'm client.");
          }
     }
}

一些主要的变化和对比注释在代码中。简单补充介绍几点:

NioEventLoopGroup 是一个处理I/O操作的多线程事件环。即为Netty4里的线程池,在3.x里,一个Channel是由ChannelFactory创建的,同时新创建的Channel会自动注册到一个隐藏的I/O线程。 4.0使用新的名为EventLoopGroup的接口来替换ChannelFactory,它由一个或多个EventLoop来构成。一个新的 Channel不会自动注册到EventLoopGroup,但用户可以显式调用EventLoopGroup.register()来注册。在Server端的Bootstrap参数中,有两个EventLoopGroup,第一个通常称为’boss’,用于接收发来的连接请求。第二个称为’worker’,,用于处理boss接受并且注册给worker的连接中的信息。

ChannelInitializer是一个特殊的handler,用于方便的配置用户自定义的handler实现,如代码中所示。在channelRegistered的生命周期中会触发用户复写的initChannel(C ch)方法,并且在调用后会讲自身从channelPipeline中移除。

阅读全文 »

又拍云存储试用体验

发表于 2013-11-13

首先,感谢又拍云存储赞助的存储空间。让我的小站以可以体验一下CDN加速的快感。

虽然我是第一次使用又拍云,甚至是第一次使用云存储,但是上手起来还是很迅速的。这得益于又拍云后台操作的简便,以及详细的操作说明。
又拍云的空间分两类,一种是文件类的,一种是图片类的。主要的区别就是图片类空间支持缩略图的功能。

对于一个wordpress个人博客而言,主要的加速就是将常用的css和js文件放在云存储上。别小看这看似小巧的文件,从本站实际感受和统计来看,访问速度却有提升,而近一个月仅js和css文件请求的流量节省就有350多mb。如果是一个访问量更大的网站而不是我的这个小小的博客,那带来的效果就会更明显了。着实令我感到欣喜。

就我这一个月的使用来看,又拍云存储的CDN加速及其带来的流量节约功能是我非常喜欢的。速度快,无峰值带宽限制。

其次是操作方便,可以用FTP上传下载文件,也可以用其提供的API和开发包进行更多个性化的使用和操作。

再就是强大的后台统计功能,可以根据域名、空间及时间跨度进行灵活查看,并记录全网访问详细数据,可以进行数据分析使用。

当然,图片空间其实也是又拍云的一大亮点,不过由于这里由于我之前购买了又拍的图片管家服务, 这部分功能就暂且没有体验,不过从后台来看,与图片管家的功能几乎是一致的,缩略图、防盗链功能一应俱全,是非常实用的。

经过我的亲身体验,个人感觉不论是针对个人博客还是中大型网站,又拍云存储,都不失为加速访问,节约流量的一个不错的选择。
 

阅读全文 »

Flume(ng) 自定义sink实现和属性注入

发表于 2013-10-29

最近需要利用flume来做收集远端日志,所以学习一些flume最基本的用法。这里仅作记录。

远端日志收集的整体思路是远端自定义实现log4j的appender把消息发送到flume端,flume端自定义实现一个sink来按照我们的规则保存日志。

自定义Sink代码:

public class LocalFileLogSink extends AbstractSink implements Configurable {
     private static final Logger logger = LoggerFactory
              . getLogger(LocalFileLogSink .class );
            private static final String PROP_KEY_ROOTPATH = "rootPath";
      private String rootPath;
     @Override
     public void configure(Context context) {
          String rootPath = context.getString(PROP_KEY_ROOTPATH );
          setRootPath(rootPath);
     }
          
          @Override
          public Status process() throws EventDeliveryException {
           logger .debug("Do process" );
       }
}

实现Configurable接口,即可在初始化时,通过configure方法从context中获取配置的参数的值。这里,我们是想从flume的配置文件中获取rootPath的值,也就是日志保存的根路径。在flume-conf.properties中配置如下:

agent.sinks = loggerSink
agent.sinks.loggerSink.rootPath = ./logs

loggerSink是自定义sink的名称,我们取值时的key,只需要loggerSink后面的部分即可,即这里的rootPath。

实际业务逻辑的执行,是通过继承复写AbstractSink中的process方法实现。从基类的getChannel方法中获取信道,从中取出Event处理即可。

 Channel ch = getChannel();
            Transaction txn = ch.getTransaction();
          txn.begin();
           try {
               logger .debug("Get event." );
              Event event = ch.take();
              txn.commit();
              status = Status. READY ;
              return status;
                    }finally {
              Log. info( "trx close.");
              txn.close();
          }
阅读全文 »

slf4j+log4j2 控制台输出错误解决

发表于 2013-10-17

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

在使用log4j2+slf4j的时候遇到这个错误提示,这不到类。在slf4j的官网有正好有相应的说明。需要添加依赖包

slf4j-api-1.7.5.jar slf4j-simple-1.7.5.jar

在Gradle的build.gradle配置文件种添加依赖

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

刷新工程。再次输出,问题解决。

阅读全文 »
1 … 21 22 23 … 36
LiHongZhe

LiHongZhe

onecoder's blog.

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