程序员shengfq

技术博客


  • Home

  • Archives

socket客户端监控案例

Posted on 2018-05-26

java socket 监控案例包含

1.客户端
 1.1 客户端心跳线程
 1.2 客户端消息发送线程
 1.3 客户端消息接收线程 
 1.4 客户端启动,关闭 方法
 1.5 接收处理器接口(心跳接收,消息接收)


2.服务端
    2.1 服务端链接看门口线程
    2.2 服务端接收线程
    2.3 服务端启动,关闭方法
    2.4 接受处理器接口(心跳接收,消息接收)

3.心跳包
    3.1 包含的时间戳和id字段

4.普通消息
    4.4 消息正文,消息类型

5.异常处理
    5.1套接字异常的处理办法


服务端

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151

package client;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;

/**
* Created by sheng on 17/12/4.
*/
public class Server {

/**
* 要处理客户端发来的对象,并返回一个对象,可实现该接口。
*/
public interface ObjectAction{
Object doAction(Object rev, Server server);
}

public final class DefaultObjectAction implements ObjectAction{
@Override
public Object doAction(Object rev,Server server) {
KeepAlive keepAlive=(KeepAlive)rev;
System.out.println("接收数据,心跳包处理:\t"+keepAlive.toString());
//心跳包解析
return keepAlive;
}
}


public class MsgObjectAction implements ObjectAction{
@Override
public Object doAction(Object obj, Server server) {
Message message=(Message)obj;
System.out.println("接收数据,消息包处理:\t"+message.toString());
// TODO 消息解析
return message;
}
}
public static void main(String[] args) {
int port = 65432;
Server server = new Server(port);
server.start();
}

private int port;
//线程运行状态控制
private volatile boolean running=false;
//接收超时时间
private long receiveTimeDelay=3000;
private ConcurrentHashMap<Class<Object>, ObjectAction> actionMapping = new ConcurrentHashMap<Class<Object>,ObjectAction>();
private Thread connWatchDog;
public Server(int port) {
this.port = port;
}

public void start(){
if(running){return;}
running=true;
connWatchDog = new Thread(new ConnWatchDog());
connWatchDog.start();
}

public void stop(){
if(running){running=false;}
if(connWatchDog!=null){connWatchDog.stop();}
}

public void addActionMap(Class<Object> cls,ObjectAction action){
actionMapping.put(cls, action);
}

class ConnWatchDog implements Runnable{
@Override
public void run(){
try {
System.out.println("server initial....");
ServerSocket ss = new ServerSocket(port,5);
while(running){
Socket s = ss.accept();
System.out.println("server accept one client ....");
new Thread(new ReceiveWatchDogs(s)).start();
}
} catch (IOException e) {
e.printStackTrace();
Server.this.stop();
}

}
}

class ReceiveWatchDogs implements Runnable{
Socket s;
boolean run=true;
long lastReceiveTime = System.currentTimeMillis();
KeepAlive lastKeepAlive;
public ReceiveWatchDogs(Socket s) {
this.s = s;
}
@Override
public void run() {
while(running && run){
if(System.currentTimeMillis()-lastReceiveTime>receiveTimeDelay){
overThis(lastKeepAlive);
}else{
try {
InputStream in = s.getInputStream();
if(in.available()>0){
ObjectInputStream ois = new ObjectInputStream(in);
Object obj = ois.readObject();
lastReceiveTime = System.currentTimeMillis();
System.out.println("server receive... :\t"+obj);
if(obj instanceof Message){
addActionMap((Class<Object>) obj.getClass(),new MsgObjectAction());
}else if(obj instanceof KeepAlive){
this.lastKeepAlive=(KeepAlive) obj;
addActionMap((Class<Object>) obj.getClass(),new DefaultObjectAction());
}

ObjectAction oa = actionMapping.get(obj.getClass());
oa = oa==null?new DefaultObjectAction():oa;
Object out = oa.doAction(obj,Server.this);
}else{
Thread.sleep(10);
}
} catch (Exception e) {
e.printStackTrace();
overThis(this.lastKeepAlive);
}
}
}
}

private void overThis(KeepAlive keepAlive) {
if(run){run=false;}
if(s!=null){
try {
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}System.out.println("client "+keepAlive.getId()+" 离线");
System.out.println("client connect over time...."+s.getRemoteSocketAddress());
}

}
}

客户端

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package client;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.UnknownHostException;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RunnableFuture;

/**
* Created by sheng on 17/12/13.
* 客户端可以发送心跳包/消息包,在 keepAliveDelay毫秒内未发送任何数据,则自动发送一个KeepAlive Object(心跳)给服务器.
*
*/
public class Client {
//sendObject 发送

//Message Object 消息

//keepAliveDelay 延时

//KeepAlive Object

//addActionMap


/**
* 处理服务端发回的对象,可实现该接口。
*/
public interface ObjectAction{
void doAction(Object obj,Client client);
}

public static final class DefaultObjectAction implements ObjectAction{
@Override
public void doAction(Object obj,Client client) {
KeepAlive alive=(KeepAlive) obj;
System.out.println("接收数据,心跳包处理:\t"+alive.toString());
}
}

public static class MsgObjectAction implements ObjectAction{
@Override
public void doAction(Object obj, Client client) {
Message message=(Message) obj;
System.out.println("接收数据,消息包处理:\t"+message.toString());
}
}

public static void main(String[] args) throws UnknownHostException, IOException {
String serverIp = "127.0.0.1";
int port = 65432;
Client client = new Client(serverIp,port);
KeepAlive keepAlive=new KeepAlive("2","2","2");
client.start(keepAlive);
}

private String serverIp;
private int port;
private Socket socket;
//连接状态
private boolean running=false;
//最后一次发送数据的时间
private long lastSendTime;

//用于保存接收消息对象类型及该类型消息处理的对象
private ConcurrentHashMap<Class, ObjectAction> actionMapping = new ConcurrentHashMap<Class,ObjectAction>();

public Client(String serverIp, int port) {
this.serverIp=serverIp;
this.port=port;
}

public void start(KeepAlive keepAlive) throws UnknownHostException, IOException {
if(running)return;
socket = new Socket(serverIp,port);
System.out.println("本地端口:"+socket.getLocalPort());
lastSendTime=System.currentTimeMillis();
running=true;
//保持长连接的线程,每隔2秒项服务器发一个一个保持连接的心跳消息
new Thread(new KeepAliveWatchDog(keepAlive)).start();
//接受消息的线程,处理消息
new Thread(new ReceiveWatchDog()).start();
//发送消息线程.
new Thread(new SendMsgPacket()).start();
}

public void stop(){
if(running)running=false;
}

/**
* 添加接收对象的处理对象。
* @param cls 待处理的对象,其所属的类。
* @param action 处理过程对象。
*/
public void addActionMap(Class<? extends Object> cls, ObjectAction action){
actionMapping.put(cls, action);
}

public void sendObject(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(obj);

System.out.println("client send..:\t"+obj);
oos.flush();
}

/**
* 心跳包发送
* */
class KeepAliveWatchDog implements Runnable{
KeepAlive keepAlive;
public KeepAliveWatchDog(KeepAlive keepAlive){
this.keepAlive=keepAlive;
}
long checkDelay = 10;
long keepAliveDelay = 2000;
@Override
public void run() {
while(running){
if(System.currentTimeMillis()-lastSendTime>keepAliveDelay){
try {
KeepAlive alive=this.keepAlive;
Client.this.sendObject(alive);
addActionMap(alive.getClass(),new DefaultObjectAction());
} catch (IOException e) {
e.printStackTrace();
Client.this.stop();
}
lastSendTime = System.currentTimeMillis();
}else{
try {
//线程休眠10毫秒
Thread.sleep(checkDelay);
} catch (InterruptedException e) {
e.printStackTrace();
Client.this.stop();
}
}
}
}
}

class SendMsgPacket implements Runnable{
boolean isrunning=true;
@Override
public void run() {
//接收控制台输入
Scanner input=new Scanner(System.in);
while(input.hasNext() && isrunning){
String str = input.next();
if(str.equals("cancel")){
isrunning=false;
Client.this.stop();
}
try {
Message msg = new Message(str, 0);
Client.this.sendObject(msg);
addActionMap(msg.getClass(),new MsgObjectAction());
}catch(IOException e){
e.printStackTrace();
Client.this.stop();
}

}

}
}

class ReceiveWatchDog implements Runnable{
@Override
public void run() {
while(running){
try {
InputStream in = socket.getInputStream();
if(in.available()>0){
ObjectInputStream ois = new ObjectInputStream(in);
Object obj = ois.readObject();
System.out.println("client receive ....:\t"+obj);
ObjectAction oa = actionMapping.get(obj.getClass());
oa = oa==null?new DefaultObjectAction():oa;
oa.doAction(obj, Client.this);
}else{
Thread.sleep(10);
}
} catch (Exception e) {
e.printStackTrace();
Client.this.stop();
}
}
}
}


}

心跳包

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package client;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
*
* 维持连接的消息对象(心跳对象)
*/
public class KeepAlive implements Serializable{
private String id;
private String ip;
private String timestamp=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
private String mac;

public KeepAlive(String id,String ip,String mac){
this.id=id;
this.ip=ip;
this.mac=mac;
}
private static final long serialVersionUID = -2813120366138988480L;
public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getIp() {
return ip;
}

public void setIp(String ip) {
this.ip = ip;
}

public String getMac() {
return mac;
}

public void setMac(String mac) {
this.mac = mac;
}

public String getTimestamp() {
return timestamp;
}
/**
* 覆盖该方法,仅用于测试使用。
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
StringBuffer sb=new StringBuffer(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
sb.append("||").append(this.id);
sb.append("||").append(this.ip);
sb.append("||").append(this.mac);
return sb.toString();
}

}

普通消息类型

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package client;

import java.io.Serializable;

/**
* Created by sheng on 17/12/14.
*/
public class Message implements Serializable {
private static final long serialVersionUID = -2813120366138988480L;
public Message(){}
public Message(String msg,Integer type){
this.msg=msg;
this.type=type;
}
private String msg;
private Integer type;

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public Integer getType() {
return type;
}

public void setType(Integer type) {
this.type = type;
}

@Override
public String toString() {
return "Message{" +
"msg='" + msg + '\'' +
", type=" + type +
'}';
}
}

异常处理

socket的异常往往是连接超时引起,如果socket丢失,则操作socket会引起IOException,只需要在异常处理中,将socket释放,将线程的run()结束就行了.

write by shengfq 17/12/13.

个人简介

Posted on 2018-05-24

