ibatis 测试时出现错误提示: “could not find sql statement to include with refid xxx” , 出现这种错误一般是下面两种情况引起:
- refid引用的sqlid不正确, 自己好好检查下。
- refid正确,但还是报错, 可能原因是: sql refid定义的位置必须在引用的语句之前,否则会出错。
DONE!
ibatis 测试时出现错误提示: “could not find sql statement to include with refid xxx” , 出现这种错误一般是下面两种情况引起:
DONE!
jdk提供了非常方便的方式来生成线程池, 如Executors.newxxxxxx的方式, 实现实际使用的都是以下ThreadPoolExecutor的方法:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
线程池的管理是这样一个过程:
首先创建线程池, 然后根据任务的数量逐步将线程增大到corePoolSize数量, 如果此时任有任务增加, 则放置到workQueue中, 直到workQueue爆满为止, 然后继续增加线程池中的线程数量,增加处理能力,最终达到maximumPoolSize。 如果此时还是有任务增加进来会怎样呢 ? 这就需要handler来处理了,或者丢弃新任务, 或者拒绝新任务,或者挤占已有任务等。在任务队列何线程池都饱和情况下,一旦有线程处于等待(任务处理完毕, 没有新任务增加)状态的时间超过keepAliveTime,则该线程终止, 也就是说池中的线程数量会足部降低, 直至为corePoolSize数量为止。 至于threadFactory,可以自己新增一个,设置线程的自定义名称, daemon状态,便于后续排查错误。
我们用一个测试程序来帮助理解线程池的运行过程:
public class ThreadPoolTest {
private static ExecutorService es = new ThreadPoolExecutor(50, 100, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(100000));
public static void main(String[] args) throws Exception {
for (int i = 0; i < 100000; i++) {
es.execute(() -> {
System.out.print(1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
ThreadPoolExecutor tpe = ((ThreadPoolExecutor) es);
while (true) {
System.out.println();
int queueSize = tpe.getQueue().size();
System.out.println(“当前排队任务数:” + queueSize);
int activeCount = tpe.getActiveCount();
System.out.println(“当前活动线程数:” + activeCount);
long completedTaskCount = tpe.getCompletedTaskCount();
System.out.println(“执行完成线程数:” + completedTaskCount);
long taskCount = tpe.getTaskCount();
System.out.println(“总线程数:” + taskCount);
Thread.sleep(3000);
}
}
}
程序每3秒打印一次线程池情况:
11111111111111111111111111111111111111111111111111
当前排队线程数:99950
当前活动线程数:50
执行完成线程数:0
总线程数:100000
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
当前排队线程数:99849
当前活动线程数:50
执行完成线程数:127
总线程数:100000
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
当前排队线程数:99700
当前活动线程数:50
执行完成线程数:250
总线程数:100000
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
当前排队线程数:99550
当前活动线程数:50
执行完成线程数:400
总线程数:100000
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
当前排队线程数:99400
当前活动线程数:50
执行完成线程数:550
总线程数:100000
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
当前排队线程数:99250
当前活动线程数:50
执行完成线程数:700
总线程数:100000
。。。。。。
可以看到当前活动的线程数永远都是50, 为什么就没有突破?怎么没到100呢? 大家可以思考下,相信看了上面的描述,很容易就能理解了,DONE!
使用intellj idea 启动tomcat测试一个简单war包,测试里面一个spring mvc 页面, 使用jstl方式时出现一个怪问题,记录下:
index.jsp页面内容如下:
<%@ page import="java.util.Date" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Index</title> </head> <body> <p> Hello Spring MVC!!! </p> <p> <% Date now1 = new Date(); %> 服务器时间1: <fmt:formatDate value="<%=now1%>" pattern="yyyy-MM-dd HH:mm:ss" /> </p> <p> 服务器时间2: <fmt:formatDate value="${now2}" pattern="yyyy-MM-dd HH:mm:ss" /> </p> </body> </html> IndexController内容:
@Controller public class IndexController { @RequestMapping(value = {"", "/index"}) public ModelAndView dicts() { ModelAndView mv = new ModelAndView("index"); mv.addObject("now2", new Date()); return mv; } }
测试结果,出现错误:
Unable to convert string [${now2}] to class [java.util.Date] for attribute [value]: [Property Editor not registered with the PropertyEditorManager]
如果把jsp页面中 “服务器时间2xxxx” 这行注释掉又一切正常,这就奇怪了。
后来在网上找到解决方案:发现有由于web.xm
声明的问题:
原来声明:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
这样写有问题,修改为:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" metadata-complete="true" version="3.0"> 。。。。 </web-app>
便不会出现这个问题了。网上有说是因为Tomcat的支持web.xml的Servlet是2.5版本。
记录下!
DONE!
一个运行于tomcat下的war包,忽然服务出现了乱码,问题排查如下:
<Connector ……
redirectPort=”443″ URIEncoding=”UTF-8″ />, 发现问题不在这里
[root@ip-172-xx-xx]# ps -ef | grep java
root 6724 1 3 14:30 pts/0 00:01:04 /usr/local/jdk/bin/java –
[root@ip-172-xxxx]# jinfo 6724 |grep enc
sun.jnu.encoding = ANSI_X3.4-1968
file.encoding.pkg = sun.io
sun.io.unicode.encoding = UnicodeLittle
file.encoding = ANSI_X3.4-1968
果然问题出在这里。 可以在tomcat的bin目录下的catalina.sh,添加给 JAVA_OPTS 的多添加两个参数
-Dsun.jnu.encoding=UTF-8 -Dfile.encoding=UTF-8
重启tomcat,DONE!
在linux系统中,由于涉及到版权问题,在大部分linux系统的发行版本中,默认都安装了OpenJDK,并且OpenJDK的java命令也已经加入到环境变量中了。
在刚装好的linux系统中,运行java -version,输出如下(根据JDK版本不同,输出的版本可能不同):
java version “1.7.0_131”
OpenJDK Runtime Environment (rhel-2.6.9.0.el6_8-x86_64 u131-b00)
但在进行java开发时,我们大多是需要使用Sun(准确的说应该是Oracle)的JDK,所以我们会去Oracle官网下载相应版本的JDK进行安装。
如何在linux上安装,在此不在赘述。
在linux上安装完JDK后,我们会在~/.bash_profile 把安装的java加入到环境变量中。原以为就万事大吉了,在运行java程序时会使用自己安装的JDK。
如果你是这么想的就大错特错了,在文章开头讲的,由于linux系统的发行版中默认安装的时OpenJDK,尽管我们把安装的SunJDK的java命令也加入到环境变量中,但是我们运行
java -version,输出确还是和之前一样:
java version “1.7.0_131”
OpenJDK Runtime Environment (rhel-2.6.9.0.el6_8-x86_64 u131-b00)
这要这么破!!!
一、查找原因
不急,先运行
whereis java,输出如下:
java: /usr/bin/java /etc/java /usr/lib/java /usr/share/java /opt/java/bin/java
在几个地方有java命令,其中/opt/java/bin/java是我自己安装的SunJDK的java命令所在的目录,而/usr/bin/java是系统中默认安装的java命令所在的目录。
原因就在这,
我们执行
ls -la /usr/bin/java,输入如下:
lrwxrwxrwx 1 root root 22 May 14 16:53 /usr/bin/java -> /etc/alternatives/java
一看这是一个软连接,我们cd到/etc/alternatives目录下看个究竟,
然后执行 ls -la,输入比较多,而且很多软链,其中有一个
lrwxrwxrwx 1 root root 46 May 14 18:22 java -> /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
到此,应该明白了,java命令其实指向的是OpenJDK中的java命令,所以才会出现我们把自己安装的SunJDK的java命令配置到环境变量中后,依然不起作用。
所以我们要做的就是需要把java的软链指向我们自己安装的SunJDK的java命令,也即/opt/java/bin/java。
二、使用安装的JDK替代系统默认的OpenJDK
linux中提供了update-alternatives命令,update-alternatives是linux系统(大部分linux系统都支持此命令)中专门维护系统命令链接符的工具,通过它可以很方便的设置系统默认使用哪个命令、哪个软件版本,而所有的这些就构成了备选方案系统(alternatives system)。
很多时候我们会将拥有相同或相似功能的不同应用程序安装在同一个操作系统上,例如同一个操作系统上的不同文本编辑器。这给了这个系统的用户在编辑文本时有了更多的选择,如果用户愿意他们可以自由选择任意一个来使用。
其实update-alternatives命令的功能很好理解,举个例子:在Window系统中,如果我们安装多个浏览器,比如有火狐、有IE、有360、有Chrome等,当我们从某个链接进去打开网页时,可能会问你使用哪个浏览器,当你选择某个浏览器时,此浏览器会问你是否把它作为默认浏览器,如果你选择了是,那下次点开某个链接时,就直接使用默认浏览器打开了!
现在我们在系统中同时安装了open jdk和sun jdk两个版本,(由于linux发行版本中默认使用OpenJDK的java命令 )而我们又希望系统默认使用的是sun jdk,那怎么办呢?通过update-alternatives就可以很方便的实现了。
首先运行以下命令查看java当前的默认配置
update-alternatives –display java,输出如下(后面有省略):
java – status is manual.
link currently points to /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
/usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java – priority 170131
slave keytool: /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/keytool
slave orbd: /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/orbd
……
可以从输出中看到系统中java默认使用的OpenJDK,其中数字170131表示优先级。
2.1 下面进入正式配置阶段
1. 把自己安装的SunJDK加入到备选系统中
运行命令(注意命令最后的数字表示优先级,其中170130 为SunJDK的alternative的优先级)
update-alternatives –install /usr/bin/java java /opt/java/bin/java 170130
2. 选择自己安装的SunJDK作为首选java命令
运行命令
update-alternatives –config java,输出如下
There are 2 programs which provide ‘java’.
Selection Command
———————————————–
*+ 1 /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
2 /opt/java/bin/java
Enter to keep the current selection[+], or type selection number:
config命令是交互式的命令,可以根据提示进行选择,此处我们选择2,即使用安装的SunJDK的java命令,然后按回车键!
至此,配置完成了!
说明:在配置步骤1中,我们指定SunJDK的优先级是170130(比当前优先级170131低),所以需要进行步骤二进行手动选择;如果我们指定的优先级比当前优先级170131高,则步骤二可以省略,系统自动会选择优先级高的作为默认alternative!
关于update-alternatives命令更详细的说明,请参照
1.http://www.cnblogs.com/pengdonglin137/p/3462492.html
2. http://www.mamicode.com/info-detail-1144825.html
3. http://persevere.iteye.com/blog/1479524
原文链接:https://blog.csdn.net/rj042/java/article/details/72034650
好文章, 转自:原文: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
R
No languages:
Language length bigger three:
Python
scala
Shell
可以看到,Stream API的过滤方法也接受一个Predicate,这意味着可以将我们定制的 filter() 方法替换成写在里面的内联代码,这也是lambda表达式的魔力!
Arthas真是好用,项目地址:https://github.com/alibaba/arthas , 回想btrace时代真是辛苦。以下文字来自官方文档摘录。
Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。
当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
下载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
Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,请复制以下内容,并粘贴到命令行中,敲 回车 执行即可:
curl -L https://alibaba.github.io/arthas/install.sh | sh
上述命令会下载启动脚本文件 as.sh 到当前目录,你可以放在任何地方或将其加入到 $PATH 中。
直接在shell下面执行./as.sh,就会进入交互界面。
也可以执行./as.sh -h来获取更多参数信息。
|
wget https://alibaba.github.io/arthas/arthas-demo.jar
|
|
java -jar arthas-demo.jar
|
arthas-demo是一个简单的程序,每隔一秒生成一个随机数,再执行质因式分解,并打印出分解结果。
arthas-demo源代码:查看
在命令行下面执行(使用和目标进程一致的用户启动,否则可能attach失败):
|
wget https://alibaba.github.io/arthas/arthas-boot.jar
|
|
java -jar arthas-boot.jar
|
选择应用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
|
|
|
|
$
|
输入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
|
thread 1会打印线程ID 1的栈,通常是main函数的线程。
|
$ thread 1 | grep 'main('
|
|
at demo.MathGame.main(MathGame.java:17)
|
|
$ 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命令来查看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],
|
|
]
|
更多的功能可以查看进阶使用。
如果只是退出当前的连接,可以用quit或者exit命令。Attach到目标进程上的arthas还会继续运行,端口会保持开放,下次连接时可以直接连接上。
如果想完全退出arthas,可以执行shutdown命令。
方法执行数据观测
让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 OGNL 表达式进行对应变量的查看。
watch 的参数比较多,主要是因为它能在 4 个不同的场景观察对象
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
express | 观察表达式 |
condition-express | 条件表达式 |
[b] | 在方法调用之前观察 |
[e] | 在方法异常之后观察 |
[s] | 在方法返回之后观察 |
[f] | 在方法结束之后(正常返回和异常返回)观察 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[x:] | 指定输出结果的属性遍历深度,默认为 1 |
这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写”{params,returnObj}”,只要是一个合法的 ognl 表达式,都能被正常支持。
观察的维度也比较多,主要体现在参数 advice 的数据结构上。Advice 参数最主要是封装了通知节点的所有信息。请参考表达式核心变量中关于该节点的描述。
特别说明:
启动快速入门里的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],
|
|
],
|
|
]
|
|
$ 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],
|
|
],
|
|
]
|
|
$ 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)
|
|
,
|
|
]
|
|
$ 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],
|
|
],
|
|
]
|
如果想查看方法运行前后,当前对象中的属性,可以使用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]
|
转自:https://www.jianshu.com/p/de903c074d77
挺好的文章介绍google authenticator,转自:https://www.jianshu.com/p/de903c074d77
在网络攻击日益泛滥的今天, 用户的密码可能会因为各种原因泄漏. 而一些涉及用户重要数据的服务, 如 QQ, 邮箱, 银行, 购物等等. 一但被有心人利用, 那么除了自己隐私泄漏的风险外, 还存在自己身份被冒充的危害, 更有可能而导致极其严重的结果. 为此谷歌推出了Google Authenticator服务, 其原理是在登录时除了输入密码外, 还需根据Google Authenticator APP输入一个实时计算的验证码. 凭借此验证码, 即使在密码泄漏的情况下, 他人也无法登录你的账户
Google Authenticator使用了一种基于 ** 时间 ** 的TOTP算法, 其中时间的选取为自1970-01-01 00:00:00以来的毫秒数除以30与 客户端及服务端约定的 ** 密钥 ** 进行计算, 计算结果为一个 **6 位数的字符串 *( 首位数可能为 0, 所以为字符串 *), 所以在Google Authenticator中我们可以看见验证码每个 30 秒就会刷新一次. 更多详情可查看 Google 账户两步验证的工作原理 一文
由上可知, 生成验证码有俩个重要的参数, 其一为 ** 客户端与服务端约定的密钥 **, 其二便为 **30 秒的个数 **
/**
* 随机生成一个密钥
*/ public static String createSecretKey() {
SecureRandom random = new SecureRandom(); byte[] bytes = new byte[20];
random.nextBytes(bytes);
Base32 base32 = new Base32();
String secretKey = base32.encodeToString(bytes); return secretKey.toLowerCase();
}
//1970-01-01 00:00:00 以来的毫秒数除以 30 long time = System.currentTimeMillis() / 1000 / 30;
根据这两个参数就可以生成一个验证码
/**
* 根据密钥获取验证码
* 返回字符串是因为验证码有可能以 0 开头
* @param secretKey 密钥
* @param time 第几个 30 秒 System.currentTimeMillis() / 1000 / 30
*/ public static String getTOTP(String secretKey, long time) {
Base32 base32 = new Base32(); byte[] bytes = base32.decode(secretKey.toUpperCase());
String hexKey = Hex.encodeHexString(bytes);
String hexTime = Long.toHexString(time); return TOTP.generateTOTP(hexKey, hexTime, "6");
}
因为Google Authenticator(* 以下简称 APP*) 计算验证码也需要 ** 密钥 ** 的参与, 而时间 APP 则会在本地获取, 所以我们需要将 ** 密钥保存在 APP 中 **, 同时为了与其他账户进行区分, 除了密钥外, 我们还需要录入 ** 服务名称 , 用户账户 ** 信息. 而为了方便用户信息的录入, 我们一般将所有信息生成一张二维码图片, 让用户通过扫码自动填写相关信息
/**
* 生成 Google Authenticator 二维码所需信息
* Google Authenticator 约定的二维码信息格式 : otpauth://totp/{issuer}:{account}?secret={secret}&issuer={issuer}
* 参数需要 url 编码 + 号需要替换成 %20
* @param secret 密钥 使用 createSecretKey 方法生成
* @param account 用户账户 如: [email protected] 138XXXXXXXX
* @param issuer 服务名称 如: Google Github 印象笔记
*/ public static String createGoogleAuthQRCodeData(String secret, String account, String issuer) {
String qrCodeData = "otpauth://totp/%s?secret=%s&issuer=%s"; try { return String.format(qrCodeData, URLEncoder.encode(issuer + ":" + account, "UTF-8").replace("+", "%20"), URLEncoder.encode(secret, "UTF-8")
.replace("+", "%20"), URLEncoder.encode(issuer, "UTF-8").replace("+", "%20"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} return "";
}
此时再根据上述信息生成二维码, 二维码生成方式可参考以下两种方案
此时选择使用Java的方式返回一个二维码图片流
/**
* 将二维码图片输出到一个流中
* @param content 二维码内容
* @param stream 输出流
* @param width 宽
* @param height 高
*/ public static void writeToStream(String content, OutputStream stream, int width, int height) throws WriterException, IOException {
BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
MatrixToImageWriter.writeToStream(bitMatrix, format, stream);
}
扫描二维码
扫描成功后会新增一栏验证码信息
再让用户输入验证码, 与服务端进行校验, 如果校验通过, 则表明用户可以完好使用该功能
因为验证码是使用基于时间的TOTP算法, 依赖于客户端与服务端时间的一致性. 如果客户端时间与服务端时间相差过大, 那在用户没有同步时间的情况下, 永远与服务端进行匹配. 同时服务端也有可能出现时间偏差的情况, 这样反而导致时间正确的用户校验无法通过
为了解决这种情况, 我们可以使用 ** 时间偏移量 ** 来解决该问题,Google Authenticator验证码的时间参数为1970-01-01 00:00:00 以来的毫秒数除以 30, 所以每 30 秒就会更新一次. 但是我们在后台进行校验时, 除了与当前生成的二维码进行校验外, 还会对当前时间参数 ** 前后偏移量 ** 生成的验证码进行校验, 只要其中任意一个能够校验通过, 就代表该验证码是有效的
/** 时间前后偏移量 */ private static final int timeExcursion = 3; /**
* 校验方法
* @param secretKey 密钥
* @param code 用户输入的 TOTP 验证码
*/ public static boolean verify(String secretKey, String code) { long time = System.currentTimeMillis() / 1000 / 30; for (int i = -timeExcursion; i <= timeExcursion; i++) {
String totp = getTOTP(secretKey, time + i); if (code.equals(totp)) { return true;
}
} return false;
}
根据以上代码我们可以简单的创建一个Google Authenticator的应用. 但是与此同时, 我们也发现Google Authenticator严重依赖手机, 又因为Google Authenticator** 没有同步功能 **, 所以如果用户一不小心删除了记录信息, 或者 APP 被卸载, 手机系统重装等情况. 就会导致Google Authenticator成为使用者的障碍. 此时我们可以使用 Authy 这款支持 ** 同步功能 ** 的 APP 以解决删除, 卸载, 重装等问题. 同时 Authy 也存在 Chrome 插件 版本, 用于解决在手机丢失的情况下获取验证码.
除了 Authy 这个选择外, 我们还可以使用 ** 备用验证码 ** 的机制用户用于解决上述问题. 即在用户绑定Google Authenticator成功后自动为用户生成多个 ** 备用验证码 **, 然后在前台显示. 并让用户进行保存, 再让用户使用备用验证码进行校验, 以确保用户保存成功, 可以参考 ** 印象笔记 ** 的用法 如何开启印象笔记登录两步验证?
以上所使用的代码可在 Google-Authenticator 中查看
另外本文同时参考了以下资料
作者:jnil
链接:https://www.jianshu.com/p/de903c074d77
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
网上找到的一篇讲解java 读取文件路径比较清楚的文章, 分享下:http://blog.csdn.net/aitangyong/article/details/36471881
使用java读取jar或war下的配置文件,是开发者经常需要处理的事情,大家是不是经常遇到FileNotFoundException呢?java读取文件的方式也有很多,比如new File(),Class.getResource(),ClassLoader.getResource(),这些方式的差别是什么呢?开源框架struts2的ClassLoaderUtils和Spring提供ClassPathResource,都提供了对资源读取进行封装的工具类,你是否了解他们的实现原理呢?本文结合网上的一些博客和自己的理解,和大家一起讨论下java的文件读取问题。
工程在硬盘和eclipse的目录结构如下:
在eclipse中运行上面的程序,发现2种方式都是能够正确读取文件的,不会抛FileNotFoundException。
使用绝对路径,虽然定位很清晰,但是不灵活。比如你将上面的工程放到D盘下,就必须要修改绝对路径路径,这显然很不方便。使用相对路径则跟工程所在的硬盘路径无关,直接导入eclipse中运行,就能够正确读取文件内容。而且实际情况是,很多时候我们并不知道文件的绝对路径,这会因为部署环境的不同而不同。比如将制作好的war放到tomcat或jboss容器下运行,很显然绝对路径是不同的,而我们的代码事先并不知道。
那么使用相对路径呢?很遗憾,也同样存在很多问题。File是java.io包的基础类,java.io 包中的类总是根据当前用户目录来分析相对路径名。也就是说以下2种方式是等价的,
也就是说相对路径是否好使,取决于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读取文件,无论是相对路径,还是绝对路径都不是好的做法,能不使用就不要使用。
Class.getResource()有2种方式,绝对路径和相对路径。绝对路径以/开头,从classpath或jar包根目录下开始搜索;
相对路径是相对当前class所在的目录,允许使用..或.来定位文件。ClassLoader.getResource()只能使用绝对路径,而且不用以/开头。
这两种方式读取资源文件,不会依赖于user.dir,也不会依赖于具体部署的环境,是推荐的做法。
可以得出结论:
可以看出spring提供的ClassPathResource,底层使用的就是Class.getResource或ClassLoader.getResource()。spring提供的读取文件API功能,自然是与JDK一致。
工程在硬盘和eclipse的目录结构如下:
在eclipse中运行上面的程序,发现2种方式都是能够正确读取文件的,不会抛FileNotFoundException。
使用绝对路径,虽然定位很清晰,但是不灵活。比如你将上面的工程放到D盘下,就必须要修改绝对路径路径,这显然很不方便。使用相对路径则跟工程所在的硬盘路径无关,直接导入eclipse中运行,就能够正确读取文件内容。而且实际情况是,很多时候我们并不知道文件的绝对路径,这会因为部署环境的不同而不同。比如将制作好的war放到tomcat或jboss容器下运行,很显然绝对路径是不同的,而我们的代码事先并不知道。
那么使用相对路径呢?很遗憾,也同样存在很多问题。File是java.io包的基础类,java.io 包中的类总是根据当前用户目录来分析相对路径名。也就是说以下2种方式是等价的,
也就是说相对路径是否好使,取决于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读取文件,无论是相对路径,还是绝对路径都不是好的做法,能不使用就不要使用。
Class.getResource()有2种方式,绝对路径和相对路径。绝对路径以/开头,从classpath或jar包根目录下开始搜索;
相对路径是相对当前class所在的目录,允许使用..或.来定位文件。ClassLoader.getResource()只能使用绝对路径,而且不用以/开头。
这两种方式读取资源文件,不会依赖于user.dir,也不会依赖于具体部署的环境,是推荐的做法。
可以得出结论:
可以看出spring提供的ClassPathResource,底层使用的就是Class.getResource或ClassLoader.getResource()。spring提供的读取文件API功能,自然是与JDK一致。
使用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);
|
好记性不如烂笔头, 不用容易忘记,记录下 scheduleWithFixedDelay() 和 scheduleFixedRate() 区别
来自:http://blog.csdn.net/butingnal/article/details/12775277
先说scheduleWithFixedDelay(),
scheduleWithFixedDelay从字面意义上可以理解为就是以固定延迟(时间)来执行线程任务,它实际上是不管线程任务的执行时间的,每次都要把任务执行完成后再延迟固定时间后再执行下一次。
而scheduleFixedRate呢,是以固定频率来执行线程任务,固定频率的含义就是可能设定的固定时间不足以完成线程任务,但是它不管,达到设定的延迟时间了就要执行下一次了。
不知道大家理解了没有,下面是示例:
执行结果:
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次。
执行结果:
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就执行下一次了。
这篇文章解释的很清楚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的名称就行,就不会错乱。
说了这么多,不知道对着几个注解是不是了解多一点了呢,貌似这些注解 没有根本上的区别,就看你习惯怎么用了。拿代码多跑几次,然后根据自己的想法改改,你就明白这几个注解的用处啦。
好文章,分享下,转自: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的环境变量,那么将抛出异常。
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,基本的属性编辑器就在此处被注册。
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&characterEncoding=UTF-8& 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
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,但是这里并不是调用,而是注册。真正的调用其实是在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 + "]"); } } }
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框架的设计理念章节。
在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事件,以保证对应的监听器可以做进一步的逻辑处理。
这是一篇翻译的文章, 版本虽不是最新,但有参考价值。
转自: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协议利用HTTP 升级头信息来把一个HTTP连接升级为一个WebSocket连接。HTML5 WebSockets 解决了许多导致HTTP不适合于实时应用的问题,并且它通过避免复杂的工作方式使得应用结构很简单。
最新的浏览器都支持WebSockets,如下图所示。该信息来自于http://caniuse.com/#feat=websockets.
每一个WebSocket连接的生命都是从一个HTTP请求开始的。HTTP请求跟其他请求很类似,除了它拥有一个Upgrade头信息。Upgrade头信息表示一个客户端希望把连接升级为不同的协议。对WebSockets来说,它希望升级为WebSocket协议。当客户端和服务器通过底层连接第一次握手时,WebSocket连接通过把HTTP协议转换升级为WebSockets协议而得以建立。一旦WebSocket连接成功建立,消息就可以在客户端和服务器之间进行双向发送。
一些可能的WebSockets使用案例有:
在Java社区中下面的情形很普遍,不同的供应商和开发者编写类库来使用某项技术,一段时间之后当该技术成熟时它就会被标准化,来使开发者可以在不同实现之间互相操作,而不用冒供应商锁定的风险。当JSR 365启动时,WebSocket就已经有了超过20个不同的Java实现。它们中的大多数都有着不同的API。JSR 356是把Java的WebSocket API进行标准化的成果。开发者们可以撇开具体的实现,直接使用JSR 356 API来创建WebSocket应用。WebSocket 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的参考实现。我们会在下一节中以独立模式用Tyrus开发一个简单应用。所有Tyrus组件都是用Java SE 7编译器进行构建的。这意味着,你也至少需要不低于Java SE 7的运行环境才能编译和运行该应用示例。它不能够在Apache Tomcat 7中运行,因为它依赖于servlet 3.1规范。
现在我们准备创建一个非常简单的单词游戏。游戏者会得到一个字母排序错乱的单词,他或她需要把这个单词恢复原样。我们将为每一次游戏使用一个单独的连接。
本应用的源代码可以从github获取 https://github.com/shekhargulati/wordgame
开始时,我们使用Maven原型来创建一个模板Java项目。使用下面的命令来创建一个基于Maven的Java项目。
$ mvn archetype:generate -DgroupId=com.shekhar -DartifactId=wordgame -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
正如上节中提到的,你需要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文件。
现在我们的项目已经设置完毕,我们将开始编写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连接关闭时它会被调用。
@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应用的开发工作变得很容易。
********************************************************************************************************************************************************
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, 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
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.
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:
The typical lifecycle event of a WebSocket interaction goes as follows:
Most of the WebSocket lifecycle events can be mapped to Java methods, both in the annotation-driven and interface-driven approaches.
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:
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:
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 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.
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 } } }); }
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:
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):
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:
Similarly, the Decoder interface has four subinterfaces:
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比较好的文章,分享下: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()方法:
第一个参数source:需要转码的源文件
第二个参数target:需转型成的目标文件
第三个参数attributes:是一个包含编码所需数据的参数
2.Encoding attributes
如上所述的encoder()方法,第三个参数是很重要的,所以,你得实例化出一个EncodingAttributes即EncodingAttributes attrs = new EncodingAttributes();
接下来看看attrs都包含了些什么方法:
从方法名可以看出是在转码音频时需要用到的方法,可以说是添加音频转码时所需音频属性。
从方法名可以看出是在转码视频时需要用到的方法,可以说是添加视频转码时所需视频属性。
这个则是设置转码格式的方法。
设置转码偏移位置的方法,例如你想在5秒后开始转码源文件则setOffset(5)。
设置转码持续时间的方法,例如你想持续30秒的转码则setDuration(30)。
3.Audio encoding attributes
同样的我们也需设置Audio的属***:AudioAttributes audio = new AudioAttributes();
看看它的方法:
4.Video encoding attributes
5.Monitoring the transcoding operation
你可以用listener监测转码操作。JAVE定义了一个EncoderProgressListener的接口。
实现EncoderProgressListener接口,需定义的方法:
6.Getting informations about a multimedia file
获取多媒体文件转码时的信息:
五、例子:
From a generic AVI to a youtube-like FLV movie, with an embedded MP3 audio stream:
Next lines extracts audio informations from an AVI and store them in a plain WAV file:
Next example takes an audio WAV file and generates a 128 kbit/s, stereo, 44100 Hz MP3 file:
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:
Next one generates an AVI with MPEG 4/DivX video and OGG Vorbis audio:
A smartphone suitable video:
总结下,以上例子看上去都大同小异,步骤就那几步固定死了。
首先,源文件与目标文件。
其次,设置视音频转码钱的属***数据。
其中setCodec()方法中的参数要对应你所转码的格式的编码encoders。
最后,设置attrs并转码。
六、支持包含在内的格式:
The JAVE built-in ffmpeg executable gives support for the following multimedia container formats:
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 |
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 |
The JAVE built-in ffmpeg executable contains the following decoders and encoders:
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 |
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 |
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 |
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.