BlogJava 首页 新随笔 联系 聚合 管理
  400 Posts :: 0 Stories :: 296 Comments :: 0 Trackbacks
Magic Maven

Written by Srikanth Shenoy November 2003

Translated by jinfeng wang 2005年三月


Part 2 : http://www.blogjava.net/jinfeng_wang/archive/2005/03/14/2074.html
Part 3 : http://www.blogjava.net/jinfeng_wang/archive/2005/03/15/2089.html




Maven is a high-level, intelligent project management, build and deployment tool from the Apache project. There is nothing that Maven does that Ant cannot do. Ant gives the ultimate power and flexibility in build and deployment to the developer. Why do you need Maven then? Maven adds a layer of abstraction above Ant (and uses Jelly). Maven can be used to build any Java application, but in this article we will investigate the applicability of Maven from a J2EE standpoint. J2EE build and deployment as we know it today is pretty much standardized. Every enterprise has some variations, but in general it is all the same: deploying EARs, WARs, and EJB-JARs. Maven captures this intelligence and lets you achieve the build and deployment in about 5-6 lines of Maven script compared to dozens of lines in an Ant build script. In other words, Maven simplifies a developer's life. Everybody loves things to be simple right?

Maven是Apache的高度智能的项目管理、构建、部署工具,它能做Ant所有能做的事情。Ant给开发者在构建部署过程中提供了极强的火力和灵活性,那么为何你还会选择Maven呢?Maven在Ant的基础上(利用Jelly)提供了一个中间抽象层。Maven可以用了build任何Java应用,但在这篇文章中我们只针对J2EE细谈Maven的适用性。正如我们已熟知的,目前J2EE的构建部署过程已经相当的标准化,虽然各企业应用之间可能仍存在着一定的区别,但总的来说它们的build、deploy过程几乎都是相同的:部署R、WAR、EJB JAR。Maven正是抓住了这其中的相同性,用户用5~6行的Maven脚本就完成build、deploy过程,而完成相同的任务Ant脚本则需要许多行。换句话说,Maven简化了开发者的工作,每个人都喜欢工作被简化,不是么?

If you think of Ant as a modular language like C, then Maven can be compared to an object oriented language like C++ or Java. Maven plugins are like the standard JDK libraries. The C language, albeit a giant leap from assembly languages, falls short of addressing the complexities of enterprise projects. Java on the other hand, being object oriented, forces the developer to program in a certain way and reduces complexities in large enterprise projects by letting you manage dependencies by breaking them into chunks via CRC - Class, Responsibility and Collaboration - which is a big paradigm shift from C. There are some tasks that C alone can do, but for the majority of enterprise programming Java will suffice.

如果把Ant比作模块化语言(例如C),那么就可以把Maven比作面向对象语言(例如C++、Java),Maven的插件(plug-in)则扮演着标准JDK库的角色。C语言虽然大大超越了汇编语言,但还是陷入了企业项目复杂性的泥潭。而作为面向对象语言的Java,强制开发者按照一定的思路进行开发,使用CRC(Class, Responsibility和Collaboration)将企业应用中的联系进行分解(这是由C转移来的新的编程范型),便于开发者掌握它们之间的依赖关系,减少大型企业项目中的复杂性。虽然某些任务仍然还是必须使用C才能完成,但对于绝大部分企业编程使用Java足够了。

Every comparison between C and Java listed above applies to Ant and Maven. Ant lets you do any variations you want, but requires a lot of scripting. Maven on the other hand mandates certain directories and file names, but it provides plugins to make life easier. The restriction imposed by Maven is that only one artifact is generated per project (A project in Maven terminology is a folder with a project.xml file in it). If you are thinking "My EAR artifact itself consists of multiple artifacts, I am out of luck, I cannot use Maven", it is time to take a closer look. A Maven project can have sub projects. Each sub project can build its own artifact. The topmost project can aggregate the artifacts into a larger one. This is synonymous to jars and WARs put together to form an EAR. Maven also provides inheritance in projects. I will address these topics in the following sections. At the end of this article you will be able to build J2EE project artifacts using Maven.


Maven simplifies build enormously by imposing certain fixed file names and acceptable restrictions like one artifact per project.



