java8 lambda表达式举例

 java  java8 lambda表达式举例已关闭评论
3月 052019
 

好文章, 转自:原文:https://blog.csdn.net/bitcarmanlee/article/details/70195403

Java8发布已经有一段时间了,这次发布的改动比较大,很多人将这次改动与Java5的升级相提并论。Java8其中一个很重要的新特性就是lambda表达式,允许我们将行为传到函数中。想想看,在Java8 

之前我们想要将行为传入函数,仅有的选择就是匿名内部类。Java8发布以后,lambda表达式将大量替代匿名内部类的使用,简化代码的同时,更突出了原来匿名内部类中最重要的那部分包含真正逻辑的代码。尤其是对于做数据的同学来说,当习惯使用类似scala之类的函数式编程语言以后,体会将更加深刻。现在我们就来看看Java8中lambda表达式的一些常见写法。

1.替代匿名内部类
毫无疑问,lambda表达式用得最多的场合就是替代匿名内部类,而实现Runnable接口是匿名内部类的经典例子。lambda表达式的功能相当强大,用()->就可以代替整个匿名内部类!请看代码:

如果使用匿名内部类:

    @Test
    public void oldRunable() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(“The old runable now is using!”);
            }
        }).start();
    }
如果使用lambda表达式:

    @Test
    public void runable() {
        new Thread(() -> System.out.println(“It’s a lambda function!”)).start();
    }

最后的输出:

The old runable now is using!
It’s a lambda function!
是不是强大到可怕?是不是简单到可怕?是不是清晰明了重点突出到可怕?这就是lambda表达式的可怕之处,用极少的代码完成了之前一个类做的事情!

2.使用lambda表达式对集合进行迭代
Java的集合类是日常开发中经常用到的,甚至说没有哪个java代码中没有使用到集合类。。。而对集合类最常见的操作就是进行迭代遍历了。请看对比:

    @Test
    public void iterTest() {
        List<String> languages = Arrays.asList(“java”,”scala”,”python”);
        //before java8
        for(String each:languages) {
            System.out.println(each);
        }
        //after java8
        languages.forEach(x -> System.out.println(x));
        languages.forEach(System.out::println);
    }
如果熟悉scala的同学,肯定对forEach不陌生。它可以迭代集合中所有的对象,并且将lambda表达式带入其中。

languages.forEach(System.out::println);

这一行看起来有点像c++里面作用域解析的写法,在这里也是可以的。

3.用lambda表达式实现map
一提到函数式编程,一提到lambda表达式,怎么能不提map。。。没错,java8肯定也是支持的。请看示例代码:

    @Test
    public void mapTest() {
        List<Double> cost = Arrays.asList(10.0, 20.0,30.0);
        cost.stream().map(x -> x + x*0.05).forEach(x -> System.out.println(x));
    }
最后的输出结果:

10.5
21.0
31.5
1
2
3
map函数可以说是函数式编程里最重要的一个方法了。map的作用是将一个对象变换为另外一个。在我们的例子中,就是通过map方法将cost增加了0,05倍的大小然后输出。

4.用lambda表达式实现map与reduce
既然提到了map,又怎能不提到reduce。reduce与map一样,也是函数式编程里最重要的几个方法之一。。。map的作用是将一个对象变为另外一个,而reduce实现的则是将所有值合并为一个,请看:

    @Test
    public void mapReduceTest() {
        List<Double> cost = Arrays.asList(10.0, 20.0,30.0);
        double allCost = cost.stream().map(x -> x+x*0.05).reduce((sum,x) -> sum + x).get();
        System.out.println(allCost);
    }
最终的结果为:

63.0
1
如果我们用for循环来做这件事情:

    @Test
    public void sumTest() {
        List<Double> cost = Arrays.asList(10.0, 20.0,30.0);
        double sum = 0;
        for(double each:cost) {
            each += each * 0.05;
            sum += each;
        }
        System.out.println(sum);
    }
相信用map+reduce+lambda表达式的写法高出不止一个level。

5.filter操作
filter也是我们经常使用的一个操作。在操作集合的时候,经常需要从原始的集合中过滤掉一部分元素。

    @Test
    public void filterTest() {
        List<Double> cost = Arrays.asList(10.0, 20.0,30.0,40.0);
        List<Double> filteredCost = cost.stream().filter(x -> x > 25.0).collect(Collectors.toList());
        filteredCost.forEach(x -> System.out.println(x));

    }
最后的结果:

30.0
40.0
1
2
将java写出了python或者scala的感觉有没有!是不是帅到爆!

6.与函数式接口Predicate配合
除了在语言层面支持函数式编程风格,Java 8也添加了一个包,叫做 java.util.function。它包含了很多类,用来支持Java的函数式编程。其中一个便是Predicate,使用 java.util.function.Predicate 函数式接口以及lambda表达式,可以向API方法添加逻辑,用更少的代码支持更多的动态行为。Predicate接口非常适用于做过滤。

    public static void filterTest(List<String> languages, Predicate<String> condition) {
        languages.stream().filter(x -> condition.test(x)).forEach(x -> System.out.println(x + ” “));
    }

    public static void main(String[] args) {
        List<String> languages = Arrays.asList(“Java”,”Python”,”scala”,”Shell”,”R”);
        System.out.println(“Language starts with J: “);
        filterTest(languages,x -> x.startsWith(“J”));
        System.out.println(“\nLanguage ends with a: “);
        filterTest(languages,x -> x.endsWith(“a”));
        System.out.println(“\nAll languages: “);
        filterTest(languages,x -> true);
        System.out.println(“\nNo languages: “);
        filterTest(languages,x -> false);
        System.out.println(“\nLanguage length bigger three: “);
        filterTest(languages,x -> x.length() > 4);
    }
最后的输出结果:

Language starts with J: 
Java 

Language ends with a: 
Java 
scala 

All languages: 
Java 
Python 
scala 
Shell 

No languages: 

Language length bigger three: 
Python 
scala 
Shell 
可以看到,Stream API的过滤方法也接受一个Predicate,这意味着可以将我们定制的 filter() 方法替换成写在里面的内联代码,这也是lambda表达式的魔力!

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 用户账户 如: example@domain.com 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一致。

guava使用简单实例

 guava  guava使用简单实例已关闭评论
9月 302017
 

使用guava包中部分功能还是能节省不少工作的,以下内容来自:http://www.cnblogs.com/langtianya/p/4520291.html  

以下代码使用guava 23.0版本, 原文章代码错误部分已做修正)

