小毛的胡思乱想

凡走过,必留痕迹.

Java Xml技术简单总结

| Comments

XML概述

  • XML,就是可扩展标记语言Extensible Markup Language,包括XML/DTD/XSD/XPATH的w3c规范,在webservice方面主要应用有SOAP/WSDL等(WSDL还不是w3c规范)
  • JAVA规范API统称JAXP,主要有DOM/SAX/STAX/XPATH等标准API,并内置默认实现。并在JAXP的基础上建立了JAXB/JAX-WS等规范
  • 常见的JAXP API(解析器)实现有xerces/crimson/woodstox/xalan等开源实现,也有一些厂商的实现(如IBM)。常用的XML操作库如dom4j/jdom是JAXP API的二次封装(其实也封装了其他一些非规范的实现)
  • 常见的webservice库如axis2/xfire/cxf等,按自己的方式实现了SOAP/WSDL等功能(XML相关功能基于JAXP),由于JAX-WS规范的兴起,这些库也实现了JAX-WS规范
  • 运行期实现类的查找模式都是类似,基本都是参数、配置、SPI、默认实现的顺序。如果有需要(如存在bug/性能问题),可以根据这个查找顺序更换不同的实现方式。

XML标准

  • 平时常用的有校验和查找相关的标准
  • 校验方面主要是DTD(Document Type Definition),后来的XSD(XML Schema Define)则支持更好
  • 查找方面除了通用的DOM模型,常见的就是XPATH了,而不是很常见的XQUERY和XPOINTER是建立在XPATH基础上的

XML常见应用

  • SOAP Simple Object Access Protocol的首字母缩写,即简单对象访问协议
  • WSDL 网络服务描述语言,Web Services Description Language
  • UDDI
  • RSS Rich Site Summary 简易资讯聚合
  • WAP无线应用协议(Wireless Application Protocol,WAP)

Java XML相关的API规范

  • JAXP(Java API for XMLProcessing),定义了处理XML的通用接口,常见的包括DOM/SAX/STAX/XPATH等标准API
  • JAXB(Java Architecture for XML Binding),基于JAXP,定义了XML和Java对象的映射处理关系
  • JAX-WS,基于JAXP/JAXB,定义了一套XML webservice的标准接口

上面只是定义了规范,就是标准接口,具体的实现通常是不需要关心的。
下面再介绍一下,在运行期是如何确定采用哪种具体实现的,在定位某些问题的时候有帮助。
以oracle jdk为例,其他jdk实现基本是差不多的,主要是默认实现有所差异。

DOM

  • 首先,有没有系统参数javax.xml.parsers.DocumentBuilderFactory
  • 如果没有找到,就检查JRE/lib/jaxp.properties是否有配置该参数
  • 如果没有找到,就通过SPI机制查找有没有实现: META-INF/services/javax.xml.parsers.DocumentBuilderFactory
  • 如果没有找到,选择默认xerces实现(oracle JDK):com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl

SAX

  • 首先,有没有系统参数javax.xml.parsers.SAXParserFactory
  • 如果没有找到,就检查JRE/lib/jaxp.properties是否有配置该参数
  • 如果没有找到,就通过SPI机制查找有没有实现: META-INF/services/javax.xml.parsers.SAXParserFactory
  • 如果没有找到,选择默认xerces实现(oracle JDK):com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl

STAX

  • 和上面有点不同,它是区分输入输出的:javax.xml.stream.XMLInputFactory、javax.xml.stream.XMLOutputFactory
  • 首先,有没有系统参数javax.xml.stream.XMLInputFactory、javax.xml.stream.XMLOutputFactory
  • 如果没有找到,就检查JRE/lib/stax.properties、jaxp.properties是否有配置该参数
  • 如果没有找到,就通过SPI机制查找有没有实现: META-INF/services/javax.xml.stream.XMLInputFactory、javax.xml.stream.XMLOutputFactory
  • 如果没有找到,选择默认内部实现(oracle JDK):com.sun.xml.internal.stream.XMLInputFactoryImpl、com.sun.xml.internal.stream.XMLOutputFactoryImpl

XPATH

  • 首先,有没有系统参数javax.xml.xpath.XPathFactory:http://java.sun.com/jaxp/xpath/dom,注意这个是有点特别的
  • 如果没有找到,就检查JRE/lib/jaxp.properties查找javax.xml.xpath.XPathFactory是否有配置该参数
  • 如果没有找到,就通过SPI机制查找有没有实现: META-INF/services/javax.xml.xpath.XPathFactory
  • 如果没有找到,选择默认xalan实现(oracle JDK):com.sun.org.apache.xpath.internal.jaxp.XPathFactoryImpl