First there was make. But make was not cross platform. Then came Ant. Ant lets you do cross-platform builds in a systematic manner. It is very elegant compared to make. However as you may have already realized, build tasks in a project are pretty standard. The tricky part is partitioning, compiling and packaging classes, resources and descriptors into the right deployment artifacts like jars, WARs and EARs and managing dependencies on the right version of libraries - both internal and third party ones. After a while you will find yourself copying Ant script snippets from one place to another and modifying them. Then there is the universal problem of classpath. We all have different versions of different libraries installed in different locations on the development workstations. Nobody can be sure if the Ant build copied the right version of the library into the deployment artifact at all. We all have seen the classic developer shrug - "It works on my machine"! Ant does little to address this problem. Enter Maven.


What is it that makes Maven an attractive option for today's enterprises? The basic tenet of Ant build scripts is copying, renaming and deleting files under various conditions into various deployable artifacts - which are again treated as files on your computer by the build script. Maven hides the fact that everything is a file and forces you to think and script to create a deployable artifact such as an EAR, that has a dependency on a particular version of a third party library residing in a shared remote (or local) enterprise repository, and then publish your library into the repository as well for others to use. No more classpath issues. No more mismatch in libraries. This sounds like a very real and practical solution to our everyday problems. It also gives you the power to embed Ant scripts within Maven scripts if absolutely essential.


In the words of Jason van Zyl, an important contributor to the Maven source code base, "the intent of Maven is to make intra-project development highly manageable in the hopes of providing more time for cross-project development. You might call it cross-project pollination or the sharing of project development knowledge; this is what Maven attempts to encourage".

Jason van Zyl(Maven源代码的重要贡献者)的话说,“Maven的目的就是使项目内部开发变得高度可控,进一步的目标是能为跨项目的开发节余更多的时间。你可以称之为跨项目授粉,或者共享项目开发知识,而这恰是Maven所鼓励的。”

Maven installation (Maven安装)

You can download the 1.0 Release Candidate from http://maven.apache.org/builds/release/1.0-rc1/.

The version of Maven available at the time of this writing was 1.0-beta 10. Everything in this article refers to the 1.0-beta 10. It is feature complete for 1.0 pending any defects. Unzip the archive onto your machine. It creates a directory called maven-1.0-beta-10. Before you go any further, set the MAVEN_HOME environment variable to this directory. Also add the MAVEN_HOME/bin to the PATH environment variable. Make sure you have set the JAVA_HOME appropriately. Go to the command line and type maven -g. If you see a long list of output, your installation has succeeded.

你可以从http://maven.apache.org/builds/release/1.0-rc1/ 下载1.0 Release版。

在写这篇文章的时候,Maven的当前版本是1.0-beta 10。因此这篇文章中的所有内容都是指1.0-beta 10。此版本已提供了所有的特性、修补了所有缺陷。将压缩包在你机器解压,它将创建名为maven-1.0-beta-10的目录。在做其他之前,请创建环境变量MAVEN_HOME,并设值为此目录。然后将MAVEN_HOME/bin添加到环境变量Path中。确认你已经正确设置了JAVA_HOME。然后转到命令行方式下,键入“maven -g”命令。如果成功看到一系列的输出,那么就说明安装成功。


Maven basics (Maven基础)

The basic concept of Maven is a project. In Maven terms, any directory that has a project.xml in it is a project. When the sub-directories underneath have their own project.xml, they are projects on their own too. Another concept in Maven is that of a repository. The repository holds the artifacts on which your project depends. There are two kinds of repository: local and remote. Both of them can be declaratively set. Unless specified otherwise, the local repository is created in a special directory called ".maven/repository". In Windows, this directory is created in C:\Documents And Settings. For example, if your project depends on commons-logging version 1.0.2, you can specify the dependency in project.xml and when maven is executed, it will copy the appropriate commons-logging jar file from the remote repository to the local repository and then use it to build your project's artifact. The maven repository folder has subfolders for each library. For instance, there is a sub folder for commons-logging. Beneath the commons-logging folder there is another subfolder called jars. This jars folder has the commons logging jar files suffixed by version number. Jars are not the only type of artifacts supported in the repository. You can have EARs and WARs too.

