一次java war包在tomcat启动后服务乱码问题解决

 java, tomcat  一次java war包在tomcat启动后服务乱码问题解决已关闭评论
3月 312021
 

一个运行于tomcat下的war包,忽然服务出现了乱码,问题排查如下:

  • 检查点一:
  • 1. 检查tomcat 的config目录下server.xml 文件:

<Connector ……
redirectPort=”443″ URIEncoding=”UTF-8″ />, 发现问题不在这里

 

  • 检查点二:
  • 2.1. 命令行下执行: ps -ef | grep java  获取java运行所在的pid为6724

[root@ip-172-xx-xx]# ps -ef | grep java
root 6724 1 3 14:30 pts/0 00:01:04 /usr/local/jdk/bin/java –

  • 2.2. 查看问题机器的jvm的编码格式

[root@ip-172-xxxx]# jinfo 6724 |grep enc
sun.jnu.encoding = ANSI_X3.4-1968
file.encoding.pkg = sun.io
sun.io.unicode.encoding = UnicodeLittle
file.encoding = ANSI_X3.4-1968

果然问题出在这里。 可以在tomcat的bin目录下的catalina.sh,添加给 JAVA_OPTS 的多添加两个参数

-Dsun.jnu.encoding=UTF-8 -Dfile.encoding=UTF-8

重启tomcat,DONE!

 

 

 

Arthas简介

 Arthas  Arthas简介已关闭评论
2月 112019
 

Arthas真是好用,项目地址:https://github.com/alibaba/arthas , 回想btrace时代真是辛苦。以下文字来自官方文档摘录。

Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  5. 是否有一个全局视角来查看系统的运行状况?
  6. 有什么办法可以监控到JVM的实时运行状态?

Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。

快速开始

使用arthas-boot(推荐)

下载arthas-boot.jar,然后用java -jar的方式启动:

wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar

打印帮助信息:

java -jar arthas-boot.jar -h
  • 如果下载速度比较慢,可以使用aliyun的镜像:java -jar arthas-boot.jar –repo-mirror aliyun –use-http

使用as.sh

Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,请复制以下内容,并粘贴到命令行中,敲 回车 执行即可:

curl -L https://alibaba.github.io/arthas/install.sh | sh

上述命令会下载启动脚本文件 as.sh 到当前目录,你可以放在任何地方或将其加入到 $PATH 中。

直接在shell下面执行./as.sh,就会进入交互界面。

也可以执行./as.sh -h来获取更多参数信息。

快速入门

1. 启动Demo

				
wget https://alibaba.github.io/arthas/arthas-demo.jar
java -jar arthas-demo.jar

arthas-demo是一个简单的程序,每隔一秒生成一个随机数,再执行质因式分解,并打印出分解结果。

arthas-demo源代码:查看

2. 启动arthas

在命令行下面执行(使用和目标进程一致的用户启动,否则可能attach失败):

				
wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar

  • 执行该程序的用户需要和目标进程具有相同的权限。比如以admin用户来执行:sudo su admin && java -jar arthas-boot.jar 或 sudo -u admin -EH java -jar arthas-boot.jar。
  • 如果attach不上目标进程,可以查看~/logs/arthas/ 目录下的日志。
  • 如果下载速度比较慢,可以使用aliyun的镜像:java -jar arthas-boot.jar –repo-mirror aliyun –use-http
  • java -jar arthas-boot.jar -h 打印更多参数信息。

选择应用java进程:

				
$ $ java -jar arthas-boot.jar
* [1]: 35542
[2]: 71560 arthas-demo.jar

Demo进程是第2个,则输入2,再输入回车/enter。Arthas会attach到目标进程上,并输出日志:

				
[INFO] Try to attach process 71560
[INFO] Attach process 71560 success.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki: https://alibaba.github.io/arthas
version: 3.0.5.20181127201536
pid: 71560
time: 2018-11-28 19:16:24
$

3. 查看dashboard

输入dashboard,按enter/回车,会展示当前进程的信息,按ctrl+c可以中断执行。

				
$ dashboard
ID NAME GROUP PRIORI STATE %CPU TIME INTERRU DAEMON
17 pool-2-thread-1 system 5 WAITIN 67 0:0 false false
27 Timer-for-arthas-dashb system 10 RUNNAB 32 0:0 false true
11 AsyncAppender-Worker-a system 9 WAITIN 0 0:0 false true
9 Attach Listener system 9 RUNNAB 0 0:0 false true
3 Finalizer system 8 WAITIN 0 0:0 false true
2 Reference Handler system 10 WAITIN 0 0:0 false true
4 Signal Dispatcher system 9 RUNNAB 0 0:0 false true
26 as-command-execute-dae system 10 TIMED_ 0 0:0 false true
13 job-timeout system 9 TIMED_ 0 0:0 false true
1 main main 5 TIMED_ 0 0:0 false false
14 nioEventLoopGroup-2-1 system 10 RUNNAB 0 0:0 false false
18 nioEventLoopGroup-2-2 system 10 RUNNAB 0 0:0 false false
23 nioEventLoopGroup-2-3 system 10 RUNNAB 0 0:0 false false
15 nioEventLoopGroup-3-1 system 10 RUNNAB 0 0:0 false false
Memory used total max usage GC
heap 32M 155M 1820M 1.77% gc.ps_scavenge.count 4
ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(m 166
ps_survivor_space 4M 5M 5M s)
ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.count 0
nonheap 20M 23M -1 gc.ps_marksweep.time( 0
code_cache 3M 5M 240M 1.32% ms)
Runtime
os.name Mac OS X
os.version 10.13.4
java.version 1.8.0_162
java.home /Library/Java/JavaVir
tualMachines/jdk1.8.0
_162.jdk/Contents/Hom
e/jre

4. 通过thread命令来获取到arthas-demo进程的Main Class

thread 1会打印线程ID 1的栈,通常是main函数的线程。

				
$ thread 1 | grep 'main('
at demo.MathGame.main(MathGame.java:17)

5. 通过jad来反编绎Main Class

				
$ jad demo.MathGame
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
Location:
/tmp/arthas-demo.jar
/*
* Decompiled with CFR 0_132.
*/
package demo;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class MathGame {
private static Random random = new Random();
private int illegalArgumentCount = 0;
public static void main(String[] args) throws InterruptedException {
MathGame game = new MathGame();
do {
game.run();
TimeUnit.SECONDS.sleep(1L);
} while (true);
}
public void run() throws InterruptedException {
try {
int number = random.nextInt();
List<Integer> primeFactors = this.primeFactors(number);
MathGame.print(number, primeFactors);
}
catch (Exception e) {
System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage());
}
}
public static void print(int number, List<Integer> primeFactors) {
StringBuffer sb = new StringBuffer("" + number + "=");
Iterator<Integer> iterator = primeFactors.iterator();
while (iterator.hasNext()) {
int factor = iterator.next();
sb.append(factor).append('*');
}
if (sb.charAt(sb.length() - 1) == '*') {
sb.deleteCharAt(sb.length() - 1);
}
System.out.println(sb);
}
public List<Integer> primeFactors(int number) {
if (number < 2) {
++this.illegalArgumentCount;
throw new IllegalArgumentException("number is: " + number + ", need >= 2");
}
ArrayList<Integer> result = new ArrayList<Integer>();
int i = 2;
while (i <= number) {
if (number % i == 0) {
result.add(i);
number /= i;
i = 2;
continue;
}
++i;
}
return result;
}
}
Affect(row-cnt:1) cost in 970 ms.


常用命令举例:

watch

通过watch命令来查看demo.MathGame#primeFactors函数的返回值:

				
$ watch demo.MathGame primeFactors returnObj
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 107 ms.
ts=2018-11-28 19:22:30; [cost=1.715367ms] result=null
ts=2018-11-28 19:22:31; [cost=0.185203ms] result=null
ts=2018-11-28 19:22:32; [cost=19.012416ms] result=@ArrayList[
@Integer[5],
@Integer[47],
@Integer[2675531],
]
ts=2018-11-28 19:22:33; [cost=0.311395ms] result=@ArrayList[
@Integer[2],
@Integer[5],
@Integer[317],
@Integer[503],
@Integer[887],
]
ts=2018-11-28 19:22:34; [cost=10.136007ms] result=@ArrayList[
@Integer[2],
@Integer[2],
@Integer[3],
@Integer[3],
@Integer[31],
@Integer[717593],
]
ts=2018-11-28 19:22:35; [cost=29.969732ms] result=@ArrayList[
@Integer[5],
@Integer[29],
@Integer[7651739],
]

更多的功能可以查看进阶使用

5. 退出arthas

如果只是退出当前的连接,可以用quit或者exit命令。Attach到目标进程上的arthas还会继续运行,端口会保持开放,下次连接时可以直接连接上。

如果想完全退出arthas,可以执行shutdown命令。

watch

方法执行数据观测

让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 OGNL 表达式进行对应变量的查看。

参数说明

watch 的参数比较多,主要是因为它能在 4 个不同的场景观察对象

参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
express 观察表达式
condition-express 条件表达式
[b] 方法调用之前观察
[e] 方法异常之后观察
[s] 方法返回之后观察
[f] 方法结束之后(正常返回和异常返回)观察
[E] 开启正则表达式匹配,默认为通配符匹配
[x:] 指定输出结果的属性遍历深度,默认为 1

这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写”{params,returnObj}”,只要是一个合法的 ognl 表达式,都能被正常支持。

观察的维度也比较多,主要体现在参数 advice 的数据结构上。Advice 参数最主要是封装了通知节点的所有信息。请参考表达式核心变量中关于该节点的描述。

特别说明

  • watch 命令定义了4个观察事件点,即 -b 方法调用前,-e 方法异常后,-s 方法返回后,-f 方法结束后
  • 4个观察事件点 -b、-e、-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出
  • 这里要注意方法入参和方法出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表方法入参外,其余事件都代表方法出参
  • 当使用 -b 时,由于观察事件点是在方法调用前,此时返回值或异常均不存在

使用参考

启动 Demo

启动快速入门里的arthas-demo。

观察方法出参和返回值

								
$ watch demo.MathGame primeFactors "{params,returnObj}" -x 2
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 44 ms.
ts=2018-12-03 19:16:51; [cost=1.280502ms] result=@ArrayList[
@Object[][
@Integer[535629513],
],
@ArrayList[
@Integer[3],
@Integer[19],
@Integer[191],
@Integer[49199],
],
]

观察方法入参

								
$ watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 50 ms.
ts=2018-12-03 19:23:23; [cost=0.0353ms] result=@ArrayList[
@Object[][
@Integer[-1077465243],
],
null,
]

  • 对比前一个例子,返回值为空(事件点为方法执行前,因此获取不到返回值)

同时观察方法调用前和方法返回后

								
$ watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 46 ms.
ts=2018-12-03 19:29:54; [cost=0.01696ms] result=@ArrayList[
@Object[][
@Integer[1544665400],
],
@MathGame[
random=@Random[java.util.Random@522b408a],
illegalArgumentCount=@Integer[13038],
],
null,
]
ts=2018-12-03 19:29:54; [cost=4.277392ms] result=@ArrayList[
@Object[][
@Integer[1544665400],
],
@MathGame[
random=@Random[java.util.Random@522b408a],
illegalArgumentCount=@Integer[13038],
],
@ArrayList[
@Integer[2],
@Integer[2],
@Integer[2],
@Integer[5],
@Integer[5],
@Integer[73],
@Integer[241],
@Integer[439],
],
]

  • 参数里-n 2,表示只执行两次
  • 这里输出结果中,第一次输出的是方法调用前的观察表达式的结果,第二次输出的是方法返回后的表达式的结果
  • 结果的输出顺序和事件发生的先后顺序一致,和命令中 -s -b 的顺序无关

调整-x的值,观察具体的方法参数值

								
$ watch demo.MathGame primeFactors "{params,target}" -x 3
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 58 ms.
ts=2018-12-03 19:34:19; [cost=0.587833ms] result=@ArrayList[
@Object[][
@Integer[47816758],
],
@MathGame[
random=@Random[
serialVersionUID=@Long[3905348978240129619],
seed=@AtomicLong[3133719055989],
multiplier=@Long[25214903917],
addend=@Long[11],
mask=@Long[281474976710655],
DOUBLE_UNIT=@Double[1.1102230246251565E-16],
BadBound=@String[bound must be positive],
BadRange=@String[bound must be greater than origin],
BadSize=@String[size must be non-negative],
seedUniquifier=@AtomicLong[-3282039941672302964],
nextNextGaussian=@Double[0.0],
haveNextNextGaussian=@Boolean[false],
serialPersistentFields=@ObjectStreamField[][isEmpty=false;size=3],
unsafe=@Unsafe[sun.misc.Unsafe@2eaa1027],
seedOffset=@Long[24],
],
illegalArgumentCount=@Integer[13159],
],
]

  • -x表示遍历深度,可以调整来打印具体的参数和结果内容,默认值是1。

条件表达式的例子

								
$ watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0"
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 68 ms.
ts=2018-12-03 19:36:04; [cost=0.530255ms] result=@ArrayList[
@Integer[-18178089],
@MathGame[demo.MathGame@41cf53f9],
]

  • 只有满足条件的调用,才会有响应。

观察异常信息的例子

								
$ watch demo.MathGame primeFactors "{params[0],throwExp}" -e -x 2
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 62 ms.
ts=2018-12-03 19:38:00; [cost=1.414993ms] result=@ArrayList[
@Integer[-1120397038],
java.lang.IllegalArgumentException: number is: -1120397038, need >= 2
at demo.MathGame.primeFactors(MathGame.java:46)
at demo.MathGame.run(MathGame.java:24)
at demo.MathGame.main(MathGame.java:16)
,
]

  • -e表示抛出异常时才触发
  • express中,表示异常信息的变量是throwExp

按照耗时进行过滤

								
$ watch demo.MathGame primeFactors '{params, returnObj}' '#cost>200' -x 2
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 66 ms.
ts=2018-12-03 19:40:28; [cost=2112.168897ms] result=@ArrayList[
@Object[][
@Integer[2141897465],
],
@ArrayList[
@Integer[5],
@Integer[428379493],
],
]

  • #cost>200(单位是ms)表示只有当耗时大于200ms时才会输出,过滤掉执行时间小于200ms的调用

观察当前对象中的属性

如果想查看方法运行前后,当前对象中的属性,可以使用target关键字,代表当前对象

								
$ watch demo.MathGame primeFactors 'target'
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 52 ms.
ts=2018-12-03 19:41:52; [cost=0.477882ms] result=@MathGame[
random=@Random[java.util.Random@522b408a],
illegalArgumentCount=@Integer[13355],
]

然后使用target.field_name访问当前对象的某个属性

								
$ watch demo.MathGame primeFactors 'target.illegalArgumentCount'
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 67 ms.
ts=2018-12-03 20:04:34; [cost=131.303498ms] result=@Integer[8]
ts=2018-12-03 20:04:35; [cost=0.961441ms] result=@Integer[8]

Java 接入 Google Authenticator

 java  Java 接入 Google Authenticator已关闭评论
11月 122018
 

转自:https://www.jianshu.com/p/de903c074d77

挺好的文章介绍google authenticator,转自:https://www.jianshu.com/p/de903c074d77

在网络攻击日益泛滥的今天, 用户的密码可能会因为各种原因泄漏. 而一些涉及用户重要数据的服务, 如 QQ, 邮箱, 银行, 购物等等. 一但被有心人利用, 那么除了自己隐私泄漏的风险外, 还存在自己身份被冒充的危害, 更有可能而导致极其严重的结果. 为此谷歌推出了Google Authenticator服务, 其原理是在登录时除了输入密码外, 还需根据Google Authenticator APP输入一个实时计算的验证码. 凭借此验证码, 即使在密码泄漏的情况下, 他人也无法登录你的账户

