53、生产部署与 Ant 任务扩展全解析

生产部署与 Ant 任务扩展全解析

1. 生产部署

在生产环境中部署应用程序时,不同的应用服务器有着各自独特的部署流程。下面为你详细介绍几种常见服务器的部署方法。

1.1 BEA WebLogic

<serverdeploy> 元素内部存在一个 <weblogic> 元素,该元素要求在类路径中包含 weblogic.jar 文件,你可以借助 classpath 属性来达成这一要求。示例代码如下:

<serverdeploy 
    action="deploy"
    source="${webapp.path}">
  <weblogic 
    application="${target.appname}"
    component="webapp:${target.server}"
    server="t3://${target.server}:7001"
    username="${target.username}"
    password="${target.password}"
    classpath="${env.WEBLOGIC_HOME}/lib/weblogic.jar"
    />
</serverdeploy>

需要注意的是,WebLogic 7.0 自带了一份 Ant,建议你对其 ant.bat ant.sh 进行重命名,防止意外使用该版本。当路径中存在多个版本的 Ant 脚本时,容易造成混淆,可能会意外使用较旧版本的 Ant,并且可能无法将可选库添加到适当的目录。

1.2 HP Bluestone 应用服务器

此应用服务器自带了部署任务,这或许是未来所有应用服务器的发展趋势。 <hpas-deploy> 任务能够将 WAR 或 EAR 文件上传至正在运行的 HP - AS 应用服务器实例,并使用作为属性提供的账户和密码对请求进行授权。示例代码如下:

<taskdef name="hpas-deploy"                                  
    classname="com.hp.mwlabs.tools.pacman.ant.HPASDeploy" />
<target name="deploy" depends="init"
    description="Deploy to HP-AS server">

  <hpas-deploy                                                
    host="${target.server}"
    uri="${target.appname}"
    port="2000"
    username="${target.username}"
    password="${target.password}"
    jarfile="${webapp.path}">
  </hpas-deploy>
</target>

你还可以在任务内部使用 <fileset> 指定要上传的一组文件,不过此时就不能再指定部署路径了,工具会使用文件集中每个文件的名称。显然,单文件部署更为灵活。

然而,该任务存在一个重大缺陷,它无法在普通的 Ant 执行环境中运行,仅能在供应商的 RadPak Ant GUI 工具中使用,目前尚不清楚具体原因,这会阻碍你通过该任务进行自动化构建和部署。

1.3 其他服务器

还有众多应用服务器,它们各自拥有独特的部署流程,但缺乏明确的 Ant 支持。部署到这些服务器的一般步骤如下:
1. 查看服务器的文档和示例部署脚本。
2. 在 Ant 中复制这些步骤。
- 基于 URL 的管理器应用程序可使用 <get> 请求。
- 辅助程序可通过 <java> <exec> 调用。
- 支持热部署的服务器可使用 <copy> 调用。

批处理文件通常是非常有价值的信息来源,因为它们展示了调用基于 Java 的程序所需的类路径和参数,你可以在应用程序中用单个 <java> 调用替换每个此类脚本文件。一般来说,编写一个可用于新服务器类型的构建文件可能需要一到两天的时间,但编写完成后就可以反复使用。

2. 验证部署

在部署过程中,即便各个组件都能提供帮助,但整体仍可能面临诸多挑战。为了确保部署成功,我们可以采用以下方法。

2.1 创建时间戳文件

为每个构建创建一个时间戳文件,并将其包含在 Web 应用程序中。Ant 可以将本地时间戳与刚部署的应用程序提供的副本进行比较,如果不同则构建失败。创建时间戳文件的示例代码如下:

<property name="timestamp.filename"
  value="timestamp.txt"/>

<property name="timestamp.path"
  location="${build.dir}/${timestamp.filename}"/> 

<target name="make-timestamp" depends="init" >
  <tstamp>
    <format property="buildtime"
      pattern="yyyy-MM-dd'T'HH:mm:ss" />
  </tstamp>
  <echo file="${timestamp.path}"
    message="build.timestamp=${buildtime}" />