JAXB

  • 首先,检查配置文件jaxb.properties有没有定义javax.xml.bind.context.factory工厂类,通过createContext生成
  • 如果没有找到,就通过SPI机制查找有没有实现: META-INF/services/javax.xml.ws.spi.Provider
  • 如果没有找到,选择默认内部实现(oracle JDK):com.sun.xml.internal.bind.v2.ContextFactory

JAX-WS

  • 首先,通过SPI机制查找有没有实现: META-INF/services/javax.xml.ws.spi.Provider
  • 如果没有找到,选择默认内部实现(oracle JDK):com.sun.xml.internal.ws.spi.ProviderImpl

第三方库实现

  • 开源的解析器(xerces, crimson, woodstox, xalan)主要是实现JAXP中的规范API
  • 还有一些专业厂商的实现,例如IBM的JAXP实现(XL XP-J, XML4J),在websphere的plugins目录就可以找到
  • 非JAXP的实现,如xpp3也实现了类似STAX的pull模式,在android sdk中内置
  • 二次封装库(dom4j, jdom),主要是包装了JAXP,提供统一处理模型(DOM/SAX/STAX等)和简易的用法
  • WebService(Axis,Axis2,XFire,CXF(XFire升级版)),实现基于SOAP的Web服务,有些库也实现JAX-WS规范

Java相关技术书推荐

| Comments

有个同事叫我推荐点Java书籍,仅供参考.

基础入门

  • Java编程思想 不推荐。要入门随便找本书就行了。
  • effective java 属于惯用法类,属于学习语法之后优先应该阅读的,适合有一定java基础的进阶,难度中级。

编码规范

  • 重构 学习优秀代码的基本技巧,难度初级
  • clean code 代码整洁之道 有中文版的,比上一本偏实战点,难度初中级
  • 重构与模式 结合重构理解模式,适合有重构基础和设计模式概念之后的学习,提高设计模式理解,难度中级
  • 修改代码的艺术 这是完全实战的重构技巧,需要有一定重构经验后学习,难度高级

java具体应用

  • 深入理解Java虚拟机:JVM高级特性与最佳实践 国产书籍,科普 难度中级
  • Java并发编程 Doug Lea大神的并发基础书,难度中级
  • Java并发编程实战 一群并发大神写的书, 难度中高级
  • Java性能优化权威指南 不知道翻译得怎样, 内容全面, 难度中级

web

  • JavaScript语言精粹 学习什么是好的javascript 难度初中级
  • JavaScript高级程序设计 可当参考书 专业搞前端的应该都看过 难度中级
  • HTTP权威指南 web开发进阶,参考书 难度初中级

软件行业,进入专业程序员,反复也读仍会有收获的经典

  • 程序员修炼之道 : 从小工到专家 难度中级
  • 代码大全 很厚一本,内容也很全面,要坚持才能看完,可当参考书 难度中级

上面的都比较厚,也可以考虑程序员的思维修炼、卓有成效的程序员、高效程序员的45个习惯等比较小篇幅的书

设计、架构

  • 设计模式 以GOF为经典,不过非java描述,难度中高级
  • head first设计模式 java描述,适合经验不多的童鞋,难度初中级
  • expert one-on-one J2EE Development without EJB 只有这本和流行框架直接关联,估计已经绝版了,在图书馆可以找到,是spring作者描述spring设计思路,难度中高级
  • 架构之美 专家的案例分析 难度高级
  • 企业应用架构模式 感觉一般,不过也可以看看, 难度中高级

杂书、理论化

  • UNIX编程艺术 其实是本计算机历史书
  • 深入理解计算机系统 如题,计算机系统底层原理,难度中高级
  • 计算机网络 黑色书皮,国外教材,每8年一新版,介绍整个计算机网络,难度中级
  • 算法 红色书皮,java描述,介绍基本算法,难度中级
  • 数据库系统导论 同理,介绍数据库原理 ,难度中级
  • 现代操作系统 介绍操作系统理论, 难度中级
  • Linux系统管理技术手册 内容全面,扫盲书,, 难度中级

上面的基本都是大部头,而且理论化,不是合适每个人 那可以参考图解HTTP、图解TCP/IP、啊哈!算法、大话数据结构、Linux操作系统之奥秘等相对比较通俗的书.

Log4j源码心得

| Comments

log4j是一个非常流行的日志框架,最近研究了一下log4j的源代码,这里记录一下心得体会。

Logger、Appender、Layout之间的结构关系?

这是log4j的基本数据结构,之间的关系还是很好理解的。首先,一个Logger都要一个名字(通常是类名或包名),它是有父子结构的,相关的实现是Hierarchy。
一个Logger会对应多个Appender,表示可以日志输出的地方。
Appender的输出格式是通过Layout来实现的,Logger在输出的时候是包装一个LogEvent对象,有Layout来格式化成一个字符串。

简单点:

1
Logger 1--* Appender 1--* Layout

加载log4j的配置文件

先考虑加载的是log4j.xml, 然后才是log4j.properties。我平时主要使用log4j.properties.

log4j果然是年代久远

以前都说log4j在jdk1.1版本就存在了,现在从源码的细节上看,的确是这样的。例如:

  1. 加载配置文件的时候,使用context class loader,它不是用标准的api,而是采用反射。
  2. 处理FileAppender的时候,针对父级目录不存在的时候需要创建目录,如果是我就直接使用mkdirs,它确是手工处理。
  3. Logger的名字是通过CategoryKey作为key存储到HashTable的,按理说用String作为key就可以了,估计是很早之前String的hashcode是不带缓存实现的吧。

其他细节

没有配置文件的时候,如何优雅处理?

正常的时候,会在static初始化的时候得到LogRepositorySelector,如果失败的话,这个值就是空的。
然后就生成一个repositorySelector = new DefaultRepositorySelector(new NOPLoggerRepository());,这个处理方式就是进行空输出。

log4j配置中的占位符处理

log4j配置中的占位符处理是在OptionConverter的substVars方法处理。优先使用系统变量,然后才是properties配置的定义 log4j.threshold用来配置log level的阀值,就是最低级别。默认是不设置。

log4j在重复加载文件时的处理

log4j在重复加载的时候,有个做法可以学习一下。这个做法和commons-logging的方式是一样的。

1
2
3
4
5
6
7
8
9
10
11
  void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
    Properties props = new Properties();
    LogLog.debug("Reading configuration from URL " + configURL);
    InputStream istream = null;
    URLConnection uConn = null;
    try {
      uConn = configURL.openConnection();
      uConn.setUseCaches(false);
      istream = uConn.getInputStream();
      props.load(istream);
    }

layout或者appender的参数设置

log4j配置中的有时候组成一个layout或者appender,都是可以设置参数的,这些是怎么对应到类中的变量的? 实现在

1
 PropertySetter.setProperties(layout, props, layoutPrefix + ".");

通常还需要实现OptionHandler来做一些后续的初始化动作.

appender filter如何排序?

log4j的appender filter是可以配置多个的,处理顺序是按照对应的key的排序来的

1
2
3
// sort filters by IDs, insantiate filters, set filter options,
// add filters to the appender
Enumeration g = new SortedKeyEnumeration(filters);

但是它为什么不使用内置的Arrays sort或者Collections sort呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
  public SortedKeyEnumeration(Hashtable ht) {
    Enumeration f = ht.keys();
    Vector keys = new Vector(ht.size());
    for (int i, last = 0; f.hasMoreElements(); ++last) {
      String key = (String) f.nextElement();
      for (i = 0; i < last; ++i) {
        String s = (String) keys.get(i);
        if (key.compareTo(s) <= 0) break;
      }
      keys.add(i, key);
    }
    e = keys.elements();
  }

输出编码没有设置会有什么问题?

如果默认编码不支持中文,可能就乱码。其他不会乱码,只是写的文件是不同编码。编码这块参考WriterAppender

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  protected
  OutputStreamWriter createWriter(OutputStream os) {
    OutputStreamWriter retval = null;

    String enc = getEncoding();
    if(enc != null) {
      try {
  retval = new OutputStreamWriter(os, enc);
      } catch(IOException e) {
          if (e instanceof InterruptedIOException) {
              Thread.currentThread().interrupt();
          }
        LogLog.warn("Error initializing output writer.");
        LogLog.warn("Unsupported encoding?");
      }
    }
    if(retval == null) {
      retval = new OutputStreamWriter(os);
    }
    return retval;
  }

bufferedIO和bufferSize、immediateFlush有什么关联?

bufferSize只有在bufferIO开启的时候才生效,对应的是BufferedWriter对象。
如果开启了bufferIO那么immediateFlush就会重置成false。

RollingFileAppender多进程写的情况?

RollingFileAppender存在一个rollOver的操作,如果文件大小超过限制,就会进行切换。
如果存在多个进程写的时候,就很可能出现文件大小明显超过限制的情况。
原因在于RollingFileAppender初始化的时候就记录当前文件的大小,每次比较的依据是文件大小+曾经写入的文件大小。

1
2
3
4
5
6
7
8
9
10
  protected
  void subAppend(LoggingEvent event) {
    super.subAppend(event);
    if(fileName != null && qw != null) {
        long size = ((CountingQuietWriter) qw).getCount();
        if (size >= maxFileSize && size >= nextRollover) {
            rollOver();
        }
    }
   }

另外,这里的文件大小限制是字节,但统计大小的时候,用的是字符串的长度,两者是有些区别的。

