传递依赖检查

我们通过maven插件

1
org.apache.maven.plugins:maven-enforcer-plugin
启用
1
<requireUpperBoundDeps/>
来检查传递依赖是否高于直接依赖,如果传递依赖的版本比直接依赖的版本高,则打包失败.

1
<requireUpperBoundDeps/>
解释如下:

1
2
3
4
5
6
7
8
9
10
11
IF:

	A-->B1-->C2

	A-->C1

	C2>C1

THEN:

	throw Exception;

我觉得这个检查很有必要,但是解析的范围太宽了.一个项目依赖了很多的开源组件,我们最好是限制这个检查只检查我们自己的jar包.

修改插件默认行为,在

1
org.apache.maven.plugins.enforcer.RequireUpperBoundDeps$RequireUpperBoundDepsVisitor#containsConflicts
中加入:

1
2
3
4
String key=  resolvedPair.constructKey();
if(key!=null && !key.startsWith("com.xxx")){//不检查groupId中包括非com.xxx开头的jar包
  		return false;
}	

demo

下面说下检查出来的提示信息分析,比如下面的情况:

1
2
3
4
5
6
7
8
Failed while enforcing RequireUpperBoundDeps. The error(s) are [
	Require upper bound dependencies error for xxx.interchange:interchange-facade-settle:1.0.0.20121009 paths to dependency are:
	+-xxx.ppm:ppm-integration:1.0.1.6
 	 		+-xxx.interchange:interchange-facade-settle:1.0.0.20121009
	and
	+-xxx.ppm:ppm-integration:1.0.1.6
 	 		+-xxx.core.payengine:payengine-facade:2.0.0.20140314
   	 			+-xxx.interchange:interchange-facade-settle:1.0.0.20121009 (managed) <-- xxx.interchange:interchange-facade-settle:1.3.0.20140303

第一个告诉我们

1
ppm-integration
–>
1
interchange-facade-settle:1.0.0.20121009
.

第二个告诉我们

1
ppm-integration
–>
1
payengine-facade:2.0.0.20140314
–>
1
interchange-facade-settle:1.3.0.20140303

根据maven

1
最短路径优先原则
,
1
ppm-integration
最终会依赖
1
interchange-facade-settle:1.0.0.20121009
.但是
1
payengine-facade:2.0.0.20140314
它依赖
1
interchange-facade-settle:1.3.0.20140303
.如果classpath中只有
1
interchange-facade-settle:1.0.0.20121009
,运行时
1
payengine-facade
就有可能报找不到类,找不到方法之类的错误.

遇到这样的场景,最好是修改我们项目的直接依赖,让

1
ppm-integration
–>
1
interchange-facade-settle:1.3.0.20140303
,然后测试下是否ok.

1.背景

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

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

上面这是一个典型的

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

2.源代码分析

翻了下源代码:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 @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;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 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 );
}

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
    }
}

先自己处理,自己处理不了的交给

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

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

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

如果配置了

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

3.实现

上面分析清楚了,实现就很简单,实现

1
javax.xml.bind.ValidationEventHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
		}
}

1
cxf:bus
中配置下就ok

1
2
3
4
5
6
    <cxf:properties>
        <entry key="jaxb-validation-event-handler">
            <bean class="IgnoreUnexpectedElementValidationEventHandler"/>
        </entry>
    </cxf:properties>
</cxf:bus>

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

java对象属性复制的工具类很多,常用的有

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

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

使用就像下面这样:

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

TA生成的源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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