1.背景

当cxf传输的数据对象结构变化时,比如请求对象减少了字段,响应对象增加了字段,在jaxb unmarsh时会抛出异常,导致接口访问失败.

javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"name"). Expected elements are <{}name>

上面这是一个典型的unexpected element异常,如果cxf客户端请求中多了一个name属性,或者cxf服务端响应中多了一个name属性,都会导致此异常.

2.源代码分析

翻了下源代码:

com.sun.xml.bind.v2.runtime.unmarshaller.StructureLoader#childElement检查是否是新增属性

 @Override
public void childElement(UnmarshallingContext.State state, TagName arg) throws SAXException {
    ChildLoader child = childUnmarshallers.get(arg.uri,arg.local);
    if(child==null) {//检查是否新增属性
        child = catchAll;
        if(child==null) {
            super.childElement(state,arg);
            return;
        }
    }

    state.loader = child.loader;
    state.receiver = child.receiver;
}

com.sun.xml.bind.v2.runtime.unmarshaller.Loader中检查是否处理此问题

 public void childElement(UnmarshallingContext.State state, TagName ea) throws SAXException {
    // notify the error, then recover by ignoring the whole element.
    reportUnexpectedChildElement(ea, true);
    state.loader = Discarder.INSTANCE;
    state.receiver = null;
}

@SuppressWarnings({"StringEquality"})
protected final void reportUnexpectedChildElement(TagName ea, boolean canRecover) throws SAXException {
    if(canRecover && !UnmarshallingContext.getInstance().parent.hasEventHandler())
    //这里默认会有个EventHandler,不会直接忽略此问题
        // this error happens particurly often (when input documents contain a lot of unexpected elements to be ignored),
        // so don't bother computing all the messages and etc if we know that
        // there's no event handler to receive the error in the end. See #286 
        return;
     //下面的代码抛出异常
    if(ea.uri!=ea.uri.intern() || ea.local!=ea.local.intern())
        reportError(Messages.UNINTERNED_STRINGS.format(), canRecover );
    else
        reportError(Messages.UNEXPECTED_ELEMENT.format(ea.uri,ea.local,computeExpectedElements()), canRecover );
}

org.apache.cxf.jaxb.io.DataReaderImpl#createUnmarshaller中设置了EventHandler,注意这里的veventHandler,默认是没有的.

if (setEventHandler) {
            um.setEventHandler(new WSUIDValidationHandler(veventHandler));
}

org.apache.cxf.jaxb.io.DataReaderImpl.WSUIDValidationHandler的代码很简单:

private static class WSUIDValidationHandler implements ValidationEventHandler {
    ValidationEventHandler origHandler;
    WSUIDValidationHandler(ValidationEventHandler o) {
        origHandler = o;
    }

    public boolean handleEvent(ValidationEvent event) {
        String msg = event.getMessage();
        System.out.println("WSUIDValidationHandler"+msg);
        if (msg != null
                && msg.contains(":Id")
                && (msg.startsWith("cvc-type.3.1.1: ")
                || msg.startsWith("cvc-type.3.2.2: ")
                || msg.startsWith("cvc-complex-type.3.1.1: ")
                || msg.startsWith("cvc-complex-type.3.2.2: "))) {
            return true;
        }
        if (origHandler != null) {
            return origHandler.handleEvent(event);
        }
        return false;
    }
}

先自己处理,自己处理不了的交给origHandler,那我们只需要自己构建一个javax.xml.bind.ValidationEventHandler来专门处理unexpected element异常,问题就得到了解决.

org.apache.cxf.jaxb.io.DataReaderImpl#setProperty中有段代码:

 veventHandler = (ValidationEventHandler)m.getContextualProperty("jaxb-validation-event-handler");
        if (veventHandler == null) {
            veventHandler = databinding.getValidationEventHandler();
        }

如果配置了jaxb-validation-event-handler属性,就可以让我们自己的javax.xml.bind.ValidationEventHandler来处理此异常.也可以设置setEventHandlerfalse,不设置异常处理器,忽略所有unmarsh异常,不过这样我感觉太暴力了点,这样做也忽略了org.apache.cxf.jaxb.io.DataReaderImpl.WSUIDValidationHandler的逻辑,点儿都不科学.

3.实现

