[09/51-52周主题] – Java Net and XML parser

本周主题:Java Net and XML Parser

分两部分:一部Java Net(大部分来自Java Doc), 一部分XML Parser, 通过一个Twitter的例子来描述简单的使用情况。

一、Java Net

java.net 包的概述:

java.net包大致可分为两部分:

低级API – 用于处理以下抽象:

  • 地址,也就是网络标识符,如 IP 地址。
  • 套接字,也就是基本双向数据通信机制。
  • 接口,用于描述网络接口。

高级API – 用于处理以下抽象:

  • URI,表示统一资源标识符。
  • URL,表示统一资源定位符。
  • 连接,表示到 URL 所指向资源的连接。

地址

在整个 java.net API 中,地址或者用作主机标识符或者用作套接字端点标识符。

InetAddress 类是表示 IP(Internet 协议)地址的抽象,它拥有两个子类:

但是,在大多数情况下,不必直接处理子类,因为 InetAddress 抽象应该覆盖大多数必需的功能。

套接字

套接字是在网络上建立机器之间的通信链接的方法。java.net 包提供 4 种套接字:

  • Socket 是 TCP 客户端 API,通常用于将 (java.net.Socket.connect(SocketAddress)) 连接到远程主机。
  • ServerSocket 是 TCP 服务器 API,通常接受 (java.net.ServerSocket.accept) 源于客户端套接字的连接。
  • DatagramSocket 是 UDP 端点 API,用于发送和接收 java.net.DatagramPackets
  • MulticastSocket 是 DatagramSocket 的子类,在处理多播组时使用。

使用 TCP 套接字的发送和接收操作需要借助 InputStream 和 OutputStream 来完成,这两者是通过 java.net.Socket.getInputStream 和 java.net.Socket.getOutputStream 方法获取的。

高级API

java.net 包中的许多类可以提供更加高级的抽象,允许方便地访问网络上的资源。这些类为:

  • URI 是表示在 RFC 2396 中指定的统一资料标识符的类。顾名思义,它只是一个标识符,不直接提供访问资源的方法。
  • URL 是表示统一资源定位符的类,它既是 URI 的旧式概念又是访问资源的方法。
  • URLConnection 是根据 URL 创建的,是用于访问 URL 所指向资源的通信链接。此抽象类将大多数工作委托给底层协议处理程序,如 http 或 ftp。
  • HttpURLConnection 是 URLConnection 的子类,提供一些特定于 HTTP 协议的附加功能。

建议的用法是使用 URI 指定资源,然后在访问资源时将其转换为 URL。从该 URL 可以获取 URLConnection 以进行良好控制,也可以直接获取 InputStream。

下面是一个示例:

URI uri = new URI("http://java.sun.com/");
URL url = uri.toURL();
InputStream in = url.openStream();

URLConnection 和 HttpURLConnection 的使用:

抽象类 URLConnection 是所有表示应用程序与 URL 之间通信链路的类的超类。该类的实例可以用来对由 URL 引用的资源进行读取和写入操作

HttpURLConnection 仅是支持了 HTTP 特定功能的 URLConnection

  1. 通过调用 URL 的 openConnection 方法产生一连接对象。
  2. 操纵设置参数和一般请求属性。
  3. 使用 connect 方法,实现对远程对象的实际连接。
  4. 远程对象一旦可用,就可以访问远程对象的域和内容。

通过如下的方法修改设置参数:

  • setAllowUserInteraction
  • setDoInput
  • setDoOutput
  • setIfModifiedSince
  • setUseCaches

通过使用如下的方法修改一般请求属性:

  • setRequestProperty

AllowUserInteractionUseCaches 参数的缺省值可以通过使用 setDefaultAllowUserInteractionsetDefaultUseCaches 方法来设置。 而一般请求属性的缺省值可以通过方法 setDefaultRequestProperty 来设置 。

上面的每个 set 方法都有相应 get 方法来获取参数和一般请求属性的值。可用的特定参数和一般请求属性是特定于协议的。

对远程对象的连接完成后,用下列方法访问报头域和内容:

  • getContent
  • getHeaderField
  • getInputStream
  • getOutputStream