Maven的基本概念就是Project,在Maven里面,每个目录包含有project.xml的目录都是一个Project。如果子目录中含有project.xml,那么他们自己就是project。在Maven中的另外一个概念就是仓库(repository)。仓库用于保存项目所使用的所有的制品。Maven有两种仓库:本地仓库和远程仓库。它们都可以用声明的方式进行配置。除非特别声明,本地仓库的位置一边在“.maven/repository”目录。在Windows中,该目录在C:\Documents And Settings。例如,如果你的项目使用了commons-logging的1.0.2版本,那么你可以在project.xml中进行声明,当运行maven命令是,它将会从远程仓库中拷贝正确的commons-logging的jar文件到本地仓库,然后构建你的项目,生成制品。Maven仓库为每个库创建生成一个子目录。例如,它将会为commons-logging创建一个子目录。在commons-logging文件夹中,会含有一个名为jars的子目录。在jars文件夹中,将会存放含有相应版本号为后缀的jar文件。在Maven仓库中,它所能够支持的制品不仅仅是jar,还有EAR和WAR等。

The role of the repository is immediately obvious. Instead of each project having its own copies of third party libraries, the repository helps developers across projects to share the libraries. Each project can also in turn generate its artifacts and publish it into the remote repository. The process of publishing a jar into the repository is called "install" in Maven lingo. This install process helps organizations to share internal artifacts across projects in a standard manner. This also holds the basis for continuous integration among inter-dependent projects. Continuous Integration is a concept that was developed by Martin Fowler and developers of Cruise Control. Individual projects can continue to build and publish the release and snapshot artifacts to corporate repositories. Daemon processes running on dedicated machines can schedule integration builds, deploy to execution platforms (application servers) and run automated tests to verify the build status. Figure 1 shows the role of project.xml, repository, goals and plugins in Maven build. The grey colored rectangles are provided by you. The green colored rectangles are provided by Maven. The pink colored rectangle shaded is the output - the deployment artifacts from your project. Custom plugins and maven.xml are optional. The rest of the inputs are mandatory.