相关原理

Google Authenticator使用了一种基于 ** 时间 ** 的TOTP算法, 其中时间的选取为自1970-01-01 00:00:00以来的毫秒数除以30与 客户端及服务端约定的 ** 密钥 ** 进行计算, 计算结果为一个 **6 位数的字符串 *( 首位数可能为 0, 所以为字符串 *), 所以在Google Authenticator中我们可以看见验证码每个 30 秒就会刷新一次. 更多详情可查看 Google 账户两步验证的工作原理 一文

实现思路

由上可知, 生成验证码有俩个重要的参数, 其一为 ** 客户端与服务端约定的密钥 **, 其二便为 **30 秒的个数 **

/**
 * 随机生成一个密钥
 */ public static String createSecretKey() {
    SecureRandom random = new SecureRandom(); byte[] bytes = new byte[20];
    random.nextBytes(bytes);
    Base32 base32 = new Base32();
    String secretKey = base32.encodeToString(bytes); return secretKey.toLowerCase();
}
//1970-01-01 00:00:00 以来的毫秒数除以 30  long time = System.currentTimeMillis() / 1000 / 30;

根据这两个参数就可以生成一个验证码

/**
 * 根据密钥获取验证码
 * 返回字符串是因为验证码有可能以 0 开头
 * @param secretKey 密钥
 * @param time      第几个 30 秒 System.currentTimeMillis() / 1000 / 30
 */ public static String getTOTP(String secretKey, long time) {
    Base32 base32 = new Base32(); byte[] bytes = base32.decode(secretKey.toUpperCase());
    String hexKey = Hex.encodeHexString(bytes);
    String hexTime = Long.toHexString(time); return TOTP.generateTOTP(hexKey, hexTime, "6");
}

因为Google Authenticator(* 以下简称 APP*) 计算验证码也需要 ** 密钥 ** 的参与, 而时间 APP 则会在本地获取, 所以我们需要将 ** 密钥保存在 APP 中 **, 同时为了与其他账户进行区分, 除了密钥外, 我们还需要录入 ** 服务名称 , 用户账户 ** 信息. 而为了方便用户信息的录入, 我们一般将所有信息生成一张二维码图片, 让用户通过扫码自动填写相关信息

/**
 * 生成 Google Authenticator 二维码所需信息
 * Google Authenticator 约定的二维码信息格式 : otpauth://totp/{issuer}:{account}?secret={secret}&issuer={issuer}
 * 参数需要 url 编码 + 号需要替换成 %20
 * @param secret  密钥 使用 createSecretKey 方法生成
 * @param account 用户账户 如: [email protected] 138XXXXXXXX
 * @param issuer  服务名称 如: Google Github 印象笔记
 */ public static String createGoogleAuthQRCodeData(String secret, String account, String issuer) {
    String qrCodeData = "otpauth://totp/%s?secret=%s&issuer=%s"; try { return String.format(qrCodeData, URLEncoder.encode(issuer + ":" + account, "UTF-8").replace("+", "%20"), URLEncoder.encode(secret, "UTF-8")
                .replace("+", "%20"), URLEncoder.encode(issuer, "UTF-8").replace("+", "%20"));
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } return "";
}

此时再根据上述信息生成二维码, 二维码生成方式可参考以下两种方案

此时选择使用Java的方式返回一个二维码图片流

/**
* 将二维码图片输出到一个流中
* @param content 二维码内容
* @param stream  输出流
* @param width   宽
* @param height  高
*/ public static void writeToStream(String content, OutputStream stream, int width, int height) throws WriterException, IOException {
  BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
  MatrixToImageWriter.writeToStream(bitMatrix, format, stream);
}

扫描二维码

扫描二维码

扫描成功后会新增一栏验证码信息

扫描后新增一栏信息

再让用户输入验证码, 与服务端进行校验, 如果校验通过, 则表明用户可以完好使用该功能
因为验证码是使用基于时间的TOTP算法, 依赖于客户端与服务端时间的一致性. 如果客户端时间与服务端时间相差过大, 那在用户没有同步时间的情况下, 永远与服务端进行匹配. 同时服务端也有可能出现时间偏差的情况, 这样反而导致时间正确的用户校验无法通过
为了解决这种情况, 我们可以使用 ** 时间偏移量 ** 来解决该问题,Google Authenticator验证码的时间参数为1970-01-01 00:00:00 以来的毫秒数除以 30, 所以每 30 秒就会更新一次. 但是我们在后台进行校验时, 除了与当前生成的二维码进行校验外, 还会对当前时间参数 ** 前后偏移量 ** 生成的验证码进行校验, 只要其中任意一个能够校验通过, 就代表该验证码是有效的

/** 时间前后偏移量 */ private static final int timeExcursion = 3; /**
 * 校验方法
 * @param secretKey 密钥
 * @param code      用户输入的 TOTP 验证码
 */ public static boolean verify(String secretKey, String code) { long time = System.currentTimeMillis() / 1000 / 30; for (int i = -timeExcursion; i <= timeExcursion; i++) {
        String totp = getTOTP(secretKey, time + i); if (code.equals(totp)) { return true;
        }
    } return false;
}

其他说明

根据以上代码我们可以简单的创建一个Google Authenticator的应用. 但是与此同时, 我们也发现Google Authenticator严重依赖手机, 又因为Google Authenticator** 没有同步功能 **, 所以如果用户一不小心删除了记录信息, 或者 APP 被卸载, 手机系统重装等情况. 就会导致Google Authenticator成为使用者的障碍. 此时我们可以使用 Authy 这款支持 ** 同步功能 ** 的 APP 以解决删除, 卸载, 重装等问题. 同时 Authy 也存在 Chrome 插件 版本, 用于解决在手机丢失的情况下获取验证码.
除了 Authy 这个选择外, 我们还可以使用 ** 备用验证码 ** 的机制用户用于解决上述问题. 即在用户绑定Google Authenticator成功后自动为用户生成多个 ** 备用验证码 **, 然后在前台显示. 并让用户进行保存, 再让用户使用备用验证码进行校验, 以确保用户保存成功, 可以参考 ** 印象笔记 ** 的用法 如何开启印象笔记登录两步验证?

以上所使用的代码可在 Google-Authenticator 中查看

另外本文同时参考了以下资料




作者:jnil
链接:https://www.jianshu.com/p/de903c074d77
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

java文件读取的路径问题解惑和最佳实践,避免FileNotFoundException

 java  java文件读取的路径问题解惑和最佳实践,避免FileNotFoundException已关闭评论
1月 042018
 

   网上找到的一篇讲解java 读取文件路径比较清楚的文章, 分享下:http://blog.csdn.net/aitangyong/article/details/36471881 

     使用java读取jar或war下的配置文件,是开发者经常需要处理的事情,大家是不是经常遇到FileNotFoundException呢?java读取文件的方式也有很多,比如new File(),Class.getResource(),ClassLoader.getResource(),这些方式的差别是什么呢?开源框架struts2的ClassLoaderUtils和Spring提供ClassPathResource,都提供了对资源读取进行封装的工具类,你是否了解他们的实现原理呢?本文结合网上的一些博客和自己的理解,和大家一起讨论下java的文件读取问题。

1.使用new File()的问题

File是java.io包下的基础类,代表硬盘上的一个文件或者目录,我们可以使用绝对路径来构造,也可以使用相对路径来构造。

工程在硬盘和eclipse的目录结构如下:






在eclipse中运行上面的程序,发现2种方式都是能够正确读取文件的,不会抛FileNotFoundException。

使用绝对路径,虽然定位很清晰,但是不灵活。比如你将上面的工程放到D盘下,就必须要修改绝对路径路径,这显然很不方便。使用相对路径则跟工程所在的硬盘路径无关,直接导入eclipse中运行,就能够正确读取文件内容。而且实际情况是,很多时候我们并不知道文件的绝对路径,这会因为部署环境的不同而不同。比如将制作好的war放到tomcat或jboss容器下运行,很显然绝对路径是不同的,而我们的代码事先并不知道。

那么使用相对路径呢?很遗憾,也同样存在很多问题。File是java.io包的基础类,java.io 包中的类总是根据当前用户目录来分析相对路径名。也就是说以下2种方式是等价的,

[java] view plain copy

  1. File file1 = new File(“demo.txt”);  
  2.           
  3. String asbPath = System.getProperty(“user.dir”) + “/demo.txt”;  
  4. File file2 = new File(asbPath);  

也就是说相对路径是否好使,取决于user.dir的值。系统属性 user.dir是JVM启动的时候设置的,通常是 Java 虚拟机的调用目录,即执行java命令所在的目录。

对于tomcat/jboss容器,user.dir是%home/bin%/目录,因为这个目录就是我们启动web容器的地方。也就是说,user.dir也是可变的,不固定的。显然使用这种方式跟绝对路径没有什么本质差别,都是不推荐的。顺便提一下,我们在eclipse中运行程序的时候,eclipse会将user.dir的值设置为工程的根目录,在我们的例子中,user.dir是c:/workspace/path_project/

可以得出结论:使用java.io.File读取文件,无论是相对路径,还是绝对路径都不是好的做法,能不使用就不要使用

2.使用Class.getResource()或ClassLoader.getResource()

这2个方法用来读取jar包中或者classpath下的资源文件。以下方式都能够正确的定位文件
[java] view plain copy

  1. TestClass.class.getResource(“test.txt”);  
  2. TestClass.class.getResource(“/net/aty/test.txt”);  
  3.           
  4. TestClass.class.getClassLoader().getResource(“net/aty/test.txt”);  

Class.getResource()有2种方式,绝对路径和相对路径。绝对路径以/开头,从classpath或jar包根目录下开始搜索;

相对路径是相对当前class所在的目录,允许使用..或.来定位文件。ClassLoader.getResource()只能使用绝对路径,而且不用以/开头。

这两种方式读取资源文件,不会依赖于user.dir,也不会依赖于具体部署的环境,是推荐的做法。

3.使用Class或ClassLoader.getResource()的相对路径和绝对路径问题

无论是相对路径还是绝对路径,都是推荐的做法。考虑下这样的场景,如果a.jar中的类,需要读取b.jar中的资源文件怎么实现呢?
用相对路径和绝对路径都可以吗?
制作b.jar,并将它加入到eclipse工程的build path下,如下图

[java] view plain copy

  1. package net.aty;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.InputStreamReader;  
  5. import java.net.URL;  
  6.   
  7. public class TestClass  
  8. {  
  9.   
  10.     public static void main(String[] args) throws Exception  
  11.     {  
  12.         // 使用相对路径,正常读取b.jar中的文件  
  13.         showContent(TestClass.class.getResource(“b.txt”));  
  14.   
  15.         // 使用相对路径,正常读取b.jar中的文件  
  16.         showContent(TestClass.class.getResource(“test/c.txt”));  
  17.   
  18.         // 使用绝对对路径,正常读取b.jar中的文件  
  19.         showContent(TestClass.class.getResource(“/net/a.txt”));  
  20.   
  21.         // 使用绝对对路径,正常读取b.jar中的文件  
  22.         showContent(TestClass.class.getClassLoader().getResource(“net/aty/test/c.txt”));  
  23.   
  24.         // 使用相对路径,正常读取本jar中的same.txt  
  25.         showContent(TestClass.class.getResource(“../same.txt”));  
  26.           
  27.         // 错误  
  28.         showContent(TestClass.class.getResource(“../a.txt”));  
  29.     }  
  30.   
  31.     public static void showContent(URL url) throws Exception  
  32.     {  
  33.         BufferedReader br = new BufferedReader(new InputStreamReader(  
  34.                 url.openStream()));  
  35.   
  36.         StringBuilder contentHolder = new StringBuilder();  
  37.   
  38.         String lineContent = null;  
  39.   
  40.         while ((lineContent = br.readLine()) != null)  
  41.         {  
  42.             contentHolder.append(lineContent);  
  43.         }  
  44.   
  45.         br.close();  
  46.   
  47.         System.out.println(“content=” + contentHolder);  
  48.   
  49.     }  
  50. }  

可以得出结论:

使用相对路径或绝对路径都能读取本jar或其他jar中的资源文件。但区别是,读取本jar包中的文件支持..这种写法,但是不能通过..读取其他jar下的文件。

4.spring框架的ClassPathResource实现

[java] view plain copy

  1. /** 
  2.      * This implementation opens an InputStream for the given class path resource. 
  3.      * @see java.lang.ClassLoader#getResourceAsStream(String) 
  4.      * @see java.lang.Class#getResourceAsStream(String) 
  5.      */  
  6.     public InputStream getInputStream() throws IOException {  
  7.         InputStream is;  
  8.         if (this.clazz != null) {  
  9.             is = this.clazz.getResourceAsStream(this.path);  
  10.         }  
  11.         else {  
  12.             is = this.classLoader.getResourceAsStream(this.path);  
  13.         }  
  14.         if (is == null) {  
  15.             throw new FileNotFoundException(getDescription() + ” cannot be opened because it does not exist”);  
  16.         }  
  17.         return is;  
  18.     }  

可以看出spring提供的ClassPathResource,底层使用的就是Class.getResource或ClassLoader.getResource()。spring提供的读取文件API功能,自然是与JDK一致。

[java] view plain copy

  1. public class TestFile  
  2. {  
  3.     public static void main(String[] args) throws Exception  
  4.     {  
  5.         File absoluteFile = new File(“c:/workspace/path_project/demo.txt”);  
  6.         File relativeFile = new File(“demo.txt”);  
  7.   
  8.         showFileContent(absoluteFile);  
  9.   
  10.         showFileContent(relativeFile);  
  11.   
  12.     }  
  13.   
  14.     public static void showFileContent(File file) throws Exception  
  15.     {  
  16.         BufferedReader br = new BufferedReader(new FileReader(file));  
  17.   
  18.         StringBuilder contentHolder = new StringBuilder();  
  19.   
  20.         String lineContent = null;  
  21.   
  22.         while ((lineContent = br.readLine()) != null)  
  23.         {  
  24.             contentHolder.append(lineContent);  
  25.         }  
  26.   
  27.         br.close();  
  28.   
  29.         System.out.println(“content=” + contentHolder);  
  30.   
  31.     }  
  32.   
  33. }  

工程在硬盘和eclipse的目录结构如下:





在eclipse中运行上面的程序,发现2种方式都是能够正确读取文件的,不会抛FileNotFoundException。

使用绝对路径,虽然定位很清晰,但是不灵活。比如你将上面的工程放到D盘下,就必须要修改绝对路径路径,这显然很不方便。使用相对路径则跟工程所在的硬盘路径无关,直接导入eclipse中运行,就能够正确读取文件内容。而且实际情况是,很多时候我们并不知道文件的绝对路径,这会因为部署环境的不同而不同。比如将制作好的war放到tomcat或jboss容器下运行,很显然绝对路径是不同的,而我们的代码事先并不知道。

那么使用相对路径呢?很遗憾,也同样存在很多问题。File是java.io包的基础类,java.io 包中的类总是根据当前用户目录来分析相对路径名。也就是说以下2种方式是等价的,

[java] view plain copy

  1. File file1 = new File(“demo.txt”);  
  2.           
  3. String asbPath = System.getProperty(“user.dir”) + “/demo.txt”;  
  4. File file2 = new File(asbPath);  

也就是说相对路径是否好使,取决于user.dir的值。系统属性 user.dir是JVM启动的时候设置的,通常是 Java 虚拟机的调用目录,即执行java命令所在的目录。