</target>
2.2 将时间戳文件添加到应用程序

为了将时间戳文件包含在应用程序中,我们需要在 <war> 任务中添加另一个文件集,并为目标添加新的依赖项。示例代码如下:

<target name="make-war"
  depends="compile,make-webxml,make-web-docs,make-timestamp,make-soap-api">
  <war destfile="${warfile}"
    compress="false"
    update="true"
    webxml="${build.webinf.dir}/web.xml">
    <classes dir="${build.classes.dir}"/>
    <webinf dir="${build.dir}" includes="index/**"/>
    <webinf dir="${struts.dir}/lib" includes="*.tld,*.dtd"/>
    <webinf dir="${build.webinf.dir}" includes="antbook.tld"/>
    <fileset dir="${build.dir}" includes="${timestamp.filename}"/>
    <fileset dir="web"/>
     ...
  </war>
</target>
2.3 验证时间戳

以下是一个用于验证部署是否成功的目标,Ant 会在基于 <telnet> 的远程部署返回后执行该目标。示例代码如下:

<target name="verify-uptodate"
    depends="install" >
  <property name="verify.url" 
    value="${test.url}/${timestamp.filename}" />
  <property name="verify.local.path"
    location="${dist.dir}/deployed-on-${target.server}.txt" 
    />  
  <waitfor timeoutproperty="deployment.failed"         
    maxwait="30"                                       
    maxwaitunit="second">                              
    <http url="${verify.url}" />                       
  </waitfor>
  <fail if="deployment.failed">
    timestamp page not found at ${verify.url}"
  </fail>

  <get src="${verify.url}"                              
    dest="${verify.local.path}" />                      

  <condition property="verify.uptodate.successful">     
    <filesmatch                                         
      file1="${timestamp.path}"                         
      file2="${verify.local.path}"                      
      />                                                
  </condition>                                          

  <loadfile property="verify.expected"
    srcFile="${timestamp.path}" />
  <loadfile property="verify.found"
    srcFile="${verify.local.path}" />
  <fail unless="verify.uptodate.successful">
    file match failed; 
      expected [${verify.expected}]
      found    [${verify.found}] 
</target>

该目标主要分为三个阶段:
1. 创建远程时间戳的 URL,并使用 <waitfor> 等待文件出现,以便让服务器有时间重新加载应用程序。
2. 如果文件存在,使用 <get> 任务将其检索并保存到本地文件。
3. 使用 <condition> 测试比较本地时间戳文件和下载的文件,如果不相等则构建失败,并给出详细的错误信息。

3. 最佳实践

在生产部署方面,有两个核心实践需要遵循:

3.1 严谨性

设计构建文件时要避免走捷径,不要对系统做过多假设,例如应用程序的安装位置。同时,要包含大量测试。
- 功能测试:用于验证程序是否正常工作。
- 配置测试:用于检查系统配置是否正确。如果功能测试失败,可能是程序或系统的问题;如果配置测试失败,则是系统的问题。测试有助于快速定位问题,从而更轻松地解决问题。

3.2 与运维协作

从运维的角度来看,理想的服务器应该是运行良好,以至于他们忘记其位置或登录方式。可以将运维需求视为用例,将遇到的问题视为缺陷进行记录、跟踪、测试和修复。

Ant 提供了统一的部署和测试系统,但尽量减少应用服务器的数量(最好为一个)会非常有帮助。

4. Ant 任务扩展

当 Ant 的内置功能无法满足需求时,你可以通过编写新任务或修改现有任务来扩展和定制 Ant。

4.1 什么是 Ant 任务

一个 Java 类成为 Ant 任务的定义很简单,它必须有一个 execute() 方法。以下是一个最简单的 Ant 任务示例:

package org.example.antbook.tasks;
public class SimpleTask {

    public void execute() {
        System.out.println(">>>> SimpleTask <<<<");
    }

}