  • 盛富强
  • java开发工程师
  • 联系电话:+86 166-8043-4083
  • 电子邮箱:shengfu.qiang@163.com
  • 个人博客:https://shengfq.github.io/
  • github:https://github.com/shengfq
  • 自我描述

    我是一名热爱分享,爱开源,爱交流的JAVA开发者.

    Hello World

    Posted on 2018-05-24

    Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

    Quick Start

    Create a new post

    1
    $ hexo new "My New Post"

    More info: Writing

    Run server

    1
    $ hexo server

    More info: Server

    Generate static files

    1
    $ hexo generate

    More info: Generating

    Deploy to remote sites

    1
    $ hexo deploy

    More info: Deployment

    Exception Hierarchies

    Posted on 2018-05-14

    异常的层级

    多catch块

    抛出条款

    设计异常的层级

    概要

    在java和c#中异常能够通过层级关系分类.这种关系一般是一个或多个异常继承自其他异常.在java中FileNotFoundException 就是IOException异常的子类.下面就是一自定义异常:
    public class MyException extends Exception{
    //constructors etc.
    }

    如你所见并没有其他内容.更高级的异常层级是你决定catch一个具体的异常,你就能自动的捕获到该异常所有子类发生时触发时点.换句话说,你能catch所有具体的异常只要你声明一个父类异常.在案例中FileNotFoundException,如果你捕获了IOException,你就能捕获到FileNotFoundException.