对于tomcat/jboss容器,user.dir是%home/bin%/目录,因为这个目录就是我们启动web容器的地方。也就是说,user.dir也是可变的,不固定的。显然使用这种方式跟绝对路径没有什么本质差别,都是不推荐的。顺便提一下,我们在eclipse中运行程序的时候,eclipse会将user.dir的值设置为工程的根目录,在我们的例子中,user.dir是c:/workspace/path_project/

可以得出结论:使用java.io.File读取文件,无论是相对路径,还是绝对路径都不是好的做法,能不使用就不要使用

2.使用Class.getResource()或ClassLoader.getResource()

这2个方法用来读取jar包中或者classpath下的资源文件。以下方式都能够正确的定位文件
[java] view plain copy

  1. TestClass.class.getResource(“test.txt”);  
  2. TestClass.class.getResource(“/net/aty/test.txt”);  
  3.           
  4. TestClass.class.getClassLoader().getResource(“net/aty/test.txt”);  

Class.getResource()有2种方式,绝对路径和相对路径。绝对路径以/开头,从classpath或jar包根目录下开始搜索;

相对路径是相对当前class所在的目录,允许使用..或.来定位文件。ClassLoader.getResource()只能使用绝对路径,而且不用以/开头。

这两种方式读取资源文件,不会依赖于user.dir,也不会依赖于具体部署的环境,是推荐的做法。

3.使用Class或ClassLoader.getResource()的相对路径和绝对路径问题

无论是相对路径还是绝对路径,都是推荐的做法。考虑下这样的场景,如果a.jar中的类,需要读取b.jar中的资源文件怎么实现呢?
用相对路径和绝对路径都可以吗?
制作b.jar,并将它加入到eclipse工程的build path下,如下图

[java] view plain copy

  1. package net.aty;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.InputStreamReader;  
  5. import java.net.URL;  
  6.   
  7. public class TestClass  
  8. {  
  9.   
  10.     public static void main(String[] args) throws Exception  
  11.     {  
  12.         // 使用相对路径,正常读取b.jar中的文件  
  13.         showContent(TestClass.class.getResource(“b.txt”));  
  14.   
  15.         // 使用相对路径,正常读取b.jar中的文件  
  16.         showContent(TestClass.class.getResource(“test/c.txt”));  
  17.   
  18.         // 使用绝对对路径,正常读取b.jar中的文件  
  19.         showContent(TestClass.class.getResource(“/net/a.txt”));  
  20.   
  21.         // 使用绝对对路径,正常读取b.jar中的文件  
  22.         showContent(TestClass.class.getClassLoader().getResource(“net/aty/test/c.txt”));  
  23.   
  24.         // 使用相对路径,正常读取本jar中的same.txt  
  25.         showContent(TestClass.class.getResource(“../same.txt”));  
  26.           
  27.         // 错误  
  28.         showContent(TestClass.class.getResource(“../a.txt”));  
  29.     }  
  30.   
  31.     public static void showContent(URL url) throws Exception  
  32.     {  
  33.         BufferedReader br = new BufferedReader(new InputStreamReader(  
  34.                 url.openStream()));  
  35.   
  36.         StringBuilder contentHolder = new StringBuilder();  
  37.   
  38.         String lineContent = null;  
  39.   
  40.         while ((lineContent = br.readLine()) != null)  
  41.         {  
  42.             contentHolder.append(lineContent);  
  43.         }  
  44.   
  45.         br.close();  
  46.   
  47.         System.out.println(“content=” + contentHolder);  
  48.   
  49.     }  
  50. }  

可以得出结论:

使用相对路径或绝对路径都能读取本jar或其他jar中的资源文件。但区别是,读取本jar包中的文件支持..这种写法,但是不能通过..读取其他jar下的文件。

4.spring框架的ClassPathResource实现

[java] view plain copy

  1. /** 
  2.      * This implementation opens an InputStream for the given class path resource. 
  3.      * @see java.lang.ClassLoader#getResourceAsStream(String) 
  4.      * @see java.lang.Class#getResourceAsStream(String) 
  5.      */  
  6.     public InputStream getInputStream() throws IOException {  
  7.         InputStream is;  
  8.         if (this.clazz != null) {  
  9.             is = this.clazz.getResourceAsStream(this.path);  
  10.         }  
  11.         else {  
  12.             is = this.classLoader.getResourceAsStream(this.path);  
  13.         }  
  14.         if (is == null) {  
  15.             throw new FileNotFoundException(getDescription() + ” cannot be opened because it does not exist”);  
  16.         }  
  17.         return is;  
  18.     }  

可以看出spring提供的ClassPathResource,底层使用的就是Class.getResource或ClassLoader.getResource()。spring提供的读取文件API功能,自然是与JDK一致。

使用JSR 356 进行java websocket编程

 java  使用JSR 356 进行java websocket编程已关闭评论
1月 032017
 

这是一篇翻译的文章, 版本虽不是最新,但有参考价值。

转自:http://www.oschina.net/translate/how-to-build-java-websocket-applications-using-the-jsr-356-api

大家都知道这样一个事实,那就是HTTP(Hypertext Transfer Protocol)是一个无状态的请求-响应式协议。HTTP协议的这种简单设计使它颇具扩展性却不够高效,并且不适合于频繁交互的实时网络应用。HTTP被设计用来进行文档共享而不是用来建立频繁交互的网络应用。HTTP天生就不太正规,对每一个http请求/响应,都要通过线路传输许多头信息。

在HTTP 1.1版本之前,每一个提交到服务器的请求都会创建一个新的链接。这种情况在HTTP 1.1中通过引入HTTP持久化连接得以改进。持久化连接允许web浏览器复用同样的连接来获取图片,脚本等等。

HTTP被设计成半双工的,这意味着同一时刻只允许向一个方向上传输数据。Walkie-talkie是一个半双工设施的例子,因为一个时刻只能有一个人说话。开发者们已经创造出了一些工作方法或者应对方法来克服HTTP的这个缺点。这些工作方法包括轮询,长效轮询和

什么是WebSocket?

一个WebSocket是通过一个独立的TCP连接实现的、异步的、双向的、全双工的消息传递实现机制。WebSockets不是一个HTTP连接,却使用HTTP来引导一个WebSocket连接。一个全双工的系统允许同时进行双向的通讯。陆地线路电话是一个全双工设施的例子,因为它们允许两个通话者同时讲话并被对方听到。最初WebSocket被提议作为HTML5规范的一部分,HTML5承诺给现代的交互式的web应用带来开发上的便利和网络效率,但是随后WebSocket被移到一个仅用来存放WebSockets规范的独立的标准文档里。它包含两件事情 — WebSocket协议规范,即2011年12月发布的RFC 6455,和WebSocket JavaScript API

WebSocket协议利用HTTP 升级头信息来把一个HTTP连接升级为一个WebSocket连接。HTML5 WebSockets 解决了许多导致HTTP不适合于实时应用的问题,并且它通过避免复杂的工作方式使得应用结构很简单。

最新的浏览器都支持WebSockets,如下图所示。该信息来自于http://caniuse.com/#feat=websockets.

WebSocket browser support

WebSocket是如何工作的?

每一个WebSocket连接的生命都是从一个HTTP请求开始的。HTTP请求跟其他请求很类似,除了它拥有一个Upgrade头信息。Upgrade头信息表示一个客户端希望把连接升级为不同的协议。对WebSockets来说,它希望升级为WebSocket协议。当客户端和服务器通过底层连接第一次握手时,WebSocket连接通过把HTTP协议转换升级为WebSockets协议而得以建立。一旦WebSocket连接成功建立,消息就可以在客户端和服务器之间进行双向发送。

WebSockets带来了性能,简单化和更少带宽消耗

  1. WebSockets比其它工作方式比如轮询更有效也更高效。因为它需要更少的带宽并且降低了延时。
  2. WebSockets简化了实时应用的结构体系。
  3. WebSockets在点到点发送消息时不需要头信息。这显著的降低了带宽。

WebSocket使用案例

一些可能的WebSockets使用案例有:

  • 聊天应用
  • 多人游戏
  • 股票交易和金融应用
  • 文档合作编辑
  • 社交应用

Java中使用WebSockets

在Java社区中下面的情形很普遍,不同的供应商和开发者编写类库来使用某项技术,一段时间之后当该技术成熟时它就会被标准化,来使开发者可以在不同实现之间互相操作,而不用冒供应商锁定的风险。当JSR 365启动时,WebSocket就已经有了超过20个不同的Java实现。它们中的大多数都有着不同的API。JSR 356是把Java的WebSocket API进行标准化的成果。开发者们可以撇开具体的实现,直接使用JSR 356 API来创建WebSocket应用。WebSocket API是完全由事件驱动的。

JSR 356 — WebSockets的Java API

JSR 356,WebSocket的Java API,规定了开发者把WebSockets 整合进他们的应用时可以使用的Java API — 包括服务器端和Java客户端。JSR 356是即将出台的Java EE 7标准中的一部分。这意味着所有Java EE 7兼容的应用服务器都将有一个遵守JSR 356标准的WebSocket协议的实现。开发者也可以在Java EE 7应用服务器之外使用JSR 356。目前Apache Tomcat 8的开发版本将会增加基于JSR 356 API的WebSocket支持。

一个Java客户端可以使用兼容JSR 356的客户端实现,来连接到WebSocket服务器。对web客户端来说,开发者可以使用WebSocket JavaScript API来和WebSocket服务器进行通讯。WebSocket客户端和WebSocket服务器之间的区别,仅在于两者之间是通过什么方式连接起来的。一个WebSocket客户端是一个WebSocket终端,它初始化了一个到对方的连接。一个WebSocket服务器也是一个WebSocket终端,它被发布出去并且等待来自对方的连接。在客户端和服务器端都有回调监听方法 —  onOpen , onMessage , onError, onClose。后面我们创建一个应用的时候再来更详细的了解这些。

Tyrus — JSR 356 参考实现

TyrusJSR 356的参考实现。我们会在下一节中以独立模式用Tyrus开发一个简单应用。所有Tyrus组件都是用Java SE 7编译器进行构建的。这意味着,你也至少需要不低于Java SE 7的运行环境才能编译和运行该应用示例。它不能够在Apache Tomcat 7中运行,因为它依赖于servlet 3.1规范。

使用WebSockets开发一个单词游戏

现在我们准备创建一个非常简单的单词游戏。游戏者会得到一个字母排序错乱的单词,他或她需要把这个单词恢复原样。我们将为每一次游戏使用一个单独的连接。

本应用的源代码可以从github获取 https://github.com/shekhargulati/wordgame

步骤 1 : 创建一个模板Maven项目

开始时,我们使用Maven原型来创建一个模板Java项目。使用下面的命令来创建一个基于Maven的Java项目。

$ mvn archetype:generate -DgroupId=com.shekhar -DartifactId=wordgame -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

步骤 2 : 向pom.xml中添加需要的依赖

正如上节中提到的,你需要Java SE 7来构建使用Tyrus的应用。要在你的maven项目中使用Java 7,你需要在配置中添加maven编译器插件来使用Java 7,如下所示。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <compilerVersion>1.7</compilerVersion>
                <source>1.7</source>
                <target>1.7</target>
            </configuration>
        </plugin>
    </plugins>
</build>

下面,添加对JSR 356 API的依赖。javax.websocket-api的当前版本是 1.0。

<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.0</version>
</dependency>

下面我们将要添加与Tyrus JSR 356实现相关的依赖。tyrus-server包提供了JSR 356服务端WebSocket API实现,tyrus-client包提供了JSR356客户端WebSocket API实现。

<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-server</artifactId>
    <version>1.1</version>
</dependency>
<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-client</artifactId>
    <version>1.1</version>
</dependency>

最后,我们添加tyrus-container-grizzly依赖到我们的pom.xml中。这将提供一个独立的容器来部署WebSocket应用。
<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-container-grizzly</artifactId>
    <version>1.1</version>
</dependency>  

你可以在这里查看完整的pom.xml文件。

步骤 3 : 编写第一个JSR 356 WebSocket服务器终端

现在我们的项目已经设置完毕,我们将开始编写WebSocket服务器终端。你可以通过使用@ServerEndpoint注解来把任何Java POJO类声明为WebSocket服务器终端。开发者也可以指定用来部署终端的URI。URI要相对于WebSocket容器的根路径,必须以”/”开头。在如下所示的代码中,我们创建了一个非常简单的WordgameServerEndpoint。

package com.shekhar.wordgame.server;
  import java.io.IOException; import java.util.logging.Logger;
  import javax.websocket.CloseReason; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.CloseReason.CloseCodes; import javax.websocket.server.ServerEndpoint;
  @ServerEndpoint(value = "/game") public class WordgameServerEndpoint {
  private Logger logger = Logger.getLogger(this.getClass().getName());
  @OnOpen public void onOpen(Session session) {
        logger.info("Connected ... " + session.getId());
    }
  @OnMessage public String onMessage(String message, Session session) { switch (message) { case "quit": try {
                session.close(new CloseReason(CloseCodes.NORMAL_CLOSURE, "Game ended"));
            } catch (IOException e) { throw new RuntimeException(e);
            } break;
        } return message;
    }
  @OnClose public void onClose(Session session, CloseReason closeReason) {
        logger.info(String.format("Session %s closed because of %s", session.getId(), closeReason));
    }
}

@OnOpen注解用来标注一个方法,在WebSocket连接被打开时它会被调用。每一个连接都有一个和它关联的session。在上面的代码中,当onOpen()方法被调用时我们打印了一下session的id。对每一个WebSocket连接来说,被@OnOpen标注的方法只会被调用一次。

@OnMessage注解用来标注一个方法,每当收到一个消息时它都会被调用。所有业务代码都需要写入该方法内。上面的代码中,当从客户端收到”quit”消息时我们会关闭连接,其它情况下我们只是把消息原封不动的返回给客户端。所以,在收到“quit”消息以前,一个WebSocket连接将会一直打开。当收到退出消息时,我们在session对象上调用了关闭方法,告诉它session的原因。在示例代码中,我们说当游戏结束时这是一个正常的关闭。

@OnClose注解用来标注一个方法,当WebSocket连接关闭时它会被调用。

步骤 4 : 编写第一个JSR 356 WebSocket客户端终端

@ClientEndpoint注解用来标记一个POJO WebSocket客户端。类似于javax.websocket.server.ServerEndpoint,通过@ClientEndpoint标注的POJO能够使它的那些使用了网络套接字方法级别注解的方法,成为网络套接字生命周期方法。

package com.shekhar.wordgame.client;
  import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URI; import java.net.URISyntaxException; import java.util.concurrent.CountDownLatch; import java.util.logging.Logger;
  import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; import javax.websocket.DeploymentException; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session;
  import org.glassfish.tyrus.client.ClientManager;
  @ClientEndpoint public class WordgameClientEndpoint {
  private Logger logger = Logger.getLogger(this.getClass().getName());
  @OnOpen public void onOpen(Session session) {
        logger.info("Connected ... " + session.getId()); try {
            session.getBasicRemote().sendText("start");
        } catch (IOException e) { throw new RuntimeException(e);
        }
    }
  @OnMessage public String onMessage(String message, Session session) {
        BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in)); try {
            logger.info("Received ...." + message);
            String userInput = bufferRead.readLine(); return userInput;
        } catch (IOException e) { throw new RuntimeException(e);
        }
    }
  @OnClose public void onClose(Session session, CloseReason closeReason) {
        logger.info(String.format("Session %s close because of %s", session.getId(), closeReason));
    }
 
 
}