该类不继承任何基类(隐式继承 java.lang.Object ),只有一个 execute 方法。 execute 方法必须是公共的,且不接受任何参数,类必须能够通过无参数构造函数实例化(即类不能是抽象的)。 execute 方法可以有返回值,但会被忽略,并且在定义任务时会生成警告。如果 execute 方法抛出异常,构建将相应地失败。

需要注意的是,在任务执行期间向 System.out System.err 写入内容是允许的,但 Ant 会捕获输出并将其记录到适当的日志级别。建议你扩展 org.apache.tools.ant.Task 并使用提供的日志记录方法。

4.2 在同一构建中编译和使用任务

以下是一个在同一构建中编译和使用任务的构建文件示例:

<?xml version="1.0" ?>
<project name="tasks" default="main">
  <property name="build.dir" location="build"/>
  <target name="init">
    <mkdir dir="${build.dir}"/>
  </target>
  <target name="compile" depends="init">
    <javac srcdir="src" destdir="${build.dir}"/>   
  </target>
  <target name="simpletask" depends="compile">
    <taskdef name="simpletask"                                
             classname="org.example.antbook.tasks.SimpleTask" 
             classpath="${build.dir}"                         
    />                                                        

    <simpletask/>   
  </target>
  <target name="clean">
    <delete dir="${build.dir}"/>
  </target>

  <target name="main" depends="simpletask"/>

</project>

该构建文件的输出如下:

simpletask:
[simpletask] >>>> SimpleTask <<<<

关键在于在执行任务之前使用 <taskdef> ,但要在编译之后。当将第三方任务集成到构建文件中时,你可以在目标之外指定 <taskdef> ,以便任务在该构建文件中全局定义。

4.3 任务生命周期

Ant 从 XML 任务声明到 Java 类的映射是一种非正式的数据绑定。在处理构建文件时,存在不同的阶段,实现任务的对象会在这些阶段中被使用。

综上所述,通过合理运用 Ant 进行部署和验证,以及必要时扩展 Ant 任务,能够有效应对生产部署中的各种挑战,确保应用程序的顺利部署和运行。

5. Ant API 基础及任务数据获取
5.1 Ant API 基础

Ant 的核心引擎拥有相对复杂的内省机制,这使得任务能够以多种灵活的方式接入。在任务执行过程中,日志记录是一个重要的环节。例如,在任务执行时向 System.out System.err 写入内容,Ant 会捕获这些输出并将其记录到相应的日志级别, System.out 使用 MSG_INFO 级别, System.err 使用 MSG_ERR 级别。不过,更推荐扩展 org.apache.tools.ant.Task 并使用其提供的日志记录方法。

5.2 任务获取数据的方式

任务获取数据主要通过属性和元素映射到 Java 方法。以下是一个简单的示例,展示如何定义一个带有属性的任务:

package org.example.antbook.tasks;
import org.apache.tools.ant.Task;

public class AttributeTask extends Task {
    private String message;

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public void execute() {
        if (message != null) {
            log("Received message: " + message);
        } else {
            log("No message provided.");
        }
    }
}

在 Ant 构建文件中使用该任务的示例如下:

<project name="attributeTaskExample" default="main">
    <taskdef name="attributeTask" classname="org.example.antbook.tasks.AttributeTask" classpath="path/to/your/classes"/>
    <target name="main">
        <attributeTask message="Hello, Ant!"/>
    </target>
</project>

在这个示例中, AttributeTask 类通过 setMessage 方法接收 message 属性的值,并在 execute 方法中进行相应的处理。

6. 创建基本 Ant 任务子类及操作文件集
6.1 创建基本 Ant 任务子类

创建基本的 Ant 任务子类通常需要继承 org.apache.tools.ant.Task 类,并实现 execute 方法。以下是一个示例:

package org.example.antbook.tasks;
import org.apache.tools.ant.Task;

public class BasicTask extends Task {
    @Override
    public void execute() {
        log("Executing basic Ant task.");
    }
}

在 Ant 构建文件中使用该任务的示例如下:

<project name="basicTaskExample" default="main">
    <taskdef name="basicTask" classname="org.example.antbook.tasks.BasicTask" classpath="path/to/your/classes"/>
    <target name="main">
        <basicTask/>
    </target>
</project>
6.2 操作文件集

Ant 任务经常需要操作文件集。以下是一个操作文件集的示例任务:

package org.example.antbook.tasks;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;

import java.io.File;

public class FileSetTask extends Task {
    private FileSet fileSet;

    public void addFileset(FileSet fileSet) {
        this.fileSet = fileSet;
    }

    @Override
    public void execute() throws BuildException {
        if (fileSet == null) {
            throw new BuildException("No fileset specified.");
        }
        DirectoryScanner scanner = fileSet.getDirectoryScanner(getProject());
        File basedir = scanner.getBasedir();
        String[] files = scanner.getIncludedFiles();
        for (String file : files) {
            log("Processing file: " + new File(basedir, file).getPath());
        }
    }
}

在 Ant 构建文件中使用该任务的示例如下:

<project name="fileSetTaskExample" default="main">
    <taskdef name="fileSetTask" classname="org.example.antbook.tasks.FileSetTask" classpath="path/to/your/classes"/>
    <target name="main">
        <fileSetTask>
            <fileset dir="your/directory" includes="*.txt"/>
        </fileSetTask>
    </target>
</project>

在这个示例中, FileSetTask 任务通过 addFileset 方法接收文件集,并在 execute 方法中遍历文件集中的文件。

7. 错误处理、测试及执行外部程序
7.1 错误处理

在 Ant 任务中,错误处理非常重要。当任务执行过程中出现异常时,应该抛出 BuildException 以终止构建。以下是一个简单的错误处理示例:

package org.example.antbook.tasks;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;

public class ErrorHandlingTask extends Task {
    @Override
    public void execute() throws BuildException {
        try {
            // 模拟可能出现异常的操作
            int result = 1 / 0;
        } catch (ArithmeticException e) {
            throw new BuildException("An arithmetic error occurred: " + e.getMessage(), e);
        }
    }
}

在 Ant 构建文件中使用该任务时,如果出现异常,构建将失败并输出相应的错误信息。

7.2 测试 Ant 任务

测试 Ant 任务可以使用 JUnit 等测试框架。以下是一个简单的测试示例:

import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Echo;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class AntTaskTest {
    @Test
    public void testEchoTask() {
        Project project = new Project();
        project.init();
        Echo echo = new Echo();
        echo.setProject(project);
        echo.setMessage("Test message");
        echo.execute();
        // 这里可以根据实际情况添加更多的断言来验证任务的执行结果
    }
}
7.3 执行外部程序

Ant 任务可以通过 <exec> <java> 等方式执行外部程序。以下是一个使用 <exec> 执行外部程序的示例:

<project name="execExample" default="main">
    <target name="main">
        <exec executable="ls">
            <arg value="-l"/>
        </exec>
    </target>
</project>

在这个示例中, exec 任务执行 ls -l 命令。

8. 执行 Java 程序及支持任意命名元素和属性
8.1 在任务中执行 Java 程序

在 Ant 任务中可以使用 <java> 任务执行 Java 程序。以下是一个示例:

<project name="javaExecutionExample" default="main">
    <target name="main">
        <java classname="com.example.MyJavaClass" fork="true">
            <classpath>
                <pathelement location="path/to/your/classes"/>
            </classpath>
        </java>
    </target>
</project>

在这个示例中, java 任务执行 com.example.MyJavaClass 类。

8.2 支持任意命名元素和属性

Ant 任务可以支持任意命名的元素和属性。以下是一个示例:

package org.example.antbook.tasks;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.HashMap;
import java.util.Map;

public class ArbitraryElementTask extends Task {
    private Map<String, String> attributes = new HashMap<>();