    ##多个catch块
    你可能知道了已经有一种多catch块的声明异常的方式,这在

    Catching Multiple Exceptions in Java 7

    Posted on 2018-05-14

    在java7中,在同一个catch块中是可以捕获到多个不同的异常的.这被称之为多次捕获.
    在java7之前你可能这样写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    try {

    // execute code that may throw 1 of the 3 exceptions below.

    } catch(SQLException e) {
    logger.log(e);

    } catch(IOException e) {
    logger.log(e);

    } catch(Exception e) {
    logger.severe(e);
    }

    你可以看到, SQLEXCEPTION AND IOEXCEPTIOn的处理方式相同,但是你需要些两个不同的catch块来处理,在java7中你可以使用这样的语法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    try {

    // execute code that may throw 1 of the 3 exceptions below.

    } catch(SQLException | IOException e) {
    logger.log(e);

    } catch(Exception e) {
    logger.severe(e);
    }

    注意:两个异常使用|分割开,声明的多个异常处理会通过同一个catch接收.

    ###Exception Hierarchies 异常的继承结构
    在java或c#中,异常被分类到继承结构体系,异常继承结构的有点是你可以使用一个特定的异常,你能自动捕获该异常下所有的子类异常.换个说法,你能捕获一个特定异常也能捕获该异常下面所有的子类异常.例如FileNotFoundException ,你能捕获IOException捕获,也能使用FileNotFoundException.

    多catch块
    你可能已经知道使用多catch块.这是在try…块发生多种异常情况下的处理.但是,多个捕获块,也可用于try块内的所有抛出的异常情况是相同的类型或类型的子类.

    1
    2
    3
    4
    5
    try{
    //call some methods that throw IOException's
    } catch (FileNotFoundException e){
    } catch (IOException e){
    }

    按照异常的继承结构,上述代码是FileNotFoundException先被捕获,如果是IOException则被第二个捕获.

    throws 语法
    如果一个方法能抛出一具体的异常A,或者该异常的子类,可以使用方法声明 抛出A或者A的子类

    1
    2
    3
    4
    5
    public void doSomething() throws IOException{
    }