在上面的代码中,当WebSocket 连接被打开时,我们发送了一个“start”消息给服务器。每当从服务器收到一个消息时,被@OnMessage注解标注的onMessage方法就会被调用。它首先记录下消息让后等待用户的输入。用户的输入随后会被发送给服务器。最后,当WebSocket 连接关闭时,@OnClose标注的onClose()方法被被调用。正如你所看到的,客户单和服务器端的代码编程模式是相同的。这使得通过JSR 356 API来编写WebSocket应用的开发工作变得很容易。

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

最后附上oracle官网关于JSR 356 websocket api 如何整合进应用说明, http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html:

JSR 356, Java API for WebSocket

by Johan Vos

Learn how to integrate WebSockets into your applications.

Published April 2013

For many Web-based client-server applications, the old HTTP request-response model has its limitations. Information has to be transmitted from the server to the client in between requests, rather than upon request only.

A number of “hacks” have been used in the past to circumvent this problem, for example, long polling and Comet. However, the need for a standards-based, bidirectional and full-duplex channel between clients and a server has only increased.

In 2011, the IETF standardized the WebSocket protocol as RFC 6455. Since then, the majority of the Web browsers are implementing client APIs that support the WebSocket protocol. Also, a number of Java libraries have been developed that implement the WebSocket protocol.

The WebSocket protocol leverages the HTTP upgrade technology to upgrade an HTTP connection to a WebSocket. Once it is upgraded, the connection is capable of sending messages (data frames) in both directions, independent of each other (full duplex). No headers or cookies are required, which considerably lowers the required bandwidth. Typically, WebSockets are used to periodically send small messages (for example, a few bytes). Additional headers would often make the overhead larger than the payload.

JSR 356

JSR 356, Java API for WebSocket, specifies the API that Java developers can use when they want to integrate WebSockets into their applications—both on the server side as well as on the Java client side. Every implementation of the WebSocket protocol that claims to be compliant with JSR 356 must implement this API. As a consequence, developers can write their WebSocket-based applications independent of the underlying WebSocket implementation. This is a huge benefit, because it prevents a vendor-lock and allows for more choices and freedom of libraries and application servers.

JSR 356 is a part of the upcoming Java EE 7 standard; hence, all Java EE 7–compliant application servers will have an implementation of the WebSocket protocol that adheres to the JSR 356 standard. Once they are established, WebSocket client and server peers are symmetrical. The difference between a client API and a server API is, therefore, minimal. JSR 356 defines a Java client API as well, which is a subset of the full API required in Java EE 7.

A client-server application leveraging WebSockets typically contains a server component and one or more client components, as shown in Figure 1:

Figure 1

Figure 1

In this example, the server application is written in Java, and the WebSocket protocol details are handled by the JSR 356 implementation contained in the Java EE 7 container.

A JavaFX client can rely on any JSR 356–compliant client implementation for handling the WebSocket-specific protocol issues. Other clients (for example, an iOS client and an HTML5 client) can use other (non-Java) implementations that are compliant with RFC 6455 in order to communicate with the server application.

Programming Model

The Expert Group that defined JSR 356 wanted to support patterns and techniques that are common to Java EE developers. As a consequence, JSR 356 leverages annotations and injection.

In general, two different programming models are supported:

  • Annotation-driven. Using annotated POJOs, developers can interact with the WebSocket lifecycle events.
  • Interface-driven. Developers can implement the Endpoint interface and the methods that interact with the lifecycle events.

Lifecycle Events

The typical lifecycle event of a WebSocket interaction goes as follows:

  • One peer (a client) initiates the connection by sending an HTTP handshake request.
  • The other peer (the server) replies with a handshake response.
  • The connection is established. From now on, the connection is completely symmetrical.
  • Both peers send and receive messages.
  • One of the peers closes the connection.

Most of the WebSocket lifecycle events can be mapped to Java methods, both in the annotation-driven and interface-driven approaches.

Annotation-Driven Approach

An endpoint that is accepting incoming WebSocket requests can be a POJO annotated with the @ServerEndpoint annotation. This annotation tells the container that the given class should be considered to be a WebSocket endpoint. The required value element specifies the path of the WebSocket endpoint.

Consider the following code snippet:

@ServerEndpoint("/hello") 
public class MyEndpoint { }

This code will publish an endpoint at the relative path hello. The path can include path parameters that are used in subsequent method calls; for example, /hello/{userid} is a valid path, where the value of {userid} can be obtained in lifecycle method calls using the @PathParam annotation.

In GlassFish, if your application is deployed with the contextroot mycontextroot in a Web container listening at port 8080 of localhost, the WebSocket will be accessible using ws://localhost:8080/mycontextroot/hello.

An endpoint that should initiate a WebSocket connection can be a POJO annotated with the @ClientEndpoint annotation. The main difference between @ClientEndpoint and a ServerEndpoint is that the ClientEndpoint does not accept a path value element, because it is not listening to incoming requests.

@ClientEndpoint 
public class MyClientEndpoint {}

Initiating a WebSocket connection in Java leveraging the annotation-driven POJO approach can be done as follows:

javax.websocket.WebSocketContainer container = 
javax.websocket.ContainerProvider.getWebSocketContainer();

container.conntectToServer(MyClientEndpoint.class, 
new URI("ws://localhost:8080/tictactoeserver/endpoint"));

Hereafter, classes annotated with @ServerEndpoint or @ClientEndpoint will be called annotated endpoints.

Once a WebSocket connection has been established, a Session is created and the method annotated with @OnOpen on the annotated endpoint will be called. This method can contain a number of parameters:

  • A javax.websocket.Session parameter, specifying the created Session
  • An EndpointConfig instance containing information about the endpoint configuration
  • Zero or more string parameters annotated with @PathParam, referring to path parameters on the endpoint path

The following method implementation will print the identifier of the session when a WebSocket is “opened”:

@OnOpen
public void myOnOpen (Session session) {
   System.out.println ("WebSocket opened: "+session.getId());
}

A Session instance is valid as long as the WebSocket is not closed. The Session class contains a number of interesting methods that allow developers to obtain more information about the connection. Also, the Session contains a hook to application-specific data, by means of the getUserProperties() method returning a Map<String, Object>. This allows developers to populate Session instances with session- and application-specific information that should be shared among method invocations.

When the WebSocket endpoint receives a message, the method annotated with @OnMessage will be called. A method annotated with @OnMessage can contain the following parameters:

  • The javax.websocket.Session parameter.
  • Zero or more string parameters annotated with @PathParam, referring to path parameters on the endpoint path.
  • The message itself. See below for an overview of possible message types.

When a text message has been sent by the other peer, the content of the message will be printed by the following code snippet:

@OnMessage
public void myOnMessage (String txt) {
   System.out.println ("WebSocket received message: "+txt);
} 

If the return type of the method annotated with @OnMessage is not void, the WebSocket implementation will send the return value to the other peer. The following code snippet returns the received text message in capitals back to the sender:

@OnMessage
public String myOnMessage (String txt) {
   return txt.toUpperCase();
} 

Another way of sending messages over a WebSocket connection is shown below:

RemoteEndpoint.Basic other = session.getBasicRemote();
other.sendText ("Hello, world");

In this approach, we start from the Session object, which can be obtained from the lifecycle callback methods (for example, the method annotated with @OnOpen). The getBasicRemote() method on the Session instance returns a representation of the other part of the WebSocket, the RemoteEndpoint. That RemoteEndpoint instance can be used for sending text or other types of messages, as described below.

When the WebSocket connection is closing, the method annotated with @OnClose is called. This method can take the following parameters:

  • The javax.websocket.Session parameter. Note that this parameter cannot be used once the WebSocket is really closed, which happens after the @OnClose annotated method returns.
  • A javax.websocket.CloseReason parameter describing the reason for closing the WebSocket, for example, normal closure, protocol error, overloaded service, and so on.
  • Zero or more string parameters annotated with @PathParam, referring to path parameters on the endpoint path.

The following code snippet will print the reason why a WebSocket is closing:

@OnClose
public void myOnClose (CloseReason reason) {
   System.out.prinlnt ("Closing a WebSocket due to "+reason.getReasonPhrase());
}

To be complete, there is one more lifecycle annotation: in case an error is received, the method annotated with @OnError will be called.

Interface-Driven Approach

The annotation-driven approach allows us to annotate a Java class and methods with lifecycle annotations. Using the interface-driven approach, a developer extends javax.websocket.Endpoint and overrides the onOpen, onClose, and onError methods:

public class myOwnEndpoint extends javax.websocket.Endpoint {
   public void onOpen(Session session, EndpointConfig config) {...}
   public void onClose(Session session, CloseReason closeReason) {...}
   public void onError (Session session, Throwable throwable) {...}
}

In order to intercept messages, a javax.websocket.MessageHandler needs to be registered in the onOpen implementation:

public void onOpen (Session session, EndpointConfig config) {
   session.addMessageHandler (new MessageHandler() {...});
}

MessageHandler is an interface with two subinterfaces: MessageHandler.Partial and MessageHandler.Whole. The MessageHandler.Partial interface should be used when the developer wants to be notified about partial deliveries of messages, and an implementation of MessageHandler.Whole should be used for notification about the arrival of a complete message.

The following code snippet listens to incoming text messages and sends the uppercase version of the text message back to the other peer:

public void onOpen (Session session, EndpointConfig config) {
   final RemoteEndpoint.Basic remote = session.getBasicRemote();
   session.addMessageHandler (new MessageHandler.Whole<String>() {
      public void onMessage(String text) {
                 try {
                     remote.sendString(text.toUpperCase());
                 } catch (IOException ioe) {
                     // handle send failure here
                 }
             }

   });
}

Message Types, Encoders, and Decoders

The Java API for WebSocket is very powerful, because it allows any Java object to be sent or received as a WebSocket message.

Basically, there are three different types of messages:

  • Text-based messages
  • Binary messages
  • Pong messages, which are about the WebSocket connection itself

When using the interface-driven model, each session can register at most one MessageHandler for each of these three different types of messages.

When using the annotation-driven model, for each different type of message, one @onMessage annotated method is allowed. The allowed parameters for specifying the message content in the annotated methods are dependent on the type of the message.

The Javadoc for the @OnMessage annotation clearly specifies the allowed message parameters based on the message type (the following is quoted from the Javadoc):

  • “if the method is handling text messages: 

    • String to receive the whole message
    • Java primitive or class equivalent to receive the whole message converted to that type
    • String and boolean pair to receive the message in parts
    • Reader to receive the whole message as a blocking stream
    • any object parameter for which the endpoint has a text decoder (Decoder.Text or Decoder.TextStream).
  • if the method is handling binary messages: 

  • if the method is handling pong messages: 

Any Java object can be encoded into a text-based or binary message using an encoder. This text-based or binary message is transmitted to the other peer, where it can be decoded into a Java object again—or it can be interpreted by another WebSocket library. Often, XML or JSON is used for the transmission of WebSocket messages, and the encoding/decoding then comes down to marshaling a Java object into XML or JSON and back.

An encoder is defined as an implementation of the javax.websocket.Encoder interface, and a decoder is an implementation of the javax.websocket.Decoder interface. Somehow, the endpoint instances need to know what the possible encoders and decoders are. Using the annotation-driven approach, a list of encoders and decoders is passed via the encoder and decoder elements in the @ClientEndpoint and @ServerEndpoint annotations.

The code in Listing 1 shows how to register a MessageEncoder class that defines the conversion of an instance of MyJavaObject to a text message. A MessageDecoder class is registered for the opposite conversion.

@ServerEndpoint(value="/endpoint", encoders = MessageEncoder.class, decoders= MessageDecoder.class)
public class MyEndpoint {
...
}

class MessageEncoder implements Encoder.Text<MyJavaObject> {
   @override
   public String encode(MyJavaObject obj) throws EncodingException {
      ...
   }
}

class MessageDecoder implements Decoder.Text<MyJavaObject> {
   @override 
   public MyJavaObject decode (String src) throws DecodeException {
      ...
   }

   @override 
   public boolean willDecode (String src) {
      // return true if we want to decode this String into a MyJavaObject instance
   }
}

Listing 1

The Encoder interface has a number of subinterfaces:

  • Encoder.Text for converting Java objects into text messages
  • Encoder.TextStream for adding Java objects to a character stream
  • Encoder.Binary for converting Java objects into binary messages
  • Encoder.BinaryStream for adding Java objects to a binary stream

Similarly, the Decoder interface has four subinterfaces:

  • Decoder.Text for converting a text message into a Java object
  • Decoder.TextStream for reading a Java object from a character stream
  • Decoder.Binary for converting a binary message into a Java object
  • Decoder.BinaryStream for reading a Java object from a binary stream

Conclusion

The Java API for WebSocket provides Java developers with a standard API to integrate with the IETF WebSocket standard. By doing so, Web clients or native clients leveraging any WebSocket implementation can easily communicate with a Java back end.

The Java API is highly configurable and flexible, and it allows Java developers to use their preferred patterns.

JAVE 视音频转码

 java  JAVE 视音频转码已关闭评论
12月 282016
 

网上找到的一篇翻译JAVE比较好的文章,分享下:http://blog.csdn.net/qllinhongyu/article/details/29817297


官方参考文档:http://www.sauronsoftware.it/projects/jave/manual.php

一、什么是JAVE

    JAVE(Java Audio Video Encoder),是一个包涵ffmpeg项目库。开发这可以运用它去实现音频(Audio)与视频(Video)文件的转码。例如你要把AVI格式文件转为MPEG文件、WAV格式文件转为MP3格式文件,同时你还能调整文件大小与比例。JAVE兼容和支持很多格式之间的转码……

二、典型案例分析

    近期在做微信开发时,需要获取用户发给公众服务号的语音留言。而从微信服务端下载来的语音格式却是amr的格式,同样的你手机录音、Android语音等也都是生成amr格式文件。但当你想在web页面去播放此文件时,就困难了。因为无论是当前HTML5的<audio>标签,还是众多的播放插件都不支持amr格式文件的播放。所以,你不得不先把它转码为常见的MP3等类型文件。

三、所需环境与配置

    JAVE requires a J2SE environment 1.4 or later and a Windows or Linux OS on a i386 / 32 bit hardware architecture. JAVE can also be easily ported to other OS and hardware configurations, see the JAVE manual for details。 嗯,你应该看得懂~:D

    

    噢~差点忘了,你在使用时当然还必须引入它的jar包,请猛戳这里点击下载:jave-1.0.2.zip

四、具体用法与文档说明:

    1.JAVE中有个最重要的类Encoder,它暴露了很多的方法,总之你在使用JAVE时,你总是要创建Encoder的实例。

    Encoder encoder = new Encoder();

    让后转码时调用 encode()方法:

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. public void encode(java.io.File source,  
  2.                    java.io.File target,  
  3.                    it.sauronsoftware.jave.EncodingAttributes attributes)  
  4.             throws java.lang.IllegalArgumentException,  
  5.                    it.sauronsoftware.jave.InputFormatException,  
  6.                    it.sauronsoftware.jave.EncoderException  

    第一个参数source:需要转码的源文件

    第二个参数target:需转型成的目标文件

    第三个参数attributes:是一个包含编码所需数据的参数

    2.Encoding attributes

    如上所述的encoder()方法,第三个参数是很重要的,所以,你得实例化出一个EncodingAttributes即EncodingAttributes attrs = new EncodingAttributes();

    接下来看看attrs都包含了些什么方法:

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. public void setAudioAttributes(it.sauronsoftware.jave.AudioAttributes audioAttributes)  

从方法名可以看出是在转码音频时需要用到的方法,可以说是添加音频转码时所需音频属性。

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. public void setVideoAttributes(it.sauronsoftware.jave.AudioAttributes videoAttributes)  

从方法名可以看出是在转码视频时需要用到的方法,可以说是添加视频转码时所需视频属性。

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. public void setFormat(java.lang.String format)  

