12
返回列表 发新帖
楼主: Sky-Tiger

Maven Magic

[复制链接]
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
11#
 楼主| 发表于 2008-11-5 23:19 | 只看该作者
Building the EJB jars
After having built the dependency jars let us turn our focus to building the ejb jars. This is no different, since there is already a ejb plugin and you can use the ejb:install goal to create and publish the ejb jars. However we would also like to remind you that the ejb jar (loaded by the EJB class loader) depends on the Services built in the previous step (See Figure 4). What this means for you is that the ejb manifest classpath should point to the foobar-services-2.0 jar. Listing 9 shows the project.xml for the Reservation EJB jar. Other ejb project definitions are similar.

NOTE: In the downloadable example supplied with this article, no EJBs are included. The reason is to have even non-ejb developers using Tomcat to see Maven in action. However this section describes what it takes to build EJBs with Maven.  

As usual the ejb project depends on the J2EE jars. However since it also depends on the foobar-services-2.0.jar (loaded by the EAR class loader), it has to add that jar file name to its manifest classpath. You can achieve this by setting the property ejb.manifest.classpath to be true. The Maven variable, pom.currentVersion stands for the current version of the project. In our case, it is 2.0. This setting indicates that the version of services project on which the ejb project depends is the same as the current version of the ejb project. This version is set on the parent project template. For future releases, the version number need not be changed for every dependency in every project. Just change it in the Master project template and it takes effect everywhere. Ah, the beauty of inheritance!

Listing 9 Reservation EJB Project Definition

<project>
  <extend>${basedir}/../project.xml</extend>
  <id>reservationejb</id>
  <name>Foobar Reservation Components</name>
  <package>foobar.reservation.*</package>
  <description>Reservation Components project</description>
  <dependencies>
    <dependency>
      <groupId>j2ee</groupId>
      <artifactId>j2ee</artifactId>
      <version>1.3</version>
    </dependency>
    <dependency>
      <groupId>Foobar-Travels</groupId>
      <artifactId>foobar-services</artifactId>
      <version>${pom.currentVersion}</version>
      <properties>
        <ejb.manifest.classpath>true</ejb.manifest.classpath>
      </properties>
    </dependency>  
  </dependencies>
</project>

The discussion of building the ejb jars is incomplete without mentioning the maven.xml file. This file is not straight forward as in the services project. Listing 10 shows the maven.xml for the Reservation EJB project. There are a few things happening here. First, the foobar-dist goal is used as a wrapper for attaining the goal ejb:install. One of the first goals to be achieved during the install is init, when the file system and other resources are initialized and then the ejb classes are compiled. The compiler expects to find the home, local and remote interfaces, failing which it will throw a compiler error. This is where XDoclet comes into play.

Listing 10 maven.xml for the Reservation EJB Project

<project default="foobar-dist" xmlns:m="jelly:maven"
                                xmlns:ant="jelly:ant">
   <goal name="foobar-dist">
     <attainGoal name="ejb:install" />
   </goal>

   <preGoal name="ejb:init">
      <attainGoal name="xdoclet:ejbdoclet"/>
   </preGoal>

   <postGoal name="ejb:install">
      <antroperty name="maven.ejb.install.dir"
       value="${maven.repo.local}/${pom.artifactDirectory}/ejbs"/>
      <ant:mkdir dir="${maven.ejb.install.dir}"/>
      <ant:copy file="${maven.build.dir}/${maven.final.name}.jar"
            tofile="${maven.ejb.install.dir}/../
                     jars/${maven.final.name}.jar"/>
   </postGoal>
</project>

XDoclet is an open source project hosted on the SourceForge. By using EJB Doclet specific tags in the Bean class (the implementation class), you signal the XDoclet to generate the home, local and remote interfaces. You will also provide the jndi names, and other information using similar tags. On parsing the file, the XDoclet will also generate appropriate deployment descriptors. Now let us get back to see how it fits in here.

XDoclet provides Maven plugins for generating the above-mentioned artifacts. By specifying the ejbdoclet as the preGoal for ejb:init, you will generate home, local and remote interfaces and xml deployment descriptors just in time for the compilation.