确认报头域能够被频繁访问。方法:

  • getContentEncoding
  • getContentLength
  • getContentType
  • getDate
  • getExpiration
  • getLastModified

提供对这些域的便利访问。 getContentType 方法由 getContent 方法调用以确定远程对象的类型; 子类来覆盖方法 getContentType 可能是有利的。

下面是一个通过Twitter API去获信息的demo:

        URL url = new URL("http://t.bbercn.com/statuses/user_timeline.xml");
        String username = (String) params[0];
        String password = (String) params[1];
        String user_paw = username + ":" + password;
        String encoding = "Basic " + Base64Converter.encode(user_paw.getBytes());
        URLConnection uc = url.openConnection();
        uc.setRequestProperty("Authorization", encoding);
        InputStream content = (InputStream) uc.getInputStream();

剩下的工作就是对InputStream 进行本地处理,Twitter返回的是XML,那么我们下面就对XML进行解析,获取我们要获取的信息。

XML Parser

关于 xml 解析的工具有很多,具体可以看这里:http://www.open-open.com/31.htm 罗列的一些。

下面通过Commons-Digester来处理上面我们获取的InputStream

public class ParseTools {

    private List statuses = new ArrayList<Status>();

    public List<Status> getStatuses() {
        Collections.sort(statuses);
        return statuses;
    }

    public void parseStatus(InputStream stream) throws IOException, SAXException {
        Digester digester = new Digester();
        digester.setValidating(false);
        digester.push(this);

        digester.addObjectCreate("statuses/status", Status.class);
        //status
        digester.addBeanPropertySetter("statuses/status/id");
        digester.addBeanPropertySetter("statuses/status/created_at");
        digester.addBeanPropertySetter("statuses/status/text");
        digester.addBeanPropertySetter("statuses/status/source");
        digester.addBeanPropertySetter("statuses/status/truncated");
        digester.addBeanPropertySetter("statuses/status/in_reply_to_status_id");
        digester.addBeanPropertySetter("statuses/status/in_reply_to_user_id");
        digester.addBeanPropertySetter("statuses/status/favorited");
        digester.addBeanPropertySetter("statuses/status/in_reply_to_screen_name");
        //user
        digester.addObjectCreate("statuses/status/user", User.class);
        digester.addBeanPropertySetter("statuses/status/user/id");
        digester.addBeanPropertySetter("statuses/status/user/name");
        digester.addBeanPropertySetter("statuses/status/user/screen_name");
        digester.addBeanPropertySetter("statuses/status/user/description");
        digester.addBeanPropertySetter("statuses/status/user/location");
        digester.addBeanPropertySetter("statuses/status/user/profile_image_url");
        digester.addBeanPropertySetter("statuses/status/user/url");
        digester.addBeanPropertySetter("statuses/status/user/isProtected");
        digester.addBeanPropertySetter("statuses/status/user/followers_count");
        digester.addBeanPropertySetter("statuses/status/user/friends_count");
        digester.addSetNext("statuses/status/user", "setUser");
        digester.addSetNext("statuses/status", "addStatuses", Status.class.getName());
        digester.parse(stream);
    }

    public void addStatuses(Status status) {
        Status d = new Status();
        d.setId(status.getId());
        d.setCreated_at(status.getCreated_at());
        d.setFavorited(status.isFavorited());
        d.setText(status.getText());
        d.setSource(status.getSource());
        d.setTruncated(status.isTruncated());
        d.setIn_reply_to_screen_name(status.getIn_reply_to_screen_name());
        d.setIn_reply_to_status_id(status.getIn_reply_to_status_id());
        d.setIn_reply_to_user_id(status.getIn_reply_to_user_id());
        d.setUser(status.getUser());
        getStatuses().add(d);
    }
}

[09/50周主题] – JQuery入门

三赶鸭子上架的组织了一次JQuery入门培训,由于时间上的原因,自己没做Demo,拿官方现成的,主要是认识JQuery,了解JQuery核心函数,介绍一些常用的选择器,还有事件处理,以及Ajax的使用,这里简单分享下:

本周主题:JQuery

一、JQuery核心认识

$ 表示JQuery类,$(…) 构造一个JQuery类,这里的JQuery类是一个集合数组对象,而非Dom对象。

