依赖管理

依赖管理

项目依赖问题很让人头痛,也出了很多事故,线上线下都很闹心。本着让大家都能开心的原则😄,在@培根童鞋的要求下,刚好我对maven很熟,刚好我对公司遇到的依赖问题也很了解,写了此文档。

请您带着一个大大的问号?阅读此文档.您可以把您遇到的依赖问题(此文档没有包含的)发给我,丰富案例库.您可以补充依赖检查中的不足之处,毕竟我个人的能力有限.您可以分享一些灰常有用的插件,方便大家.您可以谈谈您对依赖风险控制的想法.Help Me Help You!

一.依赖管理目标

  1. 此规范更新后,使用方不需要修改代码
  2. 规范开源jar包版本
  3. 检查传递依赖
  4. 检查项目classpath中是否有类名相同的依赖jar包
  5. 检查已知不能使用的jar包
  6. 定义常用插件
  7. 优化yjf-common-util依赖,不是每个项目都使用的包定义为provided,由项目自己引入

二.如何实现目标

1.创建公共父pom

  • 定义所有易极付项目都依赖的父pom com.yiji.yiji-parent
  • 此pom为SNAPSHOT
  • 此pom在dependencyManagement中定义常用jar包的版本
  • 此pom显示依赖yjf-common-util guava log
  • 此pom使用maven-enforcer-plugin来规范传递依赖,要求当前依赖的版本和传递依赖版本一样或者比传递依赖版本高(比如A->Cv1 ,A->D->Cv2,如果v1<v2,则打包失败)
  • 此pom使用maven-enforcer-plugin来规范引入会导致已知问题的包(比如我们的项目都使用slf4jlogback,那我们的依赖中不能出现org.slf4j:slf4j-log4j12)
  • 此pom使用com.yiji.maven.yiji-maven-plugin来检查classpath中是否有类名相同的jar包出现.如果有,在console中也会提示警告,在执行打包命令的目录生成dependency-check.log文件,此文件中会记录检查了哪些包.同时,以后我们也可以通过此日志文件来了解我们项目间的依赖情况.
  • 此pom包含常用maven插件maven-compiler-plugin maven-source-plugin maven-eclipse-plugin findbugs-maven-plugin maven-pmd-plugin方便大家日常使用
  • 此pom中的开源依赖,我会定期check是否有更新,是否有bug修复

2.开发com.yiji.maven.yiji-maven-plugin

此插件已开发完毕,代码也很简单,可以发现一些类加载顺序不一致导致的潜在的问题.

目前此插件只检查不同的jar包中是否有相同的类名.还可以增加对资源文件的检查,文件名相同还可以增加对内容的检查.这些需求如有必要,以后在加上.此插件也是SNAPSHOT的,以后我升级了,大家不用改动任何代码.

3.定制settings.xml

  • 此文件定义snapshot依赖为每次打包检查
  • 其他大多人不需要关心的东东

三.如何实施

目前已完成cs项目的改造,使用上面的东东,cs的pom文件还廋身不少.

由于传递依赖,不敢贸然大规模推广,先选择被依赖较少的项目使用.和@培根商量,先选择boss项目\易融通项目\易房保项目使用,使用过程中出现任何问题,请联系我(也可以顺带请我喝茶)

如果这几个项目把雷踩完了,需要找一个统一的时间点,大家一起修改\测试\上线

四.如何搞

1.替换setting.xml

下载svn://192.168.45.206/common/yiji-parent/settings.xml,替换maven安装目录中的setting.xml

2.配置项目父pom

拿cs为例,在cs的父pom中加入

<parent>
    <groupId>com.yiji</groupId>
    <artifactId>yiji-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>

去掉dependencyManagement中的开源jar依赖(公司内部的依赖不要去掉, com.yiji.yiji-parent中没有定义这些东东)。检查项目中的开源依赖是否有版本号,如果有并且IDE提示重复的定义,去掉此版本号;如果没有提示,应该是在 com.yiji.yiji-parent里没有加入此依赖,请联系我。

3.测试打包

执行mvnp试试,如果有传递依赖问题,打包会失败,请先联系我。如果打包成功,请检查dependency-check.log文件中有没有警告