Now let us look at the postGoal for ejb:install. As you know, the ejb:install generates the ejb jar as the artifact and puts it in the repository in the location "C:/Documents And Settings/<login-id>/.maven/repository/Foobar-Online/jars". Suppose that you want the ejbs to be in a separate directory called ejbs instead of the jars, you have to do it after the ejb:install is finished with its business. That's why it is a postGoal. For those familiar with Ant, the postGoal is nothing but a collection of Ant tasks. Again, a couple of Maven specific properties are used. Don't try to remember all of the Maven defined properties at once. Over time, these will become second nature.

You will notice there are two distinct categories of properties - those with their name beginning with pom and the rest of them. The properties with their names beginning with pom are the individual elements in the project.xml. For instance, the currentVersion element defined in project.xml has a property with the name pom.currentVersion and so on. The project.xml is loaded to create the POM model by Maven. POM model attribute values are evaluated by using the $ sign before the attribute names. As more plugins are added new properties emerge. This is going to be a challenge. You may not find adequate documentation of these properties. The only way to find out is by opening the scripts for each of these plugins in plugin.jelly file. Luckily there aren't very many properties that you will have to know.

In your project, if you find yourself writing the same pregoals and postgoals for every ejb subproject for example, it is time to roll your own plugin.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
12#
 楼主| 发表于 2008-11-5 23:19 | 只看该作者
Building the WAR
After the ejb jar, it is wartime. We will accomplish quite a bit in this section. First we will introduce you to the project.xml. Then we will add a minor missing functionality into the war plugin. Then we will show you how to use XDoclet to generate tlds for a sample tag. Then we will end the section with maven.xml.

Listing 11 shows the project.xml for the Foobar web application. The actual project definition has much more dependencies and is downloadable. Here we are showing only the relevant portions to illustrate the concepts. The important part is the dependencies section. The web application depends on the J2EE APIs provided by the container at runtime as well as compile time. However there is no need to bundle the J2EE API with the WAR since it is the servlet container's responsibility. In addition the web application depends on Struts 1.1 jar file. The property setting <war.bundle>true</war.bundle> indicates that struts.jar has to be bundled with the WAR. Whenever the war plugin is instructed to bundle the jars, it puts them under the WEB-INF/lib directory of the WAR. Compare this to manually copying the jars into the WEB-INF/lib directory in Ant. The WAR also depends on the Foobar Services jar file, foobar-services-2.0.jar. However we do not want to copy this jar into the WEB-INF/lib since it is a dependency library and shared by both the web tier and the ejbs. (Recall that any jars and classes residing in WEB-INF/lib and WEB-INF/classes are loaded by the WAR class loader.) Hence we set the war.bundle property as false for foobar-services jar file. You get all this functionality out of the box with Maven war plugin.

Listing 11 maven.xml for the Web Project

<project>
  <extend>${basedir}/../project.xml</extend>
  <id>foobar-web</id>
  <name>Foobar web application</name>
  <package>foobar.webapp.*</package>
  <description>Foobar Web project.</description>

  <dependencies>
    <dependency>
      <groupId>j2ee</groupId>
      <artifactId>j2ee</artifactId>
      <version>1.3.1</version>
    </dependency>

    <dependency>
      <groupId>jakarta-struts</groupId>
      <artifactId>jakarta-struts</artifactId>
      <version>1.0.2</version>
      <properties>
         <war.bundle>true</war.bundle>
      </properties>
    </dependency>

    <dependency>
      <groupId>Foobar-Travels</groupId>
      <artifactId>foobar-services</artifactId>
      <version>${pom.currentVersion}</version>
      <properties>
         <war.bundle>false</war.bundle>
         <war.manifest.classpath>true</war.manifest.classpath>
      </properties>
    </dependency>
  </dependencies>
</project>

However the Maven war plugin lacks one feature. It does not have the capability to set the manifest classpath. Without this feature you cannot expect to use dependency libraries. Hence I decided to add this feature to the war plugin. Only two simple changes were required to achieve this. I decided to illustrate this change to show how intuitive and easy it is to write plugins in Jelly or customize the ones that already exist. Open the plugin.jelly for the war plugin from C:/Documents And Settings/<login-id>/.maven/plugins/maven-war-plugin-<version>-[SNAPSHOT]. These changes have to be done in the goal named war (The actual WAR - deployment archive, is built in a goal named as war in the war plugin). Add the following code in that goal.