guava是在原先google-collection 的基础上发展过来的,是一个比较优秀的外部开源包,最近项目中使用的比较多,列举一些点。刚刚接触就被guava吸引了。。。

        这个是guava的一个官网ppt里面的介绍:

1
2
3
4
5
其实你可能发现,一些基本的校验完全可以自己写,但是。。
这些东西仅仅是看起来比较简单,实际上可能比我们想想的要复杂;
用一个公共的lib,别人可能更加容易理解你的代码;
当你用一个主流的开源包的时候,你可能在主流中;
当你发现一个可以提升的点的 时候,如果仅仅是修改自己的私有包,可能没有多少人能够受益;

 

1、非空检查,参数在赋值的时候就做一个检查

1
2
String inputName = “iamzhongyong”;
String name = Preconditions.checkNotNull(inputName);

这个减少了代码行数,认为变量必须赋值才可以使用。

多条件校验

1
2
String inputName = “iamzhongyong”;
Preconditions.checkArgument(inputName!=null && !””.equals(inputName),”input is null”);

能够有效的减少代码行数

此外Preconditions中还有多个校验方法,可以优雅的进行判断了。

 

2、优雅的使用null

空指针异常是我们再写代码的时候经常遇到的,guava提供了Optional来让你不得不思考null的问题

Optional.of(T):获得一个Optional对象,其内部包含了一个非null的T数据类型实例,若T=null,则立刻报错。

Optional.absent():获得一个Optional对象,其内部包含了空值,内部代码看,是返回了Absent的一个实例。

Optional.fromNullable(T):将一个T的实例转换为Optional对象,T的实例可以不为空,也可以为空[Optional.fromNullable(null),和Optional.absent()等价。

1
2
3
4
Optional<String> name = Optional.of(“iamzhongyong”);
        if(name.isPresent()){
            System.out.println(name.get());
        }

 

3、Object中常用的方法

例如我们常见的equals方法和hashCode方法,Objects中都有与之对应的方法提供;

同时,toString是我们比较常用的,Objects.toStringHelper方法十分方便

1
2
3
4
5
6
7
8
public class StringHelp {
    public String name;
    public int age;
    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this).add(“name”, this.name).add(“age”, this.age).toString();
    }
}

 

4、字符转链接(Joiner 类)

1
2
3
4
5
6
7
8
public static void main(String[] args) {
        List<String> names = Lists.newArrayList();
        names.add(“iamzhongyong”);
        names.add(“bixiao.zy”);
        StringBuilder sb = new StringBuilder();
        String rs = Joiner.on(“–“).appendTo(sb, names).toString();
        System.out.println(rs);
    }

 

5、字符串分隔符

这个时候你可能说JDK自己有分割器,但是其实guava这个更加灵活,其实JDK的那个性能不咋滴

1
2
3
4
5
6
public static void main(String[] args) {
        String s = “dd  sfsfs  , dsfsf,ssfdfsdffsdfsf.sdfsfs,msfds”;
        for(String name : Splitter.on(“,”).trimResults().split(s)){
            System.out.println(name);
        }
    }

 

6、不可变的集合(Immutable)

这里就拿一个map为例子,初始化一个集合,然后向里面放置几个不变的集合

1
2
3
Map<Integer,String> mapa = new HashMap<Integer,String>();
        mapa.put(122, “iamzhongyong”);
        mapa.put(1222, “bixiao.zy”);

现在用Immutable就可以很简单的完成了

1
2
ImmutableMap<Integer, String> map = ImmutableMap.of(122,”iamzhongyong”,1222,”bixiao.zy”);
        System.out.println(map.toString());

 

7、一个key对应多个Valve的情况

我们经常遇到这种,一个key有多个value的情况,一般的做法如下:Map<Key,List<Value>>的数据结构去搞,

但是现在又办法了,可以使用Multimap来解决这个问题,简洁明了

1
2
3
4
5
Multimap<Integer, String> keyValues = ArrayListMultimap.create();
        keyValues.put(1, “a”);
        keyValues.put(1, “b”);
        keyValues.put(2, “c”);
        System.out.println(keyValues.toString());

 

8、本地缓存

guava的缓存设计的比较巧妙,这里就简单介绍一下,guava的缓存创建分为两种,一种是CacheLoader的方式,一种是callback的方式

参数的话就不多介绍了,直接在CacheBuilder中可以看到,类似这个

1
2
3
4
5
CacheBuilder.newBuilder()
            .maximumSize(10)
            .initialCapacity(3)
            .expireAfterWrite(10, TimeUnit.SECONDS)
            .build();

下面这个例子是callback的形式

1
2
3
4
5
6
7
8
9
10
11
12
public void getNameFromLocalCache() throws Exception{
        //new一个cache的对象出来
        Cache<String/*name*/,String/*nick*/> cache = CacheBuilder.newBuilder().maximumSize(10).build();
        //在get的时候,如果缓存里面没有,则通过实现一个callback的方法去获取
        String name = cache.get(“bixiao”, new Callable<String>() {
            public String call() throws Exception {
                return “bixiao.zy”+”-“+”iamzhongyong”;
            }
        });
        System.out.println(name);
        System.out.println(cache.toString());
    }

下面这个例子是LoadingCache的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void getNameLoadingCache(String name) throws Exception{
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
            //设置大小,条目数
            .maximumSize(20)
            //设置失效时间,创建时间
            .expireAfterWrite(20, TimeUnit.SECONDS)
            //设置时效时间,最后一次被访问
            .expireAfterAccess(20, TimeUnit.HOURS)
            //移除缓存的监听器
            .removalListener(new RemovalListener<String, String>() {
                public void onRemoval(RemovalNotification<String, String> notification) {
                    System.out.println(“有缓存数据被移除了”);
                }})
            //缓存构建的回调
            .build(new CacheLoader<String, String>(){//加载缓存
                @Override
                public String load(String key) throws Exception {
                    return key + “-” + “iamzhongyong”;
                }
        });
 
        System.out.println(cache.get(name));
        cache.invalidateAll();
    }
    

 

9、集合 并集、交集、补集的方法

1
2
3
4
5
6
7
8
9
10
11

    
Set setA = ImmutableSet.of(1, 2, 3, 4, 5);
Set setB = ImmutableSet.of(4, 5, 6, 7, 8);

 
        
    SetView union = Sets.union(setA, setB); 
    System.out.println(union);
        
    SetView difference = Sets.difference(setA, setB); 
    System.out.println(difference);  
        
    SetView intersection = Sets.intersection(setA, setB); 
    System.out.println(intersection);