仓库的角色是相当明显的,每个项目不再需要各自包含自己所依赖的第三方库,仓库将会帮助开发者在多个项目间共享库。反过来,每个项目也可以自己build制品,然后deploy到远程仓库中。按照Maven的术语,往仓库中发布jar的过程被称为“安装(install”。安装过程可以帮助开发者用一种标准的方式在项目间共享内部制品。这也就是相互依赖的项目间持续集成(continuous integration)的基础。持续集成是由Martin Fowler提出的概念,他还开发了Cruise Control工具。项目可以连续的build,最新releasesnapshot制品可连续的发布到公司仓库中。在专门机器运行的守护进程(daemon process)将会定时的持续构建、发布制品到应用服务器中、进行自动测试并检验build状态。图1展示了在Maven构建过程中project.xml、仓库、goalplug-in各自的角色。灰色方框的内容将由你提供,绿色方框的内容则由Maven提供,粉红色方框的内容则用于输出―项目生成的部署制品。其中定制的plug-inmaven.xml则是可选的,而其他输入部分则必须的。



Figure 1 Overview of Maven build elements

1 Maven构建元素总览


project.xml details (project.xml细述)

The project.xml is divided into four main parts namely, Project Management Section, Project Dependency Section, Project Build Section and Project Reports Section. Listing 1 shows the outline of a typical project.xml. The mandatory items are shown in bold.


Listing 1 Outline of project.xml

01 <?xml version="1.0"?>
02 <project>
03   <pomVersion>3</pomVersion>
04   <groupId>Sample-Maven-Project</groupId>
05   <id>sample-project</id>
06   <currentVersion>1.1</currentVersion>
07   <name>Sample Maven Project</name>
09   <!--         Project Management section  goes here        -->
11   <!--         Project Dependency section  goes here        -->
13   <!--            Project Build section goes here           -->
15   <!--           Project Reports section goes here          -->
17 </project>

  • Line 02 - Root element defining the project.
  • Line 03 - Project Object Model (POM) Version. This tag is unused but needed.
  • Line 04 - A directory with this name is created in Maven repository to hold the artifacts of projects sharing the group id.
  • Line 05, 06 - The id and version is used to create the artifact name as -.jar
  • Line 07 - Name of the Project

1 project.xml概要

  01 <?xml version="1.0"?>
02 <project>
03   <pomVersion>3</pomVersion>
04   <groupId>Sample-Maven-Project</groupId>
05   <id>sample-project</id>
06   <currentVersion>1.1</currentVersion>
07   <name>Sample Maven Project</name>
09   <!--         项目管理部分        -->
11    <!--         项目依赖部分        -->
13  <!--         项目构建部分          -->
15  <!--         项目报告部分         -->
17 </project>


  • 3行:Project Object Model (POM)版本,此tag虽然无用,但却是必需的。
  • 4行:在Maven仓库中将会创建一个用此命名的目录,所有相同groupId的项目生成的制品都存放于此目录。
  • 56行:idversion,用于做为生成制品的文件名,-.jar
  • 7行项目名。

The Project Management Section is shown in detail in Listing 2 and has general information on the organization, its web site, project web site, location of SCM, deployment and issue tracking site, developer list, mailing lists to name a few. Most of the items in this section is boilerplate and are optional. Maven allows inheritance in the project.xml. Every organization can have an enterprise wide template which can be extended for projects. Each subproject will simply fill in the relevant section of this file. Most of the project management section elements get defined in the enterprise wide template and top-level project template. The only mandatory element is the organization name.

在表2中详细列出项目管理部分的内容,它包含的信息有:开发组织、开发组织网站、项目站点,SCM地点、发布以及问题跟踪站点、开发者列表、邮件列表等。在项目管理部分的绝大部分item都可以作为模板使用,并且它们大都是可选的。Maven支持在project.xml中继承。每一个开发组织都可以拥有一个企业范围内的模板,供各项目使用。每一个子项目只需要填写相应部分project.xml中的部分内容即可,项目管理部分的信息已经在企业范围的模板或者在最顶层项目模板中进行了定义。这里开发组织名(organization name)是必需的。

Listing 2 Project Management Section in project.xml

01   <organization>
<name>Foobar Travelsname>
<logo>http://www.foobar.com/project-logo.jpglogo>          |
<description>Project description goes heredescription>
<shortDescription>Short DescriptionshortDescription>
<name>Dev Listname>
<name>Srikanth Shenoyname>

Lines 01-05 - Organization details

  • Line 08 - Top level package for the project
  • Line 09 - Project Logo
  • Line 12 - Project web site
  • Line 14 - The site where the project is hosted
  • Line 15 - Physical location of project deployment
  • Line 16 - Physical location where the project distributions are available
  • Lines 18-21 - SCM to access the project source
  • Lines 23-31 - Mailing list for the project
  • Lines 33-41 - Developers in the project
  • 1~5行:开发组组织描述。
  • 8行:项目的最顶层包
  • 9行:项目logo
  • 12行:项目的网址
  • 14行:项目的发布网址
  • 15行:项目部署的物理位置
  • 16行:项目的发行地址
  • 1821行:项目源代码的SCM位置
  • 2331行:项目的邮件列表
  • 3341行-项目的开发者


Figure 2 Sample Maven Project Organization

2 Maven项目组织举例


The Project Build Section describes the location of source, test and resource files. This section is generally defined at the organization level to standardize templates for all projects in the organization, or at the main project level to standardize the templates for all underlying sub projects respectively. Listing 3 shows the project build section. All the information about project organization shown in Figure 2 is defined here. The project organization can be changed and so can the project.xml. All the elements in this section are optional. However, note that if this section is not specified, no build ever gets done. Once the build is complete, Maven automatically runs the tests specified in the unit test section.

项目构建部分具体描述了源代码、测试代码、和资源文件的位置。此部分可以在组织级别进行定义,对整个组织内的所有项目采用统一的模板;也可以由主项目进行定义,对其下面的所有子项目采用标准化模板。表3中给出了项目构建部分的样例。所有在图2中的项目组织信息都在此部分定义。当然,项目的具体组织结构和project.xml是可以根据各自情况进行修改的,这部分中的所有内容都是可选的。但是注意,如果整个项目构建部分没用任何的内容,那么就有任何的项目build动作。一旦项目build完毕,Maven就会自动运行在单元测试(unit test)中描述的测试。

Listing 3 Project Build Section in project.xml

3  project.xml中的项目构建部分

01 <build>

    • Line 02 - Email address to send notification about the build status
    • Line 03 - Folder containing the source files for the project. The source can be java, jsp and so on.
    • Line 04 - Directory containing the unit test files for the project.
    • Lines 05-09 - The test file name pattern to run after the build is completed
    • Lines 11-19 - Resources to be copied in case a jar is created.
    • 2行:用于发送“build status”通知的Email地址
    • 3行:项目中的所有源代码文件夹,包含javajsp
    • 4行:项目中的所有单元测试文件的文件夹
    • 59行:单元测试文件名的正则表达式,这些测试将会在构建结束后运行
    • 1119行:在创建jar文件时需要拷贝的资源。

Once the build is done, different kinds of reports and documentation can be generated to report the status of the build/release. The target audience of the reports can vary from other developers in the same project to sponsors, stakeholders or users from other projects. For instance, the javadoc and java cross reference (jsr) reports target the programmers, jdepend reports are of interest to the architect. File and developer activity reports might be of interest to the Configuration Manager. The reports are meant to effectively communicate and collaborate with the team and the stakeholders about the project's status. Listing 4 shows the Project Reports Section in the project.xml. Do not worry about the specifics of each of the entries. You can pick them up as you use Maven.

一旦build结束,就会创建各种用于报告build/release状态的报告和文档。对报告感兴趣的可以是项目中的其他开发者、项目的发起者、项目监理、或者其他项目的人员。例如,程序员关注于javadocjava cross reference(jsr)报告发送给,系统架构师则更关心jdepend报告,配置管理师则更关心开发者和文件的活动报告。这些报告对项目组内部的沟通和协作以及项目监理对项目状态的了解都是有意义的。表4对project.xml中的项目报告部分进行了描述。暂且别急于了解其各项的具体内容,在使用Maven的过程中,你会了解它们的。

Listing 4 Project Reports Section in project.xml


The final section covers the Project Dependency and is the key to every project. Consider Listing 5 that shows the dependencies for the Sample Maven Project. The first dependency states that this project depends on a jar file in a folder named BeanUtils. The name of the jar file is commons-beanutils-1.5.jar [according to the id-version.jar convention described earlier]. The folder BeanUtils exists in the .maven/repository folder.

project.xml中最好一部分是有关项目依赖部分的内容,它正是项目的关键部分。在表5中展示了Sample Maven Project的依赖,该项目的第一个依赖是BeanUtils文件夹中一个jar文件,它的名字是commons-beanutils-1.5.jar(根据早些提到的id-version.jar风格)。BeanUtils文件夹位于.maven/repository中。

Listing 5 Project dependency Section in project.xml

5 project.xml中的项目依赖关系部分


Maven Hands-On ( Maven动手)


Now that we have sufficient understanding of the project.xml file let us get on try something out. At the time of this writing Maven 1.0 beta 10 - a feature complete version for the 1.0 release is generally available, which we will use in the course of this article. After downloading Maven from maven.apache.org, unzip the archive into your local directory. Set the JAVA_HOME variable to point to the JDK installation and MAVEN_HOME to point to the Maven directory. Also add the MAVEN_HOME/bin to the PATH environment variable. With these settings, you are ready to use Maven.

既然我们对project.xml已经有了一定的了解,那我们就开始动手吧。在写这篇文章的时候,特性完整的Maven 1.0 release版已经可以下载了,在这篇文章中我们将它进行演示。在从maven.org.maven下载了Maven之后,将它在本机进行解压缩,设置环境变量JAVA_HOME指向JDK安装目录,设置环境变量MAVEN_HOME指向Maven的安装目录,将MAVEN_HOME/bin加入到环境变量Path中。当这一切设置好之后,你就可以使用Maven了。

Create a directory structure as shown in Figure 2. Then create a simple MyApp.java class without any dependencies outside JDK libraries. Create a project.xml with the information supplied in Listing 1 through 5. Since your sample project does not depend on external entities, the only exceptions is that you will have an empty dependencies section like this: . Go to the command line and cd to the Sample-Maven-Project directory and type in

C:\Sample-Maven-Project> maven java:compile

You will see that all the java files in src and test directory are compiled. A maven temp directory called target is created directly under C:\Sample-Maven-Project, which contains all the compiled class files. As I have stated earlier Maven's power lies in its plugins. You might not know it, but you have just used one of its plugins. When you typed maven java:compile you signaled Maven to use the "java" plugin and attain the "compile" goal. Maven uses the project.xml and the repository to execute a "goal" on the project. The generic format of the maven command is

maven <plugin name>:<goal name>

按照图2创建目录结构,然后新建一个不依赖于JDK库的MyApp.java文件,创建类如表1到表5构成的project.xml。由于你的Sample project不依赖于任何的外部实体,所以只需将依赖部分设为空即可:。然后转到命令行方式下,进入Sample-Maven-Project目录,键入下面的命令:

C:\Sample-Maven-Project> maven java:compile

你可以看到src和test目录下面的所有java文件都已经被编译了,在C:\Sample-Maven-Project目录下面,将会创建临时目录target,所有的编译文件都存放于其中。正如我前面所提到的,Maven的威力在于其强大的plug-in。也许你还不知道它,但就在刚才你已经使用了一个plug-in了。当你敲入命令“maven java:compile”的时候,你就是在使用使用“java”plug-in的“compile”goal。Maevn使用project.xml和仓库对本项目执行“goal”。Maven的统一的命令格式为:

maven <plugin name>:<goal name>

A plugin is a logical collection of goals written using Jelly - an XML based scripting language. The conditional constructs from Jelly control the execution of ready-made ant scripts for the pre defined tasks. Each such task is called a Goal. Although I do not cover Jelly in this article a sample Jelly snippet will make things clear as to how it wraps an Ant script. This snippet is from the Maven java plugin. The plugin internally invokes the to compile the java code.

Plug-in是使用Jelly(基于XML的标记语言)编写的一系列命令。Jelly中的条件结构(conditional construct,这里指标签)将会调用Ant中预先定义好任务(task)。每个这样的任务就被称为Goal。虽然在这篇文章中,我并不细谈Jelly,但我想给出一段Jelly脚本将会有益于读者理解Jelly是如何封装Ant的。下面的这段脚本摘于Maven的java plug-in,它将调用对java代码进行编译。

<j:when test="${sourcesPresent == 'true'}">
<ant:javac  destdir="${maven.build.dest}"
<ant:path refid="maven.compile.src.set"/>

A plugin may have a default goal. In such a case you just have to type in maven . In this case the compile goal in java plugin has the knowledge to go to the src and test folders specified in the project.xml and recursively compile all the java files. Similarly a jar plugin has a default goal of "jar" - i.e. to jar the class files after compiling. The jar plugin invokes java:compile or something to that effect to compile the java classes first and then creates the jar file from the classes and automatically generates the manifest. A plugin thus captures the common practices into a reusable "library" saving you from reinventing the wheel and cutting and pasting ant scripts. Now let us go ahead and execute the jar:install goal. Type the following on the command linePlug-in可能有默认goal,在这种情况下,你今需要敲入“maven ”即可。在上面的例子中java plugin中的compile可以从project.xml中获取src和test的设置,然后循环编译所有的java文件。同样jar plugin拥有默认goal“jar”-在编译之后将所有的class文件打包。Jar plugin将会首先调用java:compile或者其他方法编译java文件,然后将这些class文件打包并自动加入manifest。Plugin正是抓住了实际中这些通用的时间,形成了可复用的“库”,也就无需再造轮子,到处拷贝粘贴脚本了。现在我们继续向前执行jar:install goal,在命令行方式下敲入如下命令:

C:\Sample-Maven-Proejct>maven jar:install

When the jar:install goal is attained, a jar file with name sample-project-1.1.jar is created and is copied into the C:/Documents And Settings//.maven/repository/Sample-Maven-Project/jars folder. This is how you publish artifacts as mentioned earlier in this section and is the basis for continuous integration.

jar:install执行完毕,将会创建名为sample-project-1.1.jar包并且将它拷贝到目录C:/Documents And Settings//.maven/repository/ Sample-Maven-Project/jars。这就是本节前面提到的发布制品的方法,它是持续集成的基础。

You can see all the goals of Maven by typing the command maven -g at the command line.

你可以通过在命令行方式下敲入 maven –g命令查看Maven的所有goal


C:\Sample-Maven-Proejct>maven jar:install

Written by Srikanth Shenoy November 2003

Translated by jinfeng wang 2005年三月


Part 2 :http://www.blogjava.net/jinfeng_wang/archive/2005/03/14/2074.html
Part 3 :http://www.blogjava.net/jinfeng_wang/archive/2005/03/15/2089.html

posted on 2005-03-11 17:38 jinfeng_wang 阅读(3465) 评论(0)  编辑  收藏 所属分类: mavenZZ