<j:forEach var="dep" items="${pom.dependencies}">
  <j:if
   test="${dep.getProperty('war.manifest.classpath')=='true'}">
    <j:set var="maven.war.classpath"
           value="${maven.war.classpath} ${dep.artifact}"/>
  </j:if>
</j:forEach>

This code iterates over each of the dependencies listed in the project.xml (identified by pom.dependencies) and checks if the war.manifest.classpath property is set to true. If so, then it appends that artifact name to a property called maven.war.classpath.

The second change is needed when the manifest file is written out. The manifest file is created by the ant plugin by executing a goal named manifest. The manifest creation code is shown below. The line in bold is the line I inserted to set the manifest classpath attribute. This uses the previously set maven.war.classpath.

<ant:manifest>
  <ant:attribute name="Built-By" value="${user.name}" />
  <ant:attribute name="Class-Path" value="${maven.war.classpath}"/>
  <ant:section name="${pom.package}">
    <ant:attribute name="Specification-Title"
                   value="${pom.artifactId}" />
    <ant:attribute name="Specification-Version"
                   value="${pom.currentVersion}" />
    <ant:attribute name="Specification-Vendor"
                   value="${pom.organization.name}" />
  </ant:section>
</ant:manifest>

That was simple wasn't it! Hopefully this will inspire you to write your own plugins when needed, instead of repetitively adding tasks into preGoals and postGoals.

Now I will show you how to use the XDoclet plugin to generate the tld automatically. I will be using XDoclet version 1.2 Beta3. Consider the declaration in the tld file for a tag say MyTag.

<tag>
   <name>mystuff</name>
   <tag-class>foobar.webapp.MyTag</tag-class>
   <body-content>JSP</body-content>
   <attribute>
      <name>locale</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
   </attribute>
</tag>

It is okay to have a readymade tld for frameworks such as Struts. However when you are developing your own tags either by customizing the existing tags or from scratch, it is natural that they change and evolve constantly during development. It can be time consuming to manually synchronize the tag code and its declaration in the tld. XDoclet is designed to ease such burdens. In the source code for the MyAppTag, add @jsp.tag name="myapptag" body-content="JSP" in its class comments section. For each of the tag attributes, add a @jsp.attribute on the getter method for that attribute. For instance, add @jsp.attribute required="false" rtexprvalue="false" to the comments on the getMyMessage method to represent that myMessage is a tag attribute. Next you invoke the webdoclet goal from the xdoclet plugin in maven.xml as shown in Listing 12. XDoclet also gives you provision to generate the web.xml too. Let us look at the maven.xml for the Web project in its entirety.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
13#
 楼主| 发表于 2008-11-5 23:19 | 只看该作者
Listing 12 maven.xml for the Web Project

<project default="foobar-dist" xmlns:m="jelly:maven"
                                xmlns:ant="jelly:ant">
   <goal name="foobar-dist">
     <attainGoal name="war:install" />
   </goal>

   <preGoal name="war:init">
      <attainGoal name="xdoclet:webdoclet"/>
   </preGoal>

</project>

NOTE: You have to add the XDoclet web module in the dependency section for the project.xml as follows.

    <dependency>
       <id>xdoclet+web-module</id>
       <version>1.2b4</version>
    </dependency>


In addition, override the following properties in the plugin.properties file for XDoclet under the Maven plugins folder
  maven.xdoclet.webdoclet.deploymentdescriptor.0=false
  maven.xdoclet.webdoclet.jsptaglib.0.destDir=
              ${maven.build.dir}/${pom.artifactId}/WEB-INF/tld

The first setting disables web.xml generation and the second setting sets the generated tld file location. In this article you will just be generating the tld file, but not generating the web.xml, nor the <taglib> entry for the tld file in web.xml. As a matter of fact, XDoclet does not seem to have the Maven equivalent for the following Ant script that adds the <taglib> to web.xml.
  <deploymentdescriptor servletspec="2.3" destdir="${WEBINF}" >
      <taglib uri="myapptaglibs" location="WEB-INF/tld/myapp.tld" />
  </deploymentdescriptor>