    public void addConfigured(Element element) {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            org.w3c.dom.Document doc = builder.newDocument();
            org.w3c.dom.Element importedElement = doc.importNode(element, true);
            for (int i = 0; i < importedElement.getAttributes().getLength(); i++) {
                org.w3c.dom.Node attr = importedElement.getAttributes().item(i);
                attributes.put(attr.getNodeName(), attr.getNodeValue());
            }
        } catch (Exception e) {
            throw new BuildException("Error processing element: " + e.getMessage(), e);
        }
    }

    @Override
    public void execute() throws BuildException {
        for (Map.Entry<String, String> entry : attributes.entrySet()) {
            log("Attribute: " + entry.getKey() + ", Value: " + entry.getValue());
        }
    }
}

在 Ant 构建文件中使用该任务的示例如下:

<project name="arbitraryElementExample" default="main">
    <taskdef name="arbitraryElementTask" classname="org.example.antbook.tasks.ArbitraryElementTask" classpath="path/to/your/classes"/>
    <target name="main">
        <arbitraryElementTask>
            <customElement attr1="value1" attr2="value2"/>
        </arbitraryElementTask>
    </target>
</project>

在这个示例中, ArbitraryElementTask 任务可以处理任意命名的元素和属性。

9. 构建任务库及支持多版本 Ant
9.1 构建任务库

构建任务库可以将多个相关的 Ant 任务组织在一起,方便复用。以下是一个简单的任务库示例:

<project name="taskLibrary" default="main">
    <taskdef resource="org/example/antbook/tasks/taskdefs.properties"/>
    <target name="main">
        <simpleTask/>
        <attributeTask message="Hello from task library!"/>
    </target>
</project>

其中, taskdefs.properties 文件内容如下:

simpleTask=org.example.antbook.tasks.SimpleTask
attributeTask=org.example.antbook.tasks.AttributeTask
9.2 支持多版本 Ant

为了确保任务在不同版本的 Ant 中都能正常工作,需要注意一些兼容性问题。例如,避免使用特定版本 Ant 才有的特性,或者在代码中进行版本检查。以下是一个简单的版本检查示例:

package org.example.antbook.tasks;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;

public class VersionCheckTask extends Task {
    @Override
    public void execute() {
        String antVersion = getProject().getProperty(Project.ANT_VERSION);
        log("Ant version: " + antVersion);
        if (antVersion.startsWith("1.9")) {
            log("This task is compatible with Ant 1.9.x.");
        } else {
            log("Compatibility with this Ant version is not guaranteed.");
        }
    }
}

在 Ant 构建文件中使用该任务的示例如下:

<project name="versionCheckExample" default="main">
    <taskdef name="versionCheckTask" classname="org.example.antbook.tasks.VersionCheckTask" classpath="path/to/your/classes"/>
    <target name="main">
        <versionCheckTask/>
    </target>
</project>
10. 总结

通过以上内容,我们详细介绍了生产部署的各种方法,包括不同应用服务器的部署、部署验证、最佳实践,以及 Ant 任务的扩展和定制。在生产部署中,要严谨设计构建文件,与运维密切协作,同时利用 Ant 的统一部署和测试系统。当 Ant 内置功能不足时,可以通过编写新任务或修改现有任务来满足需求。掌握 Ant 任务的创建、数据获取、错误处理、测试等技能,能够帮助我们更好地应对复杂的构建和部署场景,确保应用程序的顺利运行。

下面是一个简单的 mermaid 流程图,展示生产部署和验证的主要流程:

graph LR
    A[开始部署] --> B[选择服务器类型]
    B --> C{是否有 Ant 支持}
    C -- 是 --> D[使用 Ant 部署]
    C -- 否 --> E[参考文档手动部署]
    D --> F[创建时间戳文件]
    E --> F
    F --> G[将时间戳文件添加到应用]
    G --> H[部署应用]
    H --> I[验证时间戳]
    I --> J{验证是否通过}
    J -- 是 --> K[部署成功]
    J -- 否 --> L[部署失败,检查问题]

总之,合理运用 Ant 进行生产部署和任务扩展,能够提高开发和运维的效率,降低部署风险。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值