4.测试项目

运行单元测试用例,看会不会出现问题,最好找@翼德同学全量回归下。

五.FAQ

1.为什么不提供方便发布的东东

原因有:
  • 容易出错,命令很简单,容易把发往生产的包发到测试环境,除非你很了解
  • setting.xml里不支持定义distributionManagement,只能在pom里面定义。因为我们有多套nexus,需要通过profile来定义不同环境对应不同的nexus,但是profile不能继承(我测试是这样的)
  • 我们大多数项目,只能把facade发布到nexus,即便profile支持继承,我也不敢把这玩意儿加到com.yiji.yiji-parent中。万一某童鞋在项目根目录执行了mvn deploy,@培根会不开心的…😠
如何解决:

可以参考com.yiji.yiji-parent中的profiles部分,请在facade中定义这个玩意儿,然后执行命令

2.有哪些常用的mvn命令,可以方便大家使用

非window用户,请在~/.bash_profile中加入

alias mvni='mvn -T 1C clean install -Dmaven.test.skip=true'
alias mvnp='mvn -T 1C clean package -Dmaven.test.skip=true'
alias mvnv='mvn versions:set -DgenerateBackupPoms=false'
alias mvnd='mvn -T 1C clean deploy -Dmaven.test.skip=true'
alias mvndd='mvn -T 1C clean deploy -P dev  -Dmaven.test.skip=true'
alias mvndo='mvn -T 1C clean deploy -P online -Dmaven.test.skip=true'
alias mvnc='mvn -T 1C clean eclipse:clean idea:clean'
alias mvne='mvn -T 1C clean eclipse:clean eclipse:eclipse  -DdownloadSources=true'

不知道各命令啥意思的童鞋请google.

window用户请把svn://192.168.45.206/common/yiji-parent/script中的脚本添加到PATH中,have a try.

3.为毛不加入自动生成doc文档插件

yjf-common-util里面用maven-javadoc-plugin来生成doc文档并在发布时上传到nexus。不过这很费时,而且我们以前使用的codetemplates里面有很多javadoc不认识的东东,警告一大堆,看着惨不忍睹。

4.IDE里面安装maven插件有什么好处

好处很多,它可以检查一些pom编写错误,也可以方便看依赖树。eclipse对maven支持很牛,依赖树看着会很爽,简单的依赖问题,用它就可以搞定。IDEA的智能提示很牛,添加依赖快捷键就可以搞定。

以后有依赖问题的童鞋,先用IDE提供的依赖树功能发现问题。找我也可以,但别让我给你安装maven插件,生命是短暂的啊。

5.maven我不熟怎么办

肯定不是凉拌,可以先看看http://www.infoq.com/cn/minibooks/maven-in-action http://www.juvenxu.com/category/maven/
后面我会给大家分享maven一些基本的东东。

6.依赖问题除了maven导致的,还有其他导致的,如何解决?

主要是把场景找出来,然后分析这些问题,我们自己来添加些防御手段.比如今天@周洋同学遇到的两个spring bean id配置一样了,导致本地开发测试ok,199启动确不正常.我们可以给spring添加点东东来检查重复id的问题.

7.我依赖的某jar版本就是不一样

有这样的的需求,现在问题比较突出的应该是金融这块.金融依赖很多银行提供的jar包,这些包可能会冲突.比如金融某项目,既依赖httpclient3又依赖httpclient4.

这种情况只能在项目里面指定版本了,使用com.yiji.maven.yiji-maven-plugin里提供的版本不是强制约束.但是建议大家都别这样做,除非没办法.

8.spring4.0都发布了,我们是不是该升级了.

我们现在用的是spring 3.1,可能有项目用的spring3.2.

spring3.2 有很多新特性,比如test framework,此次升级也把spring升级到3.2.6.

spring 4.0改动太大,暂时不考虑

9.在公共父pom中升级一个版本,风险怎么把控

以后大家都继承此父pom,升级一个版本意味着大家都升级了.风险确实很大.

首先,我们会去分析此依赖的release notes,评估升级的必要性和影响面.

然后,我们会找bops这样的大杂烩项目来做测试,测试相关特性是否受影响.