ScheduledExecutorService类 scheduleWithFixedDelay() 和 scheduleFixedRate() 区别

 java  ScheduledExecutorService类 scheduleWithFixedDelay() 和 scheduleFixedRate() 区别已关闭评论
7月 312017
 

好记性不如烂笔头, 不用容易忘记,记录下 scheduleWithFixedDelay() 和 scheduleFixedRate() 区别

来自:http://blog.csdn.net/butingnal/article/details/12775277

先说scheduleWithFixedDelay(),

scheduleWithFixedDelay从字面意义上可以理解为就是以固定延迟(时间)来执行线程任务,它实际上是不管线程任务的执行时间的,每次都要把任务执行完成后再延迟固定时间后再执行下一次。

而scheduleFixedRate呢,是以固定频率来执行线程任务,固定频率的含义就是可能设定的固定时间不足以完成线程任务,但是它不管,达到设定的延迟时间了就要执行下一次了。

不知道大家理解了没有,下面是示例:

 

[java] view plain copy

  1. public static void scheduleWithFixedDelay() {  
  2.   
  3.     final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);  
  4.     // 响铃线程  
  5.     final Runnable beeper = new Runnable() {  
  6.         public void run() {  
  7.             SimpleDateFormat sf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);  
  8.             long time = (long) (Math.random() * 1000);  
  9.             // 输出线程的名字和使用目标对象及休眠的时间  
  10.             System.out.println(sf.format(new Date())+“线程:”+Thread.currentThread().getName()+“:Sleeping”+time+“ms”);  
  11.             try {  
  12.                 Thread.sleep(time);  
  13.             } catch (InterruptedException e) {  
[java] view plain copy

  1.         }  
  2.         }  
  3.     };  
  4.     // 设定执行线程计划,初始10s延迟,每次任务完成后延迟10s再执行一次任务  
  5.     final ScheduledFuture<?> sFuture=scheduledExecutorService.scheduleWithFixedDelay(beeper,10,10,TimeUnit.SECONDS);  
  6.   
  7.     // 40s后取消线程任务  
  8.     scheduledExecutorService.schedule(new Runnable() {  
  9.         public void run() {  
  10.             sFuture.cancel(true);  
  11.             scheduledExecutorService.shutdown();  
  12.         }  
  13.     }, 40, TimeUnit.SECONDS);  
  14.   
  15. }  


执行结果:

2013-10-16 10:45:51 线程:pool-1-thread-2:Sleeping 726ms
2013-10-16 10:46:02 线程:pool-1-thread-2:Sleeping 288ms
2013-10-16 10:46:12 线程:pool-1-thread-2:Sleeping 294ms

从执行结果数量看只执行了3次,因为每次要把任务执行完成再执行下一次,导致40s按10s的延迟时间不足以执行4次。

 

 

[html] view plain copy

  1. public static void scheduleAtFixedRate() {  
  2.     // 声明线程池  
  3.     final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);  
  4.     // 响铃线程  
  5.     final Runnable beeper = new Runnable() {  
  6.         public void run() {  
  7.             SimpleDateFormat sf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);  
  8.             long time = (long) (Math.random() * 1000);  
  9.             // 输出线程的名字和使用目标对象及休眠的时间  
  10.             System.out.println(sf.format(new Date()) + ” 线程:” + Thread.currentThread().getName() + “:Sleeping ” + time + “ms”);  
  11.             try {  
  12.                 Thread.sleep(time);  
  13.             } catch (InterruptedException e) {  
  14.             }  
  15.         }  
  16.     };  
  17.     // 计划响铃,初始延迟10s然后以10s的频率执行响铃  
  18.     final ScheduledFuture<?> beeperHandle = scheduledExecutorService.scheduleAtFixedRate(beeper, 10, 10, TimeUnit.SECONDS);  
  19.   
  20.     // 取消响铃并关闭线程  
  21.     final Runnable cancelBeeper = new Runnable() {  
  22.         public void run() {  
  23.             System.out.println(Thread.currentThread().getName() + “CANCEL…”);  
  24.             beeperHandle.cancel(true);  
  25.             scheduledExecutorService.shutdown();  
  26.         }  
  27.     };  
  28.     // 60s后执行scheduleAtFixedRate  
  29.     scheduledExecutorService.schedule(cancelBeeper, 40, TimeUnit.SECONDS);  
  30. }  

 

执行结果:

2013-10-16 10:16:50 线程:pool-1-thread-1:Sleeping 868ms
2013-10-16 10:17:00 线程:pool-1-thread-1:Sleeping 587ms
2013-10-16 10:17:10 线程:pool-1-thread-1:Sleeping 313ms
2013-10-16 10:17:20 线程:pool-1-thread-1:Sleeping 969ms
pool-1-thread-1CANCEL…

看时间我们就可以知道是每个10s就执行下一次了。

Spring注解@Component、@Repository、@Service、@Controller @Resource、@Autowired、@Qualifier 解析

 java, spring  Spring注解@Component、@Repository、@Service、@Controller @Resource、@Autowired、@Qualifier 解析已关闭评论
1月 102017
 

这篇文章解释的很清楚http://www.ulewo.com/user/10001/blog/273,记录下:

我们在使用spring的时候经常会用到这些注解,那么这些注解到底有什么区别呢。我们先来看代码

同样分三层来看:

Action 层:

package com.ulewo.ioc; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @Controller public class IocAction {
    @Autowired     private IocService service;
    
    public void add(){
        service.add();
    }
}

service层:(service就直接定义类了,没有定义接口,定义接口也是一样的)

package com.ulewo.ioc; import javax.annotation.Resource; import org.springframework.stereotype.Service; @Service public class IocService {
    @Resource     private IIocDao iocDao;
    public void add(){
        iocDao.add();
    }
}

Dao层

先定义一个接口

package com.ulewo.ioc; public interface IIocDao {
    public void add();
}

然后实现类:

package com.ulewo.ioc; import org.springframework.stereotype.Repository; @Repository public class IocDao implements IIocDao{
    public void add(){
        System.out.println("调用了dao");
    }
}