上面分析清楚了,实现就很简单,实现javax.xml.bind.ValidationEventHandler

public class IgnoreUnexpectedElementValidationEventHandler implements ValidationEventHandler {
		private static final Logger logger = LoggerFactory.getLogger(IgnoreUnexpectedElementValidationEventHandler.class);

		@Override
		public boolean handleEvent(ValidationEvent event) {
   		 	String msg = event.getMessage();
   		 	if (msg != null && msg.startsWith("unexpected element")) {
       		 	logger.warn("{}", msg);
       		 	return true;
    	 	}
      		return false;
		}
}

cxf:bus中配置下就ok

    <cxf:properties>
        <entry key="jaxb-validation-event-handler">
            <bean class="IgnoreUnexpectedElementValidationEventHandler"/>
        </entry>
    </cxf:properties>
</cxf:bus>

建议只在线上环境启用此东东,线下还是不要开启,早点发现问题是好事.

java对象属性复制的工具类很多,常用的有org.springframework.beans.BeanUtils#copyProperties,org.apache.commons.beanutils.BeanUtils#copyProperties,这两个都是通过反射实现的,性能嘛,你关心TA就在那里,你不关心TA还是在那里. 高级点的有net.sf.cglib.beans.BeanCopier,通过生成源代码实现属性复制,但是他的api很难使用.而且不支持基本类型和包装器类型的转换(java的boxing和unboxing只是语法糖而已).

so,重新造了个轮子,采用javassit来生成源代码,并且提供方便使用的api.

使用就像下面这样:

TestBean target = new TestBean();
Copier.copy(TestBean1.createTest(), target);

TA生成的源代码如下:

public class CopierImpl1002
 	 implements Copier.Copy
{
  public void copy(Object paramObject1, Object paramObject2)
  {
    TestBean localTestBean = (TestBean)paramObject1;
	TestBean1 localTestBean1 = (TestBean1)paramObject2;
	if (localTestBean.getA1() != null)
  		localTestBean1.setA1(localTestBean.getA1().longValue());
	localTestBean1.setA10(localTestBean.getA10());
 	   	localTestBean1.setA2(localTestBean.isA2());
 	  	localTestBean1.setA3(Integer.valueOf(localTestBean.getA3()));
   		localTestBean1.setB8(Short.valueOf(localTestBean.getB8()));
  	  	localTestBean1.setList(localTestBean.getList());
 	 }
}

源代码见:https://gist.github.com/bohrqiu/5046a2a7d983996f0e5a

oh-my-zsh

https://github.com/robbyrussell/oh-my-zsh/wiki/Plugins-Overview

https://github.com/robbyrussell/oh-my-zsh/wiki/Themes

zsh才是王道啊,各种插件,很爽.

我用colored-man colorize sublime mvn terminalapp插件.theme用avit,再装上Solarized Dark,prefect!!!

Portia, the open source visual web scraper!

http://blog.scrapinghub.com/2014/04/01/announcing-portia/

https://github.com/scrapinghub/portia

可视化的网页抓取工具,多么牛掰啊.

java8,javadoc对格式要求更严格了.

http://docs.oracle.com/javase/8/docs/technotes/guides/javadoc/whatsnew-8.html

The javadoc tool now has support for checking the content of javadoc comments for issues that could lead to various problems, such as invalid HTML or accessibility issues, in the files that are generated by javadoc. The feature is enabled by default, and can also be controlled by the new -Xdoclint option. For more details, see the output from running “javadoc -X”. This feature is also available in javac, although it is not enabled by default there.

所以以前能成功生成文档的现在变为:

/Users/bohr/code/yjf/maven-plugin/target/generated-sources/plugin/com/yiji/maven/HelpMojo.java:26: warning: no description for @author
 	* @author
   	^
/Users/bohr/code/yjf/maven-plugin/target/generated-sources/plugin/com/yiji/maven/	HelpMojo.java:27: warning: no description for @version
 	* @version
 	  ^

修改maven doc插件如下,忽略异常先:

            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-javadoc-plugin</artifactId>
            <version>2.9.1</version>
            <configuration>
                <failOnError>false</failOnError>
            </configuration>
            <executions>
                <execution>
                    <id>attach-javadocs</id>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

##Performance Considerations in Distributed Applications

http://apmblog.compuware.com/2009/09/28/performance-considerations-in-distributed-applications/