10.为什么不把setting.xml的配置移到pom中

这样做的目的是为了做到环境感知,不同环境的maven setting.xml会不一样,这是信息中心和我需要做的事情.对于大家,只需要使用svn repos中的setting.xml.放到pom中,这个pom需要定义不同的profile,还需要修改我们现有的各个环境的打包脚本.

11.常见踩雷问题

11.1 java.lang.NoClassDefFoundError: org/springframework/ui/velocity/VelocityEngineFactory

这个原因是spring把相关的类放到了spring-context-support里面.如果你用spring的声明式cache,也会遇到找不到类,都加入下面的依赖.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
</dependency>

11.2 velocity报错

java.lang.IllegalStateException: Cannot convert value of type     [org.springframework.web.servlet.view.velocity.VelocityConfigurer] to required type [org.apache.velocity.app.VelocityEngine] for property 'velocityEngine': no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:267)

报错是因为配置的bean org.springframework.web.servlet.view.velocity.VelocityConfigurer id为velocityEngine.此id覆盖了默认的velocityEngine,把这个id改为velocityConfigurer就ok

11.3 net.sf.ehcache.util.UpdateChecker - Update check failed:

关闭ehcache启动时检查版本,在ehcache配置根元素上添加属性updateCheck="false"

11.4 BeanCopier报错

‘net.sf.cglib.core.TypeUtils.parseType(Ljava/lang/String;)Lorg/objectweb/asm/Type;’
这是以为spring 3.2对asm有改动Migrating to Spring Framework 3.2(D.3和D.4讲了这些东东),咱也得跟着改动,遇到这个错误,去掉cglib-nodep的依赖就ok

加入下面的依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-util</artifactId>
</dependency>

11.5 Spring MVC controller 处理ajax请求报错 406 Not Acceptable

spring 3.2引入了内容协商的概念,此概念很REST,资源输出格式由客户端来定义.目前支持三种:

  • 请求后缀名
    比如getUser.html getUser.xml getUser.json 分别代表请求输出为html/xml/json
  • 参数
    比如请求为getUser?type=xml getUser?type=json
  • http header
    在http请求中设置Accept header,由客户端编程定义接收什么格式的返回.

这三种方式,我个人觉得第三种最优雅,很适合编程实现对资源的访问.第一种很直观,第二种有点破坏REST的味道了.参考Content Negotiation using Spring MVC

遇到这个错误,很可能是因为我们在Controller中定义@RequestMappingvalue带有html后缀,但是我们在方法上也加上了@ResponseBody,这让spring很困惑,你请求为html,返回输出又要去解析为json.

配置:

1.引入正确的schema
把schemaLocation中spring mvc schema版本去掉

http://www.springframework.org/schema/mvc
  http://www.springframework.org/schema/mvc/spring-mvc.xsd

2.配置contentNegotiationManager

<bean id="contentNegotiationManager"
      class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="favorPathExtension" value="false"/>
    <property name="favorParameter" value="false"/>
    <property name="ignoreAcceptHeader" value="true"/>
    <property name="useJaf" value="false"/>
    <property name="defaultContentType" value="application/json"/>
</bean>

3.配置json转换器

<mvc:annotation-driven
         content-negotiation-manager="contentNegotiationManager">
    <mvc:message-converters register-defaults="true">
        <bean id="fastJsonHttpMessageConverter"
              class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <value>application/json;charset=UTF-8</value>
                </list>
            </property>
            <property name="features" value="WriteDateUseDateFormat"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

我们很多童鞋spring mvc用得很不地道,建议看看官方demo mvc-showcase-screencast

11.6 使用com.yjf.common.web.CrossScriptingFilter报找不到ESAPI

添加依赖.

<dependency>
    <groupId>org.owasp.esapi</groupId>
    <artifactId>esapi</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>log4j</artifactId>
            <groupId>log4j</groupId>
        </exclusion>
        <exclusion>
            <groupId>xerces</groupId>
            <artifactId>xercesImpl</artifactId>
        </exclusion>
     </exclusions>
</dependency>
<dependency>
    <groupId>xerces</groupId>
    <artifactId>xercesImpl</artifactId>
</dependency>
给攻城狮一个小小的鼓励!