然后spring的配置,这个配置就很简单了,因为是基于注解的,我们不需要再xml中来定义很多

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>   <beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:jee="http://www.springframework.org/schema/jee"    xmlns:tx="http://www.springframework.org/schema/tx"    xmlns:context="http://www.springframework.org/schema/context"    xmlns:aop="http://www.springframework.org/schema/aop"    xmlns:task="http://www.springframework.org/schema/task"    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                       http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
                       http://www.springframework.org/schema/tx 
                       http://www.springframework.org/schema/tx/spring-tx-3.2.xsd 
                       http://www.springframework.org/schema/jee 
                       http://www.springframework.org/schema/jee/spring-jee-3.2.xsd 
                       http://www.springframework.org/schema/context 
                       http://www.springframework.org/schema/context/spring-context-3.2.xsd
                       http://www.springframework.org/schema/aop 
                       http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
                       http://www.springframework.org/schema/task 
                       http://www.springframework.org/schema/task/spring-task-3.2.xsd">     <context:annotation-config />         <context:component-scan base-package="com.ulewo.ioc" >     </context:component-scan>   </beans>

让spring自动扫描包就行了。

然后是我们的测试类:

IocTest:

package com.ulewo.ioc; import junit.framework.TestCase; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; public class IocTest extends TestCase{
    
    public void testIoc(){
        BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
        IocAction action = factory.getBean("iocAction", IocAction.class);
        action.add();
    }
}

运行后,我们会发现 控制台打印:调用了dao

@Component、@Repository、@Service、@Controller @Resource、@Autowired、@Qualifier 

这几个基本都用到了 除了 @Component  @Qualifier

我们观察会发现@Repository、@Service、@Controller 这几个是一个类型,其实@Component 跟他们也是一个类型的

Spring 2.5 中除了提供 @Component 注释外,还定义了几个拥有特殊语义的注释,它们分别是:@Repository、@Service和 @Controller 其实这三个跟@Component 功能是等效的

@Service用于标注业务层组件(我们通常定义的service层就用这个)

@Controller用于标注控制层组件(如struts中的action)

@Repository用于标注数据访问组件,即DAO组件

@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

这几个注解是当你需要定义某个类为一个bean,则在这个类的类名前一行使用@Service(“XXX”),就相当于讲这个类定义为一个bean,bean名称为XXX; 这几个是基于类的,我们可以定义名称,也可以不定义,不定义会默认以类名为bean的名称(类首字母小写)。

然后我们在看后面的几个注解

@Resource、@Autowired、@Qualifier

当需要在某个类中定义一个属性,并且该属性是一个已存在的bean,要为该属性赋值我们就用着三个。我们看上面的代码可以看到这三个都是定义在一个属性上的,比如

@Resource private IIocDao iocDao;
@Autowired private IocService service;

那这几个到底有什么区别呢?

我们先看@Resource,它是javax.annotation.Resource; 这个包中,也就是说是javaEE中的,并不是spring中的

而且@Resource(“xxx”) 是可以定义bean名称的,就是说我这个属性要用那个bean来赋值。

@Autowired,它是org.springframework.beans.factory.annotation.Autowired 是这个包中,它是spring的包。

而且它没有@Autowired(“xxx”),那我要为这个bean定义名称怎么办这个时候可以用@Qualifier(“xxx”) 这个也是spring中的。这个xxx定义bean名称有什么用呢?我们回头看下刚才的代码。

在IIocDao 这个接口中,我们定义的实现类IocDao 只有一个,好那么我们再定义一个实现类:

package com.ulewo.ioc; import org.springframework.stereotype.Repository; @Repository public class IocDao2 implements IIocDao{
    public void add(){
        System.out.println("调用了dao2");
    }
}

其他不变,我们再运行:testIoc(),控制台打印出 调用了dao,所以在service层中

@Resource
private IIocDao iocDao;

这个iocDao 注入的是IocDao 这个实现。奇怪了,它怎么知道我要调用哪个实现呢?

好我们修改一下,把 private IIocDao iocDao;改一下,改成 private IIocDao iocDaox 把属性名改一下,再运行,会报错:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.ulewo.ioc.IIocDao] is defined: expected single matching bean but found 2: iocDao,iocDao2

错误很明显啊,有两个bean iocDao和iocDao2,但是我们的是iocDaox所以找不到了。所以可以看出来在用 @Repository注解来生成bean的时候,如果没有定义名称那么就会根据类名来生成。所以我们要调用第二个实现的时候可以 定义为private IIocDao iocDao2 。我们再运行:调用了dao2,所以可以根据属性名来区分,到底注入那个bean。但是有的人说,我不想定义bean名称跟类实现一样,我要定义其他的,那怎么玩呢,方法有2种:

第一种:我们在生成bean的时候就给bean定义个名称 

@Repository(“myIocDao”)
public class IocDao implements IIocDao{
    public void add(){
        System.out.println(“调用了dao”);
    }
}

当然@Service是一样的,这样就把这个实现定义为myIocDao了,而不是默认的类名 iocDao。

那么我们在使用这个bean的时候就要这么定义了:

@Resource
private IIocDao myIocDao;

运行 输出:调用了dao

如果你这里不是用的 myIocDao,你又多加了一个x,成了myIocDaox,你运行会是这样的:

 org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.ulewo.ioc.IIocDao] is defined: expected single matching bean but found 2: myIocDao,iocDao2

所以,要自定义bean的名称可以在类注解的时候指定。

第二种:在注入bean的时候指定名称:

先看@Resource

我们这么定义下:

@Resource(name=”iocDao”)
private IIocDao xx;

注意:

@Repository
public class IocDao implements IIocDao{
    public void add(){
        System.out.println(“调用了dao”);
    }
}

这里还是用会默认的,也就是这个实现对应的是 iocDao这bean。如果你要为这个类指定别名bean,@Repository(“myIocDao”),那@Resource(name=”myIocDao”) 就要这么写了。就是这里的name要跟实现类对应的bean名称保持一致。private IIocDao xx; 这个属性名就随便写了。

运行:调用了dao

如果用Autowired就要这么写了

@Autowired
@Qualifier(“iocDao”)
private IIocDao xx;

因为Autowired 不能像Resource 那样带个参数指定一个name,就要用Qualifier来指定了。

而且还可以这么用

@Resource
@Qualifier(“iocDao”)
private IIocDao xx;

等同于

@Resource(name=”iocDao”)
private IIocDao xx;

记住一点:@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,如果发现找到多个bean,则,又按照byName方式比对,如果还有多个,则报出异常 而@Resource默认按 byName自动注入罢了。其实spring注解,最常用的还是根据名称,根据类型啊,构造方法啊,用的非常少。所以在多个实现的时候我们定义好bean的名称就行,就不会错乱。

