# 几个常见问题

# 1. maven-archetype-webapp 骨架的 Servlet 版本问题

通过 maven-archetype-webapp 骨架去创建 java web 项目时,自动生成的 web.xml 配置文件所使用的 Servlet 的版本比较低(2.3),而在低版本的 Servlet 中 EL 表达式默认是关闭的。

通常,我们使用的 Servlet 至少会是 3.1

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">
  ...
</web-app>

# 2. .lastUpdated 文件问题

.lastUpdated 文件是 Maven 在下载依赖包时的中间文件。例如:

slf4j-api-1.7.5.pom.lastUpdated
surefire-junit4-2.12.4.jar.lastUpdated

在下载成功完成后,.lastUpdated 文件会被移除,成功下载的包文件将会出现。

当你发现你的本地仓库中的某个包的目录下存在 .lastUpdated 文件,那么这意味着发生了以下 2 种情况之一:

  • Maven 正在下载这个包。通常不会是这个情况,因为配置国内的中央仓库镜像后,下载包的速度较快,再加上各个包实际并不大,这种情况的 .lastUpdated 文件只存在极短的一瞬间,不会让你『长期看到』它。

  • 上一次 maven 下载这个包时失败,而遗留下来的,通常你可能『长期看到』一个 .lastUpdated 文件就是因为这个原因。

如果是第二种情况,则会给你带来麻烦:因为 maven 不支持断点续传,而 .lastUpdated 文件既不可用,又会导致 maven 不会重新下载。

因此,理论上,你要清除掉你本地仓库中的 .lastUpdated 文件,以免它干扰你的 maven 的正常使用。

  • cleanLastUpdated.bat

    @echo off
    
    rem 这里写你的仓库路径
    set REPOSITORY_PATH=C:\Users\xxx\.m2\repository
    rem 正在搜索...
    for /f "delims=" %%i in ('dir /b /s "%REPOSITORY_PATH%\*lastUpdated*"') do (
        echo %%i
        del /s /q "%%i"
    )
    rem 搜索完毕
    pause
    

# 3. IDEA Maven 默认使用 JDK 1.5 编译问题

IDEA 在『调用』maven 时,IDEA 默认都会采用 JDK 1.5 编译,不管你安装的 JDK 版本是 JDK 7 还是 JDK 8 或者更高。这样一来非常不方便,尤其是时不时使用 JDK 7/8 的新特性时。如果使用新特性,编译器直接报错。

对于此类问题,解决办法不止一种。Maven 官方推荐做法是固定 JDK 的编译版本。

需要在 pom.xml 文件中加入:

<properties>
  <java.version>1.8</java.version>
  <maven.compiler.source>1.8</maven.compiler.source>
  <maven.compiler.target>1.8</maven.compiler.target>
</properties>

# 4. tomcat7-maven-plugin 版本『低』的问题

相较于当前最新版的 tomcat 10 而言,tomcat7-maven-plugin 确实看起来很显老旧。但是,这个问题并不是问题,至少不是大问题。

  1. tomcat7-maven-plugin 仅用于我们(程序员)开发环境中,最终项目交付以后的运行环境,是 tomcat7 还是更高版本的 tomcat,甚至是其它的 Servlet 容器,这就是另一个问题了,这和 tomcat7-maven-plugin 的版本无关。

  2. tomcat7 支持 Servlet 3.0,tomcat8 支持 Servlet 3.1,实际上 Servlet 3.0 和 3.1 的区别并不大,甚至说,从 Servlet 3.0 开始,tomcat 的新特性在绝大多数的项目中都用不少,所以,tomcat8-maven-plugin 的高级也没高到哪里去。

    这里涉及到一个小知识,从 3.1 开始,Servlet 开始内置了文件上传功能。但是,经测试实际上,3.0 就已经有了这个功能。另外,Servlet 内置的上传功能,在上传文件名是中文的文件是有中文乱码问题,不是很好解决。所以,实际上上传文件的方案,还是 commons-fileupload 更常见,Servlet 3.0 开始出现的内置的上传方案并没有那么美好。

  3. tomcat7-maven-plugin 是 tomcat-maven-plugin 的子项目,tomcat-maven-plugin 的最高版本是 2.2 ,版本新特性就是支持 tomcat7 。也就是说 tomcat8-maven-plugin 并非官方项目,这也是为什么,中央仓库没中 tomcat8-maven-plugin 的原因。

    如果对 tomcat7 的版本仍疑虑,也不一定非要使用非官方的 tomcat8-maven-plugin。可以使用其它的 Servlet 容器的 maven 插件,例如 jetty 。

# 5. mirrorOf 是 central 还是 * 的问题

在配置阿里对官方中央仓库的镜像服务器时,我们使用到了 <mirror> 元素。

故名思意,<mirror> 用于配置网络仓库的『镜像』。当你准备去默认的中央仓库下载包和插件时,Maven 会『拦截』你的下载请求,转而去你所配置的『镜像网址』下载,从而也能实现从指定的国内的网址下载。

<mirror> 元素的子元素 <mirrorOf> 的值常见两种:central 和通配符 *

central 和 * 的区别在于:

  • 如果是 central,那么 maven 只会将你的从『中央仓库』下载请求(无论是 jar 包,还是插件包),转向你所配置的镜像地址。

  • 如果是 * ,那么 maven 会将你的『所有』下载请求转向你所配置的镜像地址。

如果你要下载一个不在中央仓库的包(或插件),例如 tomcat8-maven-plugin

  • 如果你配置的值是 central,那么 maven 会去你所配置的『别的』仓库下载,因为中央仓库没有这个包;

  • 如果你配置的值是 *,那么无论这个包在不在中央仓库,maven 只会去中央仓库下载。有,则有;没有,则没有。

# 6. 高版本的 lombok 和 tomcat 7 插件冲突问题

在开发期间,当我们使用 tomcat7-maven-plugin 来作为运行环境运行我们项目使,如果我们项目中使用了 1.16.20 及以上版本的 lombok 包,项目启动时会报错:

for annotations org.apache.tomcat.util.bcel.classfile.ClassFormatException: Invalid byte tag in constant pool: 19

原因在于,从 1.16.20 开始 lombok 包中有了一个叫 module-info.class 的文件,而低版本的 tomcat 不能识别这个文件,从而导致运行时出错。

其实这个问题对于我们而言不是问题,因为项目部署时,肯定不会部署到 tomcat 7 上,至少是 tomcat 8.5 。

对于这个问题,解决办法有 2 个:

  1. 使用 tomcat8-maven-plugin 。

  2. 将 lombok 的 <scope> 设置为 provided,这样,maven 在打包时就不会将 lombok 包含在 jar/war 包内。我们对 lombok 的使用也就是在编译时使用,运行时不需要它。

  3. 使用低版本的 lombok,例如,1.14.x 的最高版本 1.14.8

# 7. hibernate-validator 高版本问题

hibernate-validator 的高版本(邮箱注解)依赖于高版本的 el-api,tomcat 8 的 el-api 是 3.0,满足需要。但是 tomcat 7 的 el-api 只有 2.2,不满足其要求。

解决办法有 2 种:

  • 低版本如 5.4.3.Final 在 tomcat7-maven-plugin 上可用。

  • 使用 tomcat8-maven-plugin 。