Another thing that I noticed in the XDoclet plugin is that the file name for the tag library definitions (tld) has to be provided in plugin.properties as follows.
              maven.xdoclet.webdoclet.jsptaglib.0.filename=myapp.tld
             
Otherwise the tld file is generated with the default name of taglib.tld. This does not seem right. Instead it should be set in the build script. For instance, in Ant this is done as follows

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
14#
 楼主| 发表于 2008-11-5 23:19 | 只看该作者
<jsptaglib jspversion="1.2" destdir="${WEBINF}/tld"
             shortname="basic"  filename="myapp.tld"/>
             
Ideally I would like to set this like this:
    <dependency>
       <id>xdoclet+web-module</id>
       <version>1.2b4</version>
       <properties>
           <jsptaglib.filename>myapp.tld</jsptaglib.filename>
       </properties>
    </dependency>

This is not a Maven defect. It just is an example showing that it takes time for third party vendors to catch up.




Building the EAR
Up until now you have seen how to create each of the individual artifacts that go into the EAR. Finally we have reached the last part - building the EAR itself. You might imagine that since the EAR is the only artifact from the project as a whole, it should be built from the project definition at the top of the hierarchy, i.e. the Foobar-Travels folder. However that is not the case. The EAR is built as an artifact from a subproject called ear (See Figure 3). Why this anomaly? First of all, the project.xml at the Foobar-Travels project level is a template for other subprojects to extend. If it were to specify the dependencies to build the EAR, then it would have resulted in a cyclic dependency - a chicken and egg situation - when the template is extended by other subprojects. If the template were to be defined elsewhere, probably at the organization level, then the project.xml in Foobar-Travels folder could have produced the EAR but then it would not be the template for the rest of the subprojects to extend.

Listing 13 shows the project.xml for the ear project. In the listing, you will not see every library on which the ear is dependent upon, just a few relevant ones that need some explanation.

Listing 13 project.xml for the ear project

01 <project>
02   <extend>${basedir}/../project.xml</extend>
03   <id>foobar-travels</id>
04   <name>Foobar EAR</name>
05   <description>Sample EAR project.</description>
06   <shortDescription>Foobar EAR project</shortDescription>
07   <dependencies>
08     <dependency>
09       <groupId>j2ee</groupId>
10       <artifactId>j2ee</artifactId>
11       <version>1.3.1</version>
12     </dependency>
13     <dependency>
14       <groupId>xerces</groupId>
15       <artifactId>xerces</artifactId>
16       <version>1.4.4</version>
17       <properties>
18          <ear.bundle>true</ear.bundle>
19       </properties>
20     </dependency>
21     <dependency>
22       <groupId>${pom.groupId}</groupId>
23       <artifactId>reservationejb</artifactId>
24       <version>${pom.currentVersion}</version>
25       <type>ejb</type>
26       <properties>
27          <ear.bundle>true</ear.bundle>
28       </properties>
29     </dependency>
30     <dependency>
31       <groupId>${pom.groupId}</groupId>
32       <artifactId>foobar-web</artifactId>
33       <version>${pom.currentVersion}</version>
34       <type>war</type>
35       <properties>
36          <ear.bundle>true</ear.bundle>
37          <ear.appxml.ear.context-root>
38               foobar-online
39          </ear.appxml.ear.context-root>
40       </properties>
41     </dependency>
42   </dependencies>
43 </project>

Line 25 Type = ejb indicates that this is a ejb jar, This is set in application.xml
Line 34 Type = war indicates that this is a war, This is set in application.xml
Lines 37-39 Sets the context root for the web application, This is set in application.xml

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
15#
 楼主| 发表于 2008-11-5 23:19 | 只看该作者
The first dependency that you will find is on J2EE itself. This library is not bundled since the container at runtime provides it. The second dependency is on the xerces xml library - a representative of the dependency library (including our very own services jar). Libraries such as these may not be provided by the application server and have to be bundled with the ear. Then comes the ejb jar itself. The type=ejb sets the application.xml appropriately. Similarly, the web application is bundled by specifying the dependency and type=war. The above project definition automatically creates application.xml for the ear. By using ear:install as the goal, the ear is also copied into the maven repository. The generated application.xml is shown in Listing 14. Obviously, setting the type has had its effect. Also note the web application context-root setting. The short description from the project.xml is used as the display name for the EAR. When Maven starts the execution of project.xml for the EAR it sees the dependencies and then proceeds to create the artifacts for the dependencies before creating the EAR itself.

