标签归档:java

Java API 设计清单

原文地址:http://theamiableapi.com/2012/01/16/java-api-design-checklist/

 

 

在设计Java API的时候总是有很多不同的规范和考量。与任何复杂的事物一样,这项工作往往就是在考验我们思考的缜密程度。就像飞行员起飞前的检查清单,这张清单将帮助软件设计者在设计Java API的过程中回忆起那些明确的或者不明确的规范。本文也可以看作为“API设计指南”这篇文章的附录。

我们还准备了一些前后比对的例子来展示这个列表如何帮助你理清设计需求,找出错误,识别糟糕的设计实践以及如何寻找改进的时机。

这个清单使用了如下的语言规范:

要 – 表示必要的设计

建议 – 表示在几个最好的设计中选择一个

考虑 – 表示一个可能的设计上的改进

避免 – 表示一个设计上的缺陷

不要 – 表示一个设计上的错误

1. 包设计清单

1.1. 共通

  • 1.1.1.    建议把API和实现放入不同的包
  • 1.1.2.    建议把API放进上层包,而把实现放进下层包
  • 1.1.3.    考虑把一组大型的API分拆进不同的包
  • 1.1.4.    考虑把API和实现打包进不同的jar包
  • 1.1.5.    避免API的实现类之间的内部依赖
  • 1.1.6.    避免把API分拆了太细
  • 1.1.7.    避免把公共实现类放到API的包中
  • 1.1.8.    不要在调用者和实现类之间建立依赖
  • 1.1.9.    不要把没有关系的API放进同一个包
  • 1.1.10.  不要把API和SPI(service provider interface)放进一个包(注:两者不同可以查看这个页面http://stackoverflow.com/questions/2954372/difference-between-spi-and-api )
  • 1.1.11.  不要移动或者重命名一个已经发布的公共API

1.2. 命名

  • 1.2.1.    (一级)包名以公司(或者组织)的根命名空间来命名
  • 1.2.2.    使用一个稳定的产品名称或者一个产品系列的名称作为包的二级名称
  • 1.2.3.    使用API名称作为包名的(三级名称)结尾
  • 1.2.4.    考虑把仅包含实现的包的名称中包含”internal”这个词(注:似乎“impl”更常见一些)
  • 1.2.5.    避免使用组合起来的名称
  • 1.2.6.    避免包名和包内的类名使用同样的名称
  • 1.2.7.    避免在包名称中使用“api”这个词
  • 1.2.8.    不要使用营销,计划,组织单元(部门)和地理名称
  • 1.2.9.    不要在包名中使用大写字母

1.3. 文档

  • 1.3.1.    为每一个包提供一个package.html
  • 1.3.2.    遵循标准的javadoc的规范
  • 1.3.3.    在API的开始处用一句短小的话来概括(描述)
  • 1.3.4.    提供足够多的细节来帮助判断是否需要使用和如何使用该API
  • 1.3.5.    指出该API的入口(主要的类或者方法)
  • 1.3.6.    包含覆盖主要的,基本功能演示的样例代码
  • 1.3.7.    包含一个指向开发者指南的超链接
  • 1.3.8.    包含一个指向手册的超链接
  • 1.3.9.    指出相关的API集合
  • 1.3.10.  包含API的版本号
  • 1.3.11.  用 @deprecated 标记出不再使用的API版本
  • 1.3.12.  考虑添加一个版权声明
  • 1.3.13.  避免过长的包概述
  • 1.3.14.  不要在发布的javadoc中包含实现相关的包

2. 类型设计清单(这里的“类型”个人理解为一组Api)

2.1. 共通

  • 2.1.1.    确保每种(设计的)类型都有单一明确的目的
  • 2.1.2.    确保每种类型代表了(业务)领域的概念,而不是为了(技术上)的抽象
  • 2.1.3.    限制类型的总数量
  • 2.1.4.    限制类型的大小
  • 2.1.5.    设计相关的类型时保持和原有的类型的一致性
  • 2.1.6.    建议为多种public的类型提供多种(private)的实现
  • 2.1.7.    建议接口的实现类和继承关系的类应该在行为上保持一致性
  • 2.1.8.    建议用抽象类而不是接口解耦Api的实现
  • 2.1.9.    建议使用枚举而不是常量
  • 2.1.10.  考虑使用泛型
  • 2.1.11.  考虑在泛型参数上增加约束
  • 2.1.12.  考虑使用接口来实现多继承的效果
  • 2.1.13.  避免为使用者的扩展(需求)进行设计
  • 2.1.14.  避免深度的继承层次
  • 2.1.15.  不要使用public内嵌的类型
  • 2.1.16.  不要申明public和protected的变量
  • 2.1.17.  不要把实现的继承关系暴露给使用者

2.2. 命名

  • 2.2.1.    使用名词或者名词词组
  • 2.2.2.    使用PascalCasing(驼峰命名法的别称详见http://en.wikipedia.org/wiki/CamelCase )
  • 2.2.3.    缩写仅第一个首字母大写
  • 2.2.4.    为类型的实际作用使用精确的名称命名
  • 2.2.5.    为最常用的类型准备最短最容易记忆的名称
  • 2.2.6.    所有异常都以“Exception”结尾
  • 2.2.7.    使用名词的单数(比如用Color而不用Colors)来命名枚举类型
  • 2.2.8.    考虑较长的名称
  • 2.2.9.    考虑派生类用基类的名称结尾
  • 2.2.10.  考虑为抽象类名称使用“Abstract”开头
  • 2.2.11.  避免使用缩略语
  • 2.2.12.  避免太通用的名词
  • 2.2.13.  避免同义词
  • 2.2.14.  避免在相关的Api中使用类型的名称
  • 2.2.15.  不要使用仅大小写不同的名称
  • 2.2.16.  不要使用前缀
  • 2.2.17.  不要以“I”作为接口的名称开头
  • 2.2.18.  不要(重复)使用Java核心包中的名称

2.3. 类

  • 2.3.1.    最小化实现使用的依赖
  • 2.3.2.    先列出public方法
  • 2.3.3.    申明实现方法为private(这里是笔误吗?)
  • 2.3.4.    为一个public抽象类定义至少一个public的shi
  • 2.3.5.    为基本的使用情况提供足够的缺省实现
  • 2.3.6.    设计基本上不变的类
  • 2.3.7.    把无状态,访问器,扩展(mutator个人理解为多种参数形式的方法)方法集合到一起
  • 2.3.8.    把扩展方法的数量控制到最少
  • 2.3.9.    考虑设计一个默认的无参的构造方法
  • 2.3.10.  考虑重写equal,hashCode方法
  • 2.3.11.  考虑实现Comparable接口
  • 2.3.12.  考虑实现Serializable接口
  • 2.3.13.  考虑使类可以容易的扩展
  • 2.3.14.  考虑申明类为final
  • 2.3.15.  考虑为类的实例化提供一个public的构造方法
  • 2.3.16.  考虑使用自定义的类型来增强类的不可变性
  • 2.3.17.  考虑设计不可变的类
  • 2.3.18.  避免静态类
  • 2.3.19.  避免使用Cloneable
  • 2.3.20.  不要向静态类中添加实例duixi
  • 2.3.21.  不要为使用者不应扩展的public抽象类提供public的构造方法
  • 2.3.22.  不要滥用初始化

2.4. 接口

  • 2.4.1.    为每一个public接口提供至少一个实现类
  • 2.4.2.    为每一个public接口设计至少一个消费方法
  • 2.4.3.    不要对一个已经发布的public接口添加新的方法
  • 2.4.4.    不要使用标记接口(标记接口详见http://en.wikipedia.org/wiki/Marker_interface_pattern )
  • 2.4.5.    不要把public接口设计成常量的容器(这个实在很常见啊……)

2.5. 枚举

  • 2.5.1.    考虑为枚举类型指定一个0值(“NONE”或者“Unspecialized”等等)
  • 2.5.2.    避免只有一个值的枚举
  • 2.5.3.    不要使用枚举实现开放式的值集合
  • 2.5.4.    不要为将来可能增加的值设计枚举
  • 2.5.5.    不要为已经发布的版本增加新的枚举值

2.6. 异常

  • 2.6.1.    确保自定义的异常可以被序列化
  • 2.6.2.    考虑为每种类型定义一个不同的异常
  • 2.6.3.    考虑为代码访问提供更多的异常信息
  • 2.6.4.    避免深层的异常继承
  • 2.6.5.    不要从Exception和RuntimeException以外的类派生自定义异常
  • 2.6.6.    不要直接从Throwable派生异常
  • 2.6.7.    不要在异常信息内包含敏感信息

2.7. 文档

  • 2.7.1.    为每种类型(的Api)配上概述
  • 2.7.2.    遵循标准Javadoc的约定
  • 2.7.3.    每种类型开头以一句短小的话概述
  • 2.7.4.    为是否使用以及如何使用该类型提供足够的细节来帮助做决定
  • 2.7.5.    解释如何实例化一个类型
  • 2.7.6.    为一个类型的主要的使用情景提供样例代码
  • 2.7.7.    包含指向到开发指南的链接
  • 2.7.8.    包含指向手册的链接
  • 2.7.9.    显示相关的类型
  • 2.7.10.  @deprecated标签申明过时的类型
  • 2.7.11.  文档类具有不可变性
  • 2.7.12.  避免冗长的类概述
  • 2.7.13.  不要为私有方法生成Javadoc

3. 方法设计清单

3.1. 共通

  • 3.1.1.    确保每个方法实现一个目的
  • 3.1.2.    确保相关的方法都是一个粒度级别的
  • 3.1.3.    确保没有混合调用方法的公共代码
  • 3.1.4.    使所有方法的调用具有原子性(原子性:http://jiangyongyuan.iteye.com/blog/364010
  • 3.1.5.    设计protected方法时要像public方法一样慎重
  • 3.1.6.    限制扩展方法的数量
  • 3.1.7.    设计扩展方法需要具有较强的稳定性
  • 3.1.8.    建议为一系列重载的方法设计一个泛型的方法
  • 3.1.9.    考虑使用泛型方法
  • 3.1.10.  考虑设计方法对,即两个方法的作用是相反的
  • 3.1.11.  避免“helper”方法
  • 3.1.12.  避免长时间执行的方法
  • 3.1.13.  避免调用者在普通使用中需要手动写循环
  • 3.1.14.  避免可选的参数影响方法的行为
  • 3.1.15.  避免不可重复调用的方法
  • 3.1.16.  不要删除一个已经发布的方法
  • 3.1.17.  不要在没有提供替换方法前把一个已经发布的方法标记为过时
  • 3.1.18.  不要修改一个已经发布的方法的签名
  • 3.1.19.  不要修改一个已经发布的方法的可观测行为(也许指的是输出之类)
  • 3.1.20.  不要增加一个已经发布方法的调用条件
  • 3.1.21.  不要减少一个已经发布方法的调用结果
  • 3.1.22.  不要为已经发布的public接口新增方法
  • 3.1.23.  不要为已经发布的Api新增重载

3.2. 命名

  • 3.2.1.    用给力的,有表达力的动词作为名称起始
  • 3.2.2.    使用驼峰命名法(好奇怪,前面写的是PascalNaming)
  • 3.2.3.    为JavaBean的私有属性预留“get”“set”“is”等访问方法
  • 3.2.4.    使用对调用者熟悉的词语
  • 3.2.5.    尽量使用英语口语
  • 3.2.6.    避免使用缩略语
  • 3.2.7.    避免使用一般的动词
  • 3.2.8.    避免同义词
  • 3.2.9.    不要使用“黑话”
  • 3.2.10.  不要依靠参数的名称和类型判断方法的意义

3.3. 参数

  • 3.3.1.    为参数选择最合适的类型
  • 3.3.2.    在相关方法的调用中对参数为null值的处理保持一致性
  • 3.3.3.    在相关方法中参数的名称,类型和顺序需要保持一致
  • 3.3.4.    在参数列表中把输出的参数放到输入参数之后
  • 3.3.5.    为重载的方法省略常用的默认参数以提供一个较短的参数列表
  • 3.3.6.    在无关的类型中为相同语义的操作提供重载方法
  • 3.3.7.    建议使用接口而不是具体类作为参数
  • 3.3.8.    建议使用集合而不是数组作为参数和返回值
  • 3.3.9.    建议使用一般集合而不是原始(无类型)集合
  • 3.3.10.  建议使用枚举而不是Boolean或者Integer作为参数
  • 3.3.11.  建议把单个的参数放到集合或者数组参数之前
  • 3.3.12.  建议把自定义类型的参数放大Java标准类型参数之前
  • 3.3.13.  建议把对象类型的参数方法值类型的参数之前
  • 3.3.14.  建议使用接口而不是具体类作为返回值
  • 3.3.15.  建议把空的集合而不是null作为返回值
  • 3.3.16.  建议把返回值设计成可以作为其他方法的合法输入参数
  • 3.3.17.  考虑为不可变参数设计一个副本
  • 3.3.18.  考虑在内部存储弱引用的对象
  • 3.3.19.  避免参数数量变更
  • 3.3.20.  避免参数长度太长(超过3个)
  • 3.3.21.  避免连续的同类型的参数
  • 3.3.22.  避免用作输出或者输入输出的参数
  • 3.3.23.  避免方法重载
  • 3.3.24.  避免参数类型暴露实现细节
  • 3.3.25.  避免boolean参数
  • 3.3.26.  避免返回null
  • 3.3.27.  除了Java核心Api,避免把类型作为不相关的Api的返回值
  • 3.3.28.  避免把可变的内部对象作为返回值来引用
  • 3.3.29.  不要把预先设置的常量作为整型值参数使用
  • 3.3.30.  不要为将来的(扩展设计)考虑预留参数
  • 3.3.31.  不要在重载方法中改变参数的名称的顺序

3.4. 异常处理

  • 3.4.1.    只有在异常情况下才抛出异常
  • 3.4.2.    只需要为可恢复的错误抛出已确认的异常
  • 3.4.3.    为了通知Api使用错误而抛出运行时异常
  • 3.4.4.    在适当的抽象层次抛出异常
  • 3.4.5.    进行运行时预置条件的检查
  • 3.4.6.    为一个被不能为null的参数抛出空指针异常
  • 3.4.7.    为一个除为null以外异常值的参数排除非法参数异常
  • 3.4.8.    为一个错误上下文环境中的方法调用抛出非法状态异常
  • 3.4.9.    在错误信息中显示出参数的预置条件
  • 3.4.10.  确保失败的方法调用不会产生单向的后果
  • 3.4.11.  为回调方法中的禁止使用的Api提供运行时检查
  • 3.4.12.  建议优先使用Java标准异常
  • 3.4.13.  建议提供抛出异常的条件的查询方法

3.5. 重写

  • 3.5.1.    使用@Override注解
  • 3.5.2.    维持或弱化预置条件
  • 3.5.3.    维持或者加强后置条件(不好翻译,大概output+effect的意思)
  • 3.5.4.    维持或者加强不可变性
  • 3.5.5.    不要抛出新增的运行时异常
  • 3.5.6.    不要更改方法的类型(无状态,访问器或者扩展方法等)

3.6. 构造方法

  • 3.6.1.    最小化构造方法中的工作
  • 3.6.2.    为所有的属性设置合理的默认值
  • 3.6.3.    仅把构造方法的参数作为一种设置参数的快捷方法
  • 3.6.4.    校验构造方法的参数
  • 3.6.5.    以参数相应的属性为其命名
  • 3.6.6.    当提供了多个构造方法时,遵循指南对其进行重载
  • 3.6.7.    建议使用构造方法而不是静态的工厂方法
  • 3.6.8.    考虑使用无参的构造方法
  • 3.6.9.    如果不是总需要新的实例,考虑使用静态的工厂方法
  • 3.6.10.  如果你需要在运行时决定一个合适的类型,考虑使用静态的工厂方法
  • 3.6.11.  如果你需要访问外部的资源,考虑使用静态的工厂方法
  • 3.6.12.  当面临非常多的参数的时候,考虑使用生成器(builder)
  • 3.6.13.  当需要回避直接实例化类的时候使用考虑private的构造函数
  • 3.6.14.  避免创建非必需的对象
  • 3.6.15.  避免finalizer
  • 3.6.16.  不要从无参的构造方法中抛出异常
  • 3.6.17.  不要向一个已经发布的类中添加显示的构造方法

3.7. Setters和getters

  • 3.7.1.    以get开头命名一个返回值不为boolean的访问属性的方法
  • 3.7.2.    以is,can开头命名一个返回值为boolean的访问属性的方法
  • 3.7.3.    以set开头命名一个更新本地变量的方法
  • 3.7.4.    校验setter方法的参数
  • 3.7.5.    最小化getter和setter方法的工作
  • 3.7.6.    考虑从一个getter方法中返回不可变的集合
  • 3.7.7.    考虑实现一个private接口的集合替代public的集合属性
  • 3.7.8.    考虑只读的属性
  • 3.7.9.    设置可变类型的属性时考虑Defensive Copy(Defensive Copy详见http://www.javapractices.com/topic/TopicAction.do?Id=15 )
  • 3.7.10.  当返回可变类型的属性时考虑Defensive Copy
  • 3.7.11.  getter方法避免返回数组
  • 3.7.12.  避免根据方法内信息无法完成的校验
  • 3.7.13.  不要从getter方法中抛出异常
  • 3.7.14.  不要设计只能set的属性方法 (仅有public的setter而没有public的getter)
  • 3.7.15.  不要依赖属性设置的顺序

3.8. 回调

  • 3.8.1.    设计时使用最严密的预置条件
  • 3.8.2.    设计时使用最弱的后置条件
  • 3.8.3.    考虑传递引用对象的方法中把回调接口作为第一个参数
  • 3.8.4.    避免有返回值的回调方法

3.9. 文档

  • 3.9.1.    为每个方法提供Javadoc注释
  • 3.9.2.    遵循标准的Javadoc约定
  • 3.9.3.    每个方法以一句短小的话作为概述
  • 3.9.4.    申明相关的方法
  • 3.9.5.    @deprecated标签申明过时的类型
  • 3.9.6.    显示所有过时方法的替换方法
  • 3.9.7.    避免冗长的zhus
  • 3.9.8.    包含常用的使用模式
  • 3.9.9.    (如果允许的话)包含null值的确切含义
  • 3.9.10.  包含方法的类型 (无状态,访问器或者扩展)
  • 3.9.11.  包含方法的预置条件
  • 3.9.12.  包含算法实现的性能特征
  • 3.9.13.  包含远程方法调用
  • 3.9.14.  包含访问外部资源的方法
  • 3.9.15.  包含哪些API可以在回调中使用
  • 3.9.16.  考虑为了描述方法的行为而包含单元测试

JSONPath对JSON的解析,与之XPath

JSONPath: https://github.com/jayway/JsonPath

Java DSL for reading and testing JSON documents

下面是一个json数据,而下面表格中是对应的插件的识别字符串,想必就会明白用JSONPath来解析json的路径问题了吧。不会的联系我。

{
    "store": {
        "book": [
            {
                "category": "reference",
                "author": "Nigel Rees",
                "title": "Sayings of the Century",
                "price": 8.95
            },
            {
                "category": "fiction",
                "author": "Evelyn Waugh",
                "title": "Sword of Honour",
                "price": 12.99
            },
            {
                "category": "fiction",
                "author": "Herman Melville",
                "title": "Moby Dick",
                "isbn": "0-553-21311-3",
                "price": 8.99
            },
            {
                "category": "fiction",
                "author": "J. R. R. Tolkien",
                "title": "The Lord of the Rings",
                "isbn": "0-395-19395-8",
                "price": 22.99
            }
        ],
        "bicycle": {
            "color": "red",
            "price": 19.95
        }
    }
}

 

XPath JSONPath Result
/store/book/author $.store.book[*].author the authors of all books in the store
//author $..author all authors
/store/* $.store.* all things in store, which are some books and a red bicycle.
/store//price $.store..price the price of everything in the store.
//book[3] $..book[2] the third book
//book[last()] $..book[(@.length-1)]
$..book[-1:]
the last book in order.
//book[position()<3] $..book[0,1]
$..book[:2]
the first two books
//book[isbn] $..book[?(@.isbn)] filter all books with isbn number
//book[price<10] $..book[?(@.price<10)] filter all books cheapier than 10
//* $..* all Elements in XML document. All members of JSON structure.

 

 

通用的用户登录过滤器(SessionFilter)

功能描述

用于检查用户是否登录了系统,如果未登录,则重定向到指的登录页面。

使用方法

在 java web 项目的 web.xml 文件中添加如下代码,对每个参数都进行了详细的说明。

 <!—检查用户是否登录了系统的过滤器配置  开始 -->
 <filter>
  <filter-name>SessionFilter</filter-name>
  <filter-class>com.hmw.filter.SessionFilter</filter-class>
  <init-param>
      <description>将当前登录的用户的信息保存在 session 中时使用的key,如果没有配置此参数,则该过滤器不起作用</description>
      <param-name>sessionKey</param-name>
      <param-value>userInfo</param-value>
  </init-param>
  <init-param>
      <description>
          如果用户未登录(即在 session 中 key 为 sessionKey 的属性不存在或为空),则将请求重定向到该 url。
          该 url 不包含web应用的 ContextPath。
          如果不配置此参数,则在用户未登录系统的情况下,直接重定向到web应用的根路径(/)
      </description>
      <param-name>redirectUrl</param-name>
      <param-value>/login.jsp</param-value>
  </init-param>
  <init-param>
      <description>
          不需要进行拦截的 url 的正则表达式,即:如果当前请求的 url 的 servletPath 能匹配该正则表达式,则直接放行(即使未登录系统)。
          此参数的值一般为 loginServlet 和 registServlet 等。
          另外,参数 redirectUrl 的值不用包含在该正则表达式中,因为 redirectUrl 对应的 url 会被自动放行。
          还有一点需要说明的是,该参数的值不包含web应用的 ContextPath。
      </description>
      <param-name>excepUrlRegex</param-name>
      <!-- 不拦截 /servlets/loginServlet 和 /servlets/registServlet -->
      <param-value>/servlets/(login|regist)Servlet</param-value>
  </init-param>
 </filter>

 <filter-mapping>
  <filter-name>SessionFilter</filter-name>
  <url-pattern>/servlets/*</url-pattern>
 </filter-mapping>
 <filter-mapping>
  <filter-name>SessionFilter</filter-name>
  <url-pattern>/jsp/*</url-pattern>
 </filter-mapping>
 <!—检查用户是否登录了系统的过滤器配置  结束 -->

过滤器源码

package com.hmw.filter;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;

/**
 * 用于检查用户是否登录了系统的过滤器<br>
 * 创建日期:2012-01-09
 * @author <a href="mailto:hemingwang0902@126.com">何明旺</a>
 */
public class SessionFilter implements Filter {

    /** 要检查的 session 的名称 */
    private String sessionKey;

    /** 需要排除(不拦截)的URL的正则表达式 */
    private Pattern excepUrlPattern;

    /** 检查不通过时,转发的URL */
    private String forwardUrl;

    @Override
    public void init(FilterConfig cfg) throws ServletException {
        sessionKey = cfg.getInitParameter("sessionKey");

        String excepUrlRegex = cfg.getInitParameter("excepUrlRegex");
        if (!StringUtils.isBlank(excepUrlRegex)) {
            excepUrlPattern = Pattern.compile(excepUrlRegex);
        }

        forwardUrl = cfg.getInitParameter("forwardUrl");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        // 如果 sessionKey 为空,则直接放行
        if (StringUtils.isBlank(sessionKey)) {
            chain.doFilter(req, res);
            return;
        }

//         * 请求 http://127.0.0.1:8080/webApp/home.jsp?&a=1&b=2 时
//          * request.getRequestURL(): http://127.0.0.1:8080/webApp/home.jsp
//         * request.getContextPath(): /webApp 
//         * request.getServletPath():/home.jsp
//         * request.getRequestURI(): /webApp/home.jsp
//         * request.getQueryString():a=1&b=2
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        String servletPath = request.getServletPath();

        // 如果请求的路径与forwardUrl相同,或请求的路径是排除的URL时,则直接放行
        if (servletPath.equals(forwardUrl) || excepUrlPattern.matcher(servletPath).matches()) {
            chain.doFilter(req, res);
            return;
        }

        Object sessionObj = request.getSession().getAttribute(sessionKey);
        // 如果Session为空,则跳转到指定页面
        if (sessionObj == null) {
            String contextPath = request.getContextPath();
            String redirect = servletPath + "?" + StringUtils.defaultString(request.getQueryString());
            /*
             * login.jsp 的 <form> 表单中新增一个隐藏表单域:
             * <input type="hidden" name="redirect" value="${param.redirect }">
             * 
             *  LoginServlet.java 的 service 的方法中新增如下代码:
             *  String redirect = request.getParamter("redirect");
             *  if(loginSuccess){
             *      if(redirect == null || redirect.length() == 0){
             *          // 跳转到项目主页(home.jsp)
             *      }else{
             *          // 跳转到登录前访问的页面(java.net.URLDecoder.decode(s, "UTF-8"))
             *      }
             *  } 
             */
            response.sendRedirect(contextPath + StringUtils.defaultIfEmpty(forwardUrl, "/")
                            + "?redirect=" + URLEncoder.encode(redirect, "UTF-8"));
        } else {
            chain.doFilter(req, res);
        }
    }

    @Override
    public void destroy() {
    }
}

HTTPClient请求网页抓取数据:验证是否301跳转

转载声明:本文章由 尚缘网络 于 2013-07-09 22:20:03 发表在 JavaApp 栏目,转载请注明出自:http://www.sxrczx.com/t/article/3864b5a8c2774191bcb7972246c8d578.htm

制作一个简单的WEB工具:在线检测永久重定向301是否设置成功,碰到了一些问题,记录如下:

DefaultHttpClient获取StatusCode,代码片段如下:

DefaultHttpClient client = new DefaultHttpClient(); 
//使用Get方式请求
HttpGet httpget = new HttpGet("http://sxrczx.com");
//执行请求
try {
HttpResponse response = client.execute(httpget);            System.out.println("httpclicent"+response.getStatusLine().getStatusCode());
} catch (ClientProtocolException e1) {
    e1.printStackTrace();
} catch (IOException e1) {
    e1.printStackTrace();
}

遇到的问题:

通过response.getStatusLine().getStatusCode()获取到的状态码永远是(在保证资源能被访问到,并且正确配置了301重定向的情况下)200

原因分析:

既然返回HTTP status为200,证明HttpClient主动帮助我们处理了301重定向后续的请求,但是我们的目标是拿到重定向状态码即可,也就是说不再需要它主动帮我们处理301后续的问题。

解决办法:

继续阅读

java+HttpClient获取百度搜索结果重定向后的地址

百度搜索结果那长条真是恶心,来个获取百度301||302重定向后的真实地址。

 

import java.io.IOException;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.AllClientPNames;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpParams;


public class Redirect {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		DefaultHttpClient client = new DefaultHttpClient();
        
        //使用Get方式请求
        HttpGet httpget = new HttpGet("http://www.baidu.com/link?url=ByBJLpHsj5nXx6DESXbmMjIrU5W4Eh0yg5wCQpe3kCQMlJK_RJBmdEYGm0DDTCoTDGaz7rH80gxjvtvoqJuYxK");
        HttpParams params = client.getParams();  
        params.setParameter(AllClientPNames.HANDLE_REDIRECTS, false);
           
        //执行请求
        try {
            HttpResponse response = client.execute(httpget);
            int statusCode=response.getStatusLine().getStatusCode();
            if(statusCode==301||statusCode==302)
            {
                Header[] hs = response.getHeaders("Location");
			     for(Header h:hs)
			     {
			    	 System.out.println(h.getName()+"--"+h.getValue());
			     }
            }
        } catch (ClientProtocolException e1) {
            e1.printStackTrace();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
	}

}

boilerpipe(Boilerplate Removal and Fulltext Extraction from HTML pages) 源码分析

转自:http://blog.csdn.net/fxjtoday/article/details/6320315

开源Java模块boilerpipe(1.1.0), http://code.google.com/p/boilerpipe/

使用例子,
URL url = new URL(“http://www.example.com/some-location/index.html “);
// NOTE: Use ArticleExtractor unless DefaultExtractor gives better results for you
String text = ArticleExtractor .INSTANCE.getText(url);
那就从ActicleExtractor开始分析, 这个类用了singleton的design pattern, 使用INSTANCE取得唯一的实例, 实际处理如下步骤

HTML Parser
The HTML Parser is based upon CyberNeko 1.9.13. It is called internally from within the Extractors.
The parser takes an HTML document and transforms it into a TextDocument , consisting of one or moreTextBlocks . It knows about specific HTML elements (SCRIPT, OPTION etc.) that are ignored automatically.
Each TextBlock stores a portion of text from the HTML document. Initially (after parsing) almost every TextBlock represents a text section from the HTML document, except for a few inline elements that do not separate per defintion (for example ‘<A>’anchor tags).
The TextBlock objects also store shallow text statistics for the block’s content such as the number of words and the number of words in anchor text.

Extractors
Extractors consist of one or more pipelined Filters . They are used to get the content of a webpage. Several different Extractors exist, ranging from a generic DefaultExtractor to extractors specific for news article extraction (ArticleExtractor).
ArticleExtractor.process() 就包含了这个pipeline filter, 这个design做的非常具有可扩展性, 把整个处理过程分成若干小的步骤分别实现, 在用的时候象搭积木一样搭成一个处理流. 当想扩展或改变处理过程时, 非常简单, 只需加上或替换其中的一块就可以了.
这样也非常方便于多语言扩展, 比如这儿用的english包里的相应的处理函数,
import de.l3s.boilerpipe.filters.english.IgnoreBlocksAfterContentFilter;
import de.l3s.boilerpipe.filters.english.KeepLargestFulltextBlockFilter;
如果要扩展到其他语言, 如韩文, 只需在filters包里面加上个korean包, 分别实现这些filter处理函数, 然后只需要修改import, 就可以实现对韩语的support.

TerminatingBlocksFinder.INSTANCE.process(doc)
| new DocumentTitleMatchClassifier(doc.getTitle()).process(doc)
| NumWordsRulesClassifier.INSTANCE.process(doc)
| IgnoreBlocksAfterContentFilter.DEFAULT_INSTANCE.process(doc)
| BlockProximityFusion.MAX_DISTANCE_1.process(doc)
| BoilerplateBlockFilter.INSTANCE.process(doc)
| BlockProximityFusion.MAX_DISTANCE_1_CONTENT_ONLY.process(doc)
| KeepLargestFulltextBlockFilter.INSTANCE.process(doc)
| ExpandTitleToContentFilter.INSTANCE.process(doc);
下面具体看一下处理流的每个环节.

TerminatingBlocksFinder
Finds blocks which are potentially indicating the end of an article text and marks them with {@link DefaultLabels#INDICATES_END_OF_TEXT}. This can be used in conjunction with a downstream {@link IgnoreBlocksAfterContentFilter}.(意思是IgnoreBlocksAfterContentFilter必须作为它的downstream)

原理很简单, 就是判断这个block, 在tb.getNumWords() < 20的情况下是否满足下面的条件,
text.startsWith(“Comments”)
|| N_COMMENTS.matcher(text).find() //N_COMMENTS = Pattern.compile(“(?msi)^[0-9]+ (Comments|users responded in)”)
|| text.contains(“What you think…”)
|| text.contains(“add your comment”)
|| text.contains(“Add your comment”)
|| text.contains(“Add Your Comment”)
|| text.contains(“Add Comment”)
|| text.contains(“Reader views”)
|| text.contains(“Have your say”)
|| text.contains(“Have Your Say”)
|| text.contains(“Reader Comments”)
|| text.equals(“Thanks for your comments – this feedback is now closed”)
|| text.startsWith(“© Reuters”)
|| text.startsWith(“Please rate this”)
如果满足就认为这个block为artical的结尾, 并加上标记tb.addLabel(DefaultLabels.INDICATES_END_OF_TEXT);

DocumentTitleMatchClassifier
这个很简单, 就是根据'<title>’的内容去页面中去标注title的位置, 做法就是根据'<title>’的内容产生一个potentialTitles列表, 然后去匹配block, 匹配上就标注成DefaultLabels.TITLE

NumWordsRulesClassifier
Classifies {@link TextBlock}s as content/not-content through rules that have been determined using the C4.8 machine learning algorithm, as described in the paper “Boilerplate Detection using Shallow Text Features” (WSDM 2010), particularly using number of words per block and link density per block.
这个模块实现了个分类器, 用于区分content/not-content , 分类器的构建参见上面这篇文章的4.3节.
分类器使用Decision Trees算法, 用标注过的google news作为训练集, 接着对训练完的Decision Trees经行剪枝, Applying reduced-error pruning we were able to simplify the decision tree to only use 6 dimensions (2 features each for current, previous and next block) without a significant loss in accuracy.
最后用伪码描述出Decision Trees的decision过程, 这就是使用Decision Trees的最大好处, 它的decision rules是可以理解的, 所以可以用各种语言描述出来.
这个模块实现的是Algorithm 2 Classifier based on Number of Words

curr_linkDensity <= 0.333333
| prev_linkDensity <= 0.555556
| | curr_numWords <= 16
| | | next_numWords <= 15
| | | | prev_numWords <= 4: BOILERPLATE
| | | | prev_numWords > 4: CONTENT
| | | next_numWords > 15: CONTENT
| | curr_numWords > 16: CONTENT
| prev_linkDensity > 0.555556
| | curr_numWords <= 40
| | | next_numWords <= 17: BOILERPLATE
| | | next_numWords > 17: CONTENT
| | curr_numWords > 40: CONTENT
curr_linkDensity > 0.333333: BOILERPLATE

有了Classifies, 接下来的事情就是对于所有block进行分类并标注.

IgnoreBlocksAfterContentFilter
Marks all blocks as “non-content” that occur after blocks that have been marked {@link DefaultLabels#INDICATES_END_OF_TEXT}. These marks are ignored unless a minimum number of words in content blocks occur before this mark (default: 60). This can be used in conjunction with an upstream {@link TerminatingBlocksFinder}.

这个模块是TerminatingBlocksFinder模块的downstream, 就是说必须在它后面做, 简单的很, 找到DefaultLabels#INDICATES_END_OF_TEXT, 后面的内容全标为BOILERPLATE.
除了前面正文length不到minimum number of words(default: 60), 还需要继续抓点文字凑数.

BlockProximityFusion
Fuses adjacent blocks if their distance (in blocks) does not exceed a certain limit. This probably makes sense only in cases where an upstream filter already has removed some blocks.
这个模块用来合并block的, 合并的依据主要是根据两个block的offset的差值不大于2, 也就是说中间最多只能隔一个block.
当要求contentOnly时, 会check两个block都标注为content时才会fusion.
int diffBlocks = block.getOffsetBlocksStart() – prevBlock.getOffsetBlocksEnd() – 1;
if (diffBlocks <= maxBlocksDistance)

那么block的offset怎么来的了, 查一下block构造的时候的代码
BoilerpipeHTMLContentHandler .flushBlock()
TextBlock tb = new TextBlock(textBuffer.toString().trim(), currentContainedTextElements, numWords, numLinkedWords, numWordsInWrappedLines, numWrappedLines, offsetBlocks);
offsetBlocks++;

TextBlock构造函数
this.offsetBlocksStart = offsetBlocks;
this.offsetBlocksEnd = offsetBlocks;
可以看出初始情况下, block的offset就是递增的, 并且再没有做过fusion的情况下, offsetBlocksStart和offsetBlocksEnd是相等的.
所以象注释讲的那样, 只有当upstream filter remove了部分blocks以后, 这个模块的合并依据才是有意义的, 不然在没有任何删除的情况下, 所有block都满足fusion条件.

看完这段代码, 我很奇怪, Paper中fusion是根据text density的, 而这儿只是根据block的offset, 有所减弱.
There, adjacent text fragments of similar text density (interpreted as /similar class”) are iteratively fused until the blocks’ densities (and therefore the text classes) are distinctive
enough.
而且我更加不理解的是, 在ArticleExtractor关于这个模块的用法如下,
BlockProximityFusion.MAX_DISTANCE_1.process(doc)
| BoilerplateBlockFilter.INSTANCE.process(doc)
| BlockProximityFusion.MAX_DISTANCE_1_CONTENT_ONLY.process(doc)
调用了BlockProximityFusion两次, 分别在BoilerplateBlockFilter(含义在下节)的down,upstream, 对于BlockProximityFusion.MAX_DISTANCE_1_CONTENT_ONLY.process(doc)的调用我还是能理解的, 再删除完非content的block后, 对剩下的block做一下fusion, 比如原来两个block中间隔了个广告. 不过这儿根据offset, 而不根据text density, 个人觉得功能有所减弱.
可是对于BlockProximityFusion.MAX_DISTANCE_1.process(doc)的调用, 可能是我没看懂, 实在无法理解, 为什么要加这步, 唯一的解释是想将一些没有标注为content的block fusion到content里面去. 奇怪的是这儿fusion是无条件的(在没有删除block的情况下,判断offset无效), 只需要当前的block是content是就和Prev进行fusion. 而且为什么只判断当前block, Prevblock是content是否也应该fusion.个人觉得这边逻辑完全不合理……

BoilerplateBlockFilter
Removes {@link TextBlock}s which have explicitly been marked as “not content”
没啥好说的, 就是遍历每个block, 把没有标注为”content”的都删掉.

KeepLargestFulltextBlockFilter
Keeps the largest {@link TextBlock} only (by the number of words). In case of more than one block with the same number of words, the first block is chosen. All discarded blocks are marked “not content” and flagged as {@link DefaultLabels#MIGHT_BE_CONTENT}
很好理解, 找出最大的文本block作为正文, 其他的标注为DefaultLabels#MIGHT_BE_CONTENT

ExpandTitleToContentFilter
Marks all {@link TextBlock}s “content” which are between the headline and the part that has already been marked content, if they are marked {@link DefaultLabels#MIGHT_BE_CONTENT}. This filter is quite specific to the news domain.
逻辑是找出标注为DefaultLabels.TITLE的block, 和content开始的那个block, 把这两个block之间的标注为MIGHT_BE_CONTENT的都改标注为Content.

TextDocument.getContent()
最后需要做的一步, 是把抽取的内容输出成文本. 遍历每一个标注为content的block, 把内容append并输出.

DefaultExtractor
下面再看看除了ArticleExtractor (针对news)以外, 很常用的DefaultExtractor
SimpleBlockFusionProcessor.INSTANCE.process(doc)
| BlockProximityFusion.MAX_DISTANCE_1.process(doc)
| DensityRulesClassifier.INSTANCE.process(doc);
相对比较简单, 就三步, 第二步很奇怪, 前面没有任何upstream会标注content, 那么这步就什么都不会做

SimpleBlockFusionProcessor
Merges two subsequent blocks if their text densities are equal.
遍历每一个block, 两个block的text densities相同就merge

DensityRulesClassifier
Classifies {@link TextBlock}s as content/not-content through rules that have been determined using the C4.8 machine learning algorithm, as described in the paper “Boilerplate Detection using Shallow Text Features”, particularly using text densities and link densities.
参照NumWordsRulesClassifier , 这儿实现了Paper里面的Algorithm 1 Densitometric Classifier
curr_linkDensity <= 0.333333
| prev_linkDensity <= 0.555556
| | curr_textDensity <= 9
| | | next_textDensity <= 10
| | | | prev_textDensity <= 4: BOILERPLATE
| | | | prev_textDensity > 4: CONTENT
| | | next_textDensity > 10: CONTENT
| | curr_textDensity > 9
| | | next_textDensity = 0: BOILERPLATE
| | | next_textDensity > 0: CONTENT
| prev_linkDensity > 0.555556
| | next_textDensity <= 11: BOILERPLATE
| | next_textDensity > 11: CONTENT
curr_linkDensity > 0.333333: BOILERPLATE

如果有兴趣, 你可以学习其他extractor, 或自己design合适自己的extractor.

使用Java操作MongoDB

HelloWorld程序

  学习任何程序的第一步,都是编写HelloWorld程序,我们也不例外,看下如何通过Java编写一个HelloWorld的程序。

  首先,要通过Java操作Mongodb,必须先下载Mongodb的Java驱动程序,可以在这里下载

  新建立一个Java工程,将下载的驱动程序放在库文件路径下,程序代码如下:

package com.mkyong.core;

import java.net.UnknownHostException;

import com.mongodb.BasicDBObject;

import com.mongodb.DB;

import com.mongodb.DBCollection;

import com.mongodb.DBCursor;

import com.mongodb.Mongo;

import com.mongodb.MongoException;



/**

* Java + MongoDB Hello world Example



*/

public class App {

public static void main(String[] args) {

try {

//实例化Mongo对象,连接27017端口

            Mongo mongo = new Mongo(“localhost”, 27017);

//连接名为yourdb的数据库,假如数据库不存在的话,mongodb会自动建立

            DB db = mongo.getDB(“yourdb”);

// Get collection from MongoDB, database named “yourDB”

//从Mongodb中获得名为yourColleection的数据集合,如果该数据集合不存在,Mongodb会为其新建立

            DBCollection collection = db.getCollection(“yourCollection”);

// 使用BasicDBObject对象创建一个mongodb的document,并给予赋值。

            BasicDBObject document = new BasicDBObject();

document.put(“id”, 1001);

document.put(“msg”, “hello world mongoDB in Java”);

//将新建立的document保存到collection中去

            collection.insert(document);

// 创建要查询的document

            BasicDBObject searchQuery = new BasicDBObject();

searchQuery.put(“id”, 1001);

// 使用collection的find方法查找document

            DBCursor cursor = collection.find(searchQuery);

//循环输出结果

            while (cursor.hasNext()) {

System.out.println(cursor.next());

}

System.out.println(“Done”); 

catch (UnknownHostException e) {

e.printStackTrace();

catch (MongoException e) {

e.printStackTrace();

}

}

}

  最后,输出的结果为:

{ “_id” : { “$oid” : “4dbe5596dceace565d229dc3”} , 

“id” : 1001 , “msg” : “hello world mongoDB in Java”}

Done

 
继续阅读

Jackson处理json使用记录

网上找半天找不到在原来的json后追加(append)json的写法,追加想了个。

 

package com.7mdm.json;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig.Feature;
import org.codehaus.jackson.node.ObjectNode;

import cn.edu.hdu.computer.infoIntegrate.daoentity.UserResEntity;

/**
 * 
 * @author modm 
 * jackson demo
 * api文档   http://jackson.codehaus.org/1.9.0/javadoc/index.html
 */
public class JacksonDemo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally
		try {
			/**
			 * 通过类的数据结构来读取json数据
			 */
			// UrlDAOEntity url = mapper.readValue(new File("d:\\user.json"),
			// UrlDAOEntity.class);
			// System.out.println(url.getUserHash());
			/**
			 * 通过map写入json1,会覆盖旧数据
			 */
//			Map userData = new HashMap();
//			Map nameStruct = new HashMap();
//			nameStruct.put("first", "Joe");
//			nameStruct.put("last", "Sixpack");
//			userData.put("name", nameStruct);
//			userData.put("gender", "MALE");
//			userData.put("verified", Boolean.FALSE);
//			userData.put("userImage", "Rm9vYmFyIQ==");
//			mapper.writeValue(new File("d:\\user-modified.json"), userData);

			/**
			 * 通过map来写入json2,
			 */
//			mapper.setVisibility(JsonMethod.FIELD, Visibility.ANY);
//			mapper.configure(Feature.INDENT_OUTPUT, true);// 序列化输出
//			Map userResMap = new HashMap();
//			UserResEntity ure = new UserResEntity();
//			ure.setUserId(11);
//			ure.setUrl("www.baidu.com/s?wd=齐码代码");
//			ure.setUserHash("dsfdssgfdgfsd");
//			ure.setFileAttr("txt");
//			ure.setResState("N");
//			userResMap.put(ure.getUserId(), ure);
//			OutputStream os = new FileOutputStream(new File("d:\\UserURlRes.json"),true);//append
//			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os,"UTF-8"));
//			mapper.writeValue(bw, userResMap);
			//String json = mapper.writeValueAsString(userResMap);
			//bw.write(json.replaceFirst("{", ","));

			/**
			 * 通过对象来写入json
			 */
//			mapper.setVisibility(JsonMethod.FIELD, Visibility.ANY);
//			mapper.configure(Feature.INDENT_OUTPUT, true);// 序列化输出
//			UserResEntity ure = new UserResEntity();
//			ure.setUserId(11);
//			ure.setUrl("www.baidu.com/s?wd=齐码代码");
//			ure.setUserHash("dsfdssgfdgfsd");
//			ure.setFileAttr("txt");
//			ure.setResState("N");
//			// System.out.println(mapper.writeValueAsString(userResMap));//return
//			// string
//			//FileWriter fw = new FileWriter(new File("d:\\UserURlRes.json"),true); // 用writer对象来写,可以append
//			OutputStream os = new FileOutputStream(new File("d:\\UserURlRes.json"),true);//append
//			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os,"UTF-8"));
//			mapper.writeValue(bw, ure);

/**
 * 用map方式读取json
 */
//			InputStream is=new FileInputStream(new File("d:\\UserURlRes.json"));
//			BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
//			UserResEntity userResEntity=mapper.readValue(br, UserResEntity.class);
//			Map> map = mapper.readValue(br,new TypeReference>>() { });
//			System.out.println(map.get("33").get("url")); //用map的方式得到值

/**
* 通过objectNode方式读取json,可增加键值对
 */
//			InputStream is=new FileInputStream(new File("d:\\UserURlRes.json"));
//			BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
//			ObjectNode jNode = mapper.readValue(br,ObjectNode.class);
//			jNode.with("11").put("key1","value1"); //添加键值对
//			System.out.println(jNode);
/**
 * 用factory方式读,	有点像指针	
 */

//			JsonFactory f = new JsonFactory();
//			JsonParser jp = f.createJsonParser(new File("d:\\UserURlRes.json"));
//			//
//			//
//			while (jp.nextToken() !=null) {
//				String hashField =jp.getCurrentName();
//				jp.nextToken();// move to value
//				if ("22".equals(hashField)) {
//					while (jp.nextToken() != JsonToken.END_OBJECT) {
//						String userField =jp.getCurrentName();
//						jp.nextToken();// move to value
//						if ("url".equals(userField)) {
//
//							System.out.println(jp.getText());
//						}
//					}
//				}
//				//
//			}

/**
 * 在已有json数据之后,增加键值对demo
 * 
 */
			InputStream is=new FileInputStream(new File("d:\\UserURlRes.json"));
			BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
			ObjectNode jNode = mapper.readValue(br,ObjectNode.class);
			UserResEntity ure2 = new UserResEntity();
			ure2.setUserId(44);
			ure2.setUrl("www.baidu.com/s?wd=齐码代码44");
			ure2.setUserHash("dsfdssgfdgfsd");
			ure2.setFileAttr("txt");
			ure2.setResState("N");
			jNode.putPOJO(String.valueOf(ure2.getUserId()), ure2);
			OutputStream os = new FileOutputStream(new File("d:\\UserURlRes.json"),false);//append
			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os,"UTF-8"));
			mapper.setVisibility(JsonMethod.FIELD, Visibility.ANY);
			mapper.configure(Feature.INDENT_OUTPUT, true);// 序列化输出
			mapper.writeValue(bw, jNode);

		} catch (JsonParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (JsonMappingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

JNDI连接数据库

一、数据源简介:
***********************************************************************************
在Java语言中,DataSource对象就是一个代表数据源实体的对象。一个数据源就是一个用来存储数据的工具,它可以是复杂的大型企业级数据库,也可以是简单得只有行和列的文件。数据源可以位于在服务器端,也可以位于客服端。

应用程序通过一个连接来访问数据源,那么一个DataSource对象就是用于提供连接数据源的工具。DataSource接口提供了两个方法用于建立和数据源的连接
使用DataSource对象建立和数据库的连接比起使用DriverManager接口更加高效,虽然两者的使用范围都很相似,并且都提供了方法用于建立和数据库的连接,设置连接的最大超时时间,获取流、登录。

但两者之间的区别更加明显。和DriverManager不同,一个DataSource对象能够识别和描述它所代表的数据源的属性,而且DataSource对象的工作和JNDI(Javatm Naming and Directory Interfaceti)具有密切的关系,DataSource的建立、发布、独立于应用程序的管理都依靠JNDI技术。

在JDBC2.0或JDBC3.0中,所有的数据库驱动程序提供商必须提供一个实现了DataSource接口的类,要使用数据源必须首先在JNDI中注册该数据源对象。
如果在JNDI中注册了数据源对象,将会比起使用DriverManager来具有两个方面的优势:
首先程序不需要像使用DriverManager一样对加载的数据库驱动程序信息进行硬编码,程序员可以选择先在JNDI中注册这个数据源对象,然后在程序中使用一个逻辑名称来引用它,JNDI会自动根据你给出的名称找到与这个名称绑定的DataSource对象。然后就可以使用这个DataSource对象来建立和具体数据库的连接了。

其次,使用实现了DataSource接口的类所具有的第二个优势体现在连接池和分布式事务上。连接池通过对连接的复用而不是新建一个物理连接来显著地提高程序的效率。从而适用于任务繁忙、负担繁重的企业级分布式事务。
***********************************************************************************

二、JNDI简介:
***********************************************************************************
JNDI是用于向Java程序提供目录和命名功能的API。它被设计成独立于特定的目录服务,所以各种各样的目录都可以通过相同的方式进行访问。

可以简单地把JNDI理解为一种将对象和名字绑定的技术,对象工厂负责生产出对象,这些对象都和惟一的名字绑定。外部程序可以通过名字来获取对某个对象的引用。

在Intranets(企业内部网)和Internates(互联网)中目录服务(Directory service)扮演了一个非常重要的角色,它能够在众多的用户、机器、网络、服务、应用程序中访问各种各样的信息。目录服务提供了一系列的命名措施,用人类可以理解的命名方式来刻画各种各样的实体之间的关系

一个企业式计算环境(computing environment)通常是由若干代表不同部分的命名复合而成。比如在一个企业级环境中DNS(Domain Name System)通常被当成顶层的命名方案(top-level namein facility)区分不同的部门或组织。而这些部门或组织自己又可以使用诸如LADP或NDS的目录服务

从用户的角度来看,这些都是由不同的命名方案构成的复合名称。URL就是一个很典型的例子,它由多个命名方案构成。使用目录服务的应用程序必须支持这种复合构成方式

使用目录服务API的Java开发人员获得的好处不仅在于API独立于特定的目录或命名服务,而且可以通过多层的命名方案无缝访问(seamless acess)目录对象。实际上,任何的应用程序都可以将自身的对象和特定的命名绑定起来,这种功能可以使到任何的Java程序查找和获取任何类型的对象

终端用户可以方便地使用逻辑名称从而轻易地在网络上查找和识别各种不同的对象,目录服务的开发人员可以使用API方便地在不同的客服端之间切换而不需要作任何更改
***********************************************************************************

三、数据源和连接池的关系:
***********************************************************************************
JDBC2.0提供了javax.sql.DataSource接口,它负责建立与数据库的连接,在应用程序访问数据库时不需要编写连接数据库的代码,可以直接从数据源获得数据库连接。

在DataSource中事先建立了多个数据库连接,这些数据库连接保存在连接池(Connect Pool)中。Java程序访问数据库时,只需要从连接池中取出空闲状态的数据库连接;当程序访问数据库结束,再将数据库连接放回连接池。
***********************************************************************************

四、数据源和JNDI的关系:
***********************************************************************************
DataSource对象是由Tomcat提供的,因此不能在程序中采用创建一个实例的方式来生产DataSource对象,而需要采用Java的另一个技术JNDI,来获得DataSource对象的引用。

Tomcat把DataSource作为一种可以配置的JNDI资源来处理。生成DataSource对象的工厂为org.apache.commons.dbcp.BasicDataSourceFactory。

在javax.naming包中提供了Context接口,该接口提供了将对象和名字绑定,以及通过名字检索对象的方法。Context中的主要方法有:
bind(String name,Object object):将对象与一个名字绑定
lookup(String name):返回与指定的名字绑定的对象
***********************************************************************************

五、Tomcat中数据源的配置:
***********************************************************************************
数据源的配置涉及修改server.xml和web.xml,在server.xml中加入定义数据源的元素<Resource>,在web.xml加入<resource-ref>元素,声明该Web应用所引用的数据

A.在server.xml中加入<Resource>元素:<Resource>元素用来定义JNDI Resource。

属性 描述
name 指定Resource的JNDI名字
auth 指定管理Resource的Manager,它有两个可选值:Container、Application
type 指定Resource所属的Java类名

<Resource name = “jdbc/BookDb”
auth = “Container”
type = “javax.sql.DataSource” />

B.在<Resource>元素中加入<ResourceParams>元素:<ResourceParams>元素用来指定各种参数值

属性 描述
factory 指定生成的DataResource的factory类名
maxActive 指定数据库连接池中处于活动状态的最大连接数目,0表示不受限制
maxIdle 指定数据库连接池中处于空闲状态的最大连接数目,0表示不受限制
maxWait 指定连接池中连接处于空闲状态的最长时间,超过会抛出异常,-1表示无限
username 指定连接数据库的用户名
password 指定连接数据库的口令
driverClassName 指定连接数据库的JDBC驱动程序
url 指定连接数据库的URL

<ResourceParams name = “jdbc/BookDb”>

<parameter>
<name>factory</name>
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
</parameter>

<parameter>
<name>maxActive</name>
<value>100</value>
</parameter>

<parameter>
<name>maxIdle</name>
<value>30</value>
</parameter>

<parameter>
<name>maxWait</name>
<value>10000</value>
</parameter>

<parameter>
<name>username</name>
<value>user</value>
</parameter>

<parameter>
<name>password</name>
<value>1234</value>
</parameter>

<parameter>
<name>driverClassName</name>
<value>com.mysql.jdbc.Driver</value>
</parameter>

<parameter>
<name>url</name>
<value>jdbc:mysql//localhost:3306/BookDb?autoReconnect=true</value>
</parameter>

</ResourceParams>

C.在web.xml中加入<resource-ref>元素:<resource-ref>元素表示在Web应用中引用JNDI资源

属性 描述
description 对所引用的资源的说明
res-ref-name 指定所引用资源的JNDI名字,与<Resource>元素中的name属性对应
res-type 指定所引用资源的类名字,与<Resource>元素中的type属性对应
res-auth 指定所引用资源的Manager,与<Resource>元素中的auth属性对应

***********************************************************************************

六、在Web应用中使用数据源:
***********************************************************************************
javax.naming.Context提供了查找JNDI Resource的接口,可以通过三个步骤来使用数据源对象:

A.获得对数据源的引用:
    Context ctx = new InitalContext();
DataSource ds = (DataSource)ctx.lookup(“java:comp/env/jdbc/BookDb”);

B.获得数据库连接对象:
Connection con = ds.getConnection();
  
C.返回数据库连接到连接池:
con.close();

在连接池中使用close()方法和在非连接池中使用close()方法的区别是:前者仅仅是把数据库连接对象返回到数据库连接池中,是连接对象又恢复到空闲状态,而非关闭数据库连接,而后者将直接关闭和数据库的连接
***********************************************************************************

七、发布使用数据源的Web应用:
***********************************************************************************
如果直接同JDBC访问数据库,可以把JDBC驱动程序拷贝到Web应用的WEB-INF/lib目录或者Tomcat安装目录下的common/lib目录下。

如果通过数据源访问数据库,由于数据源由Servlet容器创建并维护,所以必须把JDBC驱动程序拷贝到Tomcat安装目录下的common/lib目录下,确保Servlet容器能够访问驱动程序。