    public void doSomething() throws IOException, FileNotFoundException{
    }

    设计异常的继承结构
    当设计一个API或者app时,为之设计一个异常体系结构是一个好主意.例如,Mr Persister(注:作者项目), 我们的持久层/ORM API的基础异常是 PersistenceException.这个基础异常能捕获和处理
    Mr.Persister抛出的所有异常.

    如果你需要更多粒度的异常抛出,例如你可能以为异常很难处理,需要增加一个子类异常到应用的基础异常库.可以选择的方法是要么捕获一个确切的异常要么捕获基础异常.在MrPersister项目中,我们增加了 ConnectionOpenException, QueryException, UpdateException, CommitException和ConnectionCloseException作为PersistenceException异常的子类.如果Mr Persister的用户想要处理ConnectionOpenException与QueryException区分开,他们可以捕获这些具体的异常使用具体的操作.如果不是,用户可以捕获PersistenceException处理所有异常.

    你能通过更多等级的异常体系结构来区分所有的异常类型,例如,你可以区分一个异常是因为url错误,还是由于数据库没有运行.这个案例中,你能创建两个异常DatabaseURLException和DatabaseNotRespondingException,他们都扩展自ConnectionOpenException.用户可以捕获这两种不同的异常.

    总结
    这章节我们了解了异常的体系结构通过创建子类.区分子类用来捕获和处理这些细分的异常,你也能创建细分的异常.

    ###Checked or Unchecked Exceptions?
    在java中有两种基础异常类型:Checked异常和unchecked异常(译者:可以类比为JVM需要强制执行异常处理/不需要强制执行).c#只有unchecked异常.他们的区别在于:
    1.checked 异常必须显式的捕获或传播通过 try-catch-finally异常处理.unchecked异常不必须.他们不需要显式的捕获.
    2.checked异常继承自java.lang.exception. unchecked exceptions 继承自 java.lang.RuntimeException.

    关于checked和unchecked有许多的争论,是否应该使用checked异常,我将通过许多争论提纲来说明,在我做之前,让我们先明确下:
    checked和unchecked异常都是功能等效的.如果无法的unchecked异常的情形那么对于checked异常同样也无能为力.

    不论你选择的是checked或unchecked,这都是一个个人或组织的习惯,跟功能好坏无关.

    一个简单案例
    在讨论这两种的优点和缺点时,我将先告诉你他们的不同用法,下面是抛出一个checked异常

    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
    26
    public void storeDataFromUrl(String url){
    try {
    String data = readDataFromUrl(url);
    } catch (BadUrlException e) {
    e.printStackTrace();
    }
    }

    public String readDataFromUrl(String url)
    throws BadUrlException{
    if(isUrlBad(url)){
    throw new BadUrlException("Bad URL: " + url);
    }

    String data = null;
    //read lots of data over HTTP and return
    //it as a String instance.

    return data;
    }

    public class BadUrlException extends Exception {
    public BadUrlException(String s) {
    super(s);
    }
    }

    storeDataFromUrl()方法有两种选择,要么捕获要么传播给访问栈

    1
    2
    3
    4
    5
    //传播给访问栈
    public void storeDataFromUrl(String url)
    throws BadUrlException{
    String data = readDataFromUrl(url);
    }

    现在,让我们看一下unchecked 异常的处理,首先让异常类继承 java.lang.RuntimeException

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class BadUrlException extends RuntimeException {
    public BadUrlException(String s) {
    super(s);
    }
    }

    public void storeDataFromUrl(String url){
    String data = readDataFromUrl(url);
    }

    public String readDataFromUrl(String url) {
    if(isUrlBad(url)){
    throw new BadUrlException("Bad URL: " + url);
    }

    String data = null;
    //read lots of data over HTTP and
    //return it as a String instance.

    return data;
    }

    storeDataFromUrl()方法可以选择捕获异常,但不是必须的了,也不是必须要传播.

    什么时候checked什么时候unchecked?

    我们看到了两种处理方式,让我们好好讨论下.
    许多java书籍建议你使用checked异常为所有 可恢复应用程序,使用unchecked异常处理应用程序不可恢复的场景.实际上大部分的应用都需要恢复包括空指针异常,无效参数异常或者其他unchecked异常. 行动/交易失败将中止,但应用程序必须活下去,准备下一步行动/事务服务。这通常是合法的关闭应用程序.例如,如果配置文件丢失了,应用程序不能做任何事情,这将导致程序关闭.

    我的建议是你要么使用checked异常要么使用unchecked异常.混合异常类型常常导致迷惑和不一致的使用.当然你应该务实一点,在具体情况具体分析.