说了这么多,不知道对着几个注解是不是了解多一点了呢,貌似这些注解 没有根本上的区别,就看你习惯怎么用了。拿代码多跑几次,然后根据自己的想法改改,你就明白这几个注解的用处啦。

ClassPathXmlApplicationContext的启动

 java, spring  ClassPathXmlApplicationContext的启动已关闭评论
1月 102017
 

好文章,分享下,转自:http://www.cnblogs.com/wade-luffy/p/6072460.html

Spring将ApplicationContext启动的全过程,refresh函数中包含了几乎ApplicationContext中提供的全部功能,而且此函数中逻辑非常清晰明了,很容易分析对应的层次及逻辑。:

ApplicationContext ac = new ClassPathXmlApplicationContext(“application.xml”);

复制代码
 @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { //准备刷新的上下文环境,例如对系统属性或者环境变量进行准备及验证。  prepareRefresh(); //初始化BeanFactory,并进行XML文件读取, //这一步之后,ClassPathXmlApplicationContext实际上就已经包含了BeanFactory所提供的功能,也就是可以进行Bean的提取等基础操作了。 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); //对BeanFactory进行各种功能填充,@Qualifier与@Autowired这两个注解正是在这一步骤中增加的支持。  //设置@Autowired和 @Qualifier注解解析器QualifierAnnotationAutowireCandidateResolver
       prepareBeanFactory(beanFactory); try { //子类覆盖方法做额外的处理,提供了一个空的函数实现postProcessBeanFactory来方便程序员在业务上做进一步扩展。  postProcessBeanFactory(beanFactory); //激活各种BeanFactory处理器  invokeBeanFactoryPostProcessors(beanFactory); //注册拦截Bean创建的Bean处理器,这里只是注册,真正的调用是在getBean时候  registerBeanPostProcessors(beanFactory); //为上下文初始化Message源,即不同语言的消息体进行国际化处理  initMessageSource(); //初始化应用消息广播器,并放入“applicationEventMulticaster”bean中  initApplicationEventMulticaster(); //留给子类来初始化其它的Bean  onRefresh(); //在所有注册的bean中查找Listener bean,注册到消息广播器中  registerListeners(); //初始化剩下的单实例(非惰性的)  finishBeanFactoryInitialization(beanFactory); //完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人  finishRefresh();
            } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore...  resetCommonCaches();
            }
        }
    }
复制代码

环境准备

复制代码
    protected void prepareRefresh() { this.startupDate = System.currentTimeMillis(); this.closed.set(false); this.active.set(true); if (logger.isInfoEnabled()) {
            logger.info("Refreshing " + this);
        } //留给子类覆盖  initPropertySources();
        //验证需要的属性文件是否都已经放入环境中  getEnvironment().validateRequiredProperties(); //to be published once the multicaster is available... this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
    }
复制代码

假如现在有这样一个需求,工程在运行过程中用到的某个设置(例如classpath)是从系统环境变量中取得的,而如果用户没有在系统环境变量中配置这个参数,那么工程可能不会工作。这一要求可能会有各种各样的解决办法,当然,在Spring中可以这样做,你可以直接修改Spring的源码,例如修改ClassPathXmlApplicationContext。当然,最好的办法还是对源码进行扩展,我们可以自定义类:

复制代码
public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext{ public MyClassPathXmlApplicationContext(String... configLocations ){ super(configLocations);
    } protected void initPropertySources() { //添加验证要求 getEnvironment().setRequiredProperties("classpath");
    }
}
复制代码

我们自定义了继承自ClassPathXmlApplicationContext的MyClassPathXmlApplicationContext,并重写了initPropertySources方法,在方法中添加了我们的个性化需求,那么在验证的时候也就是程序走到getEnvironment().validateRequiredProperties()代码的时候,如果系统并没有检测到对应classpath的环境变量,那么将抛出异常。

加载BeanFactory

复制代码
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { //初始化BeanFactory,并进行XML文件读取,并将得到的BeanFacotry记录在当前实体的属性中  refreshBeanFactory(); //返回当前实体的beanFactory属性 ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        } return beanFactory;
    }

    AbstractRefreshableApplicationContext.java
    @Override protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        } try { //在介绍BeanFactory的时候,声明方式为:BeanFactory bf = new XmlBeanFactory("beanFactoryTest.xml"),
       //其中的XmlBeanFactory继承自DefaultListableBeanFactory,并提供了XmlBeanDefinitionReader类型的reader属性,
       //也就是说DefaultListableBean Factory是容器的基础。必须首先要实例化,那么在这里就是实例化DefaultListableBeanFactory的步骤。 DefaultListableBeanFactory beanFactory = createBeanFactory(); //为了序列化指定id,如果需要的话,让这个BeanFactory从id反序列化到BeanFactory对象  beanFactory.setSerializationId(getId()); //定制beanFactory,设置相关属性,包括是否允许覆盖同名称的不同定义的对象以及循环依赖以及  customizeBeanFactory(beanFactory); //初始化DodumentReader,并进行XML文件读取及解析  loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { //使用全局变量记录BeanFactory类实例。 this.beanFactory = beanFactory;
            }
        } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    } protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { //如果属性allowBeanDefinitionOverriding不为空,设置给beanFactory对象相应属性, //此属性的含义:是否允许覆盖同名称的不同定义的对象 if (this.allowBeanDefinitionOverriding != null) {
            beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        } //如果属性allowCircularReferences不为空,设置给beanFactory对象相应属性, //此属性的含义:是否允许bean之间存在循环依赖 if (this.allowCircularReferences != null) {
            beanFactory.setAllowCircularReferences(this.allowCircularReferences);
        } }


    @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { //为指定beanFactory创建XmlBeanDefinitionReader XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); //对beanDefinitionReader进行环境变量的设置 beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); //对BeanDefinitionReader进行设置,可以覆盖  initBeanDefinitionReader(beanDefinitionReader); //使用XmlBeanDefinitionReader的loadBeanDefinitions方法进行配置文件的加载及注册  loadBeanDefinitions(beanDefinitionReader);
    }
复制代码

功能扩展