这个则是设置转码格式的方法。

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. public void setOffset(java.lang.Float offset)  

设置转码偏移位置的方法,例如你想在5秒后开始转码源文件则setOffset(5)。

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. public void setDuration(java.lang.Float duration)  

设置转码持续时间的方法,例如你想持续30秒的转码则setDuration(30)。


 3.Audio encoding attributes

    同样的我们也需设置Audio的属***:AudioAttributes audio = new AudioAttributes();

    看看它的方法:

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. public void setCodec(java.lang.String codec)//设置编码器  
  2.   
  3. public void setBitRate(java.lang.Integer bitRate)//设置比特率  
  4.   
  5. public void setSamplingRate(java.lang.Integer bitRate)//设置节录率  
  6.   
  7. public void setChannels(java.lang.Integer channels)//设置声音频道  
  8.   
  9. public void setVolume(java.lang.Integer volume)//设置音量  

4.Video encoding attributes

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. public void setCodec(java.lang.String codec)//设置编码器  
  2.       
  3. public void setTag(java.lang.String tag)//设置标签(通常用多媒体播放器所选择的视频解码)  
  4.       
  5. public void setBitRate(java.lang.Integer bitRate)//设置比特率  
  6.       
  7. public void setFrameRate(java.lang.Integer bitRate)//设置帧率  
  8.       
  9. public void setSize(it.sauronsoftware.jave.VideoSize size)//设置大小  

5.Monitoring the transcoding operation

    你可以用listener监测转码操作。JAVE定义了一个EncoderProgressListener的接口。

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. public void encode(java.io.File source,  
  2.                    java.io.File target,  
  3.                    it.sauronsoftware.jave.EncodingAttributes attributes,  
  4.                    it.sauronsoftware.jave.EncoderProgressListener listener)  
  5.             throws java.lang.IllegalArgumentException,  
  6.                    it.sauronsoftware.jave.InputFormatException,  
  7.                    it.sauronsoftware.jave.EncoderException  

实现EncoderProgressListener接口,需定义的方法:

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. public void sourceInfo(it.sauronsoftware.jave.MultimediaInfo info)//源文件信息  
  2.       
  3. public void progress(int permil)//增长千分率  
  4.   
  5. public void message(java.lang.String message)//转码信息提示  

6.Getting informations about a multimedia file

    获取多媒体文件转码时的信息:

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. public it.sauronsoftware.jave.MultimediaInfo getInfo(java.io.File source)  
  2.                                              throws it.sauronsoftware.jave.InputFormatException,  
  3.                                                     it.sauronsoftware.jave.EncoderException  

五、例子:

From a generic AVI to a youtube-like FLV movie, with an embedded MP3 audio stream:

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. File source = new File(“source.avi”);  
  2. File target = new File(“target.flv”);  
  3. AudioAttributes audio = new AudioAttributes();  
  4. audio.setCodec(“libmp3lame”);  
  5. audio.setBitRate(new Integer(64000));  
  6. audio.setChannels(new Integer(1));  
  7. audio.setSamplingRate(new Integer(22050));  
  8. VideoAttributes video = new VideoAttributes();  
  9. video.setCodec(“flv”);  
  10. video.setBitRate(new Integer(160000));  
  11. video.setFrameRate(new Integer(15));  
  12. video.setSize(new VideoSize(400300));  
  13. EncodingAttributes attrs = new EncodingAttributes();  
  14. attrs.setFormat(“flv”);  
  15. attrs.setAudioAttributes(audio);  
  16. attrs.setVideoAttributes(video);  
  17. Encoder encoder = new Encoder();  
  18. encoder.encode(source, target, attrs);  

Next lines extracts audio informations from an AVI and store them in a plain WAV file:

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. File source = new File(“source.avi”);  
  2. File target = new File(“target.wav”);  
  3. AudioAttributes audio = new AudioAttributes();  
  4. audio.setCodec(“pcm_s16le”);  
  5. EncodingAttributes attrs = new EncodingAttributes();  
  6. attrs.setFormat(“wav”);  
  7. attrs.setAudioAttributes(audio);  
  8. Encoder encoder = new Encoder();  
  9. encoder.encode(source, target, attrs);  

Next example takes an audio WAV file and generates a 128 kbit/s, stereo, 44100 Hz MP3 file:

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. File source = new File(“source.wav”);  
  2. File target = new File(“target.mp3”);  
  3. AudioAttributes audio = new AudioAttributes();  
  4. audio.setCodec(“libmp3lame”);  
  5. audio.setBitRate(new Integer(128000));  
  6. audio.setChannels(new Integer(2));  
  7. audio.setSamplingRate(new Integer(44100));  
  8. EncodingAttributes attrs = new EncodingAttributes();  
  9. attrs.setFormat(“mp3”);  
  10. attrs.setAudioAttributes(audio);  
  11. Encoder encoder = new Encoder();  
  12. encoder.encode(source, target, attrs);  

Next one decodes a generic AVI file and creates another one with the same video stream of the source and a re-encoded low quality MP3 audio stream:

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. File source = new File(“source.avi”);  
  2. File target = new File(“target.avi”);  
  3. AudioAttributes audio = new AudioAttributes();  
  4. audio.setCodec(“libmp3lame”);  
  5. audio.setBitRate(new Integer(56000));  
  6. audio.setChannels(new Integer(1));  
  7. audio.setSamplingRate(new Integer(22050));  
  8. VideoAttributes video = new VideoAttributes();  
  9. video.setCodec(VideoAttributes.DIRECT_STREAM_COPY);  
  10. EncodingAttributes attrs = new EncodingAttributes();  
  11. attrs.setFormat(“avi”);  
  12. attrs.setAudioAttributes(audio);  
  13. attrs.setVideoAttributes(video);  
  14. Encoder encoder = new Encoder();  
  15. encoder.encode(source, target, attrs);  

Next one generates an AVI with MPEG 4/DivX video and OGG Vorbis audio:

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. File source = new File(“source.avi”);  
  2. File target = new File(“target.avi”);  
  3. AudioAttributes audio = new AudioAttributes();  
  4. audio.setCodec(“libvorbis”);  
  5. VideoAttributes video = new VideoAttributes();  
  6. video.setCodec(“mpeg4”);  
  7. video.setTag(“DIVX”);  
  8. video.setBitRate(new Integer(160000));  
  9. video.setFrameRate(new Integer(30));  
  10. EncodingAttributes attrs = new EncodingAttributes();  
  11. attrs.setFormat(“mpegvideo”);  
  12. attrs.setAudioAttributes(audio);  
  13. attrs.setVideoAttributes(video);  
  14. Encoder encoder = new Encoder();  
  15. encoder.encode(source, target, attrs);  

A smartphone suitable video:

[java] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. File source = new File(“source.avi”);  
  2. File target = new File(“target.3gp”);  
  3. AudioAttributes audio = new AudioAttributes();  
  4. audio.setCodec(“libfaac”);  
  5. audio.setBitRate(new Integer(128000));  
  6. audio.setSamplingRate(new Integer(44100));  
  7. audio.setChannels(new Integer(2));  
  8. VideoAttributes video = new VideoAttributes();  
  9. video.setCodec(“mpeg4”);  
  10. video.setBitRate(new Integer(160000));  
  11. video.setFrameRate(new Integer(15));  
  12. video.setSize(new VideoSize(176144));  
  13. EncodingAttributes attrs = new EncodingAttributes();  
  14. attrs.setFormat(“3gp”);  
  15. attrs.setAudioAttributes(audio);  
  16. attrs.setVideoAttributes(video);  
  17. Encoder encoder = new Encoder();  
  18. encoder.encode(source, target, attrs);  

总结下,以上例子看上去都大同小异,步骤就那几步固定死了。

首先,源文件与目标文件。

其次,设置视音频转码钱的属***数据。

      其中setCodec()方法中的参数要对应你所转码的格式的编码encoders。

最后,设置attrs并转码。

六、支持包含在内的格式:

Supported container formats

The JAVE built-in ffmpeg executable gives support for the following multimedia container formats:

Decoding

Formato Descrizione
4xm 4X Technologies format
MTV MTV format
RoQ Id RoQ format
aac ADTS AAC
ac3 raw ac3
aiff Audio IFF
alaw pcm A law format
amr 3gpp amr file format
apc CRYO APC format
ape Monkey’s Audio
asf asf format
au SUN AU Format
avi avi format
avs AVISynth
bethsoftvid Bethesda Softworks ‘Daggerfall’ VID format
c93 Interplay C93
daud D-Cinema audio format
dsicin Delphine Software International CIN format
dts raw dts
dv DV video format
dxa dxa
ea Electronic Arts Multimedia Format
ea_cdata Electronic Arts cdata
ffm ffm format
film_cpk Sega FILM/CPK format
flac raw flac
flic FLI/FLC/FLX animation format
flv flv format
gif GIF Animation
gxf GXF format
h261 raw h261
h263 raw h263
h264 raw H264 video format
idcin Id CIN format
image2 image2 sequence
image2pipe piped image2 sequence
ingenient Ingenient MJPEG
ipmovie Interplay MVE format
libnut nut format
m4v raw MPEG4 video format
matroska Matroska File Format
mjpeg MJPEG video
mm American Laser Games MM format
mmf mmf format
mov,mp4,m4a,3gp,3g2,mj2 QuickTime/MPEG4/Motion JPEG 2000 format
mp3 MPEG audio layer 3
mpc musepack
mpc8 musepack8
mpeg MPEG1 System format
mpegts MPEG2 transport stream format
mpegtsraw MPEG2 raw transport stream format
mpegvideo MPEG video
mulaw pcm mu law format
mxf MXF format
nsv NullSoft Video format
nut nut format
nuv NuppelVideo format
ogg Ogg format
psxstr Sony Playstation STR format
rawvideo raw video format
redir Redirector format
rm rm format
rtsp RTSP input format
s16be pcm signed 16 bit big endian format
s16le pcm signed 16 bit little endian format
s8 pcm signed 8 bit format
sdp SDP
shn raw shorten
siff Beam Software SIFF
smk Smacker Video
sol Sierra SOL Format
swf Flash format
thp THP
tiertexseq Tiertex Limited SEQ format
tta true-audio
txd txd format
u16be pcm unsigned 16 bit big endian format
u16le pcm unsigned 16 bit little endian format
u8 pcm unsigned 8 bit format
vc1 raw vc1
vmd Sierra VMD format
voc Creative Voice File format
wav wav format
wc3movie Wing Commander III movie format
wsaud Westwood Studios audio format
wsvqa Westwood Studios VQA format
wv WavPack
yuv4mpegpipe YUV4MPEG pipe format

Encoding

Formato Descrizione
3g2 3gp2 format
3gp 3gp format
RoQ Id RoQ format
ac3 raw ac3
adts ADTS AAC
aiff Audio IFF
alaw pcm A law format
amr 3gpp amr file format
asf asf format
asf_stream asf format
au SUN AU Format
avi avi format
crc crc testing format
dv DV video format
dvd MPEG2 PS format (DVD VOB)
ffm ffm format
flac raw flac
flv flv format
framecrc framecrc testing format
gif GIF Animation
gxf GXF format
h261 raw h261
h263 raw h263
h264 raw H264 video format
image2 image2 sequence
image2pipe piped image2 sequence
libnut nut format
m4v raw MPEG4 video format
matroska Matroska File Format
mjpeg MJPEG video
mmf mmf format
mov mov format
mp2 MPEG audio layer 2
mp3 MPEG audio layer 3
mp4 mp4 format
mpeg MPEG1 System format
mpeg1video MPEG video
mpeg2video MPEG2 video
mpegts MPEG2 transport stream format
mpjpeg Mime multipart JPEG format
mulaw pcm mu law format
null null video format
nut nut format
ogg Ogg format
psp psp mp4 format
rawvideo raw video format
rm rm format
rtp RTP output format
s16be pcm signed 16 bit big endian format
s16le pcm signed 16 bit little endian format
s8 pcm signed 8 bit format
svcd MPEG2 PS format (VOB)
swf Flash format
u16be pcm unsigned 16 bit big endian format
u16le pcm unsigned 16 bit little endian format
u8 pcm unsigned 8 bit format
vcd MPEG1 System format (VCD)
vob MPEG2 PS format (VOB)
voc Creative Voice File format
wav wav format
yuv4mpegpipe YUV4MPEG pipe format

Built-in decoders and encoders

The JAVE built-in ffmpeg executable contains the following decoders and encoders:

Audio decoders

adpcm_4xm adpcm_adx adpcm_ct adpcm_ea adpcm_ea_r1
adpcm_ea_r2 adpcm_ea_r3 adpcm_ea_xas adpcm_ima_amv adpcm_ima_dk3
adpcm_ima_dk4 adpcm_ima_ea_eacs adpcm_ima_ea_sead adpcm_ima_qt adpcm_ima_smjpeg
adpcm_ima_wav adpcm_ima_ws adpcm_ms adpcm_sbpro_2 adpcm_sbpro_3
adpcm_sbpro_4 adpcm_swf adpcm_thp adpcm_xa adpcm_yamaha
alac ape atrac 3 cook dca
dsicinaudio flac g726 imc interplay_dpcm
liba52 libamr_nb libamr_wb libfaad libgsm
libgsm_ms mace3 mace6 mp2 mp3
mp3adu mp3on4 mpc sv7 mpc sv8 mpeg4aac
nellymoser pcm_alaw pcm_mulaw pcm_s16be pcm_s16le
pcm_s16le_planar pcm_s24be pcm_s24daud pcm_s24le pcm_s32be
pcm_s32le pcm_s8 pcm_u16be pcm_u16le pcm_u24be
pcm_u24le pcm_u32be pcm_u32le pcm_u8 pcm_zork
qdm2 real_144 real_288 roq_dpcm shorten
smackaud sol_dpcm sonic truespeech tta
vmdaudio vorbis wavpack wmav1 wmav2
ws_snd1 xan_dpcm      

Audio encoders

ac3 adpcm_adx adpcm_ima_wav adpcm_ms adpcm_swf
adpcm_yamaha flac g726 libamr_nb libamr_wb
libfaac libgsm libgsm_ms libmp3lame libvorbis
mp2 pcm_alaw pcm_mulaw pcm_s16be pcm_s16le
pcm_s24be pcm_s24daud pcm_s24le pcm_s32be pcm_s32le
pcm_s8 pcm_u16be pcm_u16le pcm_u24be pcm_u24le
pcm_u32be pcm_u32le pcm_u8 pcm_zork roq_dpcm
sonic sonicls vorbis wmav1 wmav2

Video decoders

4xm 8bps VMware video aasc amv
asv1 asv2 avs bethsoftvid bmp
c93 camstudio camtasia cavs cinepak
cljr cyuv dnxhd dsicinvideo dvvideo
dxa ffv1 ffvhuff flashsv flic
flv fraps gif h261 h263
h263i h264 huffyuv idcinvideo indeo2
indeo3 interplayvideo jpegls kmvc loco
mdec mjpeg mjpegb mmvideo mpeg1video
mpeg2video mpeg4 mpegvideo msmpeg4 msmpeg4v1
msmpeg4v2 msrle msvideo1 mszh nuv
pam pbm pgm pgmyuv png
ppm ptx qdraw qpeg qtrle
rawvideo roqvideo rpza rv10 rv20
sgi smackvid smc snow sp5x
svq1 svq3 targa theora thp
tiertexseqvideo tiff truemotion1 truemotion2 txd
ultimotion vb vc1 vcr1 vmdvideo
vp3 vp5 vp6 vp6a vp6f
vqavideo wmv1 wmv2 wmv3 wnv1
xan_wc3 xl zlib zmbv  

Video encoders