    下面的列表是最常见的争论关于checked和unchecked异常.最常见的区别是pro-checked=con-unchecked,pro-unchecked=con-checked.因此
    1.高级的检查异常
    编译器强制捕获或传播检查异常是为了不让开发者忘记处理异常.
    2.非检查异常是编译器不强制开发者捕获和传播异常.
    3.高级非检查异常
    检查异常被传播到访问栈顶层方法,因为这些方法需要强制声明抛出他们声明的异常.
    4.当一个方法没有声明非检查异常,他们一旦出现异常将很难处理.
    5.检查异常的声明成为方法声明的一部分,增加一个或删除一个异常声明在需要变更方法声明时会很困难.

    争论一

    1
    2
    3
    4
    try{
    callMethodThatThrowsException();
    catch(Exception e){
    }

    上面这种情况,强制捕获或传播异常让开发者粗心的处理风险,只会让开发者忽略错误.

    争论二
    非检查异常很容易让开发者忘记异常处理,因为编译器不强制开发者处理.
    没有比粗心的异常处理更糟糕的,当强制进行异常处理的时候.
    在一个最近的大项目里,我们决定使用非检查异常.我个人的经验是,当使用非检查异常的方法趋向于抛出异常.我常常合理的有意识的考虑异常.不仅仅是检查下异常.我都会考虑一个方法的异常处理.

    许多标准java API方法没有声明任何可检查异常仍然可能抛出非检查异常例如空指针和无效参数.你的应用程序需要处理这些非检查异常.你可以考虑这些检查异常很容易忘记处理非检查异常的情况,因为他们不需要声明.

    argument3
    传播到访问栈顶层方法的可检查异常,因为方法需要声明抛出异常,所以在顶层的方法要声明的异常是一种底层异常,例如.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public long readNumberFromUrl(String url)
    throws BadUrlExceptions, BadNumberException{
    String data = readDataFromUrl(url);
    long number = convertData(data);
    return number;
    }

    private String readDataFromUrl(String url)
    throws BadUrlException {
    //throw BadUrlException if url is bad.
    //read data and return it.
    }

    private long convertData(String data)
    throws BadNumberException{
    //convert data to long.
    //throw BadNumberException if number isn't within valid range.
    }

    你能看到readNumberFromUrl()方法需要声明BadUrlException和BadNumberException,因为他的访问栈方法抛出了异常.想想,如果成千上万的类里面的顶级方法需要声明多少种异常.

    争论1:
    异常声明聚合通常发生在实际的应用程序中.通常开发者使用异常包装器代替,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public void readNumberFromUrl(String url)
    throws ApplicationException{
    try{
    String data = readDataFromUrl(url);
    long number = convertData(data);
    } catch (BadUrlException e){
    throw new ApplicationException(e);
    } catch (BadNumberException e){
    throw new ApplicationException(e);
    }
    }

    你能看到readNumberFromUrl()方法声明了ApplicationException.而异常BadUrlException和BadNumberException被捕获并包装到一个通用的ApplicationException.这就是异常包装避免异常声明聚合.我个人的意见是,如果你所做的是包装一个异常,而不是提供附加的信息,你为什么要包装他?try-catch块附加一个代码而不做其他.使得这些异常成为unchecked 异常更容易,下面是以unchecked版本:

    1
    2
    3
    4
    public void readNumberFromUrl(String url){
    String data = readDataFromUrl(url);
    long number = convertData(data);
    }

    包装一个unchecked异常如果你想.下面是一个包装版本,注意,方法签名并不声明抛出的异常类型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public void readNumberFromUrl(String url)
    try{
    String data = readDataFromUrl(url);
    long number = convertData(data);
    } catch (BadUrlException e){
    throw new ApplicationException(
    "Error reading number from URL", e);
    } catch (BadNumberException e){
    throw new ApplicationException(
    "Error reading number from URL", e);
    }

    另外一个常用技术是避免异常声明传播到访问栈而创建的一个基础异常声明类.所有的异常捕获都是该基础异常的子类.所有的方法抛出异常仅需要声明throw一个基础异常, 一个方法抛出基类的子类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public long readNumberFromUrl(String url)
    throws ApplicationException {
    String data = readDataFromUrl(url);
    long number = convertData(data);
    return number;
    }

    private String readDataFromUrl(String url)
    throws BadUrlException {
    //throw BadUrlException if url is bad.
    //read data and return it.
    }

    private long convertData(String data)
    throws BadNumberException{
    //convert data to long.
    //throw BadNumberException if number isn't within valid range.
    }


    public class ApplicationException extends Exception{ }
    public class BadNumberException extends ApplicationException{}
    public class BadUrlException extends ApplicationException{}

    注意子类异常也不再声明了 也不捕获和包装了,因为他们的超类已经将异常传播给访问栈了.
    我的意见跟异常包装一样:如果所有的方法都声明抛出基类异常,为什么不让基类成为unchecked异常 保存一些try-catch块,抛出一个基类.

    当一个方法不再声明非检查异常,但是他们可能抛出这种异常将会很难处理.不声明你就不知道什么异常会抛出.你也许不会知道如何处理,当然除了你已经通过代码处理他们那些可能抛出的异常.
    争论1:
    在大多数情况下你,对于异常的处理你除了显示打印错误信息给调用者,不能做其他,写一条日志,回滚事务.不管什么异常发生在很多情况下处理他们是用相同的方式.因为应用程序通常有一些中央和通用的异常处理代码.因此知道实际发生了什么可能抛出的异常就没那么重要了.

    总结
    我通常赞成检查异常但是最近我凯撒改变我的主意.个人喜欢Rod johnson,anders hejlsberg,joshua bloch和其他人让我重新思考真实的益处来自检查异常.最近我们尝试使用unchecked异常在大项目上,他们工作很好.异常处理集中在一部分类.这里我们必须做本地异常处理代替传播异常到主异常处理代码.但是这不是非常多.我们的代码可读性更高,没有try-catch快.在另一方面,这里有更少的catch-rethrow try-catch块在代码里使用检查异常.我将提醒所有使用unchecked异常的人,至少使用try.我说明下原因:
    1.非检查异常不需要杂乱的代码使用非必要的try-catch块.
    2.非检查异常不需要杂乱的方法头声明去传播异常.
    3.你很容易忘记处理非检查异常的论证是这不适合我的经验.
    4.非检查异常避免了版本升级的问题.

    Try-with-resources in Java 7

    Posted on 2018-05-14

    try..with..resources是java7的一种新的异常处理机制,能够很容易的使用try…catch块正确关闭资源回收.

    对于有资源回收管理的异常处理方式,老式学校的处理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    private static void printFile() throws IOException {
    InputStream input = null;

    try {
    input = new FileInputStream("file.txt");//1

    int data = input.read();//2
    while(data != -1){
    System.out.print((char) data);
    data = input.read();//3
    }
    } finally {
    if(input != null){
    input.close();//4
    }
    }
    }

    有4处地方容易发生异常,你可以看到,三处在try块,1处在finally块.finally块总是会执行,不管try块是否有异常抛出,这意味着, inputstream是会关闭的或者说试图关闭.
    inputstream的close() 方法意味着也会抛出异常,如果关闭失败了. 想象一下异常从try块跑出来,finally块会执行.当异常冲finally块抛出的时候,这个异常会不会上抛给访问栈?
    从finally块抛出的异常会传播给访问栈,即使try块的异常可能是更相关的传播。

    try-with-resources
    在java7你能写试用try-with-resource结构的代码来改造上面的案例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    private static void printFileJava7() throws IOException {

    try(FileInputStream input = new FileInputStream("file.txt")) {

    int data = input.read();
    while(data != -1){
    System.out.print((char) data);
    data = input.read();
    }
    }
    }

    注意第一行,这里的try-with-resources结构, 变量FileInputStream被声明在大括号里面,另外一FileInputStream是一个实例分配给一个变量.
    当try块执行完成,FileInputStream将自动关闭.这是因为FileInputStream实现了java.lang.AutoCloseable接口.所有实现该接口的类都可以放在try-with-resources结构中.

    如果一个异常从try-with-resources块中抛出,fileinputstream已经关闭了,try块中抛出的异常将抛到外层.异常会被抑制.这刚好和开头的案例相反(使用旧有异常处理).

    使用多个资源
    也可以使用try-with-resources块处理多个资源:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    private static void printFileJava7() throws IOException {

    try( FileInputStream input = new FileInputStream("file.txt");
    BufferedInputStream bufferedInput = new BufferedInputStream(input)
    ) {

    int data = bufferedInput.read();
    while(data != -1){
    System.out.print((char) data);
    data = bufferedInput.read();
    }
    }
    }

    这个案例创建了两个资源通过try关键字后面的括号. FileInputStream 和 BufferedInputStream.两个资源都会在执行流离开try块后自动关闭.
    资源会通过反向初始化顺序的关闭, BufferedInputStream 先关闭, FileInputStream后关闭.

    自定义可关闭资源的实现
    使用try-with-resources结构不仅仅适用于java内建类.也能通过实现java.lang.autocloseable接口实现自己类使用.
    下面是AutoCloseable接口:

    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
    26
    27
    28

    public interface AutoClosable {

    public void close() throws Exception;
    }

    public class MyAutoClosable implements AutoCloseable {

    public void doIt() {
    System.out.println("MyAutoClosable doing it!");
    }

    @Override
    public void close() throws Exception {
    System.out.println("MyAutoClosable closed!");
    }
    }

    private static void myAutoClosable() throws Exception {

    try(MyAutoClosable myAutoClosable = new MyAutoClosable()){
    myAutoClosable.doIt();
    }
    }

    //console output
    MyAutoClosable doing it!
    MyAutoClosable closed!

    你可以看到,try-with-resources是一个十分强大的方式确保使用在try-catch中的资源能正常关闭,不管这个资源时自己创建的还是java自带的组件.
    译者:如果用户在使用一个类似于事务处理的业务时,可以通过实现该接口,完成一致性处理.

    java 异常处理

    Posted on 2018-05-14

    java 异常处理让你能轻松处理应用程序中的错误.异常处理在编写健壮的应用或组件的时候很容易被忽略的.当一个错误发生在一个Java程序通常会导致一个异常被抛出。你若能抛出,捕获,处理这些异常得当,将有很多方法来处理,但并不是所有的都有效并且安全失败(程序执行失败逻辑要安全处理).

    本次训练(文集)深度讲解java当中的异常处理.这次训练包含了各种应该的活不建议的异常处理,也包含了一些高效、不易出错的异常处理.希望你能从这些文字中受益.

    这个手册中使用的java版本是jdk6 或者jdk7.这些技术也能很好的适用于jdk5.jdk4.

    下面是一个快速介绍你能从异常处理中所见到的内容.
    基本的java异常处理
    头两篇文章包含基本的异常抛出和处理理论,异常层级:
    基本的try..catch..finally
    异常的结构层级

    检查异常和非检查异常的区别
    java是一门支持检查\非检查异常的语言,在文章[检查或非检查异常]中我讨论了这两种方式的不同,最后我也推荐使用非检查异常少用检查异常.

    通用的异常处理建议
    手册6片文章讲述 失败安全异常处理,异常日志写在哪里,建议在验证数据中抛出异常,在另一方面,建议如何设计你的应用程序抛出和处理异常.
    高级异常处理建议
    在下面2篇文章,异常处理的模板和异常丰富讨论更多高级异常处理的技术,让你的代码更简洁,异常模板把所有的try..catch代码重用到一个异常处理的模板方法里,帮助你避免很长的异常栈日志,应用程序获得真实的唯一异常代码.

    异常处理的策略
    我写了很多建议如何把异常技术描述到这个手册进行浓缩了异常处理的策略,这通常很有效.

    Basic try-catch-finally Exception Handling in Java

    Posted on 2018-05-14

    概要内容
    1.访问栈解析
    2.抛出异常
    3.捕获异常
    4.上诉异常
    5.案例:捕获IOException
    6.案例:上报IOException
    7.finally关键字
    8.捕获或上报异常的策略

    这片文章基于如何try..catch…finally 处理,这个案例是java语言,但是同样适用于c#.java和c#在异常处理上不同的地方就是c#没有检查性异常.这在后续文章讨论.
    异常在程序中的表示一些错误或不可预料的情况发生了.应该要执行一些操作能让程序刘继续运行,直到异常被处理.一个方法可能会抛出很多异常,例如输入参数和无效的参数.

    访问栈解释
    call stack多次提到这个概念是因为 访问栈意味着一系列访问当前方法和返回到主方法的程序流.如果方法A访问B,B访问C,访问栈就是ABC.
    当方法C返回到访问栈就只剩下A和B,如果B访问了方法D,访问栈就是ABD

    了解访问栈是学习异常上报概念很重要的知识,异常被访问栈上报,从发生的地方抛出,直到访问栈catch住.

    抛出异常
    如果一个方法需要抛出异常,应该先在方法签名中申明这个异常,并且包含在throw语句的方法中.下面是案例:

    1
    2
    3
    4
    5
    6
    7
    public void divide(int numberToDivide, int numberToDivideBy)
    throws BadNumberException{
    if(numberToDivideBy == 0){
    throw new BadNumberException("Cannot divide by 0");
    }
    return numberToDivide / numberToDivideBy;
    }

    当一个异常通过throw语句抛出后阻止了执行流继续.任何throw语句下面的的语句都不会执行.上面的案例numberToDivide / numberToDivideBy 不会执行.程序当异常被catch块捕获的时候,程序将恢复执行.捕获异常稍后解释.

    你能抛出任何类型的异常,只需要你的方法签名声明了.你也能定义自己的异常,异常都是继承自java.lang.Exception.或者其他的内置异常类.如果一个方法声明抛出异常A,他的子类重写的A方法也应该声明.

    捕获异常
    检查性异常声明的方法,在被方法栈调用时强制捕获或者上报.捕获异常使用try..catch块:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void callDivide(){
    try {
    int result = divide(2,1);
    System.out.println(result);
    } catch (BadNumberException e) {
    //do something clever with the exception
    System.out.println(e.getMessage());
    }
    System.out.println("Division attempt done");
    }

    BadNumberException 参数e指向异常抛出的地方.如果没有异常抛出在try块中,catch块会被忽略执行.
    如果异常被try抛出,方法栈程序流callDivide被中止.在有catch块的访问栈能catch住抛出的异常,程序流也将恢复执行[译者:只有在catch块的方法能恢复,调用栈底层的方法上报的异常,底层是不会恢复执行的],上面的案例中System.out.println(result);不会执行,执行将执行catch块.如果catch当中抛出了异常没有被捕获,这个catch就会被终止,就像上面的try块.

    当一个catch块完成程序将继续catch下面的后语, System.out.println(“Division attempt done”)将执行.

    上报异常
    你不必捕获从其他方法抛出的异常,如果你不能做任何事情的时候,你可以让方法上报这个异常到方法访问栈顶层,这样做就需要声明抛出异常,如下:

    1
    2
    3
    4
    public void callDivide() throws BadNumberException{
    int result = divide(2,1);
    System.out.println(result);
    }

    try…catch块不见了,方法声明了可能抛出的异常类型,这个程序直到抛出异常被打断.”System.out.println(result);将不会执行,但是这个方法没有恢复,这个异常上报了.直到遇到catch块的访问栈捕获这个异常之前程序都不会恢复.抛出异常和捕获异常的方法在访问栈中都有他们自己执行停止,当异常抛出或上报时.

    案例:捕获IOException
    当一个异常抛出在一系列的语句中的try…catch中时,这一系列的语将直接被忽略直接跳到catch块.这样的代码被异常打断在很多地方:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public void openFile(){
    try {
    // constructor may throw FileNotFoundException
    FileReader reader = new FileReader("someFile");
    int i=0;
    while(i != -1){
    //reader.read() may throw IOException
    i = reader.read();
    System.out.println((char) i );
    }
    reader.close();
    System.out.println("--- File End ---");
    } catch (FileNotFoundException e) {
    //do something clever with the exception
    } catch (IOException e) {
    //do something clever with the exception
    }
    }

    上面的语句如果new FileReader(“someFile”)抛出异常,下面try块没有语句会执行

    上报IOException:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void openFile() throws IOException {
    FileReader reader = new FileReader("someFile");
    int i=0;
    while(i != -1){
    i = reader.read();
    System.out.println((char) i );
    }
    reader.close();
    System.out.println("--- File End ---");
    }

    如果reader.read()方法抛出异常,程序将终止,异常通过访问栈传递到访问openFile()的栈顶.如果栈顶有try..catch快,异常被捕获.如果上层也只是抛出,这个访问openfile()的地方也会终止,异常再次上传.直到被catch住或者虚拟机来处理.

    finally关键字
    你能附加一个finally块到try…catch.finally块的代码始终会执行.如果你的try..catch中有return语句,finally块先执行,下面是案例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public void openFile(){
    FileReader reader = null;
    try {
    reader = new FileReader("someFile");
    int i=0;
    while(i != -1){
    i = reader.read();
    System.out.println((char) i );
    }
    } catch (IOException e) {
    //do something clever with the exception
    } finally {
    if(reader != null){
    try {
    reader.close();
    } catch (IOException e) {
    //do something clever with the exception
    }
    }
    System.out.println("--- File End ---");
    }
    }

    不管异常是否抛出,finally都会执行,上面的案例reader都会关闭.

    注意:如果finally块抛出异常,没有捕获,finally块也将被打断就像try..catch一样,这就是上面为什么reader.close()方法要包含在try-catch中
    你不必再catch和finally块,你只需要在其一使用try块,这个代码不会捕获,但是会让它上报给访问栈,因为finally块关闭这个文件阅读器,当异常抛出时.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public void openFile() throws IOException {
    FileReader reader = null;
    try {
    reader = new FileReader("someFile");
    int i=0;
    while(i != -1){
    i = reader.read();
    System.out.println((char) i );
    }
    } finally {
    if(reader != null){
    try {
    reader.close();
    } catch (IOException e) {
    //do something clever with the exception
    }
    }
    System.out.println("--- File End ---");
    }
    }

    是捕获还是上报异常
    你可能好奇你是否该捕获还是上报这个异常.这取决于具体的情形.在很多应用中你不能做太多除了高数请求程序当前失败.在这些应用程序通常可以捕获所有或大部分异常集中的第一个方法的调用堆栈,你能处理资源当异常上报后,例如,一个错误访问数据库连接的web程序,你鞥关闭连接在finally中,即使你不能做任何事情,只有告诉方法访问者访问失败,你最终处理异常还取决于你是否为应用程序选择检查或未经检查的异常。下面的张杰将会说明.

    123

    盛富强

    blogs and research

    28 posts
    17 tags
    github
    © 2023 盛富强
    Powered by Hexo
    |
    Theme — NexT.Muse v5.1.4