1
2
3
4
5
6
7
8
9
10
  public
  void write(String string) {
    try {
      out.write(string);
      count += string.length();
    }
    catch(IOException e) {
      errorHandler.error("Write failure.", e, ErrorCode.WRITE_FAILURE);
    }
  }

DailyRollingFileAppender切换

基于一种时间窗口的算法。
首先,如何知道是按分钟、小时还是天数来切换呢?就是靠猜,例如猜测是否是小时,那么就是给一个时间去格式化,再加上一小时去格式化。 如果两个格式化的字符串一样,那么说明不是。按时间间隔,从小到大判断就可以做到。
接下来,根据日期格式,就可以计算下次切换的时间点(文件名)。
最后,判断切换的时候,根据当前时间去格式化,看看是不是不一样了,如果是,那么就开始切换了。

不过,如果多个进程写的话,就很容易出现问题。因为下面的renameTo很容易失败,然后就会继续写到原来的文件。

1
2
3
4
5
6
7
File file = new File(fileName);
boolean result = file.renameTo(target);
if(result) {
  LogLog.debug(fileName +" -> "+ scheduledFilename);
} else {
  LogLog.error("Failed to rename ["+fileName+"] to ["+scheduledFilename+"].");
}

避免对象未知状态

| Comments

今天在使用一个库的时候就遇到一个很纠结的问题。

通常我们应该避免对象new的时候出错,一种常见的设计方案是采用初始化方法。不过也有new出异常的情况,这种情况应该确保对象占用的资源能够被释放。

不过今天遇到的这个情况,是一个链接短信网关的库,假设是Proxy对象,它在初始化的时候,生成2个线程,一个是心跳线程,一个是接受消息的线程。 在链接不上的情况下,初始化会抛出异常,但是仍然会尝试重连。

1
2
3
4
5
6
7
8
9
10
11
12
public class MyProxy extends SMProxy {
    private final RecvMsgHandler handler;
    public MyProxy(Map props, RecvMsgHandler handler){
        super(props);
  this.handler = handler;
    }

    @override
    public void onRecvMsg(Message msg){
        handler.recvMsg(msg);
    }
}

上面是我想实现的构造方法,打算给他注入一个回调方法,让它在特定事件可以通知到我。 由于可能出异常,导致这个对象只是构造了一部分,一旦出现回调的时候,会存在指针异常。

另外一个问题是,本来我想管控一下所有的链接,这样可以随时控制数量。 不过由于出异常的时候,我没法拿到这个对象,所以管控不到这个可能重连的对象,相当于漏掉了这个连接。

这2个问题,特别是第二个,还真的没有什么好的解决方案。

总而言之,这个SMProxy的设计缺陷太明显,看来要绕过这个类的实现才行。

Commons-logging源码学习

| Comments

commons-logging是一个流行的logging统一接口,代码非常简单,具体的logging可以使用不同的实现,如log4j,jdk log等,即使没有这些,它还是能在控制台上输出,它可以帮你选择一种合适的logging实现。

commons-logging有LogFactory和Log两个主要接口,LogFactory实现了如何找到合适的Log,而Log是一个标准的logging接口。

LogFactory是一个抽象类,它的具体实现通过以下顺序确定:

  1. 系统属性org.apache.commons.logging.LogFactory
  2. META-INF/services/org.apache.commons.logging.LogFactory
  3. commons-logging.properties的org.apache.commons.logging.LogFactory
  4. org.apache.commons.logging.impl.LogFactoryImpl

基本上,都会是LogFactoryImpl这个实现。

另外,commons-logging可以采用SPI指定LogFactory实现,不过commons-logging并没有使用标准的ServiceLoader来处理,可能是由于commons-logging要兼容老的java版本吧。

还有,commons-logging.properties是支持重新加载的,按道理classpath加载资源是有缓存的, 见LogFactory中的getProperties,它不是直接使用getResourceAsStream,而是采用getResources/getResource,对得到的URL进行openConnection得到URLConnection对象,对它设置setUseCache为false,这的确是个小技巧。

对于Log实现,如果没有通过commons-logging.properties的的org.apache.commons.logging.Log这个key指定的话,是按照下面的方式尝试获取的:

  1. org.apache.commons.logging.impl.Log4JLogger
  2. org.apache.commons.logging.impl.Jdk14Logger
  3. org.apache.commons.logging.impl.Jdk13LumberjackLogger
  4. org.apache.commons.logging.impl.SimpleLog

很明显,只要有log4j的jar包就会选择log4j,否则就会选择jdk logging或者System.err。

所以正常情况下,不需要设置这个commons-logging.properties里边的org.apache.commons.logging.Log对应的属性值(其实这个配置文件都是不需要的)。

曾经就有项目,依赖一个自己写的jar包,就把commons-logging.properties打包进去,里边配置成SimpleLog,导致很多框架的日志不能输出到log4j指定的文件中去。