jQuery(expression,[context])

$("div > p");
$("input:radio", document.forms[0]);
$("div", xml.responseXML);

jQuery( html )

$("<div>Hello</div>").appendTo("body");

主要用来加载html

jQuery( elements )

$(document.body).css("background-color", "black");
$(myForm.elements).hide();

jQuery( callback )

$(function(){
  alert('hello');
});

JQuey中执行方法是在页面加载结束后,官方解释是:A shorthand for $(document).ready().

二、JQuery选择器

JQuery选择器是JQuery的一大亮点,快捷,高效的遍历整个HTML文档,常用的如下面几种:

$("#id");
$("div");
$(".className");
$("div,span,p.myClass")
$("form input")
$("form > input")

强烈建议直接看官方文档:http://docs.jquery.com/Selectors

三、JQuery事件

$("p").bind("click", function(e){});
$("p").click(function() {})
trigger( event, data ) //触发事件
triggerHandler( event, data ) //触发事件,但不触发浏览器的默认事件
$("p").live("click", function(){ //在p以及p的下面的子elements上都加上click事件
      $(this).after("<p>Another paragraph!</p>");
});

更多,参见官方文档:http://docs.jquery.com/Events

四、JQuery Ajax

ajax这块感觉和其他框架没多少差别,用起来基本类似:
1、$.post(…) 和 $.get(…)

$.post('save.cgi', {
    text: 'my string',
    number: 23
}, function() {
    alert('Your data has been saved.');
});

2、$.ajax(…)

$.ajax({
    url: 'getdata.action',
    type: 'GET',
    dataType: 'html',
    Timeout: 1000,
    beforeSend: function(){
     // Handle the beforeSend event
   },
    success: function(html){
     // do something with html
   },
    complete: function(){
     // Handle the complete event
   },
    error: function(){
     alert('Error loading HTML document');
    },
});

五、推荐阅读

1、http://jquery.com/
2、jQuery 1.3 API 中文参考文档
3、http://jqueryui.com/
4、http://jquery.org.cn/
5、有关Ajax的一个中文文章

周主题推迟一周

于最近另一个项目的启动,工作方面基本上是掐着时间在忙,所以周主题的任务就只能稍微往后推一周了。

预计本周四结合公司的培训内容,出一个有关JQuery的入门介绍,这个内容顺便就补给上周的周主题。

顺便预告下:接下来这周和下周会整理有关Java.Net包下一些常用类的使用,包括HttpClient的使用,以及XML Parser方面的汇总。

通过Java实现你的twitter应用

关于Twitter这里就不多介绍了, 基本上能看到这篇文章的都用过的, 当然不知道也没关系, Google一下.由于Twitter的API 开放性, 所以其应用现在可以用多如牛毛也描述了, 各种各样的, 本文基于Java, 通过twitter API来获取twitter消息, 并以PDF的形式进行归档.准备工具:

1. HttpClient 3.x 库文件, 通过此库来从Twitter API获取消息, 同时需要 Comomons LoggingCodec 两个库文件. 请自行下载.

2. dom4j 库文件, 通过此库来解析Twitter的消息XML和获取中间的数据.

3. iText 库文件, 用来创建PDF文档.


每个Twitter应用都是基于twitter API来实现的, 有关Twitter API的使用可以看这里: Twitter API wiki(官方的,需要翻墙), 这里我们假设你只对获取数据感兴趣, 同时想以某种方式显示他们, 至于其他功能, 相信通过本教程的介绍, 你可以自己去钻研了.从TweetConsumer开始:

import org.dom4j.Element;
public interface TweetConsumer {
    public String tweet(Element element) throws TweetException;
}

Twitter API提供XML, JSON 和RSS几种格式, 这里我们选择XML格式, 通过dom4j来解析, 上面是一个叫做TweetConsumer的借口, 作用就是来处理消息的, 其实现在后面会讲到.

自定义一个消息异常, 当出现问题时抛出:

public class TweetException extends Exception {
    private static final long serialVersionUID = 7577136074623618615L;
    public TweetException(Exception e) {
        super(e);
    }
}

接下来是本文的重点, 通过Twitter API去获取消息:

public class TweetProducer {
    protected HttpClient client;
    protected TweetConsumer consumer;

    public void createClient(String username, String password) {
        client = new HttpClient();
        client.getState().setCredentials(
            //由于GFW的原因, 直连twitter肯定是不行了, 建议使用第三方API,比如我的GAE应用:xiao-quan.appspot.com
            new AuthScope("twitter.com", 80, AuthScope.ANY_REALM),
            new UsernamePasswordCredentials(username, password));
        client.getParams().setAuthenticationPreemptive(true);
    }

    public void setConsumer(TweetConsumer consumer) {
        this.consumer = consumer;
    }

    public String execute(String since_id) throws TweetException {
        String id = null;
        String tmp;
        //由于GFW的原因, 直连twitter肯定是不行了, 建议使用第三方API,比如我的GAE应用:http://xiao-quan.appspot.com/api/statuses/friends_timeline.xml
        GetMethod get = new GetMethod("http://twitter.com/statuses/friends_timeline.xml");
        if (since_id != null &amp;&amp; since_id.length() > 0) {
            get.setQueryString("?count=200&amp;since_id=" + since_id);
        }
        else {
            get.setQueryString("?count=200");
        }
        get.setDoAuthentication(true);
        try {
            client.executeMethod(get);
            SAXReader reader = new SAXReader();
            Document document = reader.read(get.getResponseBodyAsStream());
            Element root = document.getRootElement();
            for (Iterator i = root.elementIterator(); i.hasNext(); ) {
                tmp = consumer.tweet((Element)i.next());
                if (id == null) id = tmp;
            }
        } catch (HttpException e) {
            throw new TweetException(e);
        } catch (IOException e) {
            throw new TweetException(e);
        } catch (DocumentException e) {
            throw new TweetException(e);
        } finally {
            get.releaseConnection();
        }
        return id;
    }
}

TweetProducer 里面有三个方法:
createClient(), 创建HttpClient对象, 通过HttpClient来连接Twitter API, 由于Twitter使用了HTTP Basic Authentication,
所以你需要提供你的twitter用户名和密码来登录, 获取你的朋友们的消息.
setConsumer(), 传一个TweetConsumer接口给TweetProducer, 我们有多种输出方案, 当然这里要通用点.
excute(), 这是才是做事情的地方, 获取哪个消息ID以后的消息, 返回一个消息ID.

获取消息:

GetMethod get = new GetMethod("http://twitter.com/statuses/friends_timeline.xml");

Twitter API默认获取的消息条数是20, 最大200, 你可以自己定义获取的数量, 同时也可以通过一些参数来指定获取第几页的消息等, 具体使用参考Twitter API WIKI上的定义.

例如, 获取消息ID为since_id以后的200个消息:

get.setQueryString("?count=200&since_id=" + since_id);

设置完查询条件后, 执行:

client.executeMethod(get);

将返回的信息传给SAXReader,以便获取消息root:

SAXReader reader = new SAXReader();
Document document = reader.read(get.getResponseBodyAsStream());
Element root = document.getRootElement();

最后迭代每个带 标签的Element:

for (Iterator i = root.elementIterator(); i.hasNext(); ) {
    tmp = consumer.tweet((Element)i.next());
    if (id == null) id = tmp;
}

由于Twitter的消息列表是按时间降序排列, 那么第一个消息就是最新的消息了, 这个消息ID就是excute()要返回的String.

获取到消息后, 接下来我们就输出消息:

import org.dom4j.Element;

public class SystemOutTweets implements TweetConsumer {
    public String tweet(Element tweet) {
        System.out.println(tweet.asXML());
        return tweet.elementText("id");
    }

    public static void main(String[] args) throws TweetException {
        TweetProducer t = new TweetProducer();
        t.createClient("gavinquan", "123456");
        t.setConsumer(new SystemOutTweets());
        System.out.println("Start tweeting:");
        String id = t.execute(null);
        System.out.println("The End; last tweet: " + id);
    }
}

将上面的介绍的代码整理好, 并执行, 就会在控制台输出, XML格式的消息列表, 如下是其中的一个片段:

<status>
  <created_at>Wed Sep 09 06:07:04 +0000 2009</created_at>
  <id>3858278078</id>
  <text>gmail服务器错误了?</text>
  <source><a href="http://twitterfox.net/" rel="nofollow">TwitterFox</a></source>
  <truncated>false</truncated>
  <in_reply_to_status_id/>
  <in_reply_to_user_id/>
  <favorited>false</favorited>
  <in_reply_to_screen_name/>
  <user>
    <id>10415752</id>
    <name>hugege</name>
    <screen_name>hugege</screen_name>
    <location>Shantou Guangdong China </location>
    <description>www.gegehost.com 博客主机服务</description>
    <profile_image_url>http://a1.twimg.com/profile_images/329549706/hugegelogo_normal.jpg</profile_image_url>
    <url>http://hugege.com/</url>
    <protected>false</protected>
    <followers_count>1607</followers_count>
    <profile_background_color>C6E2EE</profile_background_color>
    <profile_text_color>663B12</profile_text_color>
    <profile_link_color>1F98C7</profile_link_color>
    <profile_sidebar_fill_color>DAECF4</profile_sidebar_fill_color>
    <profile_sidebar_border_color>C6E2EE</profile_sidebar_border_color>
    <friends_count>1335</friends_count>
    <created_at>Tue Nov 20 16:00:58 +0000 2007</created_at>
    <favourites_count>3</favourites_count>
    <utc_offset>28800</utc_offset>
    <time_zone>Beijing</time_zone>
    <profile_background_image_url>http://s.twimg.com/a/1252448032/images/themes/theme2/bg.gif</profile_background_image_url>
    <profile_background_tile>false</profile_background_tile>
    <statuses_count>1540</statuses_count>
    <notifications>false</notifications>
    <verified>false</verified>
    <following>false</following>
  </user>
</status>

同时会输出, 你的最后一个消息的消息ID:

The End; last tweet: 3858279409

当然你也可以直接通过这个ID, 来去获取你的该ID以后的消息:

t.execute("3858279409");

OK, 到这里, 我们的任务已经过去一半多了, 获取twitter消息, 那么接下来的内容, 我们来看如何将消息以PDF的形式进行归档.

通过iText生成PDF, 需要5步, 请看下面的Demo :

import java.io.FileOutputStream;
import com.lowagie.text.Document;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfWriter;

public class HelloWorld {
    public static void main(String args[]) {
        try {
            // step 1: create a Document object
            Document document = new Document();
            // step 2: connect the Document with an OutputStream using a PdfWriter
            PdfWriter.getInstance(document, new FileOutputStream("hello.pdf"));
            // step 3: open the document

            document.open();
            // step 4: add content
            document.add(new Paragraph("Hello World"));
            // step 5: close the document
            document.close();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

大体工作流程就如上面所说,相信都可以看懂, OK, 下面我就直接给出代码, 重点地方提一下.

PDFWriter类

import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.PageSize;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import com.quanlei.exception.TweetException;
import com.quanlei.twitter.TweetConsumer;
import com.quanlei.twitter.TweetProducer;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class PdfTwitter implements TweetConsumer {

    private int counter = 0;
    private Document document;
    private Font small_white = new Font(Font.HELVETICA, 8, Font.BOLD);
    private Font bold = new Font(Font.HELVETICA, 11, Font.BOLD);
    private BaseFont bfChinese = BaseFont.createFont("STSong-Light",
            "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
    private Font FontChinese = new Font(bfChinese, 12, Font.NORMAL);
    private SimpleDateFormat simpleDateTimeFormat =
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public PdfTwitter() throws DocumentException, IOException {
        document = new Document(PageSize.A4, 72, 36, 36, 36);

        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(
                "world.pdf"));
        writer.setPageEvent(new TwitterPage());
        document.open();
    }

    public void close() {
        document.close();
    }

    public String tweet(org.dom4j.Element tweet) throws TweetException {
        try {
            org.dom4j.Element user = tweet.element("user");
            PdfPTable table = new PdfPTable(new float[]{1, 10, 12});
            table.setTableEvent(new TableBackground());

            table.setWidthPercentage(100);
            table.setSpacingBefore(8);
            table.getDefaultCell().setPadding(5);
            table.getDefaultCell().setBorder(Rectangle.NO_BORDER);
            table.addCell("");
            table.addCell(new Phrase(user.elementText("screen_name"),
                    small_white));
            table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_RIGHT);

            table.addCell(new Phrase(simpleDateTimeFormat.format(
                    new Date(tweet.elementText("created_at"))),
                    small_white));
            table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_LEFT);
            table.getDefaultCell().setFixedHeight(38);
            table.addCell(new Phrase(String.valueOf(++counter), small_white));
            table.getDefaultCell().setColspan(2);
            Phrase p = new Phrase();
            p.add(new Phrase(user.elementText("name"), FontChinese));
            p.add(new Phrase(": ", bold));
            p.add(new Phrase(tweet.elementText("text"), FontChinese));
            table.addCell(p);
            document.add(table);
            return tweet.elementText("id");
        } catch (DocumentException e) {
            e.printStackTrace();
            throw new TweetException(e);
        } catch (IOException e) {
            e.printStackTrace();
            throw new TweetException(e);
        }
    }

    public static void main(String[] args)
            throws DocumentException, org.dom4j.DocumentException,
            FileNotFoundException, IOException, TweetException {
        PdfTwitter pdf = new PdfTwitter();
        TweetProducer t = new TweetProducer();
        t.createClient("gavinquan", "123456");
        t.setConsumer(pdf);
        t.execute(null);
        pdf.close();
    }
}

上面的类已经可以输出PDF了, 但是不是很美观, 加点修饰, 每个Twitter消息一个框, 这是背景:

import java.io.IOException;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfPTableEvent;

public class TableBackground implements PdfPTableEvent {