asv1 asv2 bmp dnxhd dvvideo
ffv1 ffvhuff flashsv flv gif
h261 h263 h263p huffyuv jpegls
libtheora libx264 libxvid ljpeg mjpeg
mpeg1video mpeg2video mpeg4 msmpeg4 msmpeg4v1
msmpeg4v2 pam pbm pgm pgmyuv
png ppm qtrle rawvideo roqvideo
rv10 rv20 sgi snow svq1
targa tiff wmv1 wmv2 zlib
zmbv        

七、执行ffmpeg的二选一 (可以通过实现FFMPEGLocator指定ffmpeg命令位置)

JAVE is not pure Java: it acts as a wrapper around an ffmpeg (http://ffmpeg.mplayerhq.hu/) executable. ffmpeg is an open source and free software project entirely written in C, so its executables cannot be easily ported from a machine to another. You need a pre-compiled version of ffmpeg in order to run JAVE on your target machine. The JAVE distribution includes two pre-compiled executables of ffmpeg: a Windows one and a Linux one, both compiled for i386/32 bit hardware achitectures. This should be enough in most cases. If it is not enough for your specific situation, you can still run JAVE, but you need to obtain a platform specific ffmpeg executable. Check the Internet for it. You can even build it by yourself getting the code (and the documentation to build it) on the official ffmpeg site. Once you have obtained a ffmpeg executable suitable for your needs, you have to hook it in the JAVE library. That’s a plain operation. JAVE gives you an abstract class called it.sauronsoftware.jave.FFMPEGLocator. Extend it. All you have to do is to define the following method:

public java.lang.String getFFMPEGExecutablePath()

This method should return a file system based path to your custom ffmpeg executable.

Once your class is ready, suppose you have called it MyFFMPEGExecutableLocator, you have to create an alternate encoder that uses it instead of the default locator:

Encoder encoder = new Encoder(new MyFFMPEGExecutableLocator())

You can use the same procedure also to switch to other versions of ffmpeg, even if you are on a platform covered by the executables bundled in the JAVE distribution.

Anyway be careful and test ever your application: JAVE it’s not guaranteed to work properly with custom ffmpeg executables different from the bundled ones.

enum使用详解

 java  enum使用详解已关闭评论
11月 032016
 

enum使用,记录下,来自:http://www.cnblogs.com/hyl8218/p/5088287.html

 

  enum 的全称为 enumeration, 是 JDK 1.5  中引入的新特性,存放在 java.lang 包中。

    下面是我在使用 enum 过程中的一些经验和总结,主要包括如下内容:

1. 原始的接口定义常量

2. 语法(定义)

3. 遍历、switch 等常用操作

4. enum 对象的常用方法介绍

5. 给 enum 自定义属性和方法

6. EnumSet,EnumMap 的应用

7. enum 的原理分析

8. 总结

原始的接口定义常量

public interface IConstants {
    String MON =”Mon”;
    String TUE =”Tue”;
    String WED =”Wed”;
    String THU =”Thu”;
    String FRI =”Fri”;
    String SAT =”Sat”;
    String SUN =”Sun”;
}

语法(定义)

    创建枚举类型要使用 enum 关键字,隐含了所创建的类型都是 java.lang.Enum 类的子类(java.lang.Enum 是一个抽象类)。枚举类型符合通用模式 Class Enum<E extends Enum<E>>,而 E 表示枚举类型的名称。枚举类型的每一个值都将映射到 protected Enum(String name, int ordinal) 构造函数中,在这里,每个值的名称都被转换成一个字符串,并且序数设置表示了此设置被创建的顺序。

package com.hmw.test;
/**
 * 枚举测试类
 * @author <a href=”mailto:[email protected]”>何明旺</a>
 */
public enum EnumTest {
    MON, TUE, WED, THU, FRI, SAT, SUN;
}

这段代码实际上调用了7次 Enum(String name, int ordinal):

new Enum<EnumTest>(“MON”,0);
new Enum<EnumTest>(“TUE”,1);
new Enum<EnumTest>(“WED”,2);
    … …

遍历、switch 等常用操作

对enum进行遍历和switch的操作示例代码:

public class Test {
    public static void main(String[] args) {
        for (EnumTest e : EnumTest.values()) {
            System.out.println(e.toString());
        }
         
        System.out.println(“—————-我是分隔线——————“);
         
        EnumTest test = EnumTest.TUE;
        switch (test) {
        case MON:
            System.out.println(“今天是星期一”);
            break;
        case TUE:
            System.out.println(“今天是星期二”);
            break;
        // … …
        default:
            System.out.println(test);
            break;
        }
    }
}

输出结果:

MON
TUE
WED
THU
FRI
SAT
SUN
—————-我是分隔线——————
今天是星期二

enum 对象的常用方法介绍

int compareTo(E o) 
          比较此枚举与指定对象的顺序。

Class<E> getDeclaringClass() 
          返回与此枚举常量的枚举类型相对应的 Class 对象。

String name() 
          返回此枚举常量的名称,在其枚举声明中对其进行声明。

int ordinal() 
          返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。

String toString()

           返回枚举常量的名称,它包含在声明中。

static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) 
          返回带指定名称的指定枚举类型的枚举常量。

public class Test {
    public static void main(String[] args) {
        EnumTest test = EnumTest.TUE;
         
        //compareTo(E o)
        switch (test.compareTo(EnumTest.MON)) {
        case -1:
            System.out.println(“TUE 在 MON 之前”);
            break;
        case 1:
            System.out.println(“TUE 在 MON 之后”);
            break;
        default:
            System.out.println(“TUE 与 MON 在同一位置”);
            break;
        }
         
        //getDeclaringClass()
        System.out.println(“getDeclaringClass(): ” + test.getDeclaringClass().getName());
         
        //name() 和  toString()
        System.out.println(“name(): ” + test.name());
        System.out.println(“toString(): ” + test.toString());
         
        //ordinal(), 返回值是从 0 开始
        System.out.println(“ordinal(): ” + test.ordinal());
    }
}

输出结果:

TUE 在 MON 之后
getDeclaringClass(): com.hmw.test.EnumTest
name(): TUE
toString(): TUE
ordinal(): 1

给 enum 自定义属性和方法

给 enum 对象加一下 value 的属性和 getValue() 的方法:

package com.hmw.test;
 
/**
 * 枚举测试类
 *
 * @author <a href=”mailto:[email protected]”>何明旺</a>
 */
public enum EnumTest {
    MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6) {
        @Override
        public boolean isRest() {
            return true;
        }
    },
    SUN(0) {
        @Override
        public boolean isRest() {
            return true;
        }
    };
 
    private int value;
 
    private EnumTest(int value) {
        this.value = value;
    }
 
    public int getValue() {
        return value;
    }
 
    public boolean isRest() {
        return false;
    }
}

public class Test {
    public static void main(String[] args) {
        System.out.println(“EnumTest.FRI 的 value = ” + EnumTest.FRI.getValue());
    }
}

输出结果:

EnumTest.FRI 的 value = 5

EnumSet,EnumMap 的应用

public class Test {
    public static void main(String[] args) {
        // EnumSet的使用
        EnumSet<EnumTest> weekSet = EnumSet.allOf(EnumTest.class);
        for (EnumTest day : weekSet) {
            System.out.println(day);
        }
 
        // EnumMap的使用
        EnumMap<EnumTest, String> weekMap =new EnumMap(EnumTest.class);
        weekMap.put(EnumTest.MON,”星期一”);
        weekMap.put(EnumTest.TUE,”星期二”);
        // … …
        for (Iterator<Entry<EnumTest, String>> iter = weekMap.entrySet().iterator(); iter.hasNext();) {
            Entry<EnumTest, String> entry = iter.next();
            System.out.println(entry.getKey().name() +”:” + entry.getValue());
        }
    }
}

原理分析

        enum 的语法结构尽管和 class 的语法不一样,但是经过编译器编译之后产生的是一个class文件。该class文件经过反编译可以看到实际上是生成了一个类,该类继承了java.lang.Enum<E>。EnumTest 经过反编译(javap com.hmw.test.EnumTest 命令)之后得到的内容如下:

public class com.hmw.test.EnumTestextends java.lang.Enum{
    public static final com.hmw.test.EnumTest MON;
    public static final com.hmw.test.EnumTest TUE;
    public static final com.hmw.test.EnumTest WED;
    public static final com.hmw.test.EnumTest THU;
    public static final com.hmw.test.EnumTest FRI;
    public static final com.hmw.test.EnumTest SAT;
    public static final com.hmw.test.EnumTest SUN;
    static {};
    public int getValue();
    public boolean isRest();
    public static com.hmw.test.EnumTest[] values();
    public static com.hmw.test.EnumTest valueOf(java.lang.String);
    com.hmw.test.EnumTest(java.lang.String,int,int, com.hmw.test.EnumTest);
}

所以,实际上 enum 就是一个 class,只不过 java 编译器帮我们做了语法的解析和编译而已。

总结

    可以把 enum 看成是一个普通的 class,它们都可以定义一些属性和方法,不同之处是:enum 不能使用 extends 关键字继承其他类,因为 enum 已经继承了 java.lang.Enum(java是单一继承)。

Java应用中使用ShutdownHook友好地清理现场

 java  Java应用中使用ShutdownHook友好地清理现场已关闭评论
8月 122016
 

在线上Java程序中经常遇到进程程挂掉,一些状态没有正确的保存下来,这时候就需要在JVM关掉的时候执行一些清理现场的代码。Java中得ShutdownHook提供了比较好的方案。

JDK在1.3之后提供了Java Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子,这个钩子可以在以下几种场景被调用:

  • 1)程序正常退出
  • 2)使用System.exit()
  • 3)终端使用Ctrl+C触发的中断
  • 4)系统关闭
  • 5)使用Kill pid命令干掉进程

注:在使用kill -9 pid是不会JVM注册的钩子不会被调用。
在JDK中方法的声明:
public void addShutdownHook(Thread hook)
参数
hook — 一个初始化但尚未启动的线程对象,注册到JVM钩子的运行代码。
异常
IllegalArgumentException — 如果指定的钩已被注册,或如果它可以判定钩已经运行或已被运行
IllegalStateException — 如果虚拟机已经是在关闭的过程中
SecurityException — 如果存在安全管理器并且它拒绝的RuntimePermission(“shutdownHooks”)
代码示例:
使用Timer模拟一个工作线程,该线程重复工作十次,使用System.exit()退出,在清理现场代码CleanWorkThread 中,取消timer运行,并输出必要的日志信息。

复制代码
package com.netease.test.java.lang; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicInteger; /** * Date: 14-6-18
 * Time: 11:01
 * 测试ShutdownHook */ public class TestShutdownHook { //简单模拟干活的 static Timer timer = new Timer("job-timer"); //计数干活次数 static AtomicInteger count = new AtomicInteger(0); /** * hook线程 */ static class CleanWorkThread extends Thread{
        @Override public void run() {
            System.out.println("clean some work.");
            timer.cancel(); try {
                Thread.sleep(2 * 1000);//sleep 2s } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    } public static void main(String[] args) throws InterruptedException { //将hook线程添加到运行时环境中去 Runtime.getRuntime().addShutdownHook(new CleanWorkThread());
        System.out.println("main class start ..... "); //简单模拟 timer.schedule(new TimerTask() {
            @Override public void run() {
                count.getAndIncrement();
                System.out.println("doing job " + count); if (count.get() == 10) { //干了10次退出 System.exit(0);
                }
            }
        }, 0, 2 * 1000);

    }
}
复制代码

 


运行后,可以模拟以上五种场景进行测试,只有kill -9 pid不会执行Hook里面的代码。

转自:http://www.cnblogs.com/nexiyi/p/java_add_ShutdownHook.html

伪静态URLRewrite学习

 java  伪静态URLRewrite学习已关闭评论
3月 312016
 

UrlRewrite

UrlRewrite就是我们通常说的地址重写,用户得到的全部都是经过处理后的URL地址,类似于Apachemod_rewrite。将我们的动态网页地址转化为静态的地址,如htmlshtml,还可以隐藏网页的真正路径,

比如:有时候需要将xxx.com/news/ type1/001.jsp 转化成显示路径为xxx.com/news_type1_001.html


有点如下:

一:提高安全性,屏蔽内部的url结构.

二:美化URL

三:更有利于搜索引擎的收入,通过对URL的一些优化,可以使搜索引擎更好的识别与收录网站的信息.


下载地址:

官网下载: http://urlrewritefilter.googlecode.com/svn/trunk/src/doc/manual/4.0/index.html#filterparams


实例展示

实例应用版本urlrewritefilter-4.0.3. Tomcat服务器端口定制为80

1. 创建web项目,增加 urlrewritefilter-4.0.3.jar 到 WEB-INF/lib 

2. 在WEB-INF/web.xml 增加urlrewritefilter过滤器 (near the top above any servlet mappings)

<?xml version=”1.0″ encoding=”UTF-8″?>
<web-app version=”2.5″ xmlns=”http://java.sun.com/xml/ns/javaee”
    xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
    xsi:schemaLocation=”http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd”>

    <!– 加到任何servlet映射的顶部,不然可能有些路径不能被过滤到
         http://urlrewritefilter.googlecode.com/svn/trunk/src/doc/manual/3.2/index.html
     –>
    <filter>
        <filter-name>UrlRewriteFilter</filter-name>
        <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
        <!– 
            设备文件重加载间隔 (0默示随时加载, -1默示不重加载, 默认-1) 
        –>
        <init-param>
            <param-name>confReloadCheckInterval</param-name>
            <param-value>60</param-value>
        </init-param>
        
        <!– 自定义配置文件的路径,是相对context的路径,(默认位置 /WEB-INF/urlrewrite.xml) –>
        <init-param>
            <param-name>confPath</param-name>
            <param-value>/WEB-INF/urlrewrite.xml</param-value>
        </init-param>
        
        <!– 
            设置日志级别(将被记录到日志中)
               可以为: TRACE, DEBUG, INFO (default), WARN, ERROR, FATAL, log4j, commons, slf4j,
               比如 sysout:DEBUG(设置到控制台调试输出级别) 
            (默认级别 WARN) –>
        <init-param>
            <param-name>logLevel</param-name>
            <param-value>DEBUG</param-value>
        </init-param>
    </filter>
    
    <filter-mapping>
        <filter-name>UrlRewriteFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>
    
    
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