复制代码
    protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { //设置beanFactory的classLoader为当前context的classLoader  beanFactory.setBeanClassLoader(getClassLoader()); //设置beanFactory的表达式语言处理器,Spring3增加了表达式语言的支持,SPEL语言。 //默认可以使用#{bean.xxx}的形式来调用相关属性值。 beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader())); //为beanFactory增加了一个默认的propertyEditor,这个主要是对bean的属性等设置管理的一个工具 beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment())); //添加BeanPostProcessor //ApplicationContextAwareProcessor实现了BeanPostProcessor接口,在bean实例化的时候会被调用 //postProcessBeforeInitialization方法中调用了invokeAwareInterfaces。从invokeAwareInterfaces方法中,
     //我们可以看出来,实现这些Aware接口的bean在被初始化之后,可以取得一些对应的资源。 beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this)); //Spring将ApplicationContextAwareProcessor注册后,在invokeAwareInterfaces方法中间调用的Aware类已经不是普通的bean了, //如ResourceLoaderAware,ApplicationEventPublisherAware等,需要在Spring做bean的依赖注入的时候忽略它们。 beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
        beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
        beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
        beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
        beanFactory.ignoreDependencyInterface(EnvironmentAware.class); //设置了几个自动装配的特殊规则 //当注册了依赖解析后,例如当注册了对BeanFactory.class的解析后,当bean的属性注入的时候,
     //一旦检测到属性为BeanFactory类型便会将beanFactory的实例注入进去。 beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
        beanFactory.registerResolvableDependency(ResourceLoader.class, this);
        beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
        beanFactory.registerResolvableDependency(ApplicationContext.class, this); //增加对AspectJ的支持 if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); // Set a temporary ClassLoader for type matching. beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        } //将相关环境变量及属性注册以单例模式注册,environment,systemProperties,systemEnvironment if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
        } if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
        } if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
        }
    }
复制代码

ResourceEditorRegistrar的registerCustomEditors的调用时机,就是AbstractBeanFactory类中的initBeanWrapper方法,这是在bean初始化时使用的一个方法,主要是在将BeanDefinition转换为BeanWrapper后用于对属性的填充。在bean的初始化后会调用ResourceEditorRegistrar的registerCustomEditors方法进行批量的通用属性编辑器注册。注册后,在属性填充的环节便可以直接让Spring使用这些编辑器进行属性的解析了。
Spring中用于封装bean的是BeanWrapper类型,而它又间接继承了PropertyEditorRegistry类型,也就是我们之前反复看到的方法参数PropertyEditorRegistry,其实大部分情况下都是BeanWrapper,对于BeanWrapper在Spring中的默认实现是BeanWrapperImpl,而BeanWrapperImpl除了实现BeanWrapper接口外还继承了PropertyEditorRegistrySupport,在PropertyEditorRegistrySupport中有这样一个方法:createDefaultEditors,基本的属性编辑器就在此处被注册。

BeanFactory的后处理

BeanFactoryPostProcessor接口跟BeanPostProcessor类似,可以对bean的定义(配置元数据)进行处理。也就是说,Spring IoC容器允许BeanFactoryPostProcessor在容器实际实例化任何其他的bean之前读取配置元数据,并有可能修改它。如果你愿意,你可以配置多个BeanFactoryPostProcessor。你还能通过设置“order”属性来控制BeanFactoryPostProcessor的执行次序(仅当BeanFactoryPostProcessor实现了Ordered接口时你才可以设置此属性,因此在实现BeanFactoryPostProcessor时,就应当考虑实现Ordered接口)。

如果你想改变实际的bean实例(例如从配置元数据创建的对象),那么你最好使用BeanPostProcessor。同样地,BeanFactoryPostProcessor的作用域范围是容器级的。它只和你所使用的容器有关。如果你在容器中定义一个BeanFactoryPostProcessor,它仅仅对此容器中的bean进行后置处理。BeanFactoryPostProcessor不会对定义在另一个容器中的bean进行后置处理,即使这两个容器都是在同一层次上。

在Spring中存在对于BeanFactoryPostProcessor的两种典型应用。

(1)比如PropertyPlaceholderConfigurer

复制代码
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="locations">
   <list>
    <value>/WEB-INF/mail.properties</value>  
    <value>classpath: conf/sqlmap/jdbc.properties</value>//注意这两种value值的写法 </list>
  </property>
   <property name="fileEncoding">
     <value>UTF-8</value>
   </property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <property name="driverClassName"value="${jdbc.driverClassName}" />
  <property name="url" value="${jdbc.url}" />
  <property name="username" value="${jdbc.username}"/>
  <property name="password"value="${jdbc.password}" />
</bean> jdbc.properties文件
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/mysqldb?useUnicode=true&amp;characterEncoding=UTF-8&amp; jdbc.username=root
jdbc.password=123456
复制代码

PropertyPlaceholderConfigurer是个bean工厂后置处理器的实现,也就是BeanFactoryPostProcessor接口的一个实现。PropertyPlaceholderConfigurer可以将上下文(配置文件)中的属性值放在另一个单独的标准java Properties文件中去。在XML文件中用${key}替换指定的properties文件中的值。这样的话,只需要对properties文件进行修改,而不用对xml配置文件进行修改。
在Spring中,使用PropertyPlaceholderConfigurer可以在XML配置文件中加入外部属性文件,当然也可以指定外部文件的编码,PropertyPlaceholderConfigurer如果在指定的Properties文件中找不到你想使用的属性,它还会在Java的System类属性中查找。可以通过System.setProperty(key, value)或者java中通过-Dnamevalue来给Spring配置文件传递参数。

查看层级结构可以看出PropertyPlaceholderConfigurer这个类间接继承了BeanFactoryPostProcessor接口。这是一个很特别的接口,当Spring加载任何实现了这个接口的bean的配置时,都会在bean工厂载入所有bean的配置之后执行postProcessBeanFactory方法。在PropertyResourceConfigurer类中实现了postProcessBeanFactory方法,在方法中先后调用了mergeProperties、convertProperties、processProperties这3个方法,分别得到配置,将得到的配置转换为合适的类型,最后将配置内容告知BeanFactory。正是通过实现BeanFactoryPostProcessor接口,BeanFactory会在实例化任何bean之前获得配置信息,从而能够正确解析bean描述文件中的变量引用。

(2)使用自定义BeanFactoryPostProcessor 

实现一个BeanFactoryPostProcessor,实现一个简单的回调处理器,它能去除潜在的”流氓”属性值,例如bean定义中留下bollocks这样的字眼。

复制代码
<bean id="bfpp" class="com.spring.ch04.ObscenityRemovingBeanFactoryPostProcessor"> <property name="obscenties"> <set> <value>bollocks</value> <value>winky</value> <value>bum</value> <value>Microsoft</value> </set> </property> </bean> <bean id="simpleBean" class="com.spring.ch04.SimplePostProcessor"> <property name="connectionString" value="bollocks"/> <property name="password" value="imaginecup"/> <property name="username" value="Microsoft"/> </bean> 
复制代码