    protected BaseFont bf;

    public TableBackground() throws DocumentException, IOException {
        bf = BaseFont.createFont(BaseFont.TIMES_BOLDITALIC, BaseFont.WINANSI,
                BaseFont.NOT_EMBEDDED);
    }

    public void tableLayout(PdfPTable table, float[][] width, float[] height,
            int headerRows, int rowStart, PdfContentByte[] canvas) {
        PdfContentByte cb = canvas[PdfPTable.BACKGROUNDCANVAS];
        cb.saveState();
        cb.setRGBColorFill(0x9a, 0xe4, 0xe8);
        cb.roundRectangle(width[0][0], height[2], width[0][3] - width[0][0],
                height[0] - height[2], 4);
        cb.roundRectangle(width[0][1], height[2] + 3,
                width[0][3] - width[0][1] - 3, height[1] - height[2] - 6, 4);
        cb.eoFill();
        cb.beginText();
        cb.setRGBColorStroke(0x9a, 0xe4, 0xe8);
        cb.setFontAndSize(bf, 28);
        cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_STROKE);
        cb.showTextAligned(Element.ALIGN_LEFT, "\"", width[0][1],
                height[1] - 10, 0);
        cb.endText();
        cb.restoreState();
    }
}

下面再画蛇添足, 加点外围修饰, 每个PDF页面左侧加上文字, 算是起点宣传作用:

import java.io.IOException;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfPageEventHelper;
import com.lowagie.text.pdf.PdfWriter;

public class TwitterPage extends PdfPageEventHelper {

    protected BaseFont bf;

    public TwitterPage() throws DocumentException, IOException {
        bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",
                BaseFont.NOT_EMBEDDED);
    }

    @Override
    public void onEndPage(PdfWriter writer, Document document) {
        PdfContentByte cb = writer.getDirectContent();
        cb.saveState();
        cb.beginText();
        cb.setRGBColorStroke(0x9a, 0xe4, 0xe8);
        cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_STROKE);
        cb.setFontAndSize(bf, 18);
        cb.showTextAligned(Element.ALIGN_RIGHT, "www.quanlei.com", 68, 806, 90);
        cb.endText();
        cb.restoreState();
    }
}

最后配个截图, 为本文画上句号, 注, 由于图缩略了, 效果有点虚了:

PDF效果图

英文原版出处: http://www.javaworld.com/javaworld/jw-03-2009/jw-03-write-your-own-twitter-app.html , 本文抽取了原英文的重点, 并配以文字介绍,加工而成.