一篇老文,主要谈到了分布式应用中一些性能关注点.文章部分内容加上自己总结如下:

###The devil in disguise

  1. 序列化

    序列化的重要性体现在,序列化本身需要消耗的cpu时间,而且序列化后的内容的大小也会影响传输时间.一般来说用二进制协议效率更高.soap协议最好还是不要在内部使用了:第一,序列化和反序列化效率不高(FI应该会快,没亲测);第二,里面无意义的信息太多了.

    除了性能,还要考虑易用性,也就是说序列化后数据的兼容性问题.这个处理不好就会引起非常严重的事故.so,我们现在的dubbo序列化选hessian,分布式缓存/消息中间件中用到序列化的地方用的是兼容性的kryo(带有字段信息).等以后性能问题凸显的时候,会考虑用dubbo自带的序列化或者kryo.在数据量较大的交互时,还需要考虑提供阀值压缩数据.

    虽然分布式服务框架一般会提供多版本功能,理论上是不要考虑兼容性的.每次升级接口时,升级版本就搞定.但是在服务器不是足够多的情况下,还是不考虑了,增加了运维成本.

  2. CONNECTIONS

    这个不多说,长连接+连接池.这里需要注意的是,长连接一定要有心跳机制来保活.

  3. 线程模型

    原文只是谈到了同步和异步处理,现在比较好的做法是有专门的io线程来io请求.不过最好不要在io线程上做序列化和反序列的操作.让io线程单纯点,io处理能力更强.

  4. 网络

    这点确实容易忽掉,而且很多开发人员对网络都不是很熟悉(include me!),万兆(40G?)交换机+千兆网卡应该是必须的吧.

The beauty of remoting technologies is …

这部分作者说了不少,我的观点是:java自带的序列化比较慢,最好不要用;distributed garbage collector 最好不要碰;内部系统不要玩WS-*;消息中间件是个好东东,一般企业到后期还是会需要到消息总线,不过神马都往总线里面丢,最终ESB implementor自己就是瓶颈,合理的规划业务很重要.

What can go wrong

  1. Anti Pattern: Wrong Protocol

    选择合适的协议,内部通信就不要用webservice了.涉及到互操性的场景,可以考虑hessian提供的二进制协议.

  2. Anti Pattern: Chatty Application

    总的来说就是,分布式对象设计第一定律:不要分布式使用对象,尽量减少远程调用+粗粒度的接口+读缓存.

  3. Anti Pattern: Big Messages

    接口不要携带多余的,不相关的信息.这条和上条需要仔细权衡了.

  4. Anti Pattern: Distributed Deployment

    还是分布式对象设计第一定律.

Don‘t Trust Your Log Files: How and Why to Monitor ALL Exceptions

http://apmblog.compuware.com/2014/04/01/dont-trust-your-log-files-how-and-why-to-monitor-all-exceptions/

异常开销比较大,所以,业务异常尽量不要去收集栈信息,还要去属性第三方api,减少异常被吞掉的情况.可以参考下这篇blog.

Heartbleed test

http://filippo.io/Heartbleed/

c语言没处理好是多么的恐怖啊.

tomcat关闭时出现NoClassDefFoundError

某dubbo应用,在tomcat在关闭时,报了下面的异常,

java.lang.NoClassDefFoundError: org/jboss/netty/util/internal/ExecutorUtil

so,顺便分析下tomcat容器关闭时的内存泄漏检测机制.

首先有个三个东东:

  1. org.apache.catalina.core.JreMemoryLeakPreventionListener

    普通的java程序的类加载顺序是由父到子(先在父classloader中找,找不到在到子classloader中加载),这样做是为了安全和节省内存.web容器类加载器为了做到隔离,一般先是在子classloader中找,找不到在委托给父classloader.

    此listener主要是通过使用java classloader或者tomcat 系统classloader来加载类,避免WebappClassLoader加载类后reload释放不了,同时也避免了内存浪费.还对一些东东进行了调整.

    源代码中段话说的很形象: Use the system classloader as the victim for all this ClassLoader pinning we're about to do.

  2. org.apache.catalina.loader.WebappClassLoader

    相关逻辑主要在stop方法中:

    • 调用org.apache.catalina.loader.JdbcLeakPrevention 来deregister Driver
    • 找出所有应用线程,如果线程还在执行,则打印警告信息.如果启用了clearReferencesStopThreads,使用反射来关闭线程池或者线程.
    • 清理ThreadLocal
    • 清理各种cache…
    • 清理资源路径

    加载类步骤:

    1. 判断WebappClassLoader是否关闭,如果已关闭,报异常,打印日志
    2. 检查resourceEntries缓存
    3. 使用当前类加载器加载
    4. 使用SystemClassLoader加载
    5. 如果启用delegate,从父加载器加载
    6. WEB-INFO/classesWEB-INFO/lib目录加载
    7. 用父类加载器来加载
    8. 加载不到抛出ClassNotFoundException
  3. org.apache.catalina.core.ThreadLocalLeakPreventionListener

    清理线程池.