java代码

复制代码
public class ObscenityRemovingBeanFactoryPostProcessor implements BeanFactoryPostProcessor { private Set<String> obscenties; public ObscenityRemovingBeanFactoryPostProcessor(){ this.obscenties=new HashSet<String>();  
    } public void postProcessBeanFactory(  
            ConfigurableListableBeanFactory beanFactory) throws BeansException {  
        String[] beanNames=beanFactory.getBeanDefinitionNames(); for(String beanName:beanNames){  
            BeanDefinition bd=beanFactory.getBeanDefinition(beanName);  
            StringValueResolver valueResover=new StringValueResolver() { public String resolveStringValue(String strVal) { if(isObscene(strVal)) return "*****"; return strVal;  
                }  
            };  
            BeanDefinitionVisitor visitor=new BeanDefinitionVisitor(valueResover);  
            visitor.visitBeanDefinition(bd);  
        }  
    } public boolean isObscene(Object value){  
        String potentialObscenity=value.toString().toUpperCase(); return this.obscenties.contains(potentialObscenity);  
    } public void setObscenties(Set<String> obscenties) { this.obscenties.clear(); for(String obscenity:obscenties){ this.obscenties.add(obscenity.toUpperCase());  
        }  
    }  
  
} 
复制代码

测试类

复制代码
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; public class PropertyConfigurerDemo { public static void main(String[] args) {  
        ConfigurableListableBeanFactory bf=new XmlBeanFactory(new ClassPathResource("/META-INF/BeanFactory.xml"));  
        BeanFactoryPostProcessor bfpp=(BeanFactoryPostProcessor)bf.getBean("bfpp");  
        bfpp.postProcessBeanFactory(bf);  
        System.out.println(bf.getBean("simpleBean"));  
          
    }  
} 
复制代码

SimplePostProcessor{connectionString=*****,username=*****,password=imaginecup

激活BeanFactoryPostProcessor

1)从invokeBeanFactoryPostProcessors的方法中我们看到,对于BeanFactoryPostProcessor的处理主要分两种情况进行,一个是对于BeanDefinitionRegistry类的特殊处理,另一种是对普通的BeanFactoryPostProcessor进行处理。而对于每种情况都需要考虑硬编码注入注册的后处理器以及通过配置注入的后处理器

对于硬编码注册的后处理器的处理,主要是通过AbstractApplicationContext中的添加处理器方法addBeanFactoryPostProcessor进行添加。

public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor beanFactoryPostProcessor) {

this.beanFactoryPostProcessors.add(beanFactoryPostProcessor);

}

添加后的后处理器会存放在beanFactoryPostProcessors中,而在处理BeanFactoryPostProcessor时候会首先检测beanFactoryPostProcessors是否有数据。当然,BeanDefinitionRegistryPostProcessor继承自BeanFactoryPostProcessor,不但有BeanFactoryPostProcessor的特性,同时还有自己定义的个性化方法,也需要在此调用。所以,这里需要从beanFactoryPostProcessors中挑出BeanDefinitionRegistryPostProcessor的后处理器,并进行其postProcessBeanDefinitionRegistry方法的激活。

(2)记录后处理器主要使用了三个List完成。

registryPostProcessors:记录通过硬编码方式注册的BeanDefinitionRegistryPostProcessor类型的处理器。

regularPostProcessors:记录通过硬编码方式注册的BeanFactoryPostProcessor类型的处理器。

registryPostProcessorBeans:记录通过配置方式注册的BeanDefinitionRegistryPostProcessor类型的处理器。

(3)对以上所记录的List中的后处理器进行统一调用BeanFactoryPostProcessor的postProcessBeanFactory方法。

(4)对beanFactoryPostProcessors中非BeanDefinitionRegistryPostProcessor类型的后处理器进行统一的BeanFactoryPostProcessor的postProcessBeanFactory方法调用。

(5)普通beanFactory处理。BeanDefinitionRegistryPostProcessor只对BeanDefinitionRegistry类型的ConfigurableListableBeanFactory有效,所以如果判断所示的beanFactory并不是BeanDefinitionRegistry,那么便可以忽略BeanDefinitionRegistryPostProcessor,而直接处理BeanFactoryPostProcessor,当然获取的方式与上面的获取类似。

对于硬编码方式手动添加的后处理器是不需要做任何排序的,但是在配置文件中读取的处理器,Spring并不保证读取的顺序。所以,为了保证用户的调用顺序的要求,Spring对于后处理器的调用支持按照PriorityOrdered或者Ordered的顺序调用。

注册BeanPostProcessor

来探索下BeanPostProcessor,但是这里并不是调用,而是注册。真正的调用其实是在bean的实例化阶段进行的。这是一个很重要的步骤,也是很多功能BeanFactory不支持的重要原因。Spring中大部分功能都是通过后处理器的方式进行扩展的,这是Spring框架的一个特性,但是在BeanFactory中其实并没有实现后处理器的自动注册,所以在调用的时候如果没有进行手动注册其实是不能使用的。

对于BeanPostProcessor的处理与BeanFactoryPostProcessor的处理极为相似,但是似乎又有些不一样的地方。经过反复的对比发现,对于BeanFactoryPostProcessor的处理要区分两种情况,一种方式是通过硬编码方式的处理,另一种是通过配置文件方式的处理。那么为什么在BeanPostProcessor的处理中只考虑了配置文件的方式而不考虑硬编码的方式呢?对于BeanFactoryPostProcessor的处理,不但要实现注册功能,而且还要实现对后处理器的激活操作,所以需要载入配置中的定义,并进行激活;而对于BeanPostProcessor并不需要马上调用,再说,硬编码的方式实现的功能是将后处理器提取并调用,这里并不需要调用,当然不需要考虑硬编码的方式了,这里的功能只需要将配置文件的BeanPostProcessor提取出来并注册进入beanFactory就可以了。

对于beanFactory的注册,也不是直接注册就可以的。在Spring中支持对于BeanPostProcessor的排序,比如根据PriorityOrdered进行排序、根据Ordered进行排序或者无序,而Spring在BeanPostProcessor的激活顺序的时候也会考虑对于顺序的问题而先进行排序。

初始化消息资源

在initMessageSource中的方法主要功能是提取配置中定义的messageSource,并将其记录在Spring的容器中,也就是AbstractApplicationContext中。当然,如果用户未设置资源文件的话,Spring中也提供了默认的配置DelegatingMessageSource。