如果觉得/*这样的通配,并不符合我的预期,我只想对部分路径进行URL的重写,/*可能会造成我想象不到的或者是许微不足道的性能浪费.我把它改成了我需要的:

<filter-mapping>
        <filter-name>UrlRewriteFilter</filter-name>
        <url-pattern>/member/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>UrlRewriteFilter</filter-name>
        <url-pattern>/article/*</url-pattern>
    </filter-mapping>

更多请参考: http://urlrewritefilter.googlecode.com/svn/trunk/src/doc/manual/3.2/index.html

3. 因为上面我们通过confPath定义了配置文件的路径,其实该默认位置就是在/WEB-INF/urlrewrite.xml,为了更能说明问题,所以显示指定下

<?xml version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE urlrewrite
    PUBLIC “-//tuckey.org//DTD UrlRewrite 4.0//EN”
    “http://www.tuckey.org/res/dtds/urlrewrite4.0.dtd”>

<urlrewrite>
    <rule>
        <from>/page/(.*).html</from>  
        <to>/index.jsp?page=$1</to> 
    </rule>
    
    <rule>
        <from>^/user/([a-z]+)/([0-9]+)$</from>
        <to>/index.jsp?nickname=$1&amp;age=$2</to>
</rule>
</urlrewrite>

此时我们就可以通过url进行模拟了.

注意:

1.urlrewrite.xmlutf-8.所以如果你要在rule上加note标签为中文的话,也一定是要utf-8.

2.UrlRewriteFilter 最好是配置在web.xml的前面filter,不然有可能对有些url转变失去作用.

3.urlrewrite属性:有仅只有一个,rule属性::至少一个.

4.在写rule的时,如果有多个参数时,中间的连接符号&应该是&

5.ruleurl重写规则,from是显示出来的地址,to是映射的实际地址,$1是重写参数,它的值与from中的正则表达式是一一对应,可以为多个,()里是匹配的正则表达式在正则表达式^指定字符的串开始,$为指定结束

6.对于中文参数要使用(.*)作为参数转义.

4.重写url演示

实例1

<rule>
 <from>/page/(.*).html</from> 
<to>/index.jsp?currentPage=$1</to>
 </rule>

index.jsp中的内容

 <body>
          <% String current = request.getParameter("currentPage"); %> 当前页码<%=current %>
  </body>

执行效果如下:

实例2

Rule规则

<rule> 
<name>World Rule</name> 
<from>^/user/([a-z]+)/([0-9]+)$</from> 
<to>/index.jsp?nickname=$1&amp;age=$2</to> 
</rule>

index.jsp中的内容

复制代码
<body>
          <% String username = request.getParameter("nickname"); int age = Integer.parseInt(request.getParameter("age")); %> 用户名: <%=username %> 年龄: <%=age %> <br>
</body>
复制代码

执行效果如下:


所以,当我们在url中输入”http://localhost/urlrewrite/user/dennisit/23”时,实际执行的就是”http://localhost/urlrewrite/index.jsp?nickname=dennisit&age=23”

实例3

同理rule规则如下时

<rule> <from>^/page/(.*)$</from> <to type="redirect">/page/$1.action</to> </rule>

这样我访问的:http://localhost/urlrewrite/page/test

则跳转到:    http://localhost/urlrewrite/page/test.action


实例4
Rule规则

 <rule> <from>^/([a-z]+)/([a-z]+)/([a-z]+)$</from> <to>/$1.do?method=$2&amp;uuid=$3</to> </rule>

index.jsp中添加如下链接:

 <a href="process/show/index">跳转</a>

当点击该链接,

地址栏中显示url是:http://localhost/urlrewrite/process/show/index,

其实际执行路径是:http://localhost/urlrewrite/process.do?method=show&uuid=index

转自:http://www.cnblogs.com/dennisit/p/3177108.html

Java扩展算法和辅助工具

 java, 加解密  Java扩展算法和辅助工具已关闭评论
12月 212015
 

Java扩展算法和辅助工具,记录下,转自:http://blog.csdn.net/danwell7/article/details/8426853

获取权限文件

Sun在其下载页面(http://www.oracle.com/technetwork/java/javase/downloads/index.html)提供了权限文件的下载地址,对应Java 7 和 Java 6

1356360474_5116

打开压缩包local_policy.jar和US_export_policy.jar是此次配置中用到的文件

配置权限文件

需要在JDKJRE环境中,或者是JRE环境配置上述两个jar文件。

     切换到%JDK_Home%\jre\lib\security目录下覆盖原有的文件。同时,可能有必要在%JRE_Home%\lib\security下对应覆盖这两个文件

配置权限文件的最终目的是为了使应用在运行环境中或德相应的权限,可以加强应用的安全性

扩充算法支持

Bouncy Castle目前提供的加密组件组件包的版本是1.47,自1.40版本开始,Bouncy Castle

提供了对IDEA(International Data Encryption Algorithm,国际数据加密算法)。下载最新的加密组件包,主要是bcprov-jdk15on-147.jarbcprov-ext-jdk15on-147.jar两个文件如下图

1356360574_8952

http://www.bouncycastle.org/latest_releases.html

配置方式

%JDK_Home%\jre\lib\security\java.security文件下配置根据:

security.provider.<n>=<className>,则加入Bouncy Castle加密组件的

安全提供者做法:

方式一:

#增进BouncyCastleProvider

security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider

最后,需要将bcprov-ext-dk15on-147.jar导入到%JRE_Home%\lib\ext即可

方式二:

通过明显的代码调用方式引入支持者,这儿使用Security类的addProvider()方法,此方法

需要在初始化密钥工厂.密钥生成器等引擎类之前,调用如下代码:

Security.addProvider(new BouncyCastleProvider())

检测安装

Java 7不支持MD4IDEA算法,做了上述配置后,使用MD4算法可以参考

public static byte[] encodeMD4(byte [] data) throws Exception{

MessageDigest md = MessageDigest.getInstance(“MD4”);

md.update(data);

return md.digest();

}

辅助工具Commons Codec

Apache旗下的一款开源软件,主要用于编码格式的转换,如Base64.二进制.十六进制.字符集和Url编码的转换。甚至Commons Codec还提供了语言编码的转换。除此之外,Commons Codec还对Java的原生消息摘要做了良好的封装,提高了方法的易用性

http://commons.apache.org/codec/download_codec.cgi

下载commons-codec-1.7-bin.zip即可

Java 环境下使用 AES 密钥长度限制等特殊情况处理及 PKCS5 和 PKCS7 填充说明( Illegal key size or default parameters问题解决)

 java, 加解密  Java 环境下使用 AES 密钥长度限制等特殊情况处理及 PKCS5 和 PKCS7 填充说明( Illegal key size or default parameters问题解决)已关闭评论
12月 182015
 

补充下AES 加密问题及PKCS5 和 PKCS7 填充说明

近些年DES使用越来越少,原因就在于其使用56位密钥,比较容易被破解,近些年来逐渐被AES替代,AES已经变成目前对称加密中最流行算法之一;AES可以使用128、192、和256位密钥,并且用128位分组加密和解密数据.

 在 Java 环境下使用 AES 加密,在密钥长度和字节填充方面有一些比较特殊的处理。

1. 密钥长度问题

    默认 Java 中仅支持 128 位密钥,当使用 256 位密钥的时候,会报告密钥长度错误(或者 Illegal key size or default parameters)

Invalid AES key length

   你需要下载一个支持更长密钥的包。这个包叫做 Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 6,可以从这里下载,下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html

   下载之后,解压后,可以看到其中包含两个包:

    local_policy.jar

    US_export_policy.jar

    看一下你的 JRE 环境,将 JRE 环境中 lib\lib\security 中的同名包替换掉。

2. Base64 问题

     Apache 提供了 Base64 的实现,可以从这里下载。

    下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi

     编码

// 编码 String asB64 = new Base64().encodeToString("some string".getBytes("utf-8"));
System.out.println(asB64); // 输出为: c29tZSBzdHJpbmc=

     解码   

// 解码 byte[] asBytes = new Base64().getDecoder().decode("c29tZSBzdHJpbmc=");
System.out.println(new String(asBytes, "utf-8")); // 输出为: some string

 

     如果你已经使用 Java 8,那么就不需要再选用第三方的实现了,在 java.util 包中已经包含了 Base64 的处理。

     编码的方式

// 编码 String asB64 = Base64.getEncoder().encodeToString("some string".getBytes("utf-8"));
System.out.println(asB64); // 输出为: c29tZSBzdHJpbmc=

     解码处理

// 解码 byte[] asBytes = Base64.getDecoder().decode("c29tZSBzdHJpbmc=");
System.out.println(new String(asBytes, "utf-8")); // 输出为: some string

 

3. 关于 PKCS5 和 PKCS7 填充问题

PKCS #7 填充字符串由一个字节序列组成,每个字节填充该填充字节序列的长度。

假定块长度为 8,数据长度为 9,
          数据: FF FF FF FF FF FF FF FF FF
PKCS7 填充: FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07

简单地说, PKCS5, PKCS7和SSL3, 以及CMS(Cryptographic Message Syntax)

有如下相同的特点:
1)填充的字节都是一个相同的字节
2)该字节的值,就是要填充的字节的个数

如果要填充8个字节,那么填充的字节的值就是0×8;
要填充7个字节,那么填入的值就是0×7;

如果只填充1个字节,那么填入的值就是0×1;

这种填充方法也叫PKCS5, 恰好8个字节时还要补8个字节的0×08

正是这种即使恰好是8个字节也需要再补充字节的规定,可以让解密的数据很确定无误的移除多余的字节。

pkcs7_padding

 

在PKCS# Padding中说:

  1. 因为恢复的明文的最后一个字节 告诉你 存在多少个填充字节, 用PKCS#5 填充 的加密方法, 即使在输入的明文长度 恰好是 块大小(Block Size)整数倍 , 也会增加一个完整的填充块. 否则,恢复出来的明文的最后一个字节可能是实际的消息字节.
  2. 因为第1个因素限制了 使用PKCS#填充的 对称加密算法的 输入块大小(Block Size, 注意不是输入的明文的总长度 total input length), 最大只能是256个字节.   因为大多数对称块加密算法 通常使用8字节或者16字节的块, 所以,这不是一个问题
  3. 使用ECB模式填充可能会有安全问题.
  4. 使用PKCS#5填充 可以很方便地检测明文中的错误.

标准

PKCS #7: Cryptographic Message Syntax

在 10.3节中讲到了上面提到的填充算法,  对Block Size并没有做规定

PKCS #5: Password-Based Cryptography Specification

在6.1.1 中对 填充做了说明
但是因为该标准 只讨论了 8字节(64位) 块的加密, 对其他块大小没有做说明
其 填充算法跟 PKCS7是一样的

后来 AES 等算法, 把BlockSize扩充到 16个字节

比如, Java中
Cipher.getInstance(“AES/CBC/PKCS5Padding”)
这个加密模式
跟C#中的
RijndaelManaged cipher = new RijndaelManaged();
cipher.KeySize = 128;
cipher.BlockSize = 128;
cipher.Mode = CipherMode.CBC;
cipher.Padding = PaddingMode.PKCS7;
的加密模式是一样的

因为AES并没有64位的块, 如果采用PKCS5, 那么实质上就是采用PKCS7

JAVA实现AES加解密(含密钥产生/合成)

 java, 加解密  JAVA实现AES加解密(含密钥产生/合成)已关闭评论
12月 082015
 

DES使用越来越少,原因就在于其使用56位密钥,比较容易被破解,近些年来逐渐被AES替代,AES已经变成目前对称加密中最流行算法之一;AES可以使用128、192、和256位密钥,并且用128位分组加密和解密数据(目前默认java仅支持128位密钥,需下载扩展包支持256位,见java扩展算法与辅助工具)。本文就简单介绍如何通过JAVA实现AES加密。

网上很多java 实现AES的代码都有这样那样问题,现摘录《java加密与解密的艺术》中关于AES的代码, 简单明了,关键是有效!!!

JAVA实现

package test.rsa;

import java.security.Key;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class AESCoder {
public static final String KEY_ALGORITHM = “AES”;

public static final String CIPHER_ALGORITHM = “AES/ECB/PKCS5Padding”;

private static Key toKey(byte[] key) throws Exception {
SecretKey secretKey = new SecretKeySpec(key, KEY_ALGORITHM);
return secretKey;
}

public static byte[] decrypt(byte[] data, byte[] key) throws Exception{
Key k = toKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, k);
return cipher.doFinal(data);
}

public static byte[] encrypt(byte[] data, byte[] key) throws Exception{
Key k = toKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, k);
return cipher.doFinal(data);
}

public static byte[] initKey() throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM);
kg.init(128);
SecretKey secretKey = kg.generateKey();
return secretKey.getEncoded();

}

public static void main(String[] args) throws Exception {
String inputStr=”测试 AES 密钥密钥密钥密钥”;
byte[] inputData = inputStr.getBytes();
System.out.println(“原文[arrays]:\t” + Arrays.toString(inputData));
System.out.println(“原文:\t” + inputStr);

byte[] key = initKey();
System.out.println(“密钥[arrays]:\t” + Arrays.toString(key));
System.out.println(“密钥[base64]:\t” + Base64.encodeBase64String(key));

inputData = encrypt(inputData, key);
System.out.println(“加密后[arrays]:\t” + Arrays.toString(inputData));
System.out.println(“加密后[base64]:\t” + Base64.encodeBase64String(inputData));

byte[] outputData= decrypt(inputData, key);
System.out.println(“解密后[arrays]:\t” + Arrays.toString(outputData));
System.out.println(“解密后[base64]:\t” + new String(outputData));

}

}

密钥的合成及base64转码方式,给我们保存及传输提供了想象空间。

java中list及数组(含数组的数组)的打印输出

 java  java中list及数组(含数组的数组)的打印输出已关闭评论
11月 042015
 

java中list及数组(含数组的数组)的输出

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Hello {

public static void main(String[] args) {  
        List<String> list = new ArrayList<String>();  
        list.add(“tom”);  
        list.add(“jack”);  
        list.add(“rose”);  
        list.add(“simon”); 
        list.add(“john”);  
        System.out.println(“print list = ” + list);  
  
  
        String[] array = new String[] { “tom”, “jack”, “rose”, “simon” , “john”};  
        System.out.println(“print array = ” + array.toString());  
        System.out.println(“print array with  (Arrays.toString)= ” +  Arrays.toString(array));  
  
  
        String[] arr1 = new String[] { “tom1”, “jack1” };  
        String[] arr2 = new String[] { “rose1”, “simon1” };  
        String[][] arrayOfArray = new String[][] { arr1, arr2 };  
        System.out.println(“print arrayOfArray = ” + arrayOfArray);  
        System.out.println(“print arrayOfArray with  (Arrays.toString)= ” + Arrays.toString(arrayOfArray));  
        System.out.println(“print arrayOfArray with  (Arrays.deepToString)= ” + Arrays.deepToString(arrayOfArray));  

    }  

}

输出结果:

print list = [tom, jack, rose, simon, john]
print array = [Ljava.lang.String;@5f186fab
print array with  (Arrays.toString)= [tom, jack, rose, simon, john]
print arrayOfArray = [[Ljava.lang.String;@3d4b7453
print arrayOfArray with  (Arrays.toString)= [[Ljava.lang.String;@24c21495, [Ljava.lang.String;@41d5550d]
print arrayOfArray with  (Arrays.deepToString)= [[tom1, jack1], [rose1, simon1]]

Java 加密解密基础

 加解密  Java 加密解密基础已关闭评论
9月 162015
 

网上的加密解密系列文章

Java  加密解密基础

密码学是研究编制密码和破译密码的技术科学。研究密码变化的客观规律,应用于编制密码以保守通信秘密的,称为编码学;应用于破译密码以获取通信情报的,称为破译学,总称密码学。

 

密码学常用术语

 

明文: 待加密数据。

密文: 明文经过加密后数据。

加密: 将明文转换为密文的过程。

加密算法: 将明文转换为密文的转换算法。

加密密钥: 通过加密算法进行加密操作的密钥。

解密: 将密文转换为铭文的过程。

解密算法: 将密文转换为明文的转换算法。

解密密钥: 通过解密短发进行解密操作的密钥。

 

密码学分类

1. 按时间分

a. 古典密码:以字符为基本加密单元。

b. 现代密码:以信息块为基本加密单元。

 

2 按保密内容的算法划分

a. 受限制算法:算法的保密性基于保持算法的秘密。

b. 基于密钥算法:算法的保密性基于对密钥的保密。

 

3. 按密钥体制划分

a. 对称密码体制:也叫单钥或私钥密码体制,加密过程与解密过程使用同一套密钥。对应的算法就是对称加密算法,例如 DES , AES 

b. 非对称密码体制:也叫双钥或公钥密码体制,加密过程与解密过程使用不同的密钥。对应的算法就是非对称加密算法,例如 RSA 

 

4. 按明文处理方式划分

a. 流密码:也称为序列密码,加密时每次加密一位或者一个字节的明文。例如 RC4 算法。

b. 分组密码:加密时将明文分成固定长度的组,用同一个密钥和算法对每一组进行加密输出也是固定长度的明文。当最后一组大小不满足指定的分组大小时,

有两种处理模式:

 

无填充模式,直接对剩余数据进行加密,此组加密后大小与剩余数据有关;

有填充模式,对于不满足指定长度分组的进行数据填充;如果恰巧最后一组数据与指定分组大小相同,那么直接添加一个指定大小的分组;填充的最后一个字节记录了填充的字节数。

 

 

 

分组密码工作模式简介  

.电子密码本模–ECB

将明文的各个分组独立的使用相同的密钥进行加密,这种方式加密时各分组的加密独立进行互不干涉,因而可并行进行。同样因为各分组独立加密的缘故,相同的明文分组加密之后具有相同的密文。该模式容易暴露明文分组的统计规律和结构特征。不能防范替换攻击。 
其实照实现来看, ECB 的过程只是把明文进行分组,然后分别加密,最后串在一起的过程。当消息长度超过一个分组时,不建议使用该模式。在每个分组中增加随机位 ( 如 128 位分组中 96 位为有效明文, 32 位的随机数 ) 则可稍微提高其安全性 , 但这样无疑造成了加密过程中数据的扩张。

优点 :

1.简单;

2.有利于并行计算;

3.误差不会被传送;

缺点 :

1.不能隐藏明文的模式;

2.可能对明文进行主动攻击;

 

2.密码分组链接模 — CBC

需要一个初始化向量 IV ,第一组明文与初始化向量进行异或运算后再加密,以后的每组明文都与前一组的密文进行异或运算后再加密。 IV 不需要保密,它可以明文形式与密文一起传送。

优点:

1.不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。

缺点:

1.不利于并行计算;

2.误差传递;

3.需要初始化向量IV

   

3. 密文反馈模式–CFB  

需要一个初始化向量IV ,加密后与第一个分组明文进行异或运算产生第一组密文,然后对第一组密文加密后再与第二组明文进行异或运算缠身第二组密文,一次类推,直到加密完毕。

优点:

1.隐藏了明文模式;

2.分组密码转化为流模式;

3.可以及时加密传送小于分组的数据;

缺点 :

1.不利于并行计算;

2.误差传送:一个明文单元损坏影响多个单元;

3.唯一的IV;

 

4. 输出反馈模式–OFB

需要一个初始化向量IV ,加密后得到第一次加密数据,此加密数据与第一个分组明文进行异或运算产生第一组密文,然后对第一次加密数据进行第二次加密,得到第二次加密数据,第二次加密数据再与第二组明文进行异或运算产生第二组密文,一次类推,直到加密完毕。

优点 :

1.隐藏了明文模式;

2.分组密码转化为流模式;

3.可以及时加密传送小于分组的数据;

缺点 :

1.不利于并行计算;

2. 对明文的主动攻击是可能的 ;

3. 误差传送:一个明文单元损坏影响多个单元 ;

 

 

 

5. 计数器模式–CTR

使用计数器,计数器初始值加密后与第一组明文进行异或运算产生第一组密文,

计数器增加,然后,加密后与下一组明文进行异或运算产生下一组密文,以此类推,直到加密完毕

优点 :

1. 可并行计算 ;

2. 安全性至少与CBC 模式一样好 ;

3. 加密与解仅涉及密码算法的加密 ;

缺点 :

1. 没有错误传播,不易确保数据完整性 ;

  

分组密码填充方式简介

PKCS5 : 填充字符串由一个值为 5 的字节序列组成,每个字节填充该字节序列的长度。 明确定义 Block 的大小是 8 位

PKCS7 : 填充字符串由一个值为 7 的字节序列组成,每个字节填充该字节序列的长度。 对于块的大小是不确定的,可以在 1-255 之间

ISO10126: 填充字符串由一个字节序列组成,此字节序列的最后一个字节填充字节序列的长度,其余字节填充随机数据。  

转自:http://aub.iteye.com/blog/1129339

Java中序列化的方式比较(json,serialize,protobuf)

 java, 开发  Java中序列化的方式比较(json,serialize,protobuf)已关闭评论
9月 162015
 

网上看到的一篇比较文章,分享下

 在java中socket传输数据时,数据类型往往比较难选择。可能要考虑带宽、跨语言、版本的兼容等问题。比较常见的做法有两种:一是把对象包装成JSON字符串传输,二是采用java对象的序列化和反序列化。随着Google工具protoBuf的开源,protobuf也是个不错的选择。对JSON,Object Serialize,ProtoBuf 做个对比。

定义一个待传输的对象UserVo:

Java代码  收藏代码

  1. public class UserVo{  
  2.     private String name;  
  3.     private int age;  
  4.     private long phone;  
  5.       
  6.     private List<UserVo> friends;  
  7. ……  
  8. }  

 初始化UserVo的实例src:

Java代码  收藏代码

  1. UserVo src = new UserVo();  
  2. src.setName(“Yaoming”);  
  3. src.setAge(30);  
  4. src.setPhone(13789878978L);  
  5.       
  6. UserVo f1 = new UserVo();  
  7. f1.setName(“tmac”);  
  8. f1.setAge(32);  
  9. f1.setPhone(138999898989L);  
  10. UserVo f2 = new UserVo();  
  11. f2.setName(“liuwei”);  
  12. f2.setAge(29);  
  13. f2.setPhone(138999899989L);  
  14.           
  15. List<UserVo> friends = new ArrayList<UserVo>();  
  16. friends.add(f1);  
  17. friends.add(f2);  
  18. src.setFriends(friends);  

JSON格式

采用Google的gson-2.2.2.jar 进行转义

Java代码  收藏代码

  1. Gson gson = new Gson();  
  2. String json = gson.toJson(src);  

 得到的字符串:

Js代码  收藏代码

  1. {“name”:“Yaoming”,“age”:30,“phone”:13789878978,“friends”:[{“name”:“tmac”,“age”:32,“phone”:138999898989},{“name”:“liuwei”,“age”:29,“phone”:138999899989}]}  

 字节数为153

Json的优点:明文结构一目了然,可以跨语言,属性的增加减少对解析端影响较小。缺点:字节数过多,依赖于不同的第三方类库。

 

Object Serialize

UserVo实现Serializalbe接口,提供唯一的版本号:

Java代码  收藏代码

  1. public class UserVo implements Serializable{  
  2.   
  3.     private static final long serialVersionUID = -5726374138698742258L;  
  4.     private String name;  
  5.     private int age;  
  6.     private long phone;  
  7.       
  8.     private List<UserVo> friends;  

 

序列化方法:

Java代码  收藏代码

  1. ByteArrayOutputStream bos = new ByteArrayOutputStream();  
  2. ObjectOutputStream os = new ObjectOutputStream(bos);  
  3. os.writeObject(src);  
  4. os.flush();  
  5. os.close();  
  6. byte[] b = bos.toByteArray();  
  7. bos.close();  

 字节数是238

 

反序列化:

Java代码  收藏代码

  1. ObjectInputStream ois = new ObjectInputStream(fis);  
  2. vo = (UserVo) ois.readObject();  
  3. ois.close();  
  4. fis.close();  

Object Serializalbe 优点:java原生支持,不需要提供第三方的类库,使用比较简单。缺点:无法跨语言,字节数占用比较大,某些情况下对于对象属性的变化比较敏感。 

对象在进行序列化和反序列化的时候,必须实现Serializable接口,但并不强制声明唯一的serialVersionUID

是否声明serialVersionUID对于对象序列化的向上向下的兼容性有很大的影响。我们来做个测试:

 
思路一

把UserVo中的serialVersionUID去掉,序列化保存。反序列化的时候,增加或减少个字段,看是否成功。

Java代码  收藏代码

  1. public class UserVo implements Serializable{  
  2.     private String name;  
  3.     private int age;  
  4.     private long phone;  
  5.       
  6.     private List<UserVo> friends;  

 

保存到文件中:

Java代码  收藏代码

  1. ByteArrayOutputStream bos = new ByteArrayOutputStream();  
  2. ObjectOutputStream os = new ObjectOutputStream(bos);  
  3. os.writeObject(src);  
  4. os.flush();  
  5. os.close();  
  6. byte[] b = bos.toByteArray();  
  7. bos.close();  
  8.   
  9. FileOutputStream fos = new FileOutputStream(dataFile);  
  10. fos.write(b);  
  11. fos.close();  

 

增加或者减少字段后,从文件中读出来,反序列化:

Java代码  收藏代码

  1. FileInputStream fis = new FileInputStream(dataFile);  
  2. ObjectInputStream ois = new ObjectInputStream(fis);  
  3. vo = (UserVo) ois.readObject();  
  4. ois.close();  
  5. fis.close();  

 

结果:抛出异常信息

Java代码  收藏代码

  1. Exception in thread “main” java.io.InvalidClassException: serialize.obj.UserVo; local class incompatible: stream classdesc serialVersionUID = 3305402508581390189, local class serialVersionUID = 7174371419787432394  
  2.     at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:560)  
  3.     at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1582)  
  4.     at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1495)  
  5.     at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1731)  
  6.     at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)  
  7.     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)  
  8.     at serialize.obj.ObjectSerialize.read(ObjectSerialize.java:74)  
  9.     at serialize.obj.ObjectSerialize.main(ObjectSerialize.java:27)  
 
思路二

eclipse指定生成一个serialVersionUID,序列化保存,修改字段后反序列化

略去代码

结果:反序列化成功

结论

如果没有明确指定serialVersionUID,序列化的时候会根据字段和特定的算法生成一个serialVersionUID,当属性有变化时这个id发生了变化,所以反序列化的时候就会失败。抛出“本地classd的唯一id和流中class的唯一id不匹配”。

 

jdk文档关于serialVersionUID的描述:

写道
如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 — serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。

 

Google ProtoBuf

protocol buffers 是google内部得一种传输协议,目前项目已经开源(http://code.google.com/p/protobuf/)。它定义了一种紧凑得可扩展得二进制协议格式,适合网络传输,并且针对多个语言有不同得版本可供选择。

以protobuf-2.5.0rc1为例,准备工作:

下载源码,解压,编译,安装

Shell代码  收藏代码

  1. tar zxvf protobuf-2.5.0rc1.tar.gz  
  2. ./configure  
  3. ./make  
  4. ./make install  

 测试:

Shell代码  收藏代码

  1. MacBook-Air:~ ming$ protoc –version  
  2. libprotoc 2.5.0  

 安装成功!进入源码得java目录,用mvn工具编译生成所需得jar包,protobuf-java-2.5.0rc1.jar

 

1、编写.proto文件,命名UserVo.proto 

Text代码  收藏代码

  1. package serialize;  
  2.   
  3. option java_package = “serialize”;  
  4. option java_outer_classname=“UserVoProtos”;  
  5.   
  6. message UserVo{  
  7.     optional string name = 1;  
  8.     optional int32 age = 2;  
  9.     optional int64 phone = 3;  
  10.     repeated serialize.UserVo friends = 4;  
  11. }  

 

2、在命令行利用protoc 工具生成builder类

Shell代码  收藏代码

  1. protoc -IPATH=.proto文件所在得目录 –java_out=java文件的输出路径  .proto的名称   

 得到UserVoProtos类

 

3、编写序列化代码

Java代码  收藏代码

  1. UserVoProtos.UserVo.Builder builder = UserVoProtos.UserVo.newBuilder();  
  2. builder.setName(“Yaoming”);  
  3. builder.setAge(30);  
  4. builder.setPhone(13789878978L);  
  5.           
  6. UserVoProtos.UserVo.Builder builder1 = UserVoProtos.UserVo.newBuilder();  
  7. builder1.setName(“tmac”);  
  8. builder1.setAge(32);  
  9. builder1.setPhone(138999898989L);  
  10.           
  11. UserVoProtos.UserVo.Builder builder2 = UserVoProtos.UserVo.newBuilder();  
  12. builder2.setName(“liuwei”);  
  13. builder2.setAge(29);  
  14. builder2.setPhone(138999899989L);  
  15.           
  16. builder.addFriends(builder1);  
  17. builder.addFriends(builder2);  
  18.           
  19. UserVoProtos.UserVo vo = builder.build();  
  20.           
  21. byte[] v = vo.toByteArray();  

 字节数53

 

4、反序列化

Java代码  收藏代码

  1. UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(dstb);  
  2. System.out.println(uvo.getFriends(0).getName());  

 结果:tmac,反序列化成功

google protobuf 优点:字节数很小,适合网络传输节省io,跨语言 。缺点:需要依赖于工具生成代码。

 

工作机制

proto文件是对数据的一个描述,包括字段名称,类型,字节中的位置。protoc工具读取proto文件生成对应builder代码的类库。protoc xxxxx  –java_out=xxxxxx 生成java类库。builder类根据自己的算法把数据序列化成字节流,或者把字节流根据反射的原理反序列化成对象。官方的示例:https://developers.google.com/protocol-buffers/docs/javatutorial。

proto文件中的字段类型和java中的对应关系:

详见:https://developers.google.com/protocol-buffers/docs/proto

 .proto Type  java Type  c++ Type
double  double  double
float  float  float
int32  int  int32
int64  long   int64
uint32  int  uint32
unint64  long  uint64
sint32  int  int32
sint64  long  int64
fixed32  int  uint32
fixed64  long  uint64
sfixed32  int  int32
sfixed64  long  int64
bool  boolean  bool
string  String  string
bytes  byte  string
字段属性的描述:
写道
required: a well-formed message must have exactly one of this field.
optional: a well-formed message can have zero or one of this field (but not more than one).
repeated: this field can be repeated any number of times (including zero) in a well-formed message. The order of the repeated values will be preserved.

 

protobuf 在序列化和反序列化的时候,是依赖于.proto文件生成的builder类完成,字段的变化如果不表现在.proto文件中就不会影响反序列化,比较适合字段变化的情况。做个测试:
把UserVo序列化到文件中:
Java代码  收藏代码

  1. UserVoProtos.UserVo vo = builder.build();  
  2. byte[] v = vo.toByteArray();  
  3. FileOutputStream fos = new FileOutputStream(dataFile);  
  4. fos.write(vo.toByteArray());  
  5. fos.close();  

 

为UserVo增加字段,对应的.proto文件:
Text代码  收藏代码

  1. package serialize;  
  2.   
  3. option java_package = “serialize”;  
  4. option java_outer_classname=“UserVoProtos”;  
  5.   
  6. message UserVo{  
  7.     optional string name = 1;  
  8.     optional int32 age = 2;  
  9.     optional int64 phone = 3;  
  10.     repeated serialize.UserVo friends = 4;  
  11.     optional string address = 5;  
  12. }  

 

从文件中反序列化回来:
Java代码  收藏代码

  1. FileInputStream fis = new FileInputStream(dataFile);  
  2. byte[] dstb = new byte[fis.available()];  
  3. for(int i=0;i<dstb.length;i++){  
  4.     dstb[i] = (byte)fis.read();  
  5. }  
  6. fis.close();  
  7. UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(dstb);  
  8. System.out.println(uvo.getFriends(0).getName());  

 成功得到结果。

三种方式对比传输同样的数据,google protobuf只有53个字节是最少的。结论:
方式 优点 缺点
JSON

跨语言、格式清晰一目了然

字节数比较大,需要第三方类库
Object Serialize java原生方法不依赖外部类库 字节数比较大,不能跨语言
Google protobuf

跨语言、字节数比较少

编写.proto配置用protoc工具生成对应的代码

 

以上测试用例覆盖面比较窄,可能无法正确反应真实情况仅代表个人观点,欢迎随时指正和讨论。

转自:http://www.iteye.com/topic/1128881