11.3 I/O类使用
由于在IO操作中,需要使用的数据源有很多,作为一个IO技术的初学者,从读写文件开始学习IO技术是一个比较好的选择。因为文件是一种常见的数据源,而且读写文件也是程序员进行IO编程的一个基本能力。本章IO类的使用就从读写文件开始。
11.3.1 文件操作
文件(File)是 最常见的数据源之一,在程序中经常需要将数据存储到文件中,例如图片文件、声音文件等数据文件,也经常需要根据需要从指定的文件中进行数据的读取。当然, 在实际使用时,文件都包含一个的格式,这个格式需要程序员根据需要进行设计,读取已有的文件时也需要熟悉对应的文件格式,才能把数据从文件中正确的读取出 来。
文件的存储介质有很多,例如硬盘、光盘和U盘等,由于IO类设计时,从数据源转换为流对象的操作由API实现了,所以存储介质的不同对于程序员来说是透明的,和实际编写代码无关。
11.3.1.1 文件的概念
文件是计算机中一种基本的数据存储形式,在实际存储数据时,如果对于数据的读写速度要求不是很高,存储的数据量不是很大时,使用文件作为一种持久数据存储的方式是比较好的选择。
存储在文件内部的数据和内存中的数据不同,存储在文件中的数据是一种“持久存储”,也就是当程序退出或计算机关机以后,数据还是存在的,而内存内部的数据在程序退出或计算机关机以后,数据就丢失了。
在不同的存储介质中,文件中的数据都是以一定的顺序依次存储起来,在实际读取时由硬件以及操作系统完成对于数据的控制,保证程序读取到的数据和存储的顺序保持一致。
每个文件以一个文件路径和文件名称进行表示,在需要访问该文件的时,只需要知道该文件的路径以及文件的全名即可。在不同的操作系统环境下,文件路径的表示形式是不一样的,例如在Windows操作系统中一般的表示形式为C:\windows\system,而Unix上的表示形式为/user/my。所以如果需要让Java程序能够在不同的操作系统下运行,书写文件路径时还需要比较注意。
11.3.1.1.1 绝对路径和相对路径
绝对路径是指书写文件的完整路径,例如d:\java\Hello.java,该路径中包含文件的完整路径d:\java以及文件的全名Hello.java。使用该路径可以唯一的找到一个文件,不会产生歧义。但是使用绝对路径在表示文件时,受到的限制很大,且不能在不同的操作系统下运行,因为不同操作系统下绝对路径的表达形式存在不同。
相对路径是指书写文件的部分路径,例如\test\Hello.java,该路径中只包含文件的部分路径\test和文件的全名Hello.java,部分路径是指当前路径下的子路径,例如当前程序在d:\abc下运行,则该文件的完整路径就是d:\abc\test。使用这种形式,可以更加通用的代表文件的位置,使得文件路径产生一定的灵活性。
在Eclipse项目中运行程序时,当前路径是项目的根目录,例如工作空间存储在d:\javaproject,当前项目名称是Test,则当前路径是:d:\javaproject\Test。在控制台下面运行程序时,当前路径是class文件所在的目录,如果class文件包含包名,则以该class文件最顶层的包名作为当前路径。
另外在Java语言的代码内部书写文件路径时,需要注意大小写,大小写需要保持一致,路径中的文件夹名称区分大小写。由于’\’是Java语言中的特殊字符,所以在代码内部书写文件路径时,例如代表“c:\test\java\Hello.java”时,需要书写成“c:\\test\\java\\Hello.java”或“c:/test/java/Hello.java”,这些都需要在代码中注意。
11.3.1.1.2 文件名称
文件名称一般采用“文件名.后缀名”的形式进行命名,其中“文件名”用来表示文件的作用,而使用后缀名来表示文件的类型,这是当前操作系统中常见的一种形式,例如“readme.txt”文件,其中readme代表该文件时说明文件,而txt后缀名代表文件时文本文件类型,在操作系统中,还会自动将特定格式的后缀名和对应的程序关联,在双击该文件时使用特定的程序打开。
其实在文件名称只是一个标示,和实际存储的文件内容没有必然的联系,只是使用这种方式方便文件的使用。在程序中需要存储数据时,如果自己设计了特定的文件格式,则可以自定义文件的后缀名,来标示自己的文件类型。
和文件路径一样,在Java代码内部书写文件名称时也区分大小写,文件名称的大小写必须和操作系统中的大小写保持一致。
另外,在书写文件名称时不要忘记书写文件的后缀名。
11.3.1.2 File类
为了很方便的代表文件的概念,以及存储一些对于文件的基本操作,在java.io包中设计了一个专门的类——File类。
在File类中包含了大部分和文件操作的功能方法,该类的对象可以代表一个具体的文件或文件夹,所以以前曾有人建议将该类的类名修改成FilePath,因为该类也可以代表一个文件夹,更准确的说是可以代表一个文件路径。
下面介绍一下File类的基本使用。
1、File对象代表文件路径
File类的对象可以代表一个具体的文件路径,在实际代表时,可以使用绝对路径也可以使用相对路径。
下面是创建的文件对象示例。
public File(String pathname)
该示例中使用一个文件路径表示一个File类的对象,例如:
File f1 = new File(“d:\\test\\1.txt”);
File f2 = new File(“1.txt”);
File f3 = new File(“e:\\abc”);
这里的f1和f2对象分别代表一个文件,f1是绝对路径,而f2是相对路径,f3则代表一个文件夹,文件夹也是文件路径的一种。
public File(String parent, String child)
也可以使用父路径和子路径结合,实现代表文件路径,例如:
File f4 = new File(“d:\\test\\”,”1.txt”);
这样代表的文件路径是:d:\test\1.txt。
2、File类常用方法
File类中包含了很多获得文件或文件夹属性的方法,使用起来比较方便,下面将常见的方法介绍如下:
a、createNewFile方法
public boolean createNewFile() throws IOException
该方法的作用是创建指定的文件。该方法只能用于创建文件,不能用于创建文件夹,且文件路径中包含的文件夹必须存在。
b、delect方法
public boolean delete()
该方法的作用是删除当前文件或文件夹。如果删除的是文件夹,则该文件夹必须为空。如果需要删除一个非空的文件夹,则需要首先删除该文件夹内部的每个文件和文件夹,然后在可以删除,这个需要书写一定的逻辑代码实现。
c、exists方法
public boolean exists()
该方法的作用是判断当前文件或文件夹是否存在。
d、getAbsolutePath方法
public String getAbsolutePath()
该方法的作用是获得当前文件或文件夹的绝对路径。例如c:\test\1.t则返回c:\test\1.t。
e、getName方法
public String getName()
该方法的作用是获得当前文件或文件夹的名称。例如c:\test\1.t,则返回1.t。
f、getParent方法
public String getParent()
该方法的作用是获得当前路径中的父路径。例如c:\test\1.t则返回c:\test。
g、isDirectory方法
public boolean isDirectory()
该方法的作用是判断当前File对象是否是目录。
h、isFile方法
public boolean isFile()
该方法的作用是判断当前File对象是否是文件。
i、length方法
public long length()
该方法的作用是返回文件存储时占用的字节数。该数值获得的是文件的实际大小,而不是文件在存储时占用的空间数。
j、list方法
public String[] list()
该方法的作用是返回当前文件夹下所有的文件名和文件夹名称。说明,该名称不是绝对路径。
k、listFiles方法
public File[] listFiles()
该方法的作用是返回当前文件夹下所有的文件对象。
l、mkdir方法
public boolean mkdir()
该方法的作用是创建当前文件文件夹,而不创建该路径中的其它文件夹。假设d盘下只有一个test文件夹,则创建d:\test\abc文件夹则成功,如果创建d:\a\b文件夹则创建失败,因为该路径中d:\a文件夹不存在。如果创建成功则返回true,否则返回false。
m、mkdirs方法
public boolean mkdirs()
该方法的作用是创建文件夹,如果当前路径中包含的父目录不存在时,也会自动根据需要创建。
n、renameTo方法
public boolean renameTo(File dest)
该方法的作用是修改文件名。在修改文件名时不能改变文件路径,如果该路径下已有该文件,则会修改失败。
o、setReadOnly方法
public boolean setReadOnly()
该方法的作用是设置当前文件或文件夹为只读。
3、File类基本示例
以上各方法实现的测试代码如下:
import java.io.File;
/**
* File类使用示例
*/
public class FileDemo {
public static void main(String[] args) {
//创建File对象
File f1 = new File("d:\\test");
File f2 = new File("1.txt");
File f3 = new File("e:\\file.txt");
File f4 = new File("d:\\","1.txt");
//创建文件
try{
boolean b = f3.createNewFile();
}catch(Exception e){
e.printStackTrace();
}
//判断文件是否存在
System.out.println(f4.exists());
//获得文件的绝对路径
System.out.println(f3.getAbsolutePath());
//获得文件名
System.out.println(f3.getName());
//获得父路径
System.out.println(f3.getParent());
//判断是否是目录
System.out.println(f1.isDirectory());
//判断是否是文件
System.out.println(f3.isFile());
//获得文件长度
System.out.println(f3.length());
//获得当前文件夹下所有文件和文件夹名称
String[] s = f1.list();
for(int i = 0;i < s.length;i++){
System.out.println(s[i]);
}
//获得文件对象
File[] f5 = f1.listFiles();
for(int i = 0;i < f5.length;i++){
System.out.println(f5[i]);
}
//创建文件夹
File f6 = new File("e:\\test\\abc");
boolean b1 = f6.mkdir();
System.out.println(b1);
b1 = f6.mkdirs();
System.out.println(b1);
//修改文件名
File f7 = new File("e:\\a.txt");
boolean b2 = f3.renameTo(f7);
System.out.println(b2);
//设置文件为只读
f7.setReadOnly();
}
}
4、File类综合示例
下面以两个示例演示File类的综合使用。第一个示例是显示某个文件夹下的所有文件和文件夹,原理是输出当前名称,然后判断当前File对 象是文件还是文件夹,如果则获得该文件夹下的所有子文件和子文件夹,并递归调用该方法实现。第二个示例是删除某个文件夹下的所有文件和文件夹,原理是判断 是否是文件,如果是文件则直接删除,如果是文件夹,则获得该文件夹下所有的子文件和子文件夹,然后递归调用该方法处理所有子文件和子文件夹,然后将空文件 夹删除。则测试时谨慎使用第二个方法,以免删除自己有用的数据文件。示例代码如下:
import java.io.File;
/**
* 文件综合使用示例
*/
public class AdvanceFileDemo {
public static void main(String[] args) {
File f = new File("e:\\Book");
printAllFile(f);
File f1 = new File("e:\\test");
deleteAll(f1);
}
/**
* 打印f路径下所有的文件和文件夹
* @param f 文件对象
*/
public static void printAllFile(File f){
//打印当前文件名
System.out.println(f.getName());
//是否是文件夹
if(f.isDirectory()){
//获得该文件夹下所有子文件和子文件夹
File[] f1 = f.listFiles();
//循环处理每个对象
int len = f1.length;
for(int i = 0;i < len;i++){
//递归调用,处理每个文件对象
printAllFile(f1[i]);
}
}
}
/**
* 删除对象f下的所有文件和文件夹
* @param f 文件路径
*/
public static void deleteAll(File f){
//文件
if(f.isFile()){
f.delete();
}else{ //文件夹
//获得当前文件夹下的所有子文件和子文件夹
File f1[] = f.listFiles();
//循环处理每个对象
int len = f1.length;
for(int i = 0;i < len;i++){
//递归调用,处理每个文件对象
deleteAll(f1[i]);
}
//删除当前文件夹
f.delete();
}
}
}
关于File类的使用就介绍这么多,其它的方法和使用时需要注意的问题还需要多进行练习和实际使用。
11.3.1.3 读取文件
虽然前面介绍了流的概念,但是这个概念对于初学者来说,还是比较抽象的,下面以实际的读取文件为例子,介绍流的概念,以及输入流的基本使用。
按照前面介绍的知识,将文件中的数据读入程序,是将程序外部的数据传入程序中,应该使用输入流——InputStream或Reader。而由于读取的是特定的数据源——文件,则可以使用输入对应的子类FileInputStream或FileReader实现。
在实际书写代码时,需要首先熟悉读取文件在程序中实现的过程。在Java语言的IO编程中,读取文件是分两个步骤:1、将文件中的数据转换为流,2、读取流内部的数据。其中第一个步骤由系统完成,只需要创建对应的流对象即可,对象创建完成以后步骤1就完成了,第二个步骤使用输入流对象中的read方法即可实现了。
使用输入流进行编程时,代码一般分为3个部分:1、创建流对象,2、读取流对象内部的数据,3、关闭流对象。下面以读取文件的代码示例:
import java.io.*;
/**
* 使用FileInputStream读取文件
*/
public class ReadFile1 {
public static void main(String[] args) {
//声明流对象
FileInputStream fis = null;
try{
//创建流对象
fis = new FileInputStream("e:\\a.txt");
//读取数据,并将读取到的数据存储到数组中
byte[] data = new byte[1024]; //数据存储的数组
int i = 0; //当前下标
//读取流中的第一个字节数据
int n = fis.read();
//依次读取后续的数据
while(n != -1){ //未到达流的末尾
//将有效数据存储到数组中
data[i] = (byte)n;
//下标增加
i++;
//读取下一个字节的数据
n = fis.read();
}
//解析数据
String s = new String(data,0,i);
//输出字符串
System.out.println(s);
}catch(Exception e){
e.printStackTrace();
}finally{
try{
//关闭流,释放资源
fis.close();
}catch(Exception e){}
}
}
}
在该示例代码中,首先创建一个FileInputStream类型的对象fis:
fis = new FileInputStream("e:\\a.txt");
这样建立了一个连接到数据源e:\a.txt的流,并将该数据源中的数据转换为流对象fis,以后程序读取数据源中的数据,只需要从流对象fis中读取即可。
读取流fis中的数据,需要使用read方法,该方法是从InputStream类中继承过来的方法,该方法的作用是每次读取流中的一个字节,如果需要读取流中的所有数据,需要使用循环读取,当到达流的末尾时,read方法的返回值是-1。
在该示例中,首先读取流中的第一个字节:
int n = fis.read();
并将读取的值赋值给int值n,如果流fis为空,则n的值是-1,否则n中的最后一个字节包含的时流fis中的第一个字节,该字节被读取以后,将被从流fis中删除。
然后循环读取流中的其它数据,如果读取到的数据不是-1,则将已经读取到的数据n强制转换为byte,即取n中的有效数据——最后一个字节,并存储到数组data中,然后调用流对象fis中的read方法继续读取流中的下一个字节的数据。一直这样循环下去,直到读取到的数据是-1,也就是读取到流的末尾则循环结束。
这里的数组长度是1024,所以要求流中的数据长度不能超过1024,所以该示例代码在这里具有一定的局限性。如果流的数据个数比较多,则可以将1024扩大到合适的个数即可。
经过上面的循环以后,就可以将流中的数据依次存储到data数组中,存储到data数组中有效数据的个数是i个,即循环次数。
其实截至到这里,IO操作中的读取数据已经完成,然后再按照数据源中的数据格式,这里是文件的格式,解析读取出的byte数组即可。
该示例代码中的解析,只是将从流对象中读取到的有效的数据,也就是data数组中的前n个数据,转换为字符串,然后进行输出。
在该示例代码中,只是在catch语句中输出异常的信息,便于代码的调试,在实际的程序中,需要根据情况进行一定的逻辑处理,例如给出提示信息等。
最后在finally语句块中,关闭流对象fis,释放流对象占用的资源,关闭数据源,实现流操作的结束工作。
上面详细介绍了读取文件的过程,其实在实际读取流数据时,还可以使用其它的read方法,下面的示例代码是使用另外一个read方法实现读取的代码:
import java.io.FileInputStream;
/**
* 使用FileInputStream读取文件
*/
public class ReadFile2 {
public static void main(String[] args) {
//声明流对象
FileInputStream fis = null;
try{
//创建流对象
fis = new FileInputStream("e:\\a.txt");
//读取数据,并将读取到的数据存储到数组中
byte[] data = new byte[1024]; //数据存储的数组
int i = fis.read(data);
//解析数据
String s = new String(data,0,i);
//输出字符串
System.out.println(s);
}catch(Exception e){
e.printStackTrace();
}finally{
try{
//关闭流,释放资源
fis.close();
}catch(Exception e){}
}
}
}
该示例代码中,只使用一行代码:
int i = fis.read(data);
就实现了将流对象fis中的数据读取到字节数组data中。该行代码的作用是将fis流中的数据读取出来,并依次存储到数组data中,返回值为实际读取的有效数据的个数。
使用该中方式在进行读取时,可以简化读取的代码。
当然,在读取文件时,也可以使用Reader类的子类FileReader进行实现,在编写代码时,只需要将上面示例代码中的byte数组替换成char数组即可。
使用FileReader读取文件时,是按照char为单位进行读取的,所以更适合于文本文件的读取,而对于二进制文件或自定义格式的文件来说,还是使用FileInputStream进行读取,方便对于读取到的数据进行解析和操作。
读取其它数据源的操作和读取文件类似,最大的区别在于建立流对象时选择的类不同,而流对象一旦建立,则基本的读取方法是一样,如果只使用最基本的read方法进行读取,则使用基本上是一致的。这也是IO类设计的初衷,使得对于流对象的操作保持一致,简化IO类使用的难度。
程。
基本的输出流包含OutputStream和Writer两个,区别是OutputStream体系中的类(也就是OutputStream的子类)是按照字节写入的,而Writer体系中的类(也就是Writer的子类)是按照字符写入的。
使用输出流进行编程的步骤是:
1、建立输出流
建立对应的输出流对象,也就是完成由流对象到外部数据源之间的转换。
2、向流中写入数据
将需要输出的数据,调用对应的write方法写入到流对象中。
3、关闭输出流
在写入完毕以后,调用流对象的close方法关闭输出流,释放资源。
在使用输出流向外部输出数据时,程序员只需要将数据写入流对象即可,底层的API实现将流对象中的内容写入外部数据源,这个写入的过程对于程序员来说是透明的,不需要专门书写代码实现。
在向文件中输出数据,也就是写文件时,使用对应的文件输出流,包括FileOutputStream和FileWriter两个类,下面以FileOutputStream为例子说明输出流的使用。示例代码如下:
import java.io.*;
/**
* 使用FileOutputStream写文件示例
*/
public class WriteFile1 {
public static void main(String[] args) {
String s = "Java语言";
int n = 100;
//声明流对象
FileOutputStream fos = null;
try{
//创建流对象
fos = new FileOutputStream("e:\\out.txt");
//转换为byte数组
byte[] b1 = s.getBytes();
//换行符
byte[] b2 = "\r\n".getBytes();
byte[] b3 = String.valueOf(n).getBytes();
//依次写入文件
fos.write(b1);
fos.write(b2);
fos.write(b3);
} catch (Exception e) {
e.printStackTrace();
}finally{
try{
fos.close();
}catch(Exception e){}
}
}
}
该示例代码写入的文件使用记事本打开以后,内容为:
Java语言
100
在该示例代码中,演示了将一个字符串和一个int类型的值依次写入到同一个文件中。在写入文件时,首先创建了一个文件输出流对象fos:
fos = new FileOutputStream("e:\\out.txt");
该对象创建以后,就实现了从流到外部数据源e:\out.txt的连接。说明:当外部文件不存在时,系统会自动创建该文件,但是如果文件路径中包含未创建的目录时将出现异常。这里书写的文件路径可以是绝对路径也可以是相对路径。
在 实际写入文件时,有两种写入文件的方式:覆盖和追加。其中“覆盖”是指清除原文件的内容,写入新的内容,默认采用该种形式写文件,“追加”是指在已有文件 的末尾写入内容,保留原来的文件内容,例如写日志文件时,一般采用追加。在实际使用时可以根据需要采用适合的形式,可以使用:
public FileOutputStream(String name, boolean append) throws FileNotFoundException
只需要使用该构造方法在构造FileOutputStream对象时,将第二个参数append的值设置为true即可。
流对象创建完成以后,就可以使用OutputStream中提供的wirte方法向流中依次写入数据了。最基本的写入方法只支持byte数组格式的数据,所以如果需要将内容写入文件,则需要把对应的内容首先转换为byte数组。
这里以如下格式写入数据:首先写入字符串s,使用String类的getBytes方法将该字符串转换为byte数组,然后写入字符串“\r\n”,转换方式同上,该字符串的作用是实现文本文件的换行显示,最后写入int数据n,首先将n转换为字符串,再转换为byte数组。这种写入数据的顺序以及转换为byte数组的方式就是流的数据格式,也就是该文件的格式。因为这里写的都是文本文件,所以写入的内容以明文的形式显示出来,也可以根据自己需要存储的数据设定特定的文件格式。
其实,所有的数据文件,包括图片文件、声音文件等等,都是以一定的数据格式存储数据的,在保存该文件时,将需要保存的数据按照该文件的数据格式依次写入即可,而在打开该文件时,将读取到的数据按照该文件的格式解析成对应的逻辑即可。
最后,在数据写入到流内部以后,如果需要立即将写入流内部的数据强制输出到外部的数据源,则可以使用流对象的flush方法实现。如果不需要强制输出,则只需要在写入结束以后,关闭流对象即可。在关闭流对象时,系统首先将流中未输出到数据源中的数据强制输出,然后再释放该流对象占用的内存空间。
使用FileWriter写入文件时,步骤和创建流对象的操作都和该示例代码一致,只是在转换数据时,需要将写入的数据转换为char数组,对于字符串来说,可以使用String中的toCharArray方法实现转换,然后按照文件格式写入数据即可。
对于其它类型的字节输出流/字符输出流来说,只是在逻辑上连接不同的数据源,在创建对象的代码上会存在一定的不同,但是一旦流对象创建完成以后,基本的写入方法都是write方法,也需要首先将需要写入的数据按照一定的格式转换为对应的byte数组/char数组,然后依次写入即可。
所以IO类的这种设计形式,只需要熟悉该体系中的某一个类的使用以后,就可以触类旁通的学会其它相同类型的类的使用,从而简化程序员的学习,使得使用时保持统一。
序言
本指南对Netty 进行了介绍并指出其意义所在。
1. 问题
现在,我们使用适合一般用途的应用或组件来和彼此通信。例如,我们常常使用一个HTTP客户端从远程服务器获取信息或者通过web services进行远程方法的调用。
然而,一个适合普通目的的协议或其实现并不具备其规模上的扩展性。例如,我们无法使用一个普通的HTTP服务器进行大型文件,电邮信息的交互,或者处理金 融信息和多人游戏数据那种要求准实时消息传递的应用场景。因此,这些都要求使用一个适用于特殊目的并经过高度优化的协议实现。例如,你可能想要实现一个对 基于AJAX的聊天应用,媒体流或大文件传输进行过特殊优化的HTTP服务器。你甚至可能想去设计和实现一个全新的,特定于你的需求的通信协议。
另一种无法避免的场景是你可能不得不使用一种专有的协议和原有系统交互。在这种情况下,你需要考虑的是如何能够快速的开发出这个协议的实现并且同时还没有牺牲最终应用的性能和稳定性。
2. 方案
Netty 是一个异步的,事件驱动的网络编程框架和工具,使用Netty 可以快速开发出可维护的,高性能、高扩展能力的协议服务及其客户端应用。
也就是说,Netty 是一个基于NIO的客户,服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。
“快速”和“简单”并不意味着会让你的最终应用产生维护性或性能上的问题。Netty 是一个吸收了多种协议的实现经验,这些协议包括FTP,SMPT,HTTP,各种二进制,文本协议,并经过相当精心设计的项目,最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。
一些用户可能找到了某些同样声称具有这些特性的编程框架,因此你们可能想问Netty 又有什么不一样的地方。这个问题的答案是Netty 项目的设计哲学。从创立之初,无论是在API还是在其实现上Netty 都致力于为你提供最为舒适的使用体验。虽然这并不是显而易见的,但你终将会认识到这种设计哲学将令你在阅读本指南和使用Netty 时变得更加得轻松和容易。
第一章. 开始
这一章节将围绕Netty的核心结构展开,同时通过一些简单的例子可以让你更快的了解Netty的使用。当你读完本章,你将有能力使用Netty完成客户端和服务端的开发。
如果你更喜欢自上而下式的学习方式,你可以首先完成 第二章:架构总览 的学习,然后再回到这里。
1.1. 开始之前
运行本章示例程序的两个最低要求是:最新版本的Netty程序以及JDK 1.5或更高版本。最新版本的Netty程序可在项目下载页 下载。下载正确版本的JDK,请到你偏好的JDK站点下载。
这就已经足够了吗?实际上你会发现,这两个条件已经足够你完成任何协议的开发了。如果不是这样,请联系Netty项目社区 ,让我们知道还缺少了什么。
最终但不是至少,当你想了解本章所介绍的类的更多信息时请参考API手册。为方便你的使用,这篇文档中所有的类名均连接至在线API手册。此外,如果本篇文档中有任何错误信息,无论是语法错误,还是打印排版错误或者你有更好的建议,请不要顾虑,立即联系Netty项目社区 。
1.2. 抛弃协议服务
在这个世界上最简化的协议不是“Hello,world!”而是抛弃协议 。这是一种丢弃接收到的任何数据并不做任何回应的协议。
实现抛弃协议(DISCARD protocol),你仅需要忽略接受到的任何数据即可。让我们直接从处理器(handler)实现开始,这个处理器处理Netty的所有I/O事件。
Java代码
package org.jboss.netty.example.discard;
@ChannelPipelineCoverage("all")1
public class DiscardServerHandler extends SimpleChannelHandler {2
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {3
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {4
e.getCause().printStackTrace();
Channel ch = e.getChannel();
ch.close();
}
}
代码说明
1)ChannelPipelineCoverage注解了一种处理器类型,这个注解标示了一个处理器是 否可被多个Channel通道共享(同时关联着ChannelPipeline)。DiscardServerHandler没有处理任何有状态的信息, 因此这里的注解是“all”。
2)DiscardServerHandler继承了SimpleChannelHandler,这也是一个ChannelHandler 的实现。SimpleChannelHandler提供了多种你可以重写的事件处理方法。目前直接继承SimpleChannelHandler已经足够 了,并不需要你完成一个自己的处理器接口。
3)我们这里重写了messageReceived事件处理方法。这个方法由一个接收了客户端传送数据的MessageEvent事件调用。在这个例子中,我们忽略接收到的任何数据,并以此来实现一个抛弃协议(DISCARD protocol)。
4)exceptionCaught 事件处理方法由一个ExceptionEvent异常事件调用,这个异常事件起因于Netty的I/O异常或一个处理器实现的内部异常。多数情况下,捕捉 到的异常应当被记录下来,并在这个方法中关闭这个channel通道。当然处理这种异常情况的方法实现可能因你的实际需求而有所不同,例如,在关闭这个连 接之前你可能会发送一个包含了错误码的响应消息。
目前进展不错,我们已经完成了抛弃协议服务器的一半开发工作。下面要做的是完成一个可以启动这个包含DiscardServerHandler处理器服务的主方法。
Java代码
package org.jboss.netty.example.discard;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
public class DiscardServer {
public static void main(String[] args) throws Exception {
ChannelFactory factory =
new NioServerSocketChannelFactory (
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
ServerBootstrap bootstrap = new ServerBootstrap (factory);
DiscardServerHandler handler = new DiscardServerHandler();
ChannelPipeline pipeline = bootstrap.getPipeline();
pipeline.addLast("handler", handler);
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setOption("child.keepAlive", true);
bootstrap.bind(new InetSocketAddress(8080));
}
}
代码说明
1)ChannelFactory 是一个创建和管理Channel通道及其相关资源的工厂接口,它处理所有的I/O请求并产生相应的I/O ChannelEvent通道事件。Netty 提供了多种 ChannelFactory 实现。这里我们需要实现一个服务端的例子,因此我们使用NioServerSocketChannelFactory实现。另一件需要注意的事情是这个工 厂并自己不负责创建I/O线程。你应当在其构造器中指定该工厂使用的线程池,这样做的好处是你获得了更高的控制力来管理你的应用环境中使用的线程,例如一 个包含了安全管理的应用服务。
2)ServerBootstrap 是一个设置服务的帮助类。你甚至可以在这个服务中直接设置一个Channel通道。然而请注意,这是一个繁琐的过程,大多数情况下并不需要这样做。
3)这里,我们将DiscardServerHandler处理器添加至默认的ChannelPipeline通道。任何时候当服务器接收到一个新的连 接,一个新的ChannelPipeline管道对象将被创建,并且所有在这里添加的ChannelHandler对象将被添加至这个新的 ChannelPipeline管道对象。这很像是一种浅拷贝操作(a shallow-copy operation);所有的Channel通道以及其对应的ChannelPipeline实例将分享相同的DiscardServerHandler 实例。
4)你也可以设置我们在这里指定的这个通道实现的配置参数。我们正在写的是一个TCP/IP服务,因此我们运行设定一些socket选项,例如 tcpNoDelay和keepAlive。请注意我们在配置选项里添加的"child."前缀。这意味着这个配置项仅适用于我们接收到的通道实例,而不 是ServerSocketChannel实例。因此,你可以这样给一个ServerSocketChannel设定参数:
bootstrap.setOption("reuseAddress", true);
5)我们继续。剩下要做的是绑定这个服务使用的端口并且启动这个服务。这里,我们绑定本机所有网卡(NICs,network interface cards)上的8080端口。当然,你现在也可以对应不同的绑定地址多次调用绑定操作。
大功告成!现在你已经完成你的第一个基于Netty的服务端程序。
1.3. 查看接收到的数据
现在你已经完成了你的第一个服务端程序,我们需要测试它是否可以真正的工作。最简单的方法是使用telnet 命令。例如,你可以在命令行中输入“telnet localhost 8080 ”或其他类型参数。
然而,我们可以认为服务器在正常工作吗?由于这是一个丢球协议服务,所以实际上我们无法真正的知道。你最终将收不到任何回应。为了证明它在真正的工作,让我们修改代码打印其接收到的数据。
我们已经知道当完成数据的接收后将产生MessageEvent消息事件,并且也会触发messageReceived处理方法。所以让我在DiscardServerHandler处理器的messageReceived方法内增加一些代码。
Java代码
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
ChannelBuffer buf = (ChannelBuffer) e.getMessage();
while(buf.readable()) {
System.out.println((char) buf.readByte());
}
}
代码说明
1) 基本上我们可以假定在socket的传输中消息类型总是ChannelBuffer。ChannelBuffer是Netty的一个基本数据结构,这个数 据结构存储了一个字节序列。ChannelBuffer类似于NIO的ByteBuffer,但是前者却更加的灵活和易于使用。例如,Netty允许你创 建一个由多个ChannelBuffer构建的复合ChannelBuffer类型,这样就可以减少不必要的内存拷贝次数。
2) 虽然ChannelBuffer有些类似于NIO的ByteBuffer,但强烈建议你参考Netty的API手册。学会如何正确的使用ChannelBuffer是无障碍使用Netty的关键一步。
如果你再次运行telnet命令,你将会看到你所接收到的数据。
抛弃协议服务的所有源代码均存放在在分发版的org.jboss.netty.example.discard包下。
1.4. 响应协议服务
目前,我们虽然使用了数据,但最终却未作任何回应。然而一般情况下,一个服务都需要回应一个请求。让我们实现ECHO协议 来学习如何完成一个客户请求的回应消息,ECHO协议规定要返回任何接收到的数据。
与我们上一节实现的抛弃协议服务唯一不同的地方是,这里需要返回所有的接收数据而不是仅仅打印在控制台之上。因此我们再次修改messageReceived方法就足够了。
Java代码
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
Channel ch = e.getChannel();
ch.write(e.getMessage());
}
代码说明
1) 一个ChannelEvent通道事件对象自身存有一个和其关联的Channel对象引用。这个返回的Channel通道对象代表了这个接收 MessageEvent消息事件的连接(connection)。因此,我们可以通过调用这个Channel通道对象的write方法向远程节点写入返 回数据。
现在如果你再次运行telnet命令,你将会看到服务器返回的你所发送的任何数据。
相应服务的所有源代码存放在分发版的org.jboss.netty.example.echo包下。
1.5. 时间协议服务
这一节需要实现的协议是TIME协议 。这是一个与先前所介绍的不同的例子。这个例子里,服务端返回一个32位的整数消息,我们不接受请求中包含的任何数据并且当消息返回完毕后立即关闭连接。通过这个例子你将学会如何构建和发送消息,以及当完成处理后如何主动关闭连接。
因为我们会忽略接收到的任何数据而只是返回消息,这应当在建立连接后就立即开始。因此这次我们不再使用messageReceived方法,取而代之的是使用channelConnected方法。下面是具体的实现:
Java代码
package org.jboss.netty.example.time;
@ChannelPipelineCoverage("all")
public class TimeServerHandler extends SimpleChannelHandler {
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
Channel ch = e.getChannel();
ChannelBuffer time = ChannelBuffers.buffer(4);
time.writeInt(System.currentTimeMillis() / 1000);
ChannelFuture f = ch.write(time);
f.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) {
Channel ch = future.getChannel();
ch.close();
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
e.getCause().printStackTrace();
e.getChannel().close();
}
}
代码说明
1) 正如我们解释过的,channelConnected方法将在一个连接建立后立即触发。因此让我们在这个方法里完成一个代表当前时间(秒)的32位整数消息的构建工作。
2) 为了发送一个消息,我们需要分配一个包含了这个消息的buffer缓冲。因为我们将要写入一个32位的整数,因此我们需要一个4字节的 ChannelBuffer。ChannelBuffers是一个可以创建buffer缓冲的帮助类。除了这个buffer方 法,ChannelBuffers还提供了很多和ChannelBuffer相关的实用方法。更多信息请参考API手册。
另外,一个很不错的方法是使用静态的导入方式:
import static org.jboss.netty.buffer.ChannelBuffers.*;
...
ChannelBuffer dynamicBuf = dynamicBuffer(256);
ChannelBuffer ordinaryBuf = buffer(1024);
3) 像通常一样,我们需要自己构造消息。
但是打住,flip在哪?过去我们在使用NIO发送消息时不是常常需要调用 ByteBuffer.flip()方法吗?实际上ChannelBuffer之所以不需要这个方法是因为 ChannelBuffer有两个指针;一个对应读操作,一个对应写操作。当你向一个 ChannelBuffer写入数据的时候写指针的索引值便会增加,但与此同时读指针的索引值不会有任何变化。读写指针的索引值分别代表了这个消息的开 始、结束位置。
与之相应的是,NIO的buffer缓冲没有为我们提供如此简洁的一种方法,除非你调用它的flip方法。因此,当你忘记调用flip方法而引起发送错误 时,你便会陷入困境。这样的错误不会再Netty中发生,因为我们对应不同的操作类型有不同的指针。你会发现就像你已习惯的这样过程变得更加容易—一种没 有flippling的体验!
另一点需要注意的是这个写方法返回了一个ChannelFuture对象。一个ChannelFuture 对象代表了一个尚未发生的I/O操作。这意味着,任何已请求的操作都可能是没有被立即执行的,因为在Netty内部所有的操作都是异步的。例如,下面的代 码可能会关闭一 个连接,这个操作甚至会发生在消息发送之前:
Channel ch = ...;
ch.write(message);
ch.close();
因此,你需要这个write方法返回的ChannelFuture对象,close方法需要等待写操作异步完成之后的ChannelFuture通知/监听触发。需要注意的是,关闭方法仍旧不是立即关闭一个连接,它同样也是返回了一个ChannelFuture对象。
4) 在写操作完成之后我们又如何得到通知?这个只需要简单的为这个返回的ChannelFuture对象增加一个ChannelFutureListener 即可。在这里我们创建了一个匿名ChannelFutureListener对象,在这个ChannelFutureListener对象内部我们处理了 异步操作完成之后的关闭操作。
另外,你也可以通过使用一个预定义的监听类来简化代码。
f.addListener(ChannelFutureListener.CLOSE);
1.6. 时间协议服务客户端
不同于DISCARD和ECHO协议服务,我们需要一个时间协议服务的客户端,因为人们无法直接将一个32位的二进制数据转换一个日历时间。在这一节我们将学习如何确保服务器端工作正常,以及如何使用Netty完成客户端的开发。
使用Netty开发服务器端和客户端代码最大的不同是要求使用不同的Bootstrap及ChannelFactory。请参照以下的代码:
Java代码
package org.jboss.netty.example.time;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
public class TimeClient {
public static void main(String[] args) throws Exception {
String host = args[0];
int port = Integer.parseInt(args[1]);
ChannelFactory factory =
new NioClientSocketChannelFactory (
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
ClientBootstrap bootstrap = new ClientBootstrap (factory);
TimeClientHandler handler = new TimeClientHandler();
bootstrap.getPipeline().addLast("handler", handler);
bootstrap.setOption("tcpNoDelay" , true);
bootstrap.setOption("keepAlive", true);
bootstrap.connect (new InetSocketAddress(host, port));
}
}
代码说明
1) 使用NioClientSocketChannelFactory而不是NioServerSocketChannelFactory来创建客户端的Channel通道对象。
2) 客户端的ClientBootstrap对应ServerBootstrap。
3) 请注意,这里不存在使用“child.”前缀的配置项,客户端的SocketChannel实例不存在父级Channel对象。
4) 我们应当调用connect连接方法,而不是之前的bind绑定方法。
正如你所看到的,这与服务端的启动过程是完全不一样的。ChannelHandler又该如何实现呢?它应当负责接收一个32位的整数,将其转换为可读的格式后,打印输出时间,并关闭这个连接。
Java代码
package org.jboss.netty.example.time;
import java.util.Date;
@ChannelPipelineCoverage("all")
public class TimeClientHandler extends SimpleChannelHandler {
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
ChannelBuffer buf = (ChannelBuffer) e.getMessage();
long currentTimeMillis = buf.readInt() * 1000L;
System.out.println(new Date(currentTimeMillis));
e.getChannel().close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
e.getCause().printStackTrace();
e.getChannel().close();
}
}
这看起来很是简单,与服务端的实现也并未有什么不同。然而,这个处理器却时常会因为抛出IndexOutOfBoundsException异常而拒绝工作。我们将在下一节讨论这个问题产生的原因。
1.7. 流数据的传输处理
1.7.1. Socket Buffer的缺陷
对于例如TCP/IP这种基于流的传输协议实现,接收到的数据会被存储在socket的接受缓冲区内。不幸的是,这种基于流的传输缓冲区并不是一个包队 列,而是一个字节队列。这意味着,即使你以两个数据包的形式发送了两条消息,操作系统却不会把它们看成是两条消息,而仅仅是一个批次的字节序列。因此,在 这种情况下我们就无法保证收到的数据恰好就是远程节点所发送的数据。例如,让我们假设一个操作系统的TCP/IP堆栈收到了三个数据包:
+-----+-----+-----+
| ABC | DEF | GHI |
+-----+-----+-----+
由于这种流传输协议的普遍性质,在你的应用中有较高的可能会把这些数据读取为另外一种形式:
+----+-------+---+---+
| AB | CDEFG | H | I |
+----+-------+---+---+
因此对于数据的接收方,不管是服务端还是客户端,应当重构这些接收到的数据,让其变成一种可让你的应用逻辑易于理解的更有意义的数据结构。在上面所述的这个例子中,接收到的数据应当重构为下面的形式:
+-----+-----+-----+
| ABC | DEF | GHI |
+-----+-----+-----+
1.7.2. 第一种方案
现在让我们回到时间协议服务客户端的例子中。我们在这里遇到了同样的问题。一个32位的整数是一个非常小的数据量,因此它常常不会被切分在不同的数据段内。然而,问题是它确实可以被切分在不同的数据段内,并且这种可能性随着流量的增加而提高。
最简单的方案是在程序内部创建一个可准确接收4字节数据的累积性缓冲。下面的代码是修复了这个问题后的TimeClientHandler实现。
Java代码
package org.jboss.netty.example.time;
import static org.jboss.netty.buffer.ChannelBuffers.*;
import java.util.Date;
@ChannelPipelineCoverage("one")
public class TimeClientHandler extends SimpleChannelHandler {
private final ChannelBuffer buf = dynamicBuffer();
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
ChannelBuffer m = (ChannelBuffer) e.getMessage();
buf.writeBytes(m);
if (buf.readableBytes() >= 4) {
long currentTimeMillis = buf.readInt() * 1000L;
System.out.println(new Date(currentTimeMillis));
e.getChannel().close();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
e.getCause().printStackTrace();
e.getChannel().close();
}
}
代码说明
1) 这一次我们使用“one”做为ChannelPipelineCoverage的注解值。这是由于这个修改后的TimeClientHandler不在不 在内部保持一个buffer缓冲,因此这个TimeClientHandler实例不可以再被多个Channel通道或ChannelPipeline共 享。否则这个内部的buffer缓冲将无法缓冲正确的数据内容。
2) 动态的buffer缓冲也是ChannelBuffer的一种实现,其拥有动态增加缓冲容量的能力。当你无法预估消息的数据长度时,动态的buffer缓冲是一种很有用的缓冲结构。
3) 首先,所有的数据将会被累积的缓冲至buf容器。
4) 之后,这个处理器将会检查是否收到了足够的数据然后再进行真实的业务逻辑处理,在这个例子中需要接收4字节数据。否则,Netty将重复调用messageReceived方法,直至4字节数据接收完成。
这里还有另一个地方需要进行修改。你是否还记得我们把TimeClientHandler实例添加到了这个ClientBootstrap实例的默 认ChannelPipeline管道里?这意味着同一个TimeClientHandler实例将被多个Channel通道共享,因此接受的数据也将受 到破坏。为了给每一个Channel通道创建一个新的TimeClientHandler实例,我们需要实现一个 ChannelPipelineFactory管道工厂:
Java代码
package org.jboss.netty.example.time;
public class TimeClientPipelineFactory implements ChannelPipelineFactory {
public ChannelPipeline getPipeline() {
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("handler", new TimeClientHandler());
return pipeline;
}
}
现在,我们需要把TimeClient下面的代码片段:
Java代码
TimeClientHandler handler = new TimeClientHandler();
bootstrap.getPipeline().addLast("handler", handler);
替换为:
Java代码
bootstrap.setPipelineFactory(new TimeClientPipelineFactory());
虽然这看上去有些复杂,并且由于在TimeClient内部我们只创建了一个连接(connection),因此我们在这里确实没必要引入TimeClientPipelineFactory实例。
然而,当你的应用变得越来越复杂,你就总会需要实现自己的ChannelPipelineFactory,这个管道工厂将会令你的管道配置变得更加具有灵活性。
1.7.3. 第二种方案
虽然第二种方案解决了时间协议客户端遇到的问题,但是这个修改后的处理器实现看上去却不再那么简洁。设想一种更为复杂的,由多个可变长度字段组成的协议。你的ChannelHandler实现将变得越来越难以维护。
正如你已注意到的,你可以为一个ChannelPipeline添加多个ChannelHandler,因此,为了减小应用的复杂性,你可以把这个臃肿的 ChannelHandler切分为多个独立的模块单元。例如,你可以把TimeClientHandler切分为两个独立的处理器:
TimeDecoder,解决数据分段的问题。
TimeClientHandler,原始版本的实现。
幸运的是,Netty提供了一个可扩展的类,这个类可以直接拿过来使用帮你完成TimeDecoder的开发:
Java代码
package org.jboss.netty.example.time;
public class TimeDecoder extends FrameDecoder {
@Override
protected Object decode(
ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {
if (buffer.readableBytes() < 4) {
return null;
}
return buffer.readBytes(4);
}
}
代码说明
1) 这里不再需要使用ChannelPipelineCoverage的注解,因为FrameDecoder总是被注解为“one”。
2) 当接收到新的数据后,FrameDecoder会调用decode方法,同时传入一个FrameDecoder内部持有的累积型buffer缓冲。
3) 如果decode返回null值,这意味着还没有接收到足够的数据。当有足够数量的数据后FrameDecoder会再次调用decode方法。
4) 如果decode方法返回一个非空值,这意味着decode方法已经成功完成一条信息的解码。FrameDecoder将丢弃这个内部的累计型缓冲。请注 意你不需要对多条消息进行解码,FrameDecoder将保持对decode方法的调用,直到decode方法返回非空对象。
如果你是一个勇于尝试的人,你或许应当使用ReplayingDecoder,ReplayingDecoder更加简化了解码的过程。为此你需要查看API手册获得更多的帮助信息。
Java代码
package org.jboss.netty.example.time;
public class TimeDecoder extends ReplayingDecoder<VoidEnum> {
@Override
protected Object decode(
ChannelHandlerContext ctx, Channel channel,
ChannelBuffer buffer, VoidEnum state) {
return buffer.readBytes(4);
}
}
此外,Netty还为你提供了一些可以直接使用的decoder实现,这些decoder实现不仅可以让你非常容易的实现大多数协议,并且还会帮你避免某些臃肿、难以维护的处理器实现。请参考下面的代码包获得更加详细的实例:
org.jboss.netty.example.factorial for a binary protocol, and
org.jboss.netty.example.telnet for a text line-based protocol
1.8. 使用POJO代替ChannelBuffer
目前为止所有的实例程序都是使用ChannelBuffer做为协议消息的原始数据结构。在这一节,我们将改进时间协议服务的客户/服务端实现,使用POJO 而不是ChannelBuffer做为协议消息的原始数据结构。
在你的ChannelHandler实现中使用POJO的优势是很明显的;从你的ChannelHandler实现中分离从ChannelBuffer获 取数据的代码,将有助于提高你的ChannelHandler实现的可维护性和可重用性。在时间协议服务的客户/服务端代码中,直接使用 ChannelBuffer读取一个32位的整数并不是一个主要的问题。然而,你会发现,当你试图实现一个真实的协议的时候,这种代码上的分离是很有必要 的。
首先,让我们定义一个称之为UnixTime的新类型。
Java代码
package org.jboss.netty.example.time;
import java.util.Date;
public class UnixTime {
private final int value;
public UnixTime(int value) {
this.value = value;
}
public int getValue() {
return value;
}
@Override
public String toString() {
return new Date(value * 1000L).toString();
}
}
现在让我们重新修改TimeDecoder实现,让其返回一个UnixTime,而不是一个ChannelBuffer。
Java代码
@Override
protected Object decode(
ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {
if (buffer.readableBytes() < 4) {
return null;
}
return new UnixTime(buffer.readInt());
}
FrameDecoder和ReplayingDecoder允许你返回一个任何类型的对象。如果它们仅允许返回一个ChannelBuffer类 型的对象,我们将不得不插入另一个可以从ChannelBuffer对象转换 为UnixTime对象的ChannelHandler实现。
有了这个修改后的decoder实现,这个TimeClientHandler便不会再依赖ChannelBuffer。
Java代码
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
UnixTime m = (UnixTime) e.getMessage();
System.out.println(m);
e.getChannel().close();
}
更加简单优雅了,不是吗?同样的技巧也可以应用在服务端,让我们现在更新TimeServerHandler的实现:
Java代码
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
UnixTime time = new UnixTime(System.currentTimeMillis() / 1000);
ChannelFuture f = e.getChannel().write(time);
f.addListener(ChannelFutureListener.CLOSE);
}
现在剩下的唯一需要修改的部分是这个ChannelHandler实现,这个ChannelHandler实现需要把一个UnixTime对象重新 转换为一个ChannelBuffer。但这却已是相当简单了,因为当你对消息进行编码的时候你不再需要处理数据包的拆分及组装。
Java代码
package org.jboss.netty.example.time;
import static org.jboss.netty.buffer.ChannelBuffers.*;
@ChannelPipelineCoverage("all")
public class TimeEncoder extends SimpleChannelHandler {
public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) {
UnixTime time = (UnixTime) e.getMessage();
ChannelBuffer buf = buffer(4);
buf.writeInt(time.getValue());
Channels.write(ctx, e.getFuture(), buf);
}
}
代码说明
1) 因为这个encoder是无状态的,所以其使用的ChannelPipelineCoverage注解值是“all”。实际上,大多数encoder实现都是无状态的。
2) 一个encoder通过重写writeRequested方法来实现对写操作请求的拦截。不过请注意虽然这个writeRequested方法使用了和 messageReceived方法一样的MessageEvent参数,但是它们却分别对应了不同的解释。一个ChannelEvent事件可以既是一 个上升流事件(upstream event)也可以是一个下降流事件(downstream event),这取决于事件流的方向。例如:一个MessageEvent消息事件可以作为一个上升流事件(upstream event)被messageReceived方法调用,也可以作为一个下降流事件(downstream event)被writeRequested方法调用。请参考API手册获得上升流事件(upstream event)和下降流事件(downstream event)的更多信息。
3) 一旦完成了POJO和ChannelBuffer转换,你应当确保把这个新的buffer缓冲转发至先前的 ChannelDownstreamHandler处理,这个下降通道的处理器由某个ChannelPipeline管理。Channels提供了多个可 以创建和发送ChannelEvent事件的帮助方法。在这个例子中,Channels.write(...)方法创建了一个新的 MessageEvent事件,并把这个事件发送给了先前的处于某个ChannelPipeline内的 ChannelDownstreamHandler处理器。
另外,一个很不错的方法是使用静态的方式导入Channels类:
import static org.jboss.netty.channel.Channels.*;
...
ChannelPipeline pipeline = pipeline();
write(ctx, e.getFuture(), buf);
fireChannelDisconnected(ctx);
最后的任务是把这个TimeEncoder插入服务端的ChannelPipeline,这是一个很简单的步骤。
1.9. 关闭你的应用
如果你运行了TimeClient,你肯定可以注意到,这个应用并没有自动退出而只是在那里保持着无意义的运行。跟踪堆栈记录你可以发现,这里有一些运行 状态的I/O线程。为了关闭这些I/O线程并让应用优雅的退出,你需要释放这些由ChannelFactory分配的资源。
一个典型的网络应用的关闭过程由以下三步组成:
关闭负责接收所有请求的server socket。
关闭所有客户端socket或服务端为响应某个请求而创建的socket。
释放ChannelFactory使用的所有资源。
为了让TimeClient执行这三步,你需要在TimeClient.main()方法内关闭唯一的客户连接以及ChannelFactory使用的所有资源,这样做便可以优雅的关闭这个应用。
Java代码
package org.jboss.netty.example.time;
public class TimeClient {
public static void main(String[] args) throws Exception {
...
ChannelFactory factory = ...;
ClientBootstrap bootstrap = ...;
...
ChannelFuture future = bootstrap.connect(...);
future.awaitUninterruptible();
if (!future.isSuccess()) {
future.getCause().printStackTrace();
}
future.getChannel().getCloseFuture().awaitUninterruptibly();
factory.releaseExternalResources();
}
}
代码说明
1) ClientBootstrap对象的connect方法返回一个ChannelFuture对象,这个ChannelFuture对象将告知这个连接操 作的成功或失败状态。同时这个ChannelFuture对象也保存了一个代表这个连接操作的Channel对象引用。
2) 阻塞式的等待,直到ChannelFuture对象返回这个连接操作的成功或失败状态。
3) 如果连接失败,我们将打印连接失败的原因。如果连接操作没有成功或者被取消,ChannelFuture对象的getCause()方法将返回连接失败的原因。
4) 现在,连接操作结束,我们需要等待并且一直到这个Channel通道返回的closeFuture关闭这个连接。每一个Channel都可获得自己的closeFuture对象,因此我们可以收到通知并在这个关闭时间点执行某种操作。
并且即使这个连接操作失败,这个closeFuture仍旧会收到通知,因为这个代表连接的 Channel对象将会在连接操作失败后自动关闭。
5) 在这个时间点,所有的连接已被关闭。剩下的唯一工作是释放ChannelFactory通道工厂使用的资源。这一步仅需要调用 releaseExternalResources()方法即可。包括NIO Secector和线程池在内的所有资源将被自动的关闭和终止。
关闭一个客户端应用是很简单的,但又该如何关闭一个服务端应用呢?你需要释放其绑定的端口并关闭所有接受和打开的连接。为了做到这一点,你需要使用一种数据结构记录所有的活动连接,但这却并不是一件容易的事。幸运的是,这里有一种解决方案,ChannelGroup。
ChannelGroup是Java 集合 API的一个特有扩展,ChannelGroup内部持有所有打开状态的Channel通道。如果一个Channel通道对象被加入到 ChannelGroup,如果这个Channel通道被关闭,ChannelGroup将自动移除这个关闭的Channel通道对象。此外,你还可以对 一个ChannelGroup对象内部的所有Channel通道对象执行相同的操作。例如,当你关闭服务端应用时你可以关闭一个ChannelGroup 内部的所有Channel通道对象。
为了记录所有打开的socket,你需要修改你的TimeServerHandler实现,将一个打开的Channel通道加入全局的ChannelGroup对象,TimeServer.allChannels:
Java代码
@Override
public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) {
TimeServer.allChannels.add(e.getChannel());
}
代码说明
是的,ChannelGroup是线程安全的。
现在,所有活动的Channel通道将被自动的维护,关闭一个服务端应用有如关闭一个客户端应用一样简单。
Java代码
package org.jboss.netty.example.time;
public class TimeServer {
static final ChannelGroup allChannels = new DefaultChannelGroup("time-server" );
public static void main(String[] args) throws Exception {
...
ChannelFactory factory = ...;
ServerBootstrap bootstrap = ...;
...
Channel channel = bootstrap.bind(...);
allChannels.add(channel);
waitForShutdownCommand();
ChannelGroupFuture future = allChannels.close();
future.awaitUninterruptibly();
factory.releaseExternalResources();
}
}
代码说明
1) DefaultChannelGroup需要一个组名作为其构造器参数。这个组名仅是区分每个ChannelGroup的一个标示。
2) ServerBootstrap对象的bind方法返回了一个绑定了本地地址的服务端Channel通道对象。调用这个Channel通道的close()方法将释放这个Channel通道绑定的本地地址。
3) 不管这个Channel对象属于服务端,客户端,还是为响应某一个请求创建,任何一种类型的Channel对象都会被加入ChannelGroup。因此,你尽可在关闭服务时关闭所有的Channel对象。
4) waitForShutdownCommand()是一个想象中等待关闭信号的方法。你可以在这里等待某个客户端的关闭信号或者JVM的关闭回调命令。
5) 你可以对ChannelGroup管理的所有Channel对象执行相同的操作。在这个例子里,我们将关闭所有的通道,这意味着绑定在服务端特定地址的 Channel通道将解除绑定,所有已建立的连接也将异步关闭。为了获得成功关闭所有连接的通知,close()方法将返回一个 ChannelGroupFuture对象,这是一个类似ChannelFuture的对象。
1.10. 总述
在这一章节,我们快速浏览并示范了如何使用Netty开发网络应用。下一章节将涉及更多的问题。同时请记住,为了帮助你以及能够让Netty基于你的回馈得到持续的改进和提高,Netty社区 将永远欢迎你的问题及建议。
第二章. 架构总览
在这个章节,我们将阐述Netty提供的核心功能以及在此基础之上如何构建一个完备的网络应用。
2.1. 丰富的缓冲实现
Netty使用自建的buffer API,而不是使用NIO的ByteBuffer来代表一个连续的字节序列。与ByteBuffer相比这种方式拥有明显的优势。Netty使用新的 buffer类型ChannelBuffer,ChannelBuffer被设计为一个可从底层解决ByteBuffer问题,并可满足日常网络应用开发 需要的缓冲类型。这些很酷的特性包括:
如果需要,允许使用自定义的缓冲类型。
复合缓冲类型中内置的透明的零拷贝实现。
开箱即用的动态缓冲类型,具有像StringBuffer一样的动态缓冲能力。
不再需要调用的flip()方法。
正常情况下具有比ByteBuffer更快的响应速度。
更多信息请参考:org.jboss.netty.buffer package description
2.2. 统一的异步 I/O API
传统的Java I/O API在应对不同的传输协议时需要使用不同的类型和方法。例如:java.net.Socket 和 java.net.DatagramSocket它们并不具有相同的超类型,因此,这就需要使用不同的调用方式执行socket操作。
这种模式上的不匹配使得在更换一个网络应用的传输协议时变得繁杂和困难。由于(Java I/O API)缺乏协议间的移植性,当你试图在不修改网络传输层的前提下增加多种协议的支持,这时便会产生问题。并且理论上讲,多种应用层协议可运行在多种传输 层协议之上例如TCP/IP,UDP/IP,SCTP和串口通信。
让这种情况变得更糟的是,Java新的I/O(NIO)API与原有的阻塞式的I/O(OIO)API并不兼容,NIO.2(AIO)也是如此。由于所有的API无论是在其设计上还是性能上的特性都与彼此不同,在进入开发阶段,你常常会被迫的选择一种你需要的API。
例如,在用户数较小的时候你可能会选择使用传统的OIO(Old I/O) API,毕竟与NIO相比使用OIO将更加容易一些。然而,当你的业务呈指数增长并且服务器需要同时处理成千上万的客户连接时你便会遇到问题。这种情况下 你可能会尝试使用NIO,但是复杂的NIO Selector编程接口又会耗费你大量时间并最终会阻碍你的快速开发。
Netty有一个叫做Channel的统一的异步I/O编程接口,这个编程接口抽象了所有点对点的通信操作。也就是说,如果你的应用是基于Netty的某 一种传输实现,那么同样的,你的应用也可以运行在Netty的另一种传输实现上。Netty提供了几种拥有相同编程接口的基本传输实现:
NIO-based TCP/IP transport (See org.jboss.netty.channel.socket.nio),
OIO-based TCP/IP transport (See org.jboss.netty.channel.socket.oio),
OIO-based UDP/IP transport, and
Local transport (See org.jboss.netty.channel.local).
切换不同的传输实现通常只需对代码进行几行的修改调整,例如选择一个不同的ChannelFactory实现。
此外,你甚至可以利用新的传输实现没有写入的优势,只需替换一些构造器的调用方法即可,例如串口通信。而且由于核心API具有高度的可扩展性,你还可以完成自己的传输实现。
2.3. 基于拦截链模式的事件模型
一个定义良好并具有扩展能力的事件模型是事件驱动开发的必要条件。Netty具有定义良好的I/O事件模型。由于严格的层次结构区分了不同的事件类型,因 此Netty也允许你在不破坏现有代码的情况下实现自己的事件类型。这是与其他框架相比另一个不同的地方。很多NIO框架没有或者仅有有限的事件模型概 念;在你试图添加一个新的事件类型的时候常常需要修改已有的代码,或者根本就不允许你进行这种扩展。
在一个ChannelPipeline内部一个ChannelEvent被一组ChannelHandler处理。这个管道是拦截过滤器 模式的一种高级形式的实现,因此对于一个事件如何被处理以及管道内部处理器间的交互过程,你都将拥有绝对的控制力。例如,你可以定义一个从socket读取到数据后的操作:
Java代码
public class MyReadHandler implements SimpleChannelHandler {
public void messageReceived(ChannelHandlerContext ctx, MessageEvent evt) {
Object message = evt.getMessage();
// Do something with the received message.
...
// And forward the event to the next handler.
ctx.sendUpstream(evt);
}
}
同时你也可以定义一种操作响应其他处理器的写操作请求:
Java代码
public class MyWriteHandler implements SimpleChannelHandler {
public void writeRequested(ChannelHandlerContext ctx, MessageEvent evt) {
Object message = evt.getMessage();
// Do something with the message to be written.
...
// And forward the event to the next handler.
ctx.sendDownstream(evt);
}
}
有关事件模型的更多信息,请参考API文档ChannelEvent和ChannelPipeline部分。
2.4. 适用快速开发的高级组件
上述所提及的核心组件已经足够实现各种类型的网络应用,除此之外,Netty也提供了一系列的高级组件来加速你的开发过程。
2.4.1. Codec框架
就像“1.8. 使用POJO代替ChannelBuffer”一节所展示的那样,从业务逻辑代码中分离协议处理部分总是一个很不错的想法。然而如果一切从零开始便会遭遇 到实现上的复杂性。你不得不处理分段的消息。一些协议是多层的(例如构建在其他低层协议之上的协议)。一些协议过于复杂以致难以在一台主机(single state machine)上实现。
因此,一个好的网络应用框架应该提供一种可扩展,可重用,可单元测试并且是多层的codec框架,为用户提供易维护的codec代码。
Netty提供了一组构建在其核心模块之上的codec实现,这些简单的或者高级的codec实现帮你解决了大部分在你进行协议处理开发过程会遇到的问题,无论这些协议是简单的还是复杂的,二进制的或是简单文本的。
2.4.2. SSL / TLS 支持
不同于传统阻塞式的I/O实现,在NIO模式下支持SSL功能是一个艰难的工作。你不能只是简单的包装一下流数据并进行加密或解密工作,你不得不借助于 javax.net.ssl.SSLEngine,SSLEngine是一个有状态的实现,其复杂性不亚于SSL自身。你必须管理所有可能的状态,例如密 码套件,密钥协商(或重新协商),证书交换以及认证等。此外,与通常期望情况相反的是SSLEngine甚至不是一个绝对的线程安全实现。
在Netty内部,SslHandler封装了所有艰难的细节以及使用SSLEngine可能带来的陷阱。你所做的仅是配置并将该SslHandler插入到你的ChannelPipeline中。同样Netty也允许你实现像StartTlS 那样所拥有的高级特性,这很容易。
2.4.3. HTTP实现
HTTP无疑是互联网上最受欢迎的协议,并且已经有了一些例如Servlet容器这样的HTTP实现。因此,为什么Netty还要在其核心模块之上构建一套HTTP实现?
与现有的HTTP实现相比Netty的HTTP实现是相当与众不同的。在HTTP消息的低层交互过程中你将拥有绝对的控制力。这是因为Netty的 HTTP实现只是一些HTTP codec和HTTP消息类的简单组合,这里不存在任何限制——例如那种被迫选择的线程模型。你可以随心所欲的编写那种可以完全按照你期望的工作方式工作 的客户端或服务器端代码。这包括线程模型,连接生命期,快编码,以及所有HTTP协议允许你做的,所有的一切,你都将拥有绝对的控制力。
由于这种高度可定制化的特性,你可以开发一个非常高效的HTTP服务器,例如:
要求持久化链接以及服务器端推送技术的聊天服务(e.g. Comet )
需要保持链接直至整个文件下载完成的媒体流服务(e.g. 2小时长的电影)
需要上传大文件并且没有内存压力的文件服务(e.g. 上传1GB文件的请求)
支持大规模mash-up应用以及数以万计连接的第三方web services异步处理平台
2.4.4. Google Protocol Buffer 整合
Google Protocol Buffers 是快速实现一个高效的二进制协议的理想方案。通过使用ProtobufEncoder和ProtobufDecoder,你可以把Google Protocol Buffers 编译器 (protoc)生成的消息类放入到Netty的codec实现中。请参考“LocalTime ”实例,这个例子也同时显示出开发一个由简单协议定义 的客户及服务端是多么的容易。
2.5. 总述
在这一章节,我们从功能特性的角度回顾了Netty的整体架构。Netty有一个简单却不失强大的架构。这个架构由三部分组成——缓冲(buffer), 通道(channel),事件模型(event model)——所有的高级特性都构建在这三个核心组件之上。一旦你理解了它们之间的工作原理,你便不难理解在本章简要提及的更多高级特性。
你可能对Netty的整体架构以及每一部分的工作原理仍旧存有疑问。如果是这样,最好的方式是告诉我们 应该如何改进这份指南
bug描述:
parseInt("08")=0;
parseInt("09")=0;
这里先回顾一下parseInt的函数声明:
/*
* 将字符串解析成数字时,从左向右依此解析,解析到第一个非法字符即停止。
* 若指定radix为2-36之间的数字,则按相应的进制进行解析;
* 若radix指定为1,或大于36的数字,则直接返回NaN
* 若指定radix为0,或未指定radix,则根据字符串开头字符确定:
* 以'1-9'开头的字符串,按10进制解析;
* 以'0'开头的字符串,按8进制解析;
* 以'0x'或'0X'开头的字符串,按16进制解析。
*
* @param string 要被解析的字符串。
* @param radix 表示要解析的数字的基数。该值介于 2 ~ 36 之间。
*/
parseInt(string, radix)
‘01’到’07’,按8进制或10进制解析会得到相同的结果。 而’08’,’09’按8进制解析会得到’0’,因为’8’、’9’在8进制中是非法字符,不会被解析。由此导致上述的bug。
找到问题根源,修复就变得很简单了,显示指定radix为10.
parseInt("08",10);
在本教程中,我们将看到使用Eclipse调试Java应用程序。调试可以帮助我们识别和解决应用程序中的缺陷。我们将重点放在运行时间的问题,而不是编译时错误。有提供像gdb的命令行调试器。在本教程中,我们将集中在基于GUI的调试,我们把我们最喜爱的IDE Eclipse来运行,通过本教程。虽然我们说的Eclipse,点大多是通用的,适用于调试使用的IDE像NetBeans。
在看这篇文章前,我推荐你看一下Eclipse 快捷键手册,你也可以到这儿:下载PDF文件我的eclipse版本是4.2 Juno。
0.三点特别提醒:
- 不要使用System.out.println作为调试工具
- 启用所有组件的详细的日志记录级别
- 使用一个日志分析器来阅读日志
[
(System.out.println()对开发人员来说,有时候也许可以是一种调试手段,但是项目一旦完成他就没有什么用途了,就变成垃圾了,得必须注释或删除掉,这样会比较麻烦。启用所有组件的详细日志记录级别,运用日志分析器来记录详细系统运行状态,这对后期网站的优化和维护会有很多作用。)这仅仅是个人理解,仅供参考!
]
1.条件断点
想象一下我们平时如何添加断点,通常的做法是双击行号的左边。在debug视图中,BreakPoint View将所有断点都列出来,但是我们可以添加一个boolean类型的条件来决定断点是否被跳过。如果条件为真,在断点处程序将停止,否则断点被跳过,程序继续执行。
2.异常断点
在断点view中有一个看起来像J!的按钮,我们可以使用它添加一个基于异常的断点,例如我们希望当NullPointerException抛出的时候程序暂停,我们可以这样:
3.观察点
这是一个很好的功能,他允许当一个选定的属性被访问或者被更改的时候程序执行暂停,并进行debug。最简单的办法是在类中声明成员变量的语句行号左边双击,就可以加入一个观察点。
4.查看变量
在选中的变量上使用Ctrl+Shift+d 或者 Ctrl+Shift+i可以查看变量值,另外我们还可以在Expressions View中添加监视。
5.更改变量的值
我们可以在Debug的时候改变其中变量的值。在Variables View中可以按下图所示操作。
6.在主方法停止
在Run/Debug设置中,我们可以按如下图所示的启用这个特性。程序将会在main方法的第一行停住
7.环境变量
我们可以很方便的在Edit Conriguration对话框中添加环境变量
8.跳出函数到选定层
这个功能非常酷,是我第二个非常喜欢的功能,Drop to frame就是说,可以重新跳到当前方法的开始处重新执行,并且所有上下文变量的值也回到那个时候。不一定是当前方法,可以点击当前调用栈中的任何一个frame跳到那里(除了最开始的那个frame)。主要用途是所有变量状态快速恢复到方法开始时候的样子重新执行一遍,即可以一遍又一遍地在那个你关注的上下文中进行多次调试(结合改变变量值等其它功能),而不用重来一遍调试到哪里了。当然,原来执行过程中产生的副作用是不可逆的(比如你往数据库中插入了一条记录)。
9.分步过滤
当我们在调试的时候摁F5将进入方法的内部,但这有个缺点有的时候可能会进入到一些库的内部(例如JDK),可能并不是我们想要的,我们可以在Preferences中添加一个过滤器,排除指定的包。
10.跳入,跳过和返回
其实这个技巧是debug最基本的知识。
- F5-Step Into:移动到下一步,如果当前的行是一个方法调用,将进入这个方法的第一行。(可以通过第九条来排除)
- F6-Step Over:移动到下一行。如果当前行有方法调用,这个方法将被执行完毕返回,然后到下一行。
- F7-Step Return:继续执行当前方法,当当前方法执行完毕的时候,控制将转到当前方法被调用的行。
- F8-移动到下一个断点处。
<a href="itms-services://?action=download-manifest&url=http://test1.gc73.com.cn/hoho.plist"> http://test1.gc73.com.cn/hoho.plist 文件格式如下
<?xml version="1.0" encoding="gbk"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>items</key> <array> <dict> <key>assets</key> <array> <dict> <key>kind</key> <string>software-package</string> <key>url</key> <string>http://dlx1.gc73.com/pokerddzV2.1-7.20.iPad.ipa</string> </dict> <dict> <key>kind</key> <string>full-size-image</string> <key>needs-shine</key> <false/> <key>url</key> <string>http://test1.gc73.com.cn/ipad_tmp.png</string> </dict> <dict> <key>kind</key> <string>display-image</string> <key>needs-shine</key> <false/> <key>url</key> <string>http://test1.gc73.com.cn/ipad_tmp.png</string> </dict> </array> <key>metadata</key> <dict> <key>bundle-identifier</key> <string>com.pokercity.fightlordiPad</string> <key>kind</key> <string>software</string> <key>subtitle</key> <string>咱的测试</string> <key>title</key> <string>咱的测试(越狱版)</string> </dict> </dict> </array> </dict> </plist>
用tortoisehg下载google code时报错abort:error
解决方案:将https: 换成http 试一试
VisualVM是Sun的一个OpenJDK项目,其目的在于为Java应用创建一个整套的问题解决工具。它集成了多个JDK命令工具的一个可视化工具,它主要用来监控JVM的运行情况,可以用它来查看和浏览Heap Dump、Thread Dump、内存对象实例情况、GC执行情况、CPU消耗以及类的装载情况。 Java开发人员可以使用 VisualVM创建必要信息的日志,系统管理人员可用来监控及控制Java应用程序在网络中的运行状况。
下载页面 : https://visualvm.dev.java.net/download.html
文档地址 : https://visualvm.dev.java.net/docindex.html
入门文档 : https://visualvm.dev.java.net/zh_CN/gettingstarted.html
安装插件
通过安装 VisualVM 更新中心提供的插件,可以向 VisualVM 添加功能。
1. 从主菜单中选择“工具”>“插件”。
2. 在“可用插件”标签中,选中该插件的“安装”复选框。单击“安装”。
3. 逐步完成插件安装程序。
功能
1. 概述
查看jvm信息及系统配置
2. 监视
了解项目运动的概况
3. visual gc
可以看到内存gc的详细情况
远程监控
1. 通过jstatd启动RMI服务
配置java安全访问,将如下的代码存为文件 jstatd.all.policy,放到JAVA_HOME/bin中,其内容如下,
grant codebase "file:${java.home}/../lib/tools.jar" {
permission java.security.AllPermission;
};
执行命令jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=192.168.1.8 &(192.168.1.8 为你服务器的ip地址,&表示用守护线程的方式运行)
jstatd命令详解 :http://hzl7652.iteye.com/blog/1183182
打开jvisualvm, 右键Remort,选择 "Add Remort Host...",在弹出框中输入你的远端IP,比如192.168.1.8. 连接成功.
2. 配置JMX管理tomcat
打开Tomcat的bin/catalina.bat,如果为linux或unix系统,则为catalina.sh文件 。
无限制访问
set JAVA_OPTS=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9008 -Dcom.sun.management.jmxremote.authenticate=false - Dcom.sun.management.jmxremote.ssl=false
需要用户名和密码访问
JAVA_OPTS='-Xms128m -Xmx256m -XX:MaxPermSize=128m
-Djava.rmi.server.hostname=192.168.1.8
-Dcom.sun.management.jmxremote.port=8088
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.password.file=/usr/java/default/jre/lib/management/jmxremote.password
-Dcom.sun.management.jmxremote.access.file=/usr/java/default/jre/lib/management/jmxremote.access'
(jmxremote.access 在JAVA_HOME\jre\lib\management下有模板)
jmxremote.access 中显示
monitorRole readonly
controlRole readwrite
jmxremote.password中显示
monitorRole QED (QED为密码)
controlRole R&D
重新在visualvm中打开远程tomcat就可以使用JMX带来的功能了
Ctrl+1 快速修复(最经典的快捷键,就不用多说了)
Ctrl+D: 删除当前行
Ctrl+Alt+↓ 复制当前行到下一行(复制增加)
Ctrl+Alt+↑ 复制当前行到上一行(复制增加)
Alt+↓ 当前行和下面一行交互位置(特别实用,可以省去先剪切,再粘贴了)
Alt+↑ 当前行和上面一行交互位置(同上)
Alt+← 前一个编辑的页面
Alt+→ 下一个编辑的页面(当然是针对上面那条来说了)
Alt+Enter 显示当前选择资源(工程,or 文件 or文件)的属性
Shift+Enter 在当前行的下一行插入空行(这时鼠标可以在当前行的任一位置,不一定是最后)
Shift+Ctrl+Enter 在当前行插入空行(原理同上条)
Ctrl+Q 定位到最后编辑的地方
Ctrl+L 定位在某行 (对于程序超过100的人就有福音了)
Ctrl+M 最大化当前的Edit或View (再按则反之)
Ctrl+/ 注释当前行,再按则取消注释
Ctrl+O 快速显示 OutLine
Ctrl+T 快速显示当前类的继承结构
Ctrl+W 关闭当前Editer
Ctrl+K 参照选中的Word快速定位到下一个
Ctrl+E 快速显示当前Editer的下拉列表(如果当前页面没有显示的用黑体表示)
Ctrl+/(小键盘) 折叠当前类中的所有代码
Ctrl+×(小键盘) 展开当前类中的所有代码
Ctrl+Space 代码助手完成一些代码的插入(但一般和输入法有冲突,可以修改输入法的热键,也可以暂用Alt+/来代替)
Ctrl+Shift+E 显示管理当前打开的所有的View的管理器(可以选择关闭,激活等操作)
Ctrl+J 正向增量查找(按下Ctrl+J后,你所输入的每个字母编辑器都提供快速匹配定位到某个单词,如果没有,则在stutes line中显示没有找到了,查一个单词时,特别实用,这个功能Idea两年前就有了)
Ctrl+Shift+J 反向增量查找(和上条相同,只不过是从后往前查)
Ctrl+Shift+F4 关闭所有打开的Editer
Ctrl+Shift+X 把当前选中的文本全部变味小写
Ctrl+Shift+Y 把当前选中的文本全部变为小写
Ctrl+Shift+F 格式化当前代码
Ctrl+Shift+P 定位到对于的匹配符(譬如{}) (从前面定位后面时,光标要在匹配符里面,后面到前面,则反之)
下面的快捷键是重构里面常用的,本人就自己喜欢且常用的整理一下(注:一般重构的快捷键都是Alt+Shift开头的了)
Alt+Shift+R 重命名 (是我自己最爱用的一个了,尤其是变量和类的Rename,比手工方法能节省很多劳动力)
Alt+Shift+M 抽取方法 (这是重构里面最常用的方法之一了,尤其是对一大堆泥团代码有用)
Alt+Shift+C 修改函数结构(比较实用,有N个函数调用了这个方法,修改一次搞定)
Alt+Shift+L 抽取本地变量( 可以直接把一些魔法数字和字符串抽取成一个变量,尤其是多处调用的时候)
Alt+Shift+F 把Class中的local变量变为field变量 (比较实用的功能)
Alt+Shift+I 合并变量(可能这样说有点不妥Inline)
Alt+Shift+V 移动函数和变量(不怎么常用)
Alt+Shift+Z 重构的后悔药(Undo)
编辑
作用域 功能 快捷键
全局 查找并替换 Ctrl+F
文本编辑器 查找上一个 Ctrl+Shift+K
文本编辑器 查找下一个 Ctrl+K
全局 撤销 Ctrl+Z
全局 复制 Ctrl+C
全局 恢复上一个选择 Alt+Shift+↓
全局 剪切 Ctrl+X
全局 快速修正 Ctrl1+1
全局 内容辅助 Alt+/
全局 全部选中 Ctrl+A
全局 删除 Delete
全局 上下文信息 Alt+?
Alt+Shift+?
Ctrl+Shift+Space
Java编辑器 显示工具提示描述 F2
Java编辑器 选择封装元素 Alt+Shift+↑
Java编辑器 选择上一个元素 Alt+Shift+←
Java编辑器 选择下一个元素 Alt+Shift+→
文本编辑器 增量查找 Ctrl+J
文本编辑器 增量逆向查找 Ctrl+Shift+J
全局 粘贴 Ctrl+V
全局 重做 Ctrl+Y
查看
作用域 功能 快捷键
全局 放大 Ctrl+=
全局 缩小 Ctrl+-
窗口
作用域 功能 快捷键
全局 激活编辑器 F12
全局 切换编辑器 Ctrl+Shift+W
全局 上一个编辑器 Ctrl+Shift+F6
全局 上一个视图 Ctrl+Shift+F7
全局 上一个透视图 Ctrl+Shift+F8
全局 下一个编辑器 Ctrl+F6
全局 下一个视图 Ctrl+F7
全局 下一个透视图 Ctrl+F8
文本编辑器 显示标尺上下文菜单 Ctrl+W
全局 显示视图菜单 Ctrl+F10
全局 显示系统菜单 Alt+-
导航
作用域 功能 快捷键
Java编辑器 打开结构 Ctrl+F3
全局 打开类型 Ctrl+Shift+T
全局 打开类型层次结构 F4
全局 打开声明 F3
全局 打开外部javadoc Shift+F2
全局 打开资源 Ctrl+Shift+R
全局 后退历史记录 Alt+←
全局 前进历史记录 Alt+→
全局 上一个 Ctrl+,
全局 下一个 Ctrl+.
Java编辑器 显示大纲 Ctrl+O
全局 在层次结构中打开类型 Ctrl+Shift+H
全局 转至匹配的括号 Ctrl+Shift+P
全局 转至上一个编辑位置 Ctrl+Q
Java编辑器 转至上一个成员 Ctrl+Shift+↑
Java编辑器 转至下一个成员 Ctrl+Shift+↓
文本编辑器 转至行 Ctrl+L
搜索
作用域 功能 快捷键
全局 出现在文件中 Ctrl+Shift+U
全局 打开搜索对话框 Ctrl+H
全局 工作区中的声明 Ctrl+G
全局 工作区中的引用 Ctrl+Shift+G
文本编辑
作用域 功能 快捷键
文本编辑器 改写切换 Insert
文本编辑器 上滚行 Ctrl+↑
文本编辑器 下滚行 Ctrl+↓
文件
作用域 功能 快捷键
全局 保存 Ctrl+X
Ctrl+S
全局 打印 Ctrl+P
全局 关闭 Ctrl+F4
全局 全部保存 Ctrl+Shift+S
全局 全部关闭 Ctrl+Shift+F4
全局 属性 Alt+Enter
全局 新建 Ctrl+N
项目
作用域 功能 快捷键
全局 全部构建 Ctrl+B
源代码
作用域 功能 快捷键
Java编辑器 格式化 Ctrl+Shift+F
Java编辑器 取消注释 Ctrl+\
Java编辑器 注释 Ctrl+/
Java编辑器 添加导入 Ctrl+Shift+M
Java编辑器 组织导入 Ctrl+Shift+O
Java编辑器 使用try/catch块来包围 未设置,太常用了,所以在这里列出,建议自己设置。
也可以使用Ctrl+1自动修正。
运行
作用域 功能 快捷键
全局 单步返回 F7
全局 单步跳过 F6
全局 单步跳入 F5
全局 单步跳入选择 Ctrl+F5
全局 调试上次启动 F11
全局 继续 F8
全局 使用过滤器单步执行 Shift+F5
全局 添加/去除断点 Ctrl+Shift+B
全局 显示 Ctrl+D
全局 运行上次启动 Ctrl+F11
全局 运行至行 Ctrl+R
全局 执行 Ctrl+U
重构
作用域 功能 快捷键
全局 撤销重构 Alt+Shift+Z
全局 抽取方法 Alt+Shift+M
全局 抽取局部变量 Alt+Shift+L
全局 内联 Alt+Shift+I
全局 移动 Alt+Shift+V
全局 重命名 Alt+Shift+R
全局 重做 Alt+Shift+Y
以字母或下划线开头!
今天找一个css的加载问题,class已经被浏览器加载到,但是属性值一直不显示出来,几经查找才发现className以数字开头命名的,导致浏览器不认可,整一个悲剧
BTrace的技术分析,本人暂没有这个技术能力,大家可以看
http://www.iteye.com/topic/1005918,
http://mgoann.iteye.com/blog/1409667 下面是个人写的一些简单实例,不过我一直没办法通过BTrace拿到局部变量的值,不知道哪位牛人帮帮解答下
Linux下:在http://kenai.com/projects/btrace下载btrace-bin.tar.gz,并解压,在/etc/profile设置环境变量: - export BTRACE_HOME=/home/workspace/btrace/
- export PATH=$BTRACE_HOME/bin:$PATH
设置完成后 source /etc/profile
给执行文件赋权限 chmod +x btrace
/* BTrace Script Template */
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
import java.lang.reflect.Field;
@BTrace
public class TracingScript {
/* put your code here */
//打印实例属性
@OnMethod(clazz = "com.gameplus.action.siteLobby.LobbyAction", method = "/.*bankPage/", location = @Location(value = Kind.ENTRY))
//clazz = "com.gameplus.action.siteLobby.LobbyAction" 表示监控的类,method = "/.*bankPage/"表示监控的方法 ,这两个参数都可以用正则匹配
//如果是接口用+号clazz = "+com.gameplus.action.siteLobby.LobbyAction"
public static void bufferMonitor(@Self Object self ){ // @Self 表示监控点实例
print(strcat(strcat(name(probeClass()), "."), probeMethod())); //probeClass()监控的类,probeMethod()监控的方法
println(self);
println(get(field(classOf(self), "money"))); //只能取值static变量
println(get(field(classOf(self), "money"),self)); //可以取值当前实例变量,static也可以取到
Field moneyField = field("com.gameplus.action.siteLobby.LobbyAction", "money"); //知道class的名称也可以取值
get(moneyField,self);
get(field("com.gameplus.action.siteLobby.LobbyAction", "money"), self);
Object montmp =get(field(getSuperclass(classOf(self)), "user"), self); //获取父类变量的方法
println(str(montmp));
long userId = (Long)get(field(classOf(montmp),"userId"),montmp); //获取superClass.Object.变量值
println(userId);
}
//打印运行时lineNumber
@OnMethod(clazz = "com.gameplus.action.siteLobby.LobbyAction", location=@Location(value=Kind.LINE, line=-1))
public static void online(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) {
print(Strings.strcat(pcn, ".")); //className
print(Strings.strcat(pmn, ":")); //methodName
println(line); //lineNumber
//结果为:com.gameplus.action.siteLobby.LobbyAction.bankPage:161
}
//打印传递的参数值
import com.sun.btrace.AnyType;
@OnMethod(clazz="com.gameplus.service.operateBankService.OperateBankService",method="/.*/")
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {
println(pcn);
println(pmn);
printArray(args);
}
//打印所有属性
@OnMethod(clazz = "com.gameplus.action.siteLobby.LobbyAction", method = "/.*bankPage/", location = @Location(value = Kind.RETURN))
public static void bufferMonitor(@Self Object self,@Return Object command ,@Duration long time){
printFields(self);
Object montmp =get(field(getSuperclass(classOf(self)), "user"), self);
printFields(montmp);
//{password=null, newPassword=null, rePassword=, bankPassword=, newBankPassword=, reBankPassword=, operateType=0, operateBankType=0, money=111, integral=0, dateStartQuery=, dateEndQuery=, isDefaultPasswd=0, }
//{userId=10918, username=titanaly11, realname=方法1, expTime=1146510, bankMoney=1317886229, bankPasswd=243b6503f2e3e83faccc89830aca1d91, ifAvailable=1, password=1bbd886460827015e5d605ed44252251, money=100000, }
}
//初始化时的变量参数
//public User(long userId,String playServerId) {
// this.userId = userId;
// this.playServerId = playServerId;
//}
@OnMethod(clazz = "com.gameplus.core.model.oracle.user.User", method="<init>")
public static void bufferMonitor(@Self Object self,long userId,String gameId){
printFields(self);
println(userId);
println(gameId);
//结果
//{userId=0, username=null, realname=null, expTime=0, bankMoney=0, bankPasswd=null, ifAvailable=0, password=null, money=0, nickName=null, iconNum=0, tim=null, infullCount=0, userToken=null, onlineTime=0,}
//10918
//222
}
//打印系统参数
static {
println("System Properties:");
printProperties();
println("VM Flags:");
printVmArguments();
println("OS Enviroment:");
printEnv();
exit(0);
}
//打印程序执行关系
//LobbyAction中所有方法执行的执行顺序
@OnMethod(clazz="com.gameplus.action.siteLobby.LobbyAction", method="/.*/",
location=@Location(value=Kind.CALL, clazz="/.*/", method="/.*/"))
public static void n(@Self Object self, @ProbeClassName String pcm, @ProbeMethodName String pmn,
@TargetInstance Object instance, @TargetMethodOrField String method){ // all calls to the methods with signature "(String)"
println(Strings.strcat("Context: ", Strings.strcat(pcm, Strings.strcat("#", pmn))));
println(instance); //被调用目标对象
println(Strings.strcat("",method)); //被调用方法
//Context: com/gameplus/action/siteLobby/LobbyAction#bankPage
//$Proxy18@807c31
//getBankPage
//Context: com/gameplus/action/siteLobby/LobbyAction#bankPage
//{userEx=10, leverRestriction=6, isDefaultPasswd=0, bankPassEqGamePass=0, moneyTransferMsg=S, isSimplePasswd=0, mobile=null}
//get
//Context: com/gameplus/action/siteLobby/LobbyAction#bankPage
//6
//intValue
//Context: com/gameplus/action/siteLobby/LobbyAction#bankPage
//{userEx=10, leverRestriction=6, isDefaultPasswd=0, bankPassEqGamePass=0, moneyTransferMsg=S, isSimplePasswd=0, mobile=null}
//get
}
最近在进行MyEclipse启动速度优化,优化发现MyEclipse报这个错误,关掉它的提示后不影响正常的使用.
用visualvm监控发现heap memory正常运转,查看了下配置参数,发现这个参数-XX:+DisableExplicitGC(禁止system.gc()的调用,gc过程完全有jvm控制),怀疑他就是报错的原因,去掉后一切正常.估计是MyEclipse在发现heap memory不足是显示调用了gc方法,然后gc方法不被jvm接受,当内存达到MyEclipse的报警值时报错提醒
http://suhuanzheng7784877.iteye.com/blog/1170585
“武林至尊,宝刀屠龙。号令天下,莫敢不从。倚天不出,谁与争锋。”。这个是我们的射雕英雄郭靖留给倚天屠龙年代的唯一财富,小说中,这笔财富在反元起义军中起到了很重要的作用。咱们不说新版的小说改动吧,就用经典版来说事。倚天剑里面是《九阴真经》,而屠龙刀里面是《武穆遗书》(最新版小说已经修改),单独来讲,倚天剑和屠龙刀都是利器,作为武器,十分锋利。紫衫龙王都说过,灭绝师太凭着倚天剑的锋利,战胜了她,所以她想用屠龙刀雪恨,这当然是紫衫龙王的一种自嘲了。之后还有张无忌决战光明顶时使用白眉鹰王的白虹剑与倚天剑对抗,白虹剑也是一柄罕见的利器了,不过还是玩完了,由此足见倚天剑的威力还是十分给力的!在倚天剑面前,其他的武器立刻成为了神马。但是很多人都不知道了藏在倚天剑的真正价值。
我们往往都是从即时反映出来的效应来发现一个东西的价值,就好比刚刚迈入软件领域的很多侠客们。记得当时笔者在校时身边就一直有很多消息宣传Java如何如何好,J2EE(当时叫J2EE)如何如何,然后很多人就去学习Java,报各种的培训班,买来很多入门书籍。后来又有一股风飘来说微软的.NET如何如何强悍,比Java优越在哪里哪里。后来又兴起PHP技术,这就是有名的3P争霸战,(当然ASP.NET和JavaEE不光是asp与jsp)。各个论坛的帖子争论也一直喋喋不休,甚至出现技术、公司、人身攻击。不仅仅是不同语言之间的斗争,就连相同语言内部也有这样的斗争,比如,Struts2、JSF、Spring Web MVC之间的竞争,为此笔者来写了一篇《Struts2与JSF的瑜亮之争》,当时没有涉及到Spring Web MVC,实事求是,因为笔者当时确实没接触过Spring Web MVC。再比如Hibernate与MyBatis(原先的IBatis)的争论,ExtJS与Jquery的争霸,咱们是用Tomcat还是Jetty,数据库到底用哪个产品啊等等。就连相同语言内部,相似功能软件之间都有这么多的争论。这对于软件使用者来说其实是好事,有争论,有非议,有批评,才更有生命力。
其实还是那句话,真正的高手其实不在乎是用什么技术,甚至不在乎使用什么语言实现软件。高手真正在乎的是如何将一个技术或者说语言发挥到极致,甚至着眼于大局,将各种技术提取优点,用它的优点,整合其他技术规避他的缺点。就比如说很多做电信行业系统的,做业务处理的时候仅仅将Java作为整个大系统的逻辑控制层,Java仅仅接收请求,负责一个业务分发的角色,而底层的核心业务的处理则采用中间件整合C++代码来完成整个业务的处理。
就像之后张无忌与赵敏手下的剑客——阿大进行剑术比试,阿大手中使用的就是之前咱们提到的——很给力的倚天剑啊,而张无忌手中拿的仅仅是一把木剑,张无忌规避倚天剑的锋芒,使用阴柔的太极剑法将阿大打败。张无忌那时候已经算是个顶级高手了,他知道如何让倚天剑发挥不了它的长处,他也知道如何利用太极剑法发挥自己手中这把小小木剑的长处。张无忌就是无论使用何种兵器对他来说都差不多,只能说如果是绝世兵器在他手中发挥得更加淋漓尽致罢了。关键还是他的修为在那里,基本上还是以武学修为做为胜负的关键因素。
从中也可以看出所谓编程语言、技术、中间件产品不过是实现某种商业目的的一种手段罢了,所有的技术幕后都是一桩桩充满铜臭味的商业运作罢了。当时年轻的笔者怀着崇敬的心等待着Java阵营将.NET阵营彻底打败,以证明当初自己的选择是正确的,谁能想到最先趴下缴械的居然就是咱们崇敬的Sun啊。被甲骨文收购后,逼迫JavaEye改名为ITEye,现在又向Google索取巨额的Java侵权费用,不得不让我们感叹,何时Java也充满了这种商业的铜臭味,没办法咱们只能接受,一切一切的技术推进都是商业巨头们的运作结果。所以技术仅仅是个手段工具罢了,如果将它作为一种崇拜对象,成为自己生命不可或缺的部分。哥们儿,姐们儿,随着时间的推移,可能会让你越来越心寒哦。
后来倚天剑和屠龙刀终于再次汇合,才将刀剑真正的价值体现出来,原来将他们的优点结合在一起,互相利用各自的锋芒,将潜藏里面的东东挖掘出来。我们再为客户做软件技术解决方案的时候也可以将不同技术、不同语言的优势融合进来,形成一个改造后的大融合系统,取长补短,将软件发挥到最极致的功能,有点瑕疵不怕,怕的是不能抛弃门户之见的执着,依然死守原有规矩,不肯也不敢进行技术思想上的大解放。
当然,可以理解一点的就是,可能对于比较感情化的朋友来说,对于第一门认真学习,并花了大把时间的技术抱有很深的感情,对于第一门认真下功夫学习的技术语言,程序员一般都有一种微妙的感情在里面。这种感情笔者称之为“技术的初恋”,“初恋”嘛,就意味着当事人想让这段感情更加持久,不希望,当然更不允许任何人对当事人的“初恋”对象说一点点不好,所以大家一般从各大论坛上看到的各种技术、语言之间的口水战都发生在刚刚进入某技术领域的朋友。这些朋友也不允许其他技术阵营的人来对自己的“技术初恋”进行所谓的评头论足,指指点点。这种感情是纯洁的,是高尚的。将心比心,大家都是从菜鸟一步步成长起来的,都是从不懂的时候慢慢找资源学习熬过来的。在学习的过程中大家或多或少有一些收获和小成就,这就好比这个“初恋情人”给你这个当事人一点点爱情的奖励似的。也有朋友将这种爱情“奖励”的过程和经验分享出来,就是我们看到很多技术Blog文章,无论怎样,都请看文章的朋友们尊重那些你们眼中的“菜鸟”,不要认为自己多么多么NB就随意践踏那些淳朴程序员们的感情,践踏这些人的劳动成果是十分残忍的事情,就和践踏别人的初恋一样。看得博客文章内容简单,你可以什么都不说,也可以对那些作者提一些自己的建议,给新人一些建设性的意见,分享一些自己的经历。开口就骂:什么“作者低能”、“这种问题都问”的人,这样恐怕不太好吧,因为请各位老鸟们记住,你也经历过那个阶段的人,只不过你比人家多长了几岁,比人家接触知识点的早一些罢了,没什么值得自豪和炫耀的。除非你投胎的时候没洗去前世的记忆,恰巧你前世又是个拿过诺贝尔奖的高手甚至是爱因斯坦转世,那另当别论,你一出生你就成功了……对新人,多一些鼓励,善莫大焉。
我只是要说,请各位纯情的程序员朋友们记住一首歌——《当爱已成往事》:只要有爱就有痛啊,有一天你会知道,人生没有它(当然指具体的技术实现或者编程语言了)并不会有什么不同。选择还是很多的,只要能顺应商业潮流,为客户解决问题,发挥自己所学东西的最大优势就好喽。无论您是刀狂还是剑痴,重要的是刀剑合一,无刀无剑,一枚绣花针也能挥洒自如,呵呵~
http://suhuanzheng7784877.iteye.com/blog/1115472
很多人认为我们程序员不会做人,至少认为我们在人际交际方面缺乏技巧。程序员一遇到人际方面的事情就发憷,和售前人员,领导,甚至是客户都不太会沟通,遇到非技术的事情,就慌了,往往作出的决定比较茫断!
反正笔者确确实实觉得是有这样的问题,不说大的方面吧,我们就看看在工作中如何做顺水人情。可能标题有点大,一般职场说不上什么恩德吧,但是总可以说得上是顺水人情吧。各位看过金庸小说的朋友觉得谁值得我们学习呢?笔者觉得做人际交流的典范就是韦小宝,他有很多值得我们学习的地方,以后比这还要说他,咱们这次只单看看他如何做顺水人情,施恩给别人的。从中我们以后遇到非技术事情要处理的时候是不是脑中多了一个Java监视器类,触发事件后时刻提醒我们:“如果是韦小宝,他会怎么做?他该怎么做?他会如何说话?”。
当康熙让韦小宝剿灭王屋山的时候,韦小宝特地找了个叫赵良栋的,第一,韦小宝不会打仗,即便是小规模的剿匪,他自己都说:“老子不是干这种事的人才”,但是康熙下令让他就得这么做。第二,他不想让第N个小老婆——曾柔受伤害,要不就亏大了。第三,王屋山和天地会(当然放到现在咱们叫反和谐社会的不法组织)有关联,如果让陈近南知道后果肯定会让韦小宝不爽。第四,王屋山本身和韦小宝本人的交情也不错,也算是以前建立起的人脉财富吧,如果干掉,那么意味着以前的人脉关系维系全他娘玩完了。韦小宝若是严格执行君令,那么,有三个损失,只有一个好处,不违背康熙的命令。好的,我们的韦爵爷才不会像Java虚拟机那么忠实,编译后class文件是什么样的,我们的编译器就怎么执行。韦小宝则更像是康熙的实现类,康熙是接口,下个命令(接口方法),很抽象,他只关心结果,不关心过程,那么韦小宝如何实现这个接口,完全由韦小宝负责具体的细节,之后康熙的目的达到了,王屋山这个匪政权确实没了,那康熙的目的就达到了(尽管康熙在实现类——韦小宝身边安装了监听器),韦小宝的人情也做足了,第一,执行命令的同时,收编曾柔小老婆,曾柔感觉韦小宝好很伟,很强大,既挽救了师兄弟,又救了自己,真帅啊,美人到手。第二,对于天地会那边也有交代,陈近南知道后只会向其他兄弟夸耀我这个徒弟有雄略,有义气,有侠气。只会更喜欢这个徒弟,后来陈近南也说了一句,“以后天地会就要靠你了”,完全有继承他衣钵的意思,师父面子上也有光,在反政府武装组织天地会中也更有声望,声誉,名望到手。第三,王屋山其他兄弟也是对他感激涕零,敢于冒死命,因为那时候反政府组织内部最讲究的就是个义气,大多数江湖反政府组织大多穷苦出身,都是哥们儿义气当先,咱们看《书剑恩仇录》中的红花会也能看得出来,那么这样韦小宝人心到手。
韦小宝这么做真叫一个高明,也是他小小年纪做事如此周详,实属难得,也许是官场的历练让他做什么事情都先权衡以下各方利弊,当然对于自己的利益权重是最大的。之后韦小宝还有很多案例都说明韦小宝做人情做到十足~不但自己的目的达到了,还为其他人某了本身不应该有的福利待遇。带领施琅炮轰神龙岛,不仅将自己最忌惮的神龙教弄得七零八落,还让一个军事人才施琅有了一展才华的机会,后来施琅也是为了还他人情,让韦小宝在通吃岛住了一段悠闲的日子。索额图、明珠、康亲王这些在政治官场上老油条也感怀韦小宝在官场上的照顾人情,当然这些人出于政治目的,但是韦小宝对官场政治着实不怎么看重,索性做顺水人情,他的潜台词就是:“你们这些人不必揣摩圣意了,不必为难。我和皇帝做个双簧戏,你们立刻知道皇上的意思了~”,主战派和主和派心里就有底了。
其实说了那么多,都是想提醒自己,假如这种非技术性质的事情到自己头上后该怎么办?我觉得第一还是先想到与此事相关的人,最好有个关系——角色映射图,一个事情的处理关系到哪些人,需要明确下来。第二就是一个事情该怎么做,有几条途径。如果只有想来想去一条路,不妨先将此事放放,转一下注意力,再回头来看看是否还有别的路可走(一般情况下应该会有另外的途径吧)。第三,就是根据每一条途径去看每一个关系人的受益和损失程度。根据每个人在此事件中担当的角色,最后找出一个最好的解决事情的途径出来。虽然很多读者都会说,照你这么做事,太麻烦了吧,不必吧,用得着吗?值得吗?笔者则认为哪怕是多么小的事情,这么做也不为过,虽然麻烦点,但是细节决定成败啊,厚人薄己得人心啊。
还有就是很多人有疑问,就是按照你这么做事情,过于麻烦,如果遇到即使就要做出决策的突发事情,比如就是上司、客户突然来了电话,那该怎么办?第一,遇到这种及时解决的问题,那么最起码“现在没有想好,需要和XXX商量商量才能决定”;“哦,不好意思,我现在电话说不清楚,环境比较杂乱”,稍后给您打过去;“容我想一想”这种缓兵之计的话总能说出口吧。总比那种脑子一热就做出决策的冲动选择要好得多吧。当然了,如果突发事件对事件关系的每个当事人都有好处,对于你来说又是十分有把握轻而易举就能完成,你当然就能及时应答下来了。这不是冲动,是自信!如果说遇到的客户,就必须要你当时做决策,笔者感觉利用以上方法经验比较多了,脑中的反应也会逐渐变快,作出的决策可能也比没有联想的决策付出的代价要小一些。说起来这个有点像咱们之前系统访问负载均衡中的最优化策略了。这样,事件的关系人也会对你怀有一种感怀的心,人情也做足了。你自己从中得到了下属的信任,上司的认可,明智客户的奖励(哪怕是口头上的夸赞呢,也行了),你自己从中也是得到了锻炼,经验,做事也越来越老练。这样各位朋友人生的路也会变得越来越宽,其他人也愿意和您这样能为他们利益着想的人公事。
执着能杀人,在执着杀死你之前,请将它杀死。看过《天龙八部》的人一定记得那经典的一段珍珑棋局吧。我们就从这盘棋局说起。逍遥掌门让苏星河布置珍珑棋局是为了替逍遥派清理门户,继承自己的衣钵。参与这场棋局的主要有四人(范百龄就算了吧),段誉、慕容复、段延庆、虚竹。其中,虚竹不怎么会下棋,假借段延庆的传音入耳和他的棋艺通过了面试,最终获得逍遥掌门的绝世内功。我们就看慕容复的执着是如何差点要他命的。慕容复胸怀大志,参与这个棋局其实目的也比较简单,扩大自己声望,结交江湖名士,为自己日后富国铺路。但是慕容复对于胜败结果太过于执着了,对胜负总是看得比谁都重要,就好像慕容复的心中就不允许失败一样,对武功的高低执着、对棋局的胜败执着,对复国的事业依然执着。看看他与段誉的区别,这个珍珑棋局变幻百端,因人而施,爱财者因贪失误,易怒者由愤坏事。段誉之前之所以败,是因为爱心太重,不肯弃子;慕容复之失,由于执着权势,虽然勇于弃子,却说什么也不肯失势。
棋局的胜败都看得如此重要,那对于其他方面的胜负就不必说了,在少林寺因为败于段誉的六脉神剑而羞愧得想自杀,这难道是一个想问鼎中原的未来复国君王应该做的事情?
过分的执着往往意味着急功近利,萧远山执着于他的报仇,却从没想到他的亲生儿子会因为他的滥杀而被整个武林误会,成为武林的公敌,才会有后来的少林寺混战。慕容复的父亲——慕容博也因为执着于他的复国之路,造成了武林的恩恩怨怨。鸠摩智执着于他的武功,功名,到最后留给后人的却不是他的独门武功,而是他的传教经典和佛法,直至今日给我们现代人,留下的究竟是丰碑还是悲风呢。段誉执着于王语嫣的痴情,其间路途也是坎坷万分,幸运的是金庸给了他一个美满的结局,换到真实世界恐怕就不那么幸运了。萧远山与慕容博到后来他们在少林遇到了无名曾,在无名曾的安排下先让萧远山看着慕容博死去,之后在自己动手让萧远山假死。等两个人醒来后,问他们:“你二人由生到死、由死到生的走了一遍,心中可还有甚么放不下?倘若适才就此死了,还有甚么兴复大燕、报复妻仇的念头?”。二人大彻大悟,终于不再执着于过去的恩恩怨怨,功名富贵,专心研究佛法,参禅。实际上无名曾杀死的不仅仅是过去的两个“大恶人”,更深的一层实际是借无名曾之手杀死了他们自己的执着。
不说《天龙八部》,比如《连城诀》中尔虞我诈的三个师兄弟;《射雕英雄传》的欧阳锋;《鹿鼎记》的陈近南;《笑傲江湖》的岳不群、左冷禅、任我行、林平之;《倚天屠龙记》的谢逊、成昆;《碧血剑》中的金蛇郎君……多少人因为过分的执着而掩盖了本应该光彩的人生?
那我们呢?我们在竞争激烈、追名逐利的今天是否也因为过分的执着而弯曲了我们原有的人生轨迹?虽然说在现代我们不会因为过于执着而献出宝贵的生命,但是回想一下,我们是不是也因为我们过分的执着于某些事情而错过了很多不应该错过的东西呢?或者还可以说,我们是不是因为执着导致我们原有的水平、能力、智慧、知识都打了折扣。张无忌在学《乾坤大挪移》的时候到最后有几句话不是很明白,如果他执着于弄明白,估计结果和阳顶天一样。当令狐冲和任盈盈同处大车之中,徜徉于青纱帐外的大路上时,对岳灵珊痴情的执着终于消失 了,他也得到心灵上的解脱。
一个项目苦战数月未有结果、一段程序苦苦调试很长时间问题依旧,这个时候我们是否可以先放一放,看看外面的风景,泡一杯茶,慢慢品味苦中有甜的滋味,过后调试程序换一种思路,项目管理大胆尝试一下新的管理方式,没准可以发现新的天地。如果一味的朝一个方向死走,一条道走到底,幸运的人可以走出去,祝贺你当时相对地选对了路,不幸运的人就会钻牛角尖了。搞研发的适时可能会去做manager的职位,也可以去搞product售前等等,就是因为很多开发人员自身很清高,不愿意,甚至说是不屑做人际、关系上的事情。那么你对人家不屑,人家对你也不屑。有时候咱们是要放下那些执念,勇于改变以前的想法和初衷。因为有可能当初的理想、志向、目标是不成熟的,而我们还依然一直执着下去……
当然笔者年龄、阅历、经验等等均有限,仅仅是将我个人的经历和心得分享出来,绝对不是站在一个教育者的高姿态来教育某某某,绝无此意。仅仅是下次看到自己曾经走过的弯路时多一份沉重,时刻提醒自己罢了,“执着会杀人,当它没有杀死你的时候趁早消灭它”。放弃执着,也许你会看到另一片你从未看到过的天地在迎接你。
http://suhuanzheng7784877.iteye.com/blog/1096125
用电影《东邪西毒》的台词作为开头,“任何人都可以狠毒,只要你尝试过什么是嫉妒,我不在乎别人怎么看我,我只是不想别人比我更开心。”。嫉妒心是人人都会有的,除非真正看破红尘的人,不在此列,哀莫大于心死,心死的人再无好胜之心,可以说嫉妒心也远离他而去,不过我们这些打工的就算了,肯定是个凡人吧。
《笑傲江湖》里面的林平之,刚开始是一个诚实、有正义感的年轻人。金庸刚开始把他写得也是有光彩的,就在福州小店,为假扮店老板的劳得诺和岳灵珊打抱不平,就能看出从骨子里他还是有正义感的。惨遭青城派灭门,林平之沿路到各个镖局分局去投奔。那个时候他也完全摒弃了富二代的架子,忍辱负重,惨遭众多磨难,终于拜了岳不群为师。可以说在《笑傲江湖》前面的章节,作者对林平之绝对是赞许的。试想一个富二代遭遇了如此重大的家庭变故,还能放下架子,忍辱负重。宁愿去给人家当小徒弟,也不去投靠外公。足可以看出,林平之前期有志气、有骨气、有侠气。而且林平之说过他只想靠一己之力报仇雪恨,绝不愿意假手其他人。如此光明磊落,说实话,没有看过笑傲江湖的读者,前期大家一定以为这应该就是《笑傲》的主角了吧。事宜愿为,林平之最后的下场大家是知道的,很多读者对他都是惋惜的态度。笔者对林平之的态度是:对他的遭遇感到同情,对他的毅力表示敬佩,对他的残忍表示愤怒。对于他的结果,我认为原因很多,但究其个人原因,我认为主要是因为林平之的嫉妒心。有人问,他嫉妒谁?毁了他的是余沧海、是木驼峰、是岳不群。林平之应该恨得是他们,何来嫉妒之心。是的!毁了福威镖局的是他们。林平之也是恨他们,但是他嫉妒的不是这些中年人,他嫉妒的是比他大不了几岁的令狐冲。笔者之前也确实有疑问,为何林平之对令狐冲如此恨得咬牙切齿。后来明白是嫉妒。
令狐冲出身不如林平之,就一个孤儿,没有什么家庭背景,和林平之家世相比甚远;令狐冲长相不如林平之俊俏,书中有描写,令狐冲是一个高大的男子,林平之是福建那边的人,长相比较像母亲,俊俏得很,真得是现在很多女孩子心目中的奶油小生;林平之教育良好,见过大世面,令狐冲就是一个穷小子,更谈不上什么高等文化了,识字,读书已经不错了,林平之的涵养在令狐冲之上。那么他到底有什么理由嫉妒他呢。正是因为以上原因,林平之就觉得自己有优越感,进入华山派后处处与令狐冲进行比较,从令狐冲初学独孤九剑打败封不平、成不忧后。林平之就开始不爽这个大师兄了,他嫉恨得不是别的,就是他的际遇不如令狐冲。之后祖千秋设酒,问令狐冲和其他华山弟子敢不敢喝就,只有林平之一个人敢站出来说:“有什么不敢的!”实际上暗中已经和大师兄较上了劲。更兼令狐冲剑法诡异,林平之不得不怀疑他的剑法从哪儿来的?虽然事后证明不是《辟邪剑谱》,但是他总是先入为主,认为令狐冲欠了他什么似的。
嫉妒心蒙蔽了他的双眼和原有的良知!使他疯狂的将一切罪过转移给了令狐冲。林平之杀了余沧海后其实仇人只剩下岳不群,但是林平之一直认为令狐冲才是他最大的对手,欲除之而后快。最后把真正的BOSS级的敌人忽略了。如果他摒弃一颗嫉妒心,目标会更明确,如果他学完辟邪剑法后继续韬光养晦,先故意输给余沧海(反正学完了辟邪剑法自保肯定没问题),之后回到岳不群身边伺机先干掉这个BOSS级人物,之后再除掉余沧海,估计林平之获得的东西会更多,最好的结果是岳不群辛苦半生的成果给了林平之做了嫁衣(哦,也许说嫁妆更合适)。
说了这么多咱们会过来看看自己的人生是不是也有过类似的现象,几个比较好的同事。刚开始大家都是怀着一颗纯真的心交往的。大家都是肝胆相照,有说有笑,像兄弟一样。尤其是那些背井离乡的同志们更有一种身在异乡,互相依赖,互相扶持的感情在里面。久而久之,因为公司的各种原因吧,将你不错的同事、同学、朋友的职位调了一级,工资翻了一倍。或者另一个同事跳槽了,各种待遇在你之上。那么你会不会心里有一丝不爽呢~呵呵,大家都不必否认,多多少少都有一点,这是人性,回避不了。在咱们这样一个和谐社会下的人们,正常思考的人们多多少少都有点嫉妒,嫉妒程度多少因人而异。我觉得有一点嫉妒心很正常,而且也是好事,它能刺激你进步,在自己的心里就会给自己一种压力:“他都到了那个程度,那我呢?大家是同一起点~”。积极的人,会因为这细微的嫉妒,自强、奋进、朝着自己的新目标前进。但是如果嫉妒心过重,就像林平之一样,迁怒旁人。典型的特点就是,坐在办公室里整天抱怨,怨天尤人。怨公司为何不给自己这样的待遇、怨机遇为何不降临自己头上、怨社会为何如此不公平、怨同事如此不和自己交心如此一来就会蒙蔽了自己的双眼,本来自己的规划也会因为种种嫉妒心引起的浮躁、不满而终究告吹。结果就是到了最后一无所有,连当初最好的朋友,这笔财富也都失去了。其实我们静下心来想想回头看看,是不是那个晋级的同事真的比咱强呢,我觉得肯定是有的。否则用人单位的领导应该不会傻到看不出来,这个地方请大家不要怀有偏激心理,以一种第三者旁观的身份审视你和你的朋友,看看他比你强在哪里,有时候嫉妒心蒙蔽了这个客观地角色,而很多时候从主观情感出发。相信以第三者的身份审视你们之间的差别,一定能找到你不足的地方。OK,这就达到目的了,这样我们找找平衡,就知道确实他比我高一筹,我应该吸取教训,自己还需努力啊。这样不仅仅是事业上没有什么损失,您的人生也进入了一个“吸星大法”的境界。
还有一点就是比较敏感的薪资问题,经常看到论坛上牛人晒自己的工资。更有39k女,43k男等贴盛行一时。其实套用一句网络上的话“网管上辈子都是折翼的天使,搞软件开发的上辈子都是身怀绝技的乞丐”。积极地一面是觉得很有希望,上面有牛人,看来还是有希望的,到不了人家那种牛级别的,退一步也能做小牛或者小小牛级别的也行啊。消极的一面则是很多人就产生了嫉妒心理,之后种种的谩骂、诋毁、诬陷等等动作一一招呼来了。嫉妒是一堵墙,他蒙蔽了你的良知、你的目标、你的修养。事业高潮时常想着居安思危,低调做人处事,事业低谷时,咱们就以一种平常心处事,但是学习的步伐不能停下,不能因为一时的状态影响了自己本该实现的目标。也许林平之心态平和一点,他的下场会更好一些。林平之的命运掌握在金老先生的笔下,只可惜金庸老先生没有给他这个机会,但是我们呢~我们的命运掌握在我们自己手中。我们的人生是我们自己给自己机会!摒弃我们强烈的嫉妒心,保留一点点可以起到积极的作用,太大了,我们就会失控。最终毁于我们自己创造出来的嫉妒心。
搞软件就像金庸小说中的侠客们闯荡江湖,快意恩仇,你死我活。有人问,职场真的就这么血雨腥风吗?职场就是江湖,用一句广播语说:“什么是江湖?有云的地方就有天下,有人的地方就有江湖。”出世前就好比闯荡江湖之前,对于大学生来说就是四年的大学学习生涯,对于专科生来说就是三年修炼。不过这也不是绝对,有人在高中,甚至是初中就接触了软件开发,不过国内比较少,我们以大多数人的情况说事儿。
我们就用射雕三部曲的主人公作比喻,郭靖好比受过传统高等教育的人士,杨过嘛~因为第一所大学不好,我们把他当做又回家重考的大学生吧,至于张无忌,因为没有明确的师门,我们就当做不屑于参加高考的90后的各位“爷“们。
郭靖为人忠厚,天资不好,学什么东西都比较慢。他的老师们都一度嫌弃他傻,学东西慢,没希望,但是呢,郭靖最大的优点就是有恒心、有毅力。笨不要紧,就怕停下脚步不前进。之后他的启蒙老师马钰就说过其实是老师教学方式有问题,学生学得方法也有问题。后来,马钰以循序渐进的方式对郭靖进行了疏导,郭靖慢慢开窍,后来有了更好的机遇,终成一代大侠。在学生时代能够赶上一个好老师和一个愿意下苦工学的学生都挺不容易的,我们作为学生无论有再好的天资也要下苦工,否则真的是浪费了自己的天资。如果没有一个好的老师进行疏导,没关系,互联网就是我们的老师,当然这需要我们进行取其精华去其糟粕。只要是想学的东西,想办法,下苦工,都是可以学到的。郭靖的例子告诉我们,是金子遇到一个机遇后总会发光的,哪怕刚起步的时候有各种各样的问题,只要找对了方式,再以良好的性格坚持下去,相信结果都会不错。我们搞软件开发也是一样的,遇到某些新技术的时候最好先自己看看怎么学习,切入点在哪里,刚开始研究一下学习一个新技术的方法其实是有效地,比如逼着觉得学习Swing和学习Hibernate的方法就不一样。虽然说都是Java领域的技术,Swing更偏向于UI的显示与事件监听机制的运用。那么笔者认为Swing学习方式就是掌握组件的使用场景和事件触发原理这些核心的即可,等需要构建不同需求的界面时我们按照组建模块的方式构建就可以了。Hibernate则更侧重于持久层对象的状态、持久层接口调用完成CRUD、优化使用缓存等等,所以根据不同业务设计实体配合研究Hibernate的源代码是最好的学习途径。两种技术侧重点不一样,因此不能以同一种方式和经验硬套。
杨过其实是一个天资甚高的家伙,从他后期能够自创武功就能看出来。第一个从师地方他看不上,选择了离开,之后为了争口气,在第二家学艺分外认真。出世前,杨过对武功就很感兴趣,再加上他的聪明、认真、又好学,想不成为高手都难。后来初现武林,也验证了这一点,黄药师就说他的境界比同等境界年轻时的黄药师提前了10年。这也验证了一个道理,长江后浪推前浪,不服老不行。尤其是IT届,新的东西一轮一轮涌过来,而我们的年龄一天天增长,精力一天不如一天,精神集中的能力也有所下降,所以得服老。杨过的经历告诉我们学东西还是要以兴趣为主,没有兴趣,学东西其实是应付别人,自己都不知道自己想要什么,对自己都敷衍了事的人,可能有太大进步吗?还有就是学生时代的我们可以任性,因为有爸爸妈妈老师宠着我们,但是一旦进入社会,还是要有所收敛,否则祸不远矣。人在江湖,什么事情都有可能发生。如果不收敛我们的个性,很难在社会,也就是江湖立足,尤其是软件开发,一山还有一山高在这个领域体现得淋漓尽致,让很多当初自以为高手的人唏嘘不已。没关系,收敛狂傲之心,兼收并蓄。像EJB学习,吸收Spring的优点,自成EJB3.0体系。让众多拥护者不至于失望。
张无忌其实并没有门派,他的父亲也没正经教过张无忌什么武功,只不过挂了一个武当派底子的虚名而已。再加上命运多舛,孩童时期就看淡了生死,这是同龄时期的郭靖、杨过没有经历过的。所以说在出世前,张无忌几乎没有学过什么像样的武功,连自保恐怕都有困难,历经种种磨难后,终于得上天的恩赐,赋予《九阳神功》终成一代“隐侠“。以张无忌作为我们学生时代出世前的例子恐怕不适合。李刚老师曾说过,武侠小说中主人公掉到一个山洞里,遇到世外高人传授武功在现实生活中是不可能的。我要说的是,人生的起点可以很低,但是我们不应该自己贬低自己,自我放弃。你看张无忌中了玄冥神掌,他可有一刻想到要自杀,他总是积极地面对人生,上天给了我什么,我就享受什么。人生的起点不代表人一生总是在这个起点,只能说自己绝不能放弃自己,认为就这样吧。我的人生就这么样得了。人生总会有机遇的,机遇总会出现的,不放弃的人,抓住了机遇,OK,人生的质变就发生了。往往在低谷期不放弃的人,他的路一般都是走得很远。张无忌后来的际遇,也是和他小时候看透人生冷暖、世代炎凉相关的。所以他格外珍惜对他好的人,当然也老受女人的骗(张无忌他妈的话全忘了,估计殷素素九泉之下得说:“这小子,他妈的,把他妈的临终遗言全忘了”)。无论学习何种技术,都是一条长远的路,任重而道远,不应该因为起点很低就一直持着消极的态度。搞软件开发起薪低,告诉自己:“没关系,只要坚持学习,增长经验,慢慢会质变的。”英雄不问出身,学历已经逐渐一年年弱化了。
三位主人公的成功多多少少都和他们出世前的经历相关。这里所谓的出世,实际上就是指我们毕业后刚踏入社会工作。个人认为,从业前的修炼因人而异,有人适合从一而终,找对了一个方向就一直往其更深层发展,直到摸透原理。代表人物就是郭靖,中年郭靖对敌一般都是降龙十八掌了,早年的那些花哨招式都不用了。有些人适合根据原有技术,挖掘优点进行改革创新,代表人物就是杨过的黯然销魂掌了,集各家之大成,配合自己的的心情,随心而发,随意而至。还有些同学喜欢摸着石头过河,公司让我做什么我就学什么,典型人物张无忌。张无忌学九阳神功是命运驱使,不学就得死!学乾坤大挪移是小昭驱使!学太极拳、太极剑也是形势所迫!硬着头皮赶上去。这就是工作需要什么,我就学什么。
笔者对杨过的态度是仰望,能在原有基础上有创新的东西,就像现在很多的开源项目不都是利用已有的资源解决现有问题吗。集大成而创新,说实话在中国的项目中不是一件容易的事。
对郭靖的态度是敬畏,能做到郭靖这种将一个东西用精、用细、用到原理中去的人真的凤毛麟角。
对张无忌的态度则是佩服,形势所迫,压力来了,硬着头皮也得顶上去。锻炼能力的时候到了。
出事前的各种修炼其实是锻炼自己的思维方式和学习习惯,良好的思维方式加上正确的学习习惯,就是出事前最大的财富。
出世前的各位同学们,你们想好你们要做什么样的侠客了吗?
http://avar.iteye.com/blog/163767
在做远程调试时,在windows系统和非windows系统下的配置,Tomcat中会有所差别,具体如下:
第一步、配置tomcat
一、在windows系统中:
打开%CATALINE_HOME%/bin下的文件catalina.bat,加入下面这行:
set CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8787
其中address=8787是没被使用的端口号。连接方式有两种,为dt_shmem和dt_socket,分别表示本机调试和远程调试。
二、在非windows系统中:
还需要把% CATALINE_HOME %/bin/startup.sh中的最后一行exec "$PRGDIR"/"$EXECUTABLE" start "$@" 中的start改成jpda start。由于默认的端口是8000,所以如果8000端口已有他用的话,还需在catalina.sh文件中设置:JPDA_ADDRESS=8787。
输入命令sh catalina.sh jpda start就可启动tomcat。
第二步、配置eclipse
在Eclipse中选择RunDebug,在弹出的对话框中右击Remote Java Application新建一个远程调试项,如下如所示:
在“Name”输入框中输入远程调试的名称,在“Project”中选择要调试的项目,在“Host”中输入需要远程调试项目的IP,也就是tomcat所在的IP,在“Port”中输入设置的端口号,比如上面设置的8787,然后钩选“Allow termination of remote VM”,点击“Apply”即可。
设置完后就可以开始调试了,大概分一下几步:
1、启动tomcat(远程),如在控制台输出“Listening for transport dt_socket at address: 8787”,即说明在tomcat中设置成功;
2、在本机设置断点,即在需要监视的代码行前双击就会出现一个小圆点;
3、进入上图界面,选择要调试的项,点击“Debug”即可进行远程调试;
4、当运行到设置了断点的代码行处即可看到如下图所示的浅绿条。
按键操作:
1、F5键与F6键均为单步调试,F5是进入本行代码中执行,F6是执行本行代码,跳到下一行;
2、F7是跳出函数;
3、F8是执行到最后。
当然,为了方便,可以新建一个批处理文件,假如取名为debug.bat,在这个文件中加入下面几行:
cd %CATALINE_HOME%/bin
set JPDA_ADDRESS=8787
set JPDA_TRANSPORT=dt_socket
set CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8787
startup
这样需要远程调试时,运行debug.bat即可;不需要远程调试时,还是运行startup.bat文件。
摘要: http://hain.iteye.com/blog/150875web.xml文件中配置<mime-mapping>下载文件类型TOMCAT在默认情况下下载.rar的文件是把文件当作text打开,以至于IE打开RAR文件为乱码,如果遇到这种情况时不必认为是浏览器的问题,大多数浏览器应该不会死皮赖脸地把二进制文件当作文本打开,一般都是服务器给什么浏览器就开什么.解决方法: &...
阅读全文