在initMessageSource中获取自定义资源文件的方式为beanFactory.getBean(MESSAGE_ SOURCE_BEAN_NAME, MessageSource.class),在这里Spring使用了硬编码的方式硬性规定了子定义资源文件必须为message,否则便会获取不到自定义资源配置。

复制代码
    protected void initMessageSource() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory(); //messageSource if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) { //如果在配置中已经配置了messageSource,那么将messageSource提取并记录在this.messageSource中 this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class); if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
                HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource; if (hms.getParentMessageSource() == null) {  hms.setParentMessageSource(getInternalParentMessageSource());
                }
            } if (logger.isDebugEnabled()) {
                logger.debug("Using MessageSource [" + this.messageSource + "]");
            }
        } else { //如果用户并没有定义配置文件,那么使用临时的DelegatingMessageSource以便于作为调用getMessage方法的返回。 DelegatingMessageSource dms = new DelegatingMessageSource();
            dms.setParentMessageSource(getInternalParentMessageSource()); this.messageSource = dms;
            beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource); if (logger.isDebugEnabled()) {
                logger.debug("Unable to locate MessageSource with name '" + MESSAGE_SOURCE_BEAN_NAME +
                        "': using default [" + this.messageSource + "]");
            }
        }
    }
复制代码

初始化ApplicationEventMulticaster

initApplicationEventMulticaster的方式比较简单,无非考虑两种情况:

如果用户自定义了事件广播器,那么使用用户自定义的事件广播器。

如果用户没有自定义事件广播器,那么使用默认的ApplicationEventMulticaster。

复制代码
    protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory(); //applicationEventMulticaster if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { this.applicationEventMulticaster = beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class); if (logger.isDebugEnabled()) {
                logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
            }
        } else { this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster); if (logger.isDebugEnabled()) {
                logger.debug("Unable to locate ApplicationEventMulticaster with name '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
                        "': using default [" + this.applicationEventMulticaster + "]");
            }
        }
    }
复制代码

按照之前介绍的顺序及逻辑,作为广播器,一定是用于存放监听器并在合适的时候调用监听器,那么进入默认的广播器实现SimpleApplicationEventMulticaster来一探究竟。

复制代码
 @Override public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor(); if (executor != null) {
                executor.execute(new Runnable() {
                    @Override public void run() {
                        invokeListener(listener, event);
                    }
                });
            } else {
                invokeListener(listener, event);
            }
        }
    } protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
   ErrorHandler errorHandler = getErrorHandler(); if (errorHandler != null) { try {
         listener.onApplicationEvent(event);
      } catch (Throwable err) {
         errorHandler.handleError(err);
      }
   } else {
      listener.onApplicationEvent(event);
   }
}
复制代码

可以推断,当产生Spring事件发生的时候会默认使用SimpleApplicationEventMulticaster的multicastEvent来广播事件,遍历所有监听器,并使用监听器中的onApplicationEvent方法来进行监听器的处理。而对于每个监听器来说其实都可以获取到产生的事件,但是是否进行处理则由事件监听器来决定。

注册监听器

复制代码
    protected void registerListeners() { //硬编码方式注册的监听器处理 for (ApplicationListener<?> listener : getApplicationListeners()) {
            getApplicationEventMulticaster().addApplicationListener(listener);
        } //配置文件注册的监听器处理 String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false); for (String listenerBeanName : listenerBeanNames) {
            getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
        }
        
     //广播早期的事件进行广播和事件处理  // Publish early application events now that we finally have a multicaster... Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents; this.earlyApplicationEvents = null; if (earlyEventsToProcess != null) { for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
                getApplicationEventMulticaster().multicastEvent(earlyEvent);
            }
        }
    }
复制代码

初始化非延迟加载单例

finishBeanFactoryInitialization完成BeanFactory的初始化工作,其中包括ConversionService的设置、配置冻结以及非延迟加载的bean的初始化工作。

复制代码
    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { //conversionService的bean会被注册 if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
            beanFactory.setConversionService(
                    beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
        } // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early. String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false); for (String weaverAwareName : weaverAwareNames) {
            getBean(weaverAwareName);
        } // Stop using the temporary ClassLoader for type matching. beanFactory.setTempClassLoader(null); //冻结所有的bean定义,说明注册的bean定义将不被修改或任何进一步的处理。  beanFactory.freezeConfiguration(); //初始化剩下的单实例(非惰性的)  beanFactory.preInstantiateSingletons();
    }
复制代码

ConversionService的设置,之前我们提到过使用自定义类型转换器从String转换为Date的方式,使用属性编辑器,那么,在Spring中还提供了另一种转换方式:使用Converter。

ApplicationContext实现的默认行为就是在启动时将所有单例bean提前进行实例化。提前实例化意味着作为初始化过程的一部分,ApplicationContext实例会创建并配置所有的单例bean。通常情况下这是一件好事,因为这样在配置中的任何错误就会即刻被发现(否则的话可能要花几个小时甚至几天)。而这个实例化的过程就是在finishBeanFactoryInitialization中完成的,详细流程见Spring框架的设计理念章节。

finishRefresh

在Spring中还提供了Lifecycle接口,Lifecycle中包含start/stop方法,实现此接口后Spring保证在启动的时候调用其start方法开始生命周期,并在Spring关闭的时候调用stop方法来结束生命周期,通常用来配置后台程序,在启动后一直运行(如对MQ进行轮询等)。而ApplicationContext的初始化最后正是保证了这一功能的实现。

复制代码
    protected void finishRefresh() { // Initialize lifecycle processor for this context.  //lifecycleProcessor的bean会被注册
     initLifecycleProcessor(); // Propagate refresh to lifecycle processor first.  getLifecycleProcessor().onRefresh(); // Publish the final event. publishEvent(new ContextRefreshedEvent(this)); // Participate in LiveBeansView MBean, if active. LiveBeansView.registerApplicationContext(this);
    }
复制代码

initLifecycleProcessor

当ApplicationContext启动或停止时,它会通过LifecycleProcessor来与所有声明的bean周期做状态更新,而在LifecycleProcessor的使用前首先需要初始化。

onRefresh

启动所有实现了Lifecycle接口的bean。

publishEvent

当完成ApplicationContext初始化的时候,要通过Spring中的事件发布机制来发出ContextRefreshedEvent事件,以保证对应的监听器可以做进一步的逻辑处理。

使用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.