`

http://juvenshun.iteye.com/blog/305865

阅读更多

“分天下为三十六郡,郡置守,尉,监” —— 《史记·秦始皇本纪》

 

所有用Maven管理的真实的项目都应该是分模块的,每个模块都对应着一个pom.xml。它们之间通过继承和聚合(也称作多模块,multi-module)相互关联。那么,为什么要这么做呢?我们明明在开发一个项目,划分模块后,导入Eclipse变成了N个项目,这会带来复杂度,给开发带来不便。

 

为了解释原因,假设有这样一个项目,很常见的Java Web应用。在这个应用中,我们分了几层:

  • Dao层负责数据库交互,封装了Hibernate交互的类。
  • Service层处理业务逻辑,放一些Service接口和实现相关的Bean。
  • Web层负责与客户端交互,主要有一些Structs的Action类。

对应的,在一个项目中,我们会看到一些包名:

  • org.myorg.app.dao
  • org.myorg.app.service
  • org.myorg.app.web
  • org.myorg.app.util

这样整个项目的框架就清晰了,但随着项目的进行,你可能会遇到如下问题:

  1. 这个应用可能需要有一个前台和一个后台管理端(web或者swing),你发现大部分dao,一些service,和大部分util是在两个应用中可。这样的问题,你一周内遇到了好几次。
  2. pom.xml中的依赖列表越来越长以重用的,但是,由于目前只有一个项目(WAR),你不得不新建一个项目依赖这个WAR,这变得非常的恶心,因为在Maven中配置对WAR的依赖远不如依赖JAR那样简单明了,而且你根本不需要org.myorg.app.web。有人修改了dao,提交到svn并且不小心导致build失败了,你在编写service的代码,发现编译不过,只能等那人把dao修复了,你才能继续进行,很多人都在修改,到后来你根本就不清楚哪个依赖是谁需要的,渐渐的,很多不必要的依赖被引入。甚至出现了一个依赖有多个版本存在。
  3. build整个项目的时间越来越长,尽管你只是一直在web层工作,但你不得不build整个项目。
  4. 某个模块,比如util,你只想让一些经验丰富的人来维护,可是,现在这种情况,每个开发者都能修改,这导致关键模块的代码质量不能达到你的要求。

我们会发现,其实这里实际上没有遵守一个设计模式原则:“高内聚,低耦合”。虽然我们通过包名划分了层次,并且你还会说,这些包的依赖都是单向的,没有包的环依赖。这很好,但还不够,因为就构建层次来说,所有东西都被耦合在一起了。因此我们需要使用Maven划分模块。

 

一个简单的Maven模块结构是这样的:

 

---- app-parent

             |-- pom.xml (pom)

             |

             |-- app-util

             |        |-- pom.xml (jar)

             |

             |-- app-dao

             |        |-- pom.xml (jar)

             |

             |-- app-service

             |        |-- pom.xml (jar)

             |

             |-- app-web

                      |-- pom.xml (war)   

 

上述简单示意图中,有一个父项目(app-parent)聚合很多子项目(app-util, app-dao, app-service, app-web)。每个项目,不管是父子,都含有一个pom.xml文件。而且要注意的是,小括号中标出了每个项目的打包类型。父项目是pom,也只能是pom。子项目有jar,或者war。根据它包含的内容具体考虑。

 

这些模块的依赖关系如下:

 

app-dao      --> app-util

app-service --> app-dao

app-web     --> app-service

 

注意依赖的传递性(大部分情况是传递的,除非你配置了特殊的依赖scope),app-dao依赖于app-util,app-service依赖于app-dao,于是app-service也依赖于app-util。同理,app-web依赖于app-dao,app-util。

 

用项目层次的划分替代包层次的划分能给我们带来如下好处:

  1. 方便重用,如果你有一个新的swing项目需要用到app-dao和app-service,添加对它们的依赖即可,你不再需要去依赖一个WAR。而有些模块,如app-util,完全可以渐渐进化成公司的一份基础工具类库,供所有项目使用。这是模块化最重要的一个目的。
  2. 由于你现在划分了模块,每个模块的配置都在各自的pom.xml里,不用再到一个混乱的纷繁复杂的总的POM中寻找自己的配置。
  3. 如果你只是在app-dao上工作,你不再需要build整个项目,只要在app-dao目录运行mvn命令进行build即可,这样可以节省时间,尤其是当项目越来越复杂,build越来越耗时后。
  4. 某些模块,如app-util被所有人依赖,但你不想给所有人修改,现在你完全可以从这个项目结构出来,做成另外一个项目,svn只给特定的人访问,但仍提供jar给别人使用。
  5. 多模块的Maven项目结构支持一些Maven的更有趣的特性(如DepencencyManagement),这留作以后讨论。

接下来讨论一下POM配置细节,实际上非常简单,先看app-parent的pom.xml:

Xml代码  收藏代码
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2.     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
  3.     <modelVersion>4.0.0</modelVersion>  
  4.     <groupId>org.myorg.myapp</groupId>  
  5.     <artifactId>app-parent</artifactId>  
  6.     <packaging>pom</packaging>  
  7.     <version>1.0-SNAPSHOT</version>  
  8.     <modules>  
  9.         <module>app-util</module>  
  10.         <module>app-dao</module>  
  11.         <module>app-service</module>  
  12.         <module>app-web</module>  
  13.     </modules>  
  14. </project>  

Maven的坐标GAV(groupId, artifactId, version)在这里进行配置,这些都是必须的。特殊的地方在于,这里的packaging为pom。所有带有子模块的项目的packaging都为pom。packaging如果不进行配置,它的默认值是jar,代表Maven会将项目打成一个jar包。

该配置重要的地方在于modules,例子中包含的子模块有app-util, app-dao, app-service, app-war。在Maven build app-parent的时候,它会根据子模块的相互依赖关系整理一个build顺序,然后依次build。

这就是一个父模块大概需要的配置,接下来看一下子模块符合配置继承父模块。、

Xml代码  收藏代码
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2.     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
  3.     <parent>  
  4.         <artifactId>app-parent</artifactId>  
  5.         <groupId>org.myorg.myapp</groupId>  
  6.         <version>1.0-SNAPSHOT</version>  
  7.     </parent>  
  8.     <modelVersion>4.0.0</modelVersion>  
  9.     <artifactId>app-util</artifactId>  
  10.     <dependencies>  
  11.         <dependency>  
  12.             <groupId>commons-lang</groupId>  
  13.             <artifactId>commons-lang</artifactId>  
  14.             <version>2.4</version>  
  15.         </dependency>  
  16.     </dependencies>  
  17. </project>  

app-util模块继承了app-parent父模块,因此这个POM的一开始就声明了对app-parent的引用,该引用是通过Maven坐标GAV实现的。而关于项目app-util本身,它却没有声明完整GAV,这里我们只看到了artifactId。这个POM并没有错,groupId和version默认从父模块继承了。实际上子模块从父模块继承一切东西,包括依赖,插件配置等等。

此外app-util配置了一个对于commons-lang的简单依赖,这是最简单的依赖配置形式。大部分情况,也是通过GAV引用的。

再看一下app-dao,它也是继承于app-parent,同时依赖于app-util:

Xml代码  收藏代码
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2.     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
  3.     <parent>  
  4.         <artifactId>app-parent</artifactId>  
  5.         <groupId>org.myorg.myapp</groupId>  
  6.         <version>1.0-SNAPSHOT</version>  
  7.     </parent>  
  8.     <modelVersion>4.0.0</modelVersion>  
  9.     <artifactId>app-dao</artifactId>  
  10.     <dependencies>  
  11.         <dependency>  
  12.             <groupId>org.myorg.myapp</groupId>  
  13.             <artifactId>app-util</artifactId>  
  14.             <version>${project.version}</version>  
  15.         </dependency>  
  16.     </dependencies>  
  17. </project>  

该配置和app-util的配置几乎没什么差别,不同的地方在于,依赖变化了,app-dao依赖于app-util。这里要注意的是version的值为${project.version},这个值是一个属性引用,指向了POM的project/version的值,也就是这个POM对应的version。由于app-dao的version继承于app-parent,因此它的值就是1.0-SNAPSHOT。而app-util也继承了这个值,因此在所有这些项目中,我们做到了保持版本一致。

这里还需要注意的是,app-dao依赖于app-util,而app-util又依赖于commons-lang,根据传递性,app-dao也拥有了对于commons-lang的依赖。

app-service我们跳过不谈,它依赖于app-dao。我们最后看一下app-web:

Xml代码  收藏代码
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2.     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
  3.     <parent>  
  4.         <artifactId>app-parent</artifactId>  
  5.         <groupId>org.myorg.myapp</groupId>  
  6.         <version>1.0-SNAPSHOT</version>  
  7.     </parent>  
  8.     <modelVersion>4.0.0</modelVersion>  
  9.     <artifactId>app-web</artifactId>  
  10.     <packaging>war</packaging>  
  11.     <dependencies>  
  12.         <dependency>  
  13.             <groupId>org.myorg.myapp</groupId>  
  14.             <artifactId>app-service</artifactId>  
  15.             <version>${project.version}</version>  
  16.         </dependency>  
  17.     </dependencies>  
  18. </project>  

app-web依赖于app-service,因此配置了对其的依赖。

由于app-web是我们最终要部署的应用,因此它的packaging是war。为此,你需要有一个目录src/main/webapp。并在这个目录下拥有web应用需要的文件,如/WEB-INF/web.xml。没有web.xml,Maven会报告build失败,此外你可能还会有这样一些子目录:/js, /img, /css ... 。

 

看看Maven是如何build整个项目的,我们在 app-parent 根目录中运行 mvn clean install ,输出的末尾会有大致这样的内容:

 

...

...

[INFO] [war:war]
[INFO] Packaging webapp
[INFO] Assembling webapp[app-web] in [/home/juven/workspaces/ws-others/myapp/app-web/target/app-web-1.0-SNAPSHOT]
[INFO] Processing war project
[INFO] Webapp assembled in[50 msecs]
[INFO] Building war: /home/juven/workspaces/ws-others/myapp/app-web/target/app-web-1.0-SNAPSHOT.war
[INFO] [install:install]
[INFO] Installing /home/juven/workspaces/ws-others/myapp/app-web/target/app-web-1.0-SNAPSHOT.war to /home/juven/.m2/repository/org/myorg/myapp/app-web/1.0-SNAPSHOT/app-web-1.0-SNAPSHOT.war
[INFO] 
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] app-parent ............................................ SUCCESS [1.191s]
[INFO] app-util .............................................. SUCCESS [1.274s]
[INFO] app-dao ............................................... SUCCESS [0.583s]
[INFO] app-service ........................................... SUCCESS [0.593s]
[INFO] app-web ............................................... SUCCESS [0.976s]
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4 seconds
[INFO] Finished at: Sat Dec 27 08:20:18 PST 2008
[INFO] Final Memory: 3M/17M
[INFO] ------------------------------------------------------------------------

 

注意Reactor Summary,整个项目根据我们希望的顺序进行build。Maven根据我们的依赖配置,智能的安排了顺序,app-util, app-dao, app-service, app-web。

 

最后,你可以在 app-web/target 目录下找到文件 app-web-1.0-SNAPSHOT.war ,打开这个war包,在 /WEB-INF/lib 目录看到了 commons-lang-2.4.jar,以及对应的app-util, app-dao, app-service 的jar包。Maven自动帮你处理了打包的事情,并且根据你的依赖配置帮你引入了相应的jar文件。

 

使用多模块的Maven配置,可以帮助项目划分模块,鼓励重用,防止POM变得过于庞大,方便某个模块的构建,而不用每次都构建整个项目,并且使得针对某个模块的特殊控制更为方便。本文同时给出了一个实际的配置样例,展示了如何使用Maven配置多模块项目。

分享到:
评论

相关推荐

    手把手教你使用Maven进行Android的从配置到开发与资源管理教程.doc

    - Maven的中文手册可参考[http://www.juvenxu.com/mvn-def-guide/](http://www.juvenxu.com/mvn-def-guide/)和[http://juvenshun.javaeye.com/5](http://juvenshun.javaeye.com/5)。 **5. Maven项目导入Eclipse** ...

    持续集成实践和Maven核心介绍

    博文链接:https://juvenshun.iteye.com/blog/249189

    COMSOL模拟碳酸钙岩石与盐酸反应的随机孔隙酸化路径及布林克曼流动形成的分形结构

    内容概要:本文详细介绍了利用COMSOL软件模拟碳酸钙(CaCO3)在岩石中与盐酸(HCl)反应过程中产生的随机孔隙酸化路径及其形成的布林克曼流动。首先,通过蒙特卡洛方法生成随机孔隙分布,模拟真实岩石内部复杂的孔隙结构。接着,采用布林克曼方程处理多孔介质中的粘性力和渗透流动,并引入化学反应模块,模拟CaCO3与HCl之间的化学反应。随着模拟的进行,酸液流动路径逐渐形成类似雪花状的分形结构,展示了流动与溶解之间的动态博弈。最后,通过自适应网格技术和粒子追踪功能,精确捕捉并可视化这些精美的分形图案。 适合人群:从事地质工程、材料科学、化学工程等领域研究的专业人士,以及对多孔介质传输现象感兴趣的科研工作者。 使用场景及目标:适用于研究多孔介质内的化学反应和流体流动特性,特别是对于优化石油开采中的酸化压裂工艺具有重要指导意义。 其他说明:文中提供了详细的MATLAB和COMSOL代码片段,帮助读者理解和重现模拟过程。此外,强调了随机性和确定性在微观尺度上的相互作用,揭示了自然界深层次的规律。

    基于滑模控制的永磁同步电机直接转矩控制仿真建模与实现

    内容概要:本文详细介绍了将滑模控制(SMC)应用于永磁同步电机(PMSM)直接转矩控制(DTC)的技术细节。首先解释了转矩和磁链误差计算方法,接着探讨了滑模面的设计及其对系统抖振的影响。文中还提供了扇区矢量选择的具体实现方式,并深入讨论了磁链观测器的改进措施。此外,文章分析了滑模控制器的设计要点以及仿真过程中需要注意的关键参数配置。通过对比传统PI控制,验证了滑模控制在提高系统鲁棒性和快速响应方面的优势。 适合人群:从事电机控制系统研究的专业人士,尤其是对永磁同步电机直接转矩控制感兴趣的科研工作者和技术人员。 使用场景及目标:适用于希望深入了解并掌握滑模控制理论及其在PMSM-DTC应用中的具体实现方法的研究人员。目标是在实际项目中能够运用滑模控制提升系统的稳定性和性能。 其他说明:文中提供的MATLAB/Simulink代码片段有助于读者更好地理解和复现实验结果。同时提醒读者关注一些常见的陷阱,如参数选择不当可能导致的问题。

    北京大学网络安全工作人员管理规定:涵盖人员职责、聘用、转岗离岗、教育培训及第三方管理

    内容概要:本文详细介绍了北京大学针对网络安全工作人员的管理规定,旨在加强网络安全管理和明确不同角色的责任。全文分为九章,涵盖了网络安全工作人员及其职责、聘用管理、转岗和离岗管理、教育培训、第三方人员管理及奖惩措施等方面的内容。重点在于明确各级单位和人员的具体职责,确保网络安全制度的有效执行,并强调了对第三方人员的严格管控和保密要求。 适合人群:适用于高校网络安全管理人员及相关技术人员,尤其是北京大学及其下属单位的网络安全工作者。 使用场景及目标:①帮助高校建立健全网络安全管理体系;②指导网络安全工作人员明确自身职责,提高工作效率;③规范第三方人员的访问和操作,降低安全风险。 其他说明:本文还提供了多个附件,如网络安全承诺书、访问申请表和保密协议模板,便于实际操作和管理。

    网络设备市场现状与发展趋势分析(2024-2030年)-技术革新与智能化应用

    内容概要:本文深入探讨了中国网络设备市场的现状及其未来发展潜力。首先介绍了网络设备的基本概念及其作为现代通信网络基础设施的重要地位,随后分析了当前市场面临的挑战和技术进步带来的机遇。文中特别强调了5G、物联网、云计算等新兴技术对网络设备性能和安全性的更高要求,以及由此催生的高带宽、低延迟产品的市场需求。此外,还讨论了软件定义网络(SDN)、网络功能虚拟化(NFV)、边缘计算等新技术的应用前景,指出未来网络设备将更加智能化、自动化,并能更好地支持AI和ML技术。最后,通过对多家领先企业的案例研究,展示了行业内竞争态势及各公司在技术创新方面的努力。 适用人群:从事网络设备相关领域的研究人员、工程师、管理人员,以及关注该领域发展的投资者。 使用场景及目标:帮助读者了解网络设备行业的最新动态和技术趋势,为制定战略决策提供依据;同时为企业和个人投资者提供市场洞察,辅助其做出合理的投资选择。 其他说明:报告基于详实的数据分析和专家意见撰写而成,旨在为专业人士提供有价值的参考资料。

    西门子1200 PLC码垛系统的SCL编程详解:涵盖变频器、机器人、视觉系统集成

    内容概要:本文详细介绍了基于西门子1200 PLC的码垛系统的设计与实现,涵盖了多个关键技术点。首先,文章讲解了Modbus TCP通讯的实现方法,展示了如何通过TSEND_C和TRCV_C功能块进行工业相机和机器人之间的数据传输,并提供了具体的报文处理代码。接着,文章深入探讨了SCL编程的优势及其在复杂逻辑处理中的应用,如托盘堆叠算法,该算法能够根据当前层数动态调整机械手的高度,确保堆叠的安全性和稳定性。此外,文章还介绍了机器人控制中的移位寄存器实现的动作队列管理和变频器的速度平滑处理,以及视觉系统的坐标解析和异常处理机制。最后,文章强调了良好的注释规范和异常处理链的重要性,确保程序的可维护性和可靠性。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是熟悉西门子PLC编程和SCL语言的从业者。 使用场景及目标:适用于需要深入了解和掌握西门子1200 PLC在码垛系统中的具体应用的技术人员。目标是帮助读者理解并实现多设备联动的复杂控制系统,提高系统的稳定性和效率。 其他说明:文中提供的代码示例和详细的解释有助于读者更好地理解和应用相关技术,同时也为后续的维护和优化提供了宝贵的参考资料。

    ZYNQ平台PS与PL端驱动程序编写

    适合从入门到进阶的驱动程序爱好者

    计算机二级上机题库答案.pdf

    计算机二级上机题库答案.pdf

    深信服下一代防火墙:构建全方位立体网络安全监测与响应体系

    内容概要:本文介绍了深信服科技推出的下一代防火墙(NGAF)网络安全监测解决方案。随着网络安全成为国家战略的一部分,企业不仅需要遵守法律法规,还需增强自身的网络安全防护能力。传统的安全措施难以应对复杂的新型威胁,如APT攻击。深信服的NGAF通过多维度的安全监测,包括入侵风险、僵尸主机、实时漏洞、数据风险、黑链风险以及对外DoS攻击监测等功能,结合云端威胁情报共享,为企业提供了一套立体化的主动防御体系。该方案不仅可以旁路或串接部署,不影响现有业务系统,还能通过外置数据中心进行日志管理和综合分析,帮助用户快速定位和解决安全问题。 适合人群:IT管理人员、网络安全专家、企业信息安全负责人。 使用场景及目标:适用于各类企业的网络安全建设,特别是需要应对复杂网络攻击的企业。目标是构建一个多层次、全方位、智能化的网络安全监测和响应体系,提高企业的安全防护能力和应急响应速度。 其他说明:深信服NGAF不仅提升了网络安全监测的效果,还降低了运维成本,改变了传统的被动防护模式,使得安全运维更加高效和智能化。

    COMSOL超表面偏振转换技术:介质半波片与1/4波片的设计与仿真

    内容概要:本文详细介绍了利用COMSOL软件进行超表面偏振转换的设计方法,主要聚焦于介质半波片和1/4波片的实现。文中首先解释了超表面的基本原理及其在光学调控中的重要作用,随后具体阐述了如何在COMSOL中设置材料属性、创建几何结构并施加适当的边界条件。针对半波片和1/4波片的不同需求,分别探讨了它们各自的设计要点、模拟步骤及优化策略。此外,还分享了一些实用的编码示例和技术诀窍,帮助研究人员更好地理解和掌握相关技能。 适合人群:从事光学工程、光电子学等领域研究的专业人士,尤其是那些希望深入了解超表面偏振转换机制并对COMSOL有一定使用经验的技术人员。 使用场景及目标:适用于需要设计高性能偏振转换器件的研究项目,旨在提高对超表面特性的认识水平,推动新型光学组件的研发进程。通过学习本文提供的理论知识和实践经验,读者可以在实际工作中运用COMSOL完成高质量的仿真实验。 其他说明:文中不仅涵盖了基本的概念介绍,还包括了许多具体的实施细节,如参数选择、模型构建、边界条件设定等,这些都是成功搭建有效仿真的关键因素。同时,作者也强调了实验过程中可能出现的问题及解决方案,为后续研究提供了宝贵的参考资料。

    机器学习中萤火虫算法优化SVM模型参数的技术解析与应用

    内容概要:本文详细介绍了将萤火虫算法应用于支持向量机(SVM)模型参数优化的方法和技术细节。首先解释了萤火虫算法的基本原理及其在参数空间中的应用方式,然后展示了具体的Python实现代码,包括萤火虫移动规则、适应度评价函数以及主循环逻辑。文中还讨论了参数范围设定、随机扰动的作用、目标函数设计等多个关键技术点,并提供了多个数据集上的实验结果对比,证明了该方法的有效性和优越性。 适合人群:对机器学习尤其是SVM模型有一定了解的研究人员和工程师,希望掌握先进的超参数优化技术。 使用场景及目标:适用于需要提高SVM模型性能的项目中,特别是在面对大规模数据集或复杂特征空间的情况下。通过使用萤火虫算法进行参数寻优,可以显著减少调参时间和成本,获得更好的分类效果。 其他说明:文章不仅提供了理论依据,还有详细的代码示例可供参考。此外,作者强调了一些实用技巧如参数范围的选择、随机扰动的应用等,有助于读者更好地理解和应用这一技术。

    软考高级信息系统项目管理师考试题型解析与备考指南

    内容概要:本文详细介绍了软考高级信息系统项目管理师考试的内容,

    网络中间设备的发展及其在SDN/NFV环境下的角色转变与挑战

    内容概要:本文探讨了网络中间设备(middlebox)在现代网络架构中的地位和发展方向。首先介绍了网络中间设备的基本概念及其与传统转发设备的区别,强调了中间设备在网络安全、流量管理和优化方面的作用。接着讨论了软件定义网络(SDN)和网络功能虚拟化(NFV)背景下,中间设备面临的机遇和挑战,以及两者融合的趋势。最后展望了未来网流监控技术的发展前景,特别是在高效载荷过滤算法和敏捷策略管理机制方面的突破。 适合人群:从事网络工程、信息安全、云计算等领域工作的技术人员,以及对网络技术和架构感兴趣的科研人员。 使用场景及目标:帮助读者理解网络中间设备的关键作用和技术发展趋势,指导他们在实际工作中更好地规划和部署相关设备和服务。 其他说明:文中引用了SIGCOMM 2012和CoNEXT 2013的相关研究成果,提供了丰富的理论依据和技术背景支持。

    计算机二级各章考点.pdf

    计算机二级各章考点.pdf

    Tomcat 部署配置指南安全配置性能调优

    Tomcat 部署配置指南安全配置性能调优

    计算机辅助光学设计.pdf

    计算机辅助光学设计.pdf

    计算机二级考试机试(南开100题全).pdf

    计算机二级考试机试(南开100题全).pdf

    计算机二级VFP历年真题及答案.pdf

    计算机二级VFP历年真题及答案.pdf

Global site tag (gtag.js) - Google Analytics