Listing 14 Generated application.xml

<application>
  <display-name>Foobar EAR project</display-name>
  <module>
    <java>xerces-logging-1.4.4.jar</java>
  </module>
  <module>
    <ejb>reservationejb-2.0.jar</ejb>
  </module>
  <module>
    <web>
      <web-uri>foobar-web-2.0.war</web-uri>
      <context-root>foobar-online</context-root>
    </web>
  </module>
</application>

We haven't looked at the maven.xml for the EAR project yet. It turns out to be quite trivial in that it just executes the ear:install goal.

<project default="foobar-dist">
    <goal name="foobar-dist">
       <attainGoal="ear:install"/>
    </goal>
</project>

Another file that you need to provide is project.properties. By default auto-generation of application.xml is turned off. To force auto-generation, you can set the property in project.properties as follows:

maven.ear.appxml.generate=true

and place the file in the ear folder under Foobar-Travels directory.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
16#
 楼主| 发表于 2008-11-5 23:20 | 只看该作者
Using the reactor
We have covered all the files in building a J2EE project, except one. This file that we have been intentionally postponing till the end is the maven.xml that accompanies the master project template. In the last subsection on building EAR, I stated "When Maven starts the execution of project.xml for the EAR it sees the dependencies and then proceeds to create the artifacts for the dependencies before creating the EAR itself". I lied! This is not completely true. The project definition for EAR is just like any other project.xml. For every dependency stated in that file, it will search the maven repository for the appropriate jar file. What this means is that you will have to run maven individually from dependency library projects, ejb projects, web application projects and finally the ear project in that order to execute jar:install, ejb:install, war:install and ear:install respectively. Now, that can be too cumbersome if not impossible in a large project. Maven offers the reactor as a solution to this problem.

Reactor is a tool for executing dependent multi-project builds. Given a set of project.xmls, the reactor determines the correct order of execution based on the dependencies listed in the respective project.xmls. More news: The reactor can be declared using Jelly scripts in the maven.xml file itself. This is what we will have in the maven.xml accompanying the master project template. The maven.xml in the Foobar-Travels folder (See Figure 3) is shown in Listing 15.

Listing 15 maven.xml using the reactor

<project default="foobar-buildall" xmlns:m="jelly:maven">
<goal name="foobar-buildall">
    <m:reactor basedir="${basedir}"
               includes="*/project.xml"
               goals="foobar-dist"
               banner="Building"
               ignoreFailures="false"/>
</goal>
</project>

The foobar-buildall goal is written as a reactor. The script for foobar-buildall in Listing 15 translates to simple English as "Starting from the base directory from where maven is executed, go to every subfolder and execute the goal identified by foobar-dist in every project.xml and stop on failure". When you go to the base directory (Foobar-Travels) and type in the command maven (since foobar-buildall is the default goal), the reactor figures out the dependency by reading all the project.xmls in the subdirectories and creates the artifacts in the required order.



Conclusion
This article has explained how to use Maven effectively in a J2EE project instead of plain old Ant scripts and bring about some order and modularity to the otherwise chaotic world of build and deployment. Maven is something that you should consider when looking for options to build and deploy in your project. But I've have only scratched the surface of what constitutes Maven. I hope this article has given you the confidence to build your J2EE projects with Maven and sparked your curiosity to learn more about it.

使用道具 举报

回复
论坛徽章:
43
ITPUB元老
日期:2007-01-14 09:32:112011新春纪念徽章
日期:2011-01-25 15:42:332011新春纪念徽章
日期:2011-01-25 15:42:56管理团队成员
日期:2011-05-07 01:45:08开发板块每日发贴之星
日期:2011-08-29 01:01:012012新春纪念徽章
日期:2012-02-13 15:11:182012新春纪念徽章
日期:2012-02-13 15:11:182012新春纪念徽章
日期:2012-02-13 15:11:182012新春纪念徽章
日期:2012-02-13 15:11:182012新春纪念徽章
日期:2012-02-13 15:11:18
17#
发表于 2009-6-18 04:24 | 只看该作者

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表