再分析下dubbo怎么关闭的:

  1. dubbo中的每个provide都是ServiceBean对象,此对象实现DisposableBean.在容器关闭时,ServiceBean取消注册.
  2. com.alibaba.dubbo.config.AbstractConfig中注册了一个ShutdownHook,调用ProtocolConfig.destroyAll(),清理资源(比如关闭和注册中心的连接和关闭netty)

这里需要注意下,调用org.apache.catalina.loader.WebappClassLoader#stop和调用ProtocolConfig.destroyAll()的不是同一个线程.而且org.apache.catalina.loader.WebappClassLoader#started字段并不是volatile的,有可能出现并发状态下的的不一致.这个时候,WebappClassLoader已经started=true了,然而ShutdownHook线程读到的值为false,继续去加载类,由于各种缓存也清空了,最终会抛出ClassNotFoundException(参考加载步骤)

参考:

http://wiki.apache.org/tomcat/MemoryLeakProtection

实战 Groovy: 使用闭包、ExpandoMetaClass 和类别进行元编程

http://www.ibm.com/developerworks/cn/java/j-pg06239.html

println "ifconfig en0".execute().text,这行代码使用fluent api+元编程,执行linux命令,打印结果.

这个特性对于测试同学来说就非常重要,就这么直接mock原类的行为,很方便.

Venkat Subramaniam 讨论多语言编程、JVM 和多核处理

这篇文章很精彩,看了这篇文章有学习scala和groovy的冲动.摘录一些原文比较精彩的内容:

1.关于多语言编程

Ola Bini 曾在他的语言金字塔中很好地阐述关于语言。在语言金字塔中,Scala 或 Java 可以非常有效地编写基础架构代码。在此基础之上,他谈到了一个更具动态性的分层。在这一层中,利用元编程功能。可以说,元编程带来的最大优势就是能够减少您需要编写的代码量。最直观的体验就是代码数量的减少,您可以在代码中编写非常灵活的内容。在此基础之上,金字塔的塔尖是更为特定于领域的语言层,即 DSL 层。

使用动态语言编程时,最重要的是设定更出色的准则,以便指导单元测试的编写,确保代码不会偏离原意,准确执行您希望的操作

2.关于多核并行:

一种方法是推行不可变性,也就是说:我不会更改任何东西。您处理的所有数据都是不可变,由于不可变,因此也不需 要同步。这解决了冲突问题,但我要如何在线程间通信?如何交换数据?为此,我们可以使用 Erlang 和 Scala 奉行的基于 Actor 的模型。使用 Actor,然后通过来回传递数据在 Actor 之间通信,但数据仍然保持不可变,整个过程极为安全。

软件事务处理内存模型所做的是将您的数据访问绑定到事务边界内,就像数据库为您提供 ACID(原子性、一致性、隔离和持久性)时一样。它为您提供了这种功能,但您无法在内存中实现数据持久性,因此它会处理原子性、一致性和隔离。 您可以通过三种方法处理并行性。在解决并行性时,最糟糕的方法就是共享的可变性,这就是同步模型。更好的方法是采用基于 Actor 的模型,在线程间传输不可变数据。如果存在不频繁的写入操作和极其频繁的读取操作,那么第三个选择就是事务软件内存。

Baby’s First Garbage Collector

http://journal.stuffwithstuff.com/2013/12/08/babys-first-garbage-collector/

https://github.com/munificent/mark-sweep/blob/master/main.c

用c实现的一个简单的gc,采用Marking and sweeping的方式,大致能明白gc如何工作了.