月光下的影子

Only the stronger survives in this world.


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

Java通过反射设置或者获取字段的值

发表于 2017-09-06 | 分类于 java

最近在做项目时使用到了直接使用了 MyBatis 的源码,在获取 BoundSql 这个对象的时候,它这个类中的所有字段全部是私有的。但是我又要动态的设置这个类中的 sql 字段的值。BoundSql的代码为:

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
public class BoundSql {
private String sql; //我需要修改这个字段
private List<ParameterMapping> parameterMappings;
private Object parameterObject;
private Map<String, Object> additionalParameters;
private MetaObject metaParameters;

public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap();
this.metaParameters = configuration.newMetaObject(this.additionalParameters);
}

public String getSql() {
return this.sql;
}

public List<ParameterMapping> getParameterMappings() {
return this.parameterMappings;
}

public Object getParameterObject() {
return this.parameterObject;
}

public boolean hasAdditionalParameter(String name) {
String paramName = (new PropertyTokenizer(name)).getName();
return this.additionalParameters.containsKey(paramName);
}

public void setAdditionalParameter(String name, Object value) {
this.metaParameters.setValue(name, value);
}

public Object getAdditionalParameter(String name) {
return this.metaParameters.getValue(name);
}
}

其实解决这个问题最简单的方法就是,我新建一个类也叫 BoundSql ,然后把其中的 sql 字段设置为public的。或者反编译 mybatis 的源码,修改为public的然后打成jar包。
这两种方式都比较麻烦,于是想起来了使用反射来完成这个操作。反射可以动态的设置一个类中的字段,就算这个字段是 private 的。代码如下:

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
public static void setFieldValue(Object target, String fname, Class<?> ftype, Object fvalue)
{
if (target == null || fname == null || "".equals(fname)
|| (fvalue != null && !ftype.isAssignableFrom(fvalue.getClass())))
{
return;
}
Class<?> clazz = target.getClass();
try
{
//先看这个类有没有set方法,有的话直接调用就ok
Method method = clazz.getMethod("set" + Character.toUpperCase(fname.charAt(0)) + fname.substring(1), ftype);
if (!Modifier.isPublic(method.getModifiers()))
{
method.setAccessible(true);
}
method.invoke(target, fvalue);
}
catch (Exception me)
{
try
{
// 没有set方法,使用Filed的set方法来完成
Field field = getFieldOfClass(clazz, fname);
if (!Modifier.isPublic(field.getModifiers()))
{
field.setAccessible(true);
}
field.set(target, fvalue);
}
catch (Exception fe)
{
if (logger.isDebugEnabled())
{
logger.debug(fe, fe);
}
}
}
}

Java中的NIO

发表于 2017-09-04 | 分类于 java

本文章主要讨论以下几个问题:

  1. 什么是NIO?NIO与IO的区别与联系?
  2. 为什么要使用NIO,它有哪些优点?
  3. NIO中的关键类,例如 Buffer、Channel 、Selector 的介绍?
  4. NIO的具体使用。

什么是NIO?NIO与普通IO的区别与联系?

  • NIO指的是Non-Block IO,也即非阻塞的IO。传统的IO是堵塞的,也就是说当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。
  • Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。
  • 面向流 的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。
  • 一个 面向缓冲区 的 I/O 系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
  • Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

为什么要使用NIO,它有哪些优点?

  • NIO 的创建目的是为了让 Java 程序员可以实现高速 I/O 而无需编写自定义的本机代码。NIO 将最耗时的 I/O 操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。
  • 第二个使用NIO的原因是它可以实现异步的IO模式。通过向 Selector进行注册事件,如监听、读操作等,这样的话当一个事件到来时,Selector就会通知该Channel来处理该事件。

异步 I/O 是一种 没有阻塞地 读写数据的方法。通常,在代码进行 read() 调用时,代码会阻塞直至有可供读取的数据。同样, write() 调用将会阻塞直至数据能够写入。
另一方面,异步 I/O 调用不会阻塞。相反,您将注册对特定 I/O 事件的兴趣 ― 可读的数据的到达、新的套接字连接,等等,而在发生这样的事件时,系统将会告诉您。
异步 I/O 的一个优势在于,它允许您同时根据大量的输入和输出执行 I/O。同步程序常常要求助于轮询,或者创建许许多多的线程以处理大量的连接。使用异步 I/O,您可以监听任何数量的通道上的事件,不用轮询,也不用额外的线程。

NIO中的关键类,例如Buffer、Channel、Selector的介绍

NIO是面向块的,也就是说写操作和读操作都是需要经过缓冲区(Buffer)。例如从一个文件中读取数据,需要首先获取到文件的Channel,然后将文件中的数据读入Buffer,最后再对数据进行处理;向一个文件中进行写的操作,先把数据写入缓冲区,然后将缓冲区的数据write到文件中。

Buffer对象

Buffer 是一个对象, 它包含一些要写入或者刚读出的数据。 在 NIO 中加入 Buffer 对象,体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中,您将数据直接写入或者将数据直接读到 Stream 对象中。
缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。

Buffer的类型

最常用的就是ByteBuffer,他按照字节来处理数据。当然对于特定的数据类型也有不同的Buffer,但是其主要操作还是一样的。如CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。

Buffer中的三个属性position、limit以及capacity

  • capacity。这个Buffer的容量,是一个不变的值。
  • limit。这个值代表的是可读可写的范围。对于读或者写操作,范围就是[position, limit]。
  • position。当前操作的位置。

三者之间的关系,具体可以看下面这段代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 // 分配一个缓存区,此时position=0,limit = capacity = 100;
ByteBuffer buffer = ByteBuffer.allocate(100);
// 往buffer里面放了两个int,每个4字节
//因此,position = 8, limit = capacity = 100
buffer.putInt(1);
buffer.putInt(2);
//flip操作就是将buffer由写模式转为读模式
//limit 置位position = 8, 然后position = 0,capacity = 100
buffer.flip();
while(buffer.hasRemaining()){
buffer.getInt();
}
// clear操作,将postion置为0, limit = capacity = 100,这样就可以继续往这个buffer中写数据了
buffer.clear();

Channel对象

  • Channel是一个对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流。通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类), 而 通道 可以用于读、写或者同时用于读写。
  • 一个Channel 总是 往一个Buffer里面读(read to) ,或者从一个buffer里面写(write from)。

Channel的种类

  • FileChannel 。从一个文件中进行读写。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
          // 创建一个FileChannel, 使用getChannel方法。
    FileChannel inChannel = new FileInputStream("hello.txt").getChannel();
    FileChannel outChannel = new FileOutputStream("world.txt").getChannel();
    ByteBuffer buffer = ByteBuffer.allocate(1024);

    while (true)
    {
    buffer.clear();

    int count = inchannel.read(buffer);
    if (count < 0)
    break;

    buffer.flip();

    outChannel.write(buffer);
    }
  • DatagramChannel 。通过UDP协议进行读写的操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    DatagramChannel channel = DatagramChannel.open();
    channel.socket().bind(new InetSocketAddress(9999));//在9999监听UDP连接

    ByteBuffer buf = ByteBuffer.allocate(48);
    buf.clear();
    // 接收数据并存到buf中
    channel.receive(buf);
    // 发送数据
    int bytesSent = channel.send(buf, new InetSocketAddress(host, 80));
  • SocketChannel。通过TCP协议进行读写的操作。

    1
    2
    3
    SocketChannel socketChannel = SocketChannel.open();
    // 连接到服务器
    socketChannel.connect(new InetSocketAddress(host, 80));
  • ServerSocketChannel。允许你监听TCP连接,并且针对每个连接创建一个SocketChannel。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    // 在9999建立监听
    serverSocketChannel.socket().bind(new InetSocketAddress(9999));

    while(true){
    SocketChannel socketChannel =
    serverSocketChannel.accept();
    //do something with socketChannel...
    }

Selector

Selector 是一个Java NIO组件,它可以检查一个或多个NIO通道,并确定哪些通道已经准备好了,例如读或写。通过这种方式,单个线程可以管理多个通道,从而实现多个网络连接。、

为什么使用Selector

使用单个线程来处理多个通道的优点是,您需要较少的线程来处理通道。实际上,您可以使用一个线程来处理所有的通道。对于操作系统来说,在线程之间切换是很昂贵的,而且每个线程在操作系统中也会占用一些资源(内存)。因此,使用的线程越少越好。

一个selector管理多个channel

创建一个Selector

1
Selector selector = Selector.open();

向Selector注册Channel

为了使用选择器的通道,您必须使用选择器注册通道。这是通过使用SelectableChannel.register() 方法,如下:

1
2
3
channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

FileChannel 是不可以注册的,因为它不能切换为 non-block 模式。

NIO的具体使用

下面这个例子展示的是Selector的使用,首先开启Selector,然后channel向Selector注册,Selector负责监听。

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
//1. 开启一个Selector
Selector selector = Selector.open();

//2. 将channel设置为非阻塞模式
channel.configureBlocking(false);

//3. channel向Selector进行注册,它将处理读事件
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);


while(true) {
//4. select() 方法将会阻塞,直到有准备好的channel
int readyChannels = selector.select();

if(readyChannels == 0) continue;

//5. 进行相应的处理
Set<SelectionKey> selectedKeys = selector.selectedKeys();

Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {

SelectionKey key = keyIterator.next();

if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.

} else if (key.isConnectable()) {
// a connection was established with a remote server.

} else if (key.isReadable()) {
// a channel is ready for reading

} else if (key.isWritable()) {
// a channel is ready for writing
}

keyIterator.remove();
}
}

参考资料

  • Java NIO Tutorial
  • NIO 入门

Linux资料收集

发表于 2017-08-31 | 分类于 Linux

收集的Linux的操作技巧文章,没事的时候可以看看。

  • 11个让你吃惊的 Linux 终端命令
  • LINUX大棚
  • 每天一个linux命令(19):find 命令概览

Linux中的I/O重定向

发表于 2017-08-30 | 分类于 Linux

Linux中的I/O重定向

I/O 重定向。”I/O”代表输入/输出, 通过这个工具,你可以重定向命令的输入输出,命令的输入来自文件或者标准输入,而输出也存到文件或者直接在Console中打印。 也可以把多个命令连接起来组成一个强大的命令管道。主要包含如下命令。

命令 含义
cat 连接文件。
sort 排序文本行。
uniq 忽略或者显示重复行
grep 打印匹配行
wc 打印文件中的换行符 ,字和字节数
head 输出文件前10行
tail 输出文件后10行
tee 从标准输入读取数据,并写入到标准输出和文件中

标准输入,输出,和错误

到目前为止,我们用到的许多程序都会产生某种输出。这种输出,经常由两种类型组成。 第一,程序运行结果;这是说,程序要完成的功能。第二,我们得到状态和错误信息, 这些告诉我们程序进展。如果我们观察一个命令,像 ls,会看到它的运行结果和错误信息 显示在屏幕上。

与 Unix 主题“任何东西都是一个文件”保持一致,程序,比方说 ls,实际上把他们的运行结果 输送到一个叫做标准输出的特殊文件(经常用 stdout 表示),而它们的状态信息则送到另一个 叫做标准错误的文件(stderr)。默认情况下,标准输出和标准错误都连接到屏幕,而不是 保存到磁盘文件。除此之外,许多程序从一个叫做标准输入(stdin)的设备得到输入,默认情况下, 标准输入连接到键盘。

I/O 重定向允许我们可以更改输出走向和输入来向。一般地,输出送到屏幕,输入来自键盘, 但是通过 I/O 重定向,我们可以改变输入输出方向。

重定向标准输出

I/O 重定向允许我们来重定义标准输出送到哪里。重定向标准输出到另一个文件除了屏幕,我们使用 “>” 重定向符,其后跟着文件名。为什么我们要这样做呢?因为有时候把一个命令的运行结果存储到 一个文件很有用处。例如,我们可以告诉 shell 把 ls 命令的运行结果输送到文件 ls-output.txt 中去, 由文件代替屏幕。
使用命令

1
yangrubing@eminem:~$ ls ./ > /tmp/ls-output.txt

这样我们就把ls的结果,重定向到ls-output.txt中。查看该文件可以看到:
ls-output.txt的结果
如果我们要追加重定向的信息,而不是覆盖重定向的信息,则可以使用>> 来替代>。

1
yangrubing@eminem:~$ ls ./ >> /tmp/ls-output.txt

重定向标准错误

重定向标准错误缺乏专用的重定向操作符。重定向标准错误,我们必须参考它的文件描述符。 一个程序可以在几个编号的文件流中的任一个上产生输出。然而我们必须把这些文件流的前三个看作标准输入,输出和错误,shell 内部参考它们为文件描述符0,1和2,各自地。shell 提供 了一种表示法来重定向文件,使用文件描述符。因为标准错误和文件描述符2一样,我们用这种表示法来重定向标准错误:

1
yangrubing@eminem:~$ ls /hello/world > /tmp/ls-output.txt 2>&1

使用这种方法,我们完成两个重定向。首先重定向标准输出到文件 ls-output.txt,然后 重定向文件描述符2(标准错误)到文件描述符1(标准输出)使用表示法2>&1。
也可以使用一个表示法 &> 来重定向标准输出和错误到文件 ls-output.txt

1
yangrubing@eminem:~$ ls /hello/world  2>> /tmp/ls-output.txt

重定向标准输入

cat - 连接文件

cat 命令读取一个或多个文件,然后复制它们到标准输出,就像这样:

1
cat [file]

在大多数情况下,你可以认为 cat 命令相似于 DOS 中的 TYPE 命令。你可以使用 cat 来显示 文件而没有分页,例如:

1
cat ls-output.txt

如果直接输入cat,不加任何参数,它将会等待从键盘中获取输入。我们可以将输入重定向到文件,使用<来完成。

1
cat < /tmp/ls-output.txt

管道线(pipeline)

命令可以从标准输入读取数据,然后再把数据输送到标准输出,命令的这种能力被 一个 shell 特性所利用,这个特性叫做管道线。使用管道操作符”|”(竖杠),一个命令的 标准输出可以管道到另一个命令的标准输入:

1
command1| command2

command1的输出将作为command2的输入。例如:

1
ls -l /usr/bin | less

将ls的结果作为less命令的输入。

过滤器

管道线经常用来对数据完成复杂的操作。有可能会把几个命令放在一起组成一个管道线。 通常,以这种方式使用的命令被称为过滤器。过滤器接受输入,以某种方式改变它,然后 输出它。第一个我们想试验的过滤器是 sort。想象一下,我们想把目录/bin 和/usr/bin 中 的可执行程序都联合在一起,再把它们排序,然后浏览执行结果:

1
ls /bin /usr/bin | sort | less

uniq查找或忽略重复行

uniq 命令经常和 sort 命令结合在一起使用。uniq 从标准输入或单个文件名参数接受数据有序 列表(详情查看 uniq 手册页),默认情况下,从数据列表中删除任何重复行。所以,为了确信 我们的列表中不包含重复句子(这是说,出现在目录/bin 和/usr/bin 中重名的程序),我们添加 uniq 到我们的管道线中:

1
ls /bin /usr/bin | sort | uniq | less

在这个例子中,我们使用 uniq 从 sort 命令的输出结果中,来删除任何重复行。如果我们想看到 重复的数据列表,让 uniq 命令带上”-d”选项,就像这样:

1
ls /bin /usr/bin | sort | uniq -d | less

wc 打印行,字和字节数

wc(字计数)命令是用来显示文件所包含的行,字和字节数。例如:

1
2
wc ls-output.txt
7902 64566 503634 ls-output.txt

在这个例子中,wc 打印出来三个数字:包含在文件 ls-output.txt 中的行数,单词数和字节数, 正如我们先前的命令,如果 wc 不带命令行参数,它接受标准输入。”-l”选项限制命令输出只能 报道行数。添加 wc 到管道线来统计数据,是个很便利的方法。查看我们的有序列表中程序个数, 我们可以这样做:

1
2
ls /bin /usr/bin | sort | uniq | wc -l
2728

grep - 打印匹配行

grep 是个很强大的程序,用来找到文件中的匹配文本。这样使用 grep 命令:

1
grep pattern [file...]

当 grep 遇到一个文件中的匹配”模式”,它会打印出包含这个类型的行。grep 能够匹配的模式可以 很复杂,但是现在我们把注意力集中在简单文本匹配上面。在后面的章节中,我们将会研究 高级模式,叫做正则表达式。
比如说,我们想在我们的程序列表中,找到文件名中包含单词”zip”的所有文件。这样一个搜索,可能让我们了解系统中的一些程序与文件压缩有关系。这样做:

1
2
3
4
5
ls /bin /usr/bin | sort | uniq | grep zip
bunzip2
bzip2
gunzip
...

grep 有一些方便的选项:”-i”使得 grep 在执行搜索时忽略大小写(通常,搜索是大小写 敏感的),”-v”选项会告诉 grep 只打印不匹配的行。

head / tail - 打印文件开头部分/结尾部分

有时候你不需要一个命令的所有输出。可能你只想要前几行或者后几行的输出内容。 head 命令打印文件的前十行,而 tail 命令打印文件的后十行。默认情况下,两个命令 都打印十行文本,但是可以通过”-n”选项来调整命令打印的行数。

1
2
3
4
5
head -n 5 ls-output.txt
total 343496
...
tail -n 5 ls-output.txt
...

它们也能用在管道线中:

1
2
3
ls /usr/bin | tail -n 5
znew
...

tail -f 可以实时的浏览数据,当文件变化时将会打印到屏幕上。

tee - 从 Stdin 读取数据,并同时输出到 Stdout 和文件

为了和我们的管道隐喻保持一致,Linux 提供了一个叫做 tee 的命令,这个命令制造了 一个”tee”,安装到我们的管道上。tee 程序从标准输入读入数据,并且同时复制数据 到标准输出(允许数据继续随着管道线流动)和一个或多个文件。当在某个中间处理 阶段来捕捉一个管道线的内容时,这很有帮助。这里,我们重复执行一个先前的例子, 这次包含 tee 命令,在 grep 过滤管道线的内容之前,来捕捉整个目录列表到文件 ls.txt:

1
2
3
4
5
# 先通过grep过滤掉一部分,然后同时输出到ls.txt 和屏幕上
ls /usr/bin | grep zip | tee ls.txt
bunzip2
bzip2
....

总结归纳

一如既往,查看这章学到的每一个命令的文档。我们已经知道了他们最基本的用法。 它们还有很多有趣的选项。随着我们 Linux 经验的积累,我们会了解命令行重定向特性 在解决特殊问题时非常有用处。有许多命令利用标准输入和输出,而几乎所有的命令行 程序都使用标准错误来显示它们的详细信息。

责任链模式

发表于 2017-08-29 | 分类于 设计模式

模式动机

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

避免将消息发送者sender和消息处理者receiver之间的耦合。将多个receiver链接在一起,如果发现自己可以处理则进行处理,否则,将该消息传递给下一个消息处理者。

维基百科对责任链模式的定义:

责任链模式在面向对象程式设计里是一种软件设计模式,它包含了一些命令对象和一系列的处理对象。每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。

总的来说就是,你给我一个请求,我能处理我就处理,并且不往下传递;否则,我就把这个请求往下传递,让其他的处理者来处理。Servlet中的 Filter 使用的就是这种,首先对请求进行预处理,然后才传递给Servlet来处理。

责任链模式的适用场景

在以下场景下可以考虑使用责任链模式:

  • 不止一个处理对象时,需要给一个请求增加一些预处理时。如 Filter。
  • 你想要向一个对象发出请求,而不想显式地指定接收方。这样就需要把消息处理者串成一个链,从而对自己可以处理的消息进行处理。
  • 需要动态的指定消息处理者时。

具体事例

考虑下面这个场景,一个兽人王国有一个国王OrcKing,国王会下达各种命令(Request), 比如:防御城堡(DEFEND_CASTLE)、折磨犯人(TORTURE_PRISONER)、收税(COLLECT_TAX)。而这些命令分别由指挥官 (OrcCommander)、政府官员(OrcOfficer )以及士兵(OrcSoldier)来执行。国王在下达命令的时候并不需要关心这个命令究竟由谁来执行。在这种场景下,可以把消息处理者OrcCommander、OrcOfficer、OrcSoldier 串成一个链,国王下达命令后,消息处理者会检查当前这个消息自己能否执行,能执行就自己执行,不然就把该消息扔给下一个任务执行者。具体的类图如下:
责任链模式的类图
一个请求包括请求的类型,以及请求的描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 一个请求
*/
public class Request {
private final RequestType requestType;
private final String requestDescription;
private boolean handled;
public Request(final RequestType requestType, final String requestDescription) {
this.requestType = Objects.requireNonNull(requestType);
this.requestDescription = Objects.requireNonNull(requestDescription);
}
public String getRequestDescription() {
return requestDescription;
}
@Override
public String toString() {
return getRequestDescription();
}
}

OrcKing 具有下达命令的权利,同时它还拥有一个请求的处理链chain。

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
/**
*
* OrcKing makes requests that are handled by the chain.
*
*/
public class OrcKing {

/**
* 责任链
*/
RequestHandler chain;

public OrcKing() {
buildChain();
}

/**
* 构建责任链
/
private void buildChain() {
chain = new OrcCommander().addRequestHandler(new OrcOfficer()).addRequestHandler(new OrcSoldier());
}
/**
* 发出请求
*/
public void makeRequest(Request req) {
chain.handleRequest(req);
}
}

下面是消息处理器,包括一个 RequestHandler 的抽象类以及OrcCommander、OrcOfficer、OrcSoldier 三个消息处理类:

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
/**
*
* RequestHandler
*
*/
public abstract class RequestHandler
{
private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class);

private RequestHandler next;

public RequestHandler()
{
}

public RequestHandler(RequestHandler next)
{
this.next = next;
}

/**
* 处理请求
*/
public void handleRequest(Request req)
{
if (next != null)
{
next.handleRequest(req);
}
}

/**
* 在责任链的尾部添加消息处理类
*/
public RequestHandler addRequestHandler(RequestHandler handler)
{
RequestHandler temp = this;
while(temp.next != null)
temp = temp.next;
temp.next = handler;
return this;
}

protected void printHandling(Request req)
{
LOGGER.info("{} handling request \"{}\"", this, req);
}

@Override
public abstract String toString();
}


/**
*
* OrcOfficer
*
*/
public class OrcOfficer extends RequestHandler {

public OrcOfficer()
{
}

public OrcOfficer(RequestHandler handler) {
super(handler);
}

/**
* 重写父类的消息处理方法
*/
@Override
public void handleRequest(Request req) {
// 如果是折磨烦人,让我来
if (req.getRequestType().equals(RequestType.TORTURE_PRISONER)) {
printHandling(req);
req.markHandled();
} else {
// 否则,让父类来处理,也就是把消息往下传递
super.handleRequest(req);
}
}

@Override
public String toString() {
return "Orc officer";
}

}

/**
*
* OrcSoldier
*
*/
public class OrcSoldier extends RequestHandler {

public OrcSoldier()
{
}

public OrcSoldier(RequestHandler handler) {
super(handler);
}

@Override
public void handleRequest(Request req) {
if (req.getRequestType().equals(RequestType.COLLECT_TAX)) {
printHandling(req);
req.markHandled();
} else {
super.handleRequest(req);
}
}

@Override
public String toString() {
return "Orc soldier";
}
}
/**
*
* OrcCommander
*
*/
public class OrcCommander extends RequestHandler {

public OrcCommander()
{
}

public OrcCommander(RequestHandler handler) {
super(handler);
}

@Override
public void handleRequest(Request req) {
if (req.getRequestType().equals(RequestType.DEFEND_CASTLE)) {
printHandling(req);
req.markHandled();
} else {
super.handleRequest(req);
}
}

@Override
public String toString() {
return "Orc commander";
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Program entry point
*
* @param args command line args
*/
public static void main(String[] args) {

OrcKing king = new OrcKing();
// 发出一个请求
king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner"));
king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle"));
king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax"));
}

运行结果:

1
2
3
13:10:43.946 [main] INFO com.iluwatar.chain.RequestHandler - Orc officer handling request "torture prisoner"
13:10:43.949 [main] INFO com.iluwatar.chain.RequestHandler - Orc commander handling request "defend castle"
13:10:43.950 [main] INFO com.iluwatar.chain.RequestHandler - Orc soldier handling request "collect tax"

在项目中的使用

在数据开发平台的项目中,由于需要执行Hive、MySQL、Oracle三种任务,客户端发送请求,后台使用三种不同的处理器分别执行不同的SQL,符合责任链模式的使用场景。因此把代码重构了下。

首先是抽象出来一个RunJobHandler借口,该接口有一个runJob方法。然后,设计一个AbstractRunJobHandler 对该接口进行基本的实现。然后,分别实现HQLJobHandler、
MySQLJobHandler、OracleJobHandler继承自AbstractRunJobHandler。
类之间的关系图如下:
数据开发平台责任链模式
具体的实现如下:

1
2
3
4
5
6
7
8
/**
* RunJobHandler接口
*/
public interface RunJobHandler
{
void runjob(WebSocketSession session,
RunJobRequestVO rjObject, ScriptDao scriptDao) throws Exception;
}

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
public abstract class AbstractRunJobHandler implements RunJobHandler
{
protected final Logger logger = LoggerFactory.getLogger(this.getClass());

AbstractRunJobHandler next;

/**
* 添加任务处理器
* @param jobHandler 任务处理器
* @return
*/
public AbstractRunJobHandler addRunJobHandler(AbstractRunJobHandler jobHandler)
{
AbstractRunJobHandler temp = this;
while (temp.next != null)
temp = temp.next;
temp.next = jobHandler;
return this;
}

@Override
public void runjob(WebSocketSession session,
RunJobRequestVO rjObject, ScriptDao scriptDao)
{
//默认实现,由next来执行
if (next != null)
next.runjob(session, rjObject, scriptDao);
}

protected List createLogList(String log)
{
// ommited...
}

/**
* 将错误发送到前端并存到数据库
* @param session
* @param scriptDao
* @param e
*/
protected void handleException(WebSocketSession session, ScriptDao scriptDao, Throwable e)
{
// ommited...
}

}

执行hive 任务的处理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class HQLJobHandler extends AbstractRunJobHandler
{

@Override
public void runjob(WebSocketSession session,
RunJobRequestVO rjObject, ScriptDao scriptDao)
{
if (BaseConstant.HIVE.equals(rjObject.getJobType()))
{
// 处理逻辑
}
else
{
super.runjob(session, rjObject, scriptDao);
}
}
}

MySQLJobHandler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MySQLJobHandler extends AbstractRunJobHandler
{

@Override
public void runjob(WebSocketSession session,
RunJobRequestVO rjObject, ScriptDao scriptDao)
{
if (BaseConstant.MYSQL.equals(rjObject.getJobType()))
{
// mysql的处理逻辑
}
else
{
super.runjob(session, rjObject, scriptDao);
}
}
}

OracleJobHandler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @author bjyangrubing
* @createTime 2017/8/29 15:15
* Description:
*/
public class OracleJobHandler extends AbstractRunJobHandler
{
@Override
public void runjob(WebSocketSession session,
RunJobRequestVO rjObject, ScriptDao scriptDao)
{
if (BaseConstant.ORACLE.equals(rjObject.getJobType()))
{
//oracle 的处理逻辑
}
else
{
super.runjob(session, rjObject, scriptDao);
}
}
}

客户端在使用的时候,首先build一个责任链:

1
2
3
4
5
6
7
8
9
   /**
* 获取到责任链
*/
private AbstractRunJobHandler getHandlerChain()
{
return new HQLJobHandler().addRunJobHandler(new OracleJobHandler()).addRunJobHandler(new MySQLJobHandler());
}
//直接运行了不用管具体怎么实现,这样就实现了方法调用者与处理者的松耦合。
jobHandler.runjob(session,rjObject, scriptDao);

其实这种情况下也可以用策略模式,本质上运行任务也是采用的不同的策略来实现的。

简单工厂模式

发表于 2017-08-28 | 分类于 设计模式

模式意图

我们希望在创建一个对象时,通过传递不同的参数而得到不同的对象。我们不需要知道对象创建的具体细节。从而,客户端在使用时只需要传递相应的参数,即可获取到对象。

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

模式应用场景

  • 当一组对象都实现同一个接口时,我们可以使用简单工厂模式来创建对象。

    具体实例

    假设有几种武器Spear、Bow、Axe、Sword 都实现了 Weapon 接口,并且每个武器内部都有自己的不同实现。我们可以使用一个 WeaponFactory 来创造这些武器,客户端只需要传递相应的武器类型即可。类之间的关系图如下图所示。
    简单工厂模式类图
    具体代码如下:
    武器接口以及各种武器的具体实现:
    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
    /**
    * Interface representing weapon.
    */
    public interface Weapon {
    }
    /**
    * Class representing Axe
    */
    public class Axe implements Weapon {
    @Override
    public String toString() {
    return "Axe";
    }
    }
    /**
    * Class representing Bows
    */
    public class Bow implements Weapon {
    @Override
    public String toString() {
    return "Bow";
    }
    }
    /**
    * Class representing Spear
    */
    public class Spear implements Weapon {
    @Override
    public String toString() {
    return "Spear";
    }
    }
    /**
    * Class representing Swords
    */
    public class Sword implements Weapon {
    @Override
    public String toString() {
    return "Sword";
    }
    }

WeaponFactory 的实现:

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
public class WeaponFactory
{
private WeaponFactory()
{
}

public static Weapon create(WeaponType type)
{
Weapon weapon = null;
switch (type)
{
case SWORD:
weapon = new Sword();
break;
case AXE:
weapon = new Axe();
break;
case BOW:
weapon = new Bow();
break;
case SPEAR:
weapon = new Spear();
break;
}
return weapon;
}
}

测试代码:

1
2
3
4
public static void main(String[] args) {
Weapon axe = WeaponFactory.create(WeaponType.AXE);
LOGGER.info(axe.toString());
}

运行结果:

1
19:48:38.921 [main] INFO com.iluwatar.factorykit.App - Axe

工厂方法模式

发表于 2017-08-28 | 分类于 设计模式

模式意图

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

定义一个创建对象的接口,但是让子类决定怎么进行实例化。工厂方法让一个类的实例化进程延迟到子类。

It provides a way to delegate the instantiation logic to child classes.

它提供了一种将实例化逻辑委托给子类的方式。
维基百科队对于工厂方法模式的解释:

In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory method—either specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes—rather than by calling a constructor.

工厂方法模式的使用场景

工厂方法模式适用于以下场景:

  • 一个类想要它的子类来指定具体创建的对象。
  • classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate(啥意思?)

具体实例

兽人(Orc)和精灵(Elf)两个部落都有铁匠(BlackSmith)。每个铁匠就相当于是一个工厂,BlackSmith有两个具体的实现,兽人有兽人的铁匠(OrcBlackSmith)、精灵有精灵的铁匠(ElfBlackSmith)。这样铁匠作为一个工厂就将具体的创造任务委托给了子类。
他们之间的类图关系如图所示:
工厂方法模式类图

代码如下,BlackSmith是一个工厂的接口,有一个manufactureWeapon()的方法。

1
2
3
4
5
6
7
8
/**
*
* The interface containing method for producing objects.
*
*/
public interface Blacksmith {
Weapon manufactureWeapon(WeaponType weaponType);
}

它有两个实现类,这两个类是工厂的具体实现,负责制造武器(manufactureWeapon):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
*
* Concrete subclass for creating new objects.
*
*/
public class OrcBlacksmith implements Blacksmith {

public Weapon manufactureWeapon(WeaponType weaponType) {
return new OrcWeapon(weaponType);
}
}
/**
*
* Concrete subclass for creating new objects.
*
*/
public class ElfBlacksmith implements Blacksmith {
public Weapon manufactureWeapon(WeaponType weaponType) {
return new ElfWeapon(weaponType);
}
}

客户端的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Program entry point
*
* @param args command line args
*/
public static void main(String[] args) {
// Lets go to war with Orc weapons
App app = new App(new OrcBlacksmith());
app.manufactureWeapons();

// Lets go to war with Elf weapons
app = new App(new ElfBlacksmith());
app.manufactureWeapons();
}

private void manufactureWeapons() {
Weapon weapon;
weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR);
LOGGER.info(weapon.toString());
weapon = blacksmith.manufactureWeapon(WeaponType.AXE);
LOGGER.info(weapon.toString());
}

输出结果:

1
2
3
4
19:21:09.545 [main] INFO com.iluwatar.factory.method.App - Orcish spear
19:21:09.548 [main] INFO com.iluwatar.factory.method.App - Orcish axe
19:21:09.548 [main] INFO com.iluwatar.factory.method.App - Elven spear
19:21:09.548 [main] INFO com.iluwatar.factory.method.App - Elven axe

抽象工厂模式

发表于 2017-08-28 | 分类于 设计模式

模式意图

通常,我们一个工厂可以生产一个产品。现在我们有个需求需要将不同的产品组合在一起在一个工厂中生产。抽象工厂方法又称为Kit,工具箱,创造一组相关的对象。

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

抽象工厂方法可以创造一个产品族,例如一个王国需要有国王King、城堡Castle、军队Army,我们想使用一个工厂可以把这一系列有关联的产品全部创造出来,同时客户端在使用的时候不需要指定具体的产品实现。抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构。
Wikipedia:

The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes.

抽象工厂方法的使用场景

在以下情况下考虑使用抽象工厂方法:

  1. 当一个系统应该独立于其产品是如何创建、组合和表示的。
  2. 当一个系统应该配置多个产品系列之一。
  3. 当需要在运行时来确定使用的是哪个产品系列。

抽象工厂方法强调的是将一系列的对象进行封装,并且将实现和定义分离开来,在运行时来决定使用哪个工厂。

具体实例

每个王国都由一系列的对象组成,King、Castle 以及Army。KingdomFactory 是一个王国的工厂接口,它负责创造一个王国,也就是一个抽象工厂。针对每个王国都有一个工厂的具体实现。如图所示。
抽象工厂方法类图
具体代码如下:
KingdomFactory 接口,王国的工厂接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
*
* KingdomFactory factory interface.
*
*/
public interface KingdomFactory {

Castle createCastle();

King createKing();

Army createArmy();

}

KingdomFactory 的两个实现类ElfKingdomFactory 和 OrcKingdomFactory :

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
/**
*
* ElfKingdomFactory concrete factory.
*
*/
public class ElfKingdomFactory implements KingdomFactory {

public Castle createCastle() {
return new ElfCastle();
}

public King createKing() {
return new ElfKing();
}

public Army createArmy() {
return new ElfArmy();
}

}

/**
*
* OrcKingdomFactory concrete factory.
*
*/
public class OrcKingdomFactory implements KingdomFactory {

public Castle createCastle() {
return new OrcCastle();
}

public King createKing() {
return new OrcKing();
}

public Army createArmy() {
return new OrcArmy();
}
}

客户端在使用时:

1
2
3
4
5
6
7
8
9
10
/**
* Creates kingdom
*/
public void createKingdom(final KingdomFactory factory) {
setKing(factory.createKing());
setCastle(factory.createCastle());
setArmy(factory.createArmy());
}
//将ElfKindomFactory或者OrcKingdomFactory作为参数传递进去就能够
//创造一个王国了 - -

运行结果:

1
2
3
4
5
6
7
8
16:12:37.079 [main] INFO com.iluwatar.abstractfactory.App - Elf Kingdom
16:12:37.090 [main] INFO com.iluwatar.abstractfactory.App - This is the Elven Army!
16:12:37.090 [main] INFO com.iluwatar.abstractfactory.App - This is the Elven castle!
16:12:37.090 [main] INFO com.iluwatar.abstractfactory.App - This is the Elven king!
16:12:37.090 [main] INFO com.iluwatar.abstractfactory.App - Orc Kingdom
16:12:37.110 [main] INFO com.iluwatar.abstractfactory.App - This is the Orc Army!
16:12:37.110 [main] INFO com.iluwatar.abstractfactory.App - This is the Orc castle!
16:12:37.110 [main] INFO com.iluwatar.abstractfactory.App - This is the Orc king!

Java线程池的使用

发表于 2017-08-25 | 分类于 java

线程池介绍

为什么要使用线程池?

Thread pools address two different problems: they usually provide improved performance when executing large numbers of asynchronous tasks, due to reduced per-task invocation overhead, and they provide a means of bounding and managing the resources, including threads, consumed when executing a collection of tasks. Each ThreadPoolExecutor also maintains some basic statistics, such as the number of completed tasks.

上面这段话摘抄自JDK中对于线程池的描述, 可以看出,线程池指的是一个线程的集合,他主要解决了两个我们在使用时的问题:

  1. 对资源进行了预处理,预先创建了线程,省去了线程创建的时间。
  2. 提供了一种对资源进行绑定和管理的方法,包括在执行一组任务时使用的线程。

一个比较容易理解的描述是,我们在使用线程完成任务时。假设线程创建需要的时间为 t1、执行耗时需要 t2、线程销毁需要 t3,那么我们执行一个线程所需的时间为。
线程执行所需时间 = t1+t2+t3
如果我们使用线程池的话,可以预先在线程池中维持一定数量的线程,这样就可以省去t1和t3 的时间,由线程池来替我们管理线程的创建和销毁。这样就可以提高系统的响应速度和吞吐率。

Java中的线程池

Java中的线程池属于Java中的Executor框架中的一部分,是ThreadPoolExecutor 类的一个实例。Java中已经给我们提供了现成的线程池,并且鼓励大家在一般情况下直接使用现成的线程池。

However, programmers are urged to use the more convenient Executors factory methods Executors.newCachedThreadPool() (unbounded thread pool, with automatic thread reclamation), Executors.newFixedThreadPool(int) (fixed size thread pool) and Executors.newSingleThreadExecutor() (single background thread), that preconfigure settings for the most common usage scenarios.

Java中提供的现成的线程池有:

  • Executors.newCachedThreadPool()。
  • Executors.newFixedThreadPool(int) 。
  • Executors.newScheduledThreadPool(int)

    CachedThreadPool

    可缓存线程池,具有以下特点:
  • 线程数无限制
  • 有空闲线程则复用空闲线程,若无空闲线程则新建线程
  • 一定程序减少频繁创建/销毁线程,减少系统开销

该线程池的实现如下:

1
2
3
4
5
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

可以看到,CachedThreadPool中使用的队列为 SynchronousQueue ,该队列会将任务直接提交给线程来执行,当没有足够的可用线程时将会报异常。并且该线程池把 maximumPoolSizes 参数设置为最大。

FixedThreadPool

定长线程池,该线程池具有以下特点:

  • 可控制线程最大并发数(同时执行的线程数)
  • 超出的线程会在队列中等待

该线程池的实现方法如下:

1
2
3
4
5
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

定长的线程池使用 LinkedBlockingQueue 作为线程池使用的队列,使用该队列的话maximumPoolSizes 不起作用,如果所有的coreThread 都是busy的话,新提交的任务将会阻塞会阻塞。

ScheduledThreadPool

用于执行周期性任务的线程池,特点:

  • 支持定时及周期性任务执行。

该线程池主要有scheduleAtFixedRate()、scheduleWithFixedDelay()、schedule()这几个方法。

自定义线程池

ThreadPoolExecutor的参数

一般情况下,Executors提供的线程池已经能够满足大多数的业务场景,如果想自定义线程池的话需要配置ThreadPoolExecutor的几个属性,主要包括如下几个:

  • int corePoolSize=> 该线程池中核心线程数最大值。 核心线程: 线程池新建线程的时候,如果当前线程总数小于 corePoolSize ,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程。 核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。如果指定 ThreadPoolExecutor 的 allowCoreThreadTimeOut 这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉。
  • int maximumPoolSize=> 该线程池中线程总数最大值 。线程总数 = 核心线程数 + 非核心线程数。
  • long keepAliveTime。 该线程池中非核心线程闲置超时时长。 一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉。 如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。
  • TimeUnit unit=>keepAliveTime 的单位,TimeUnit 是一个枚举类型。
  • BlockingQueue<Runnable> workQueue 。 该线程池中的任务队列:维护着等待执行的Runnable 对象 当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。

常用的 workQueue 类型:>

  1. SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
  2. LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
  3. ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
  4. DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务。

ThreadPoolExecutor 的策略

上面介绍参数的时候其实已经说到了 ThreadPoolExecutor 执行的策略,这里给总结一下,当一个任务被添加进线程池时:

  • 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
  • 线程数量达到了corePoolSize,则将任务移入队列等待
  • 队列已满,新建线程(非核心线程)执行任务
  • 队列已满,总线程数又达到了maximumPoolSize,就会由RejectedExecutionHandler抛出异常

代理模式

发表于 2017-08-25 | 分类于 设计模式

代理模式提供了一种方法来控制对目标类的访问,当为一个目标对象创建代理对象以后,代理对象将拦截对目标对象的方法调用,从而控制对于目标类的访问。代理也可以看成是一个包装类,对目标对象的包装。

代理模式的意图

Provide a surrogate or placeholder for another object to control access to it.

为其他的对象提供一个代理,从而控制 client 对于目标对象的访问。


代理模式的适用场景

下面是几种常见的代理模式适用的情况:

  • 当需要控制对于目标对象的访问时,比如权限控制。
  • 懒加载( Lazy Initialization )可以使用动态代理实现。代理类和目标类实现了相同的接口,第一次调用的时候代理对象将加载目标对象,并将以后的调用都委托给它。
  • 日志记录。通过拦截方法调用实现。
  • 代理模式还可以用来促进网络连接以及计算对于一个对象的引用。

具体实例

下面这个例子使用的是静态代理,实现的方法是通过与目标对象实现同样的接口,并持有一个目标对象的引用来实现;本例还使用了动态代理的方法来实现动态代理。
主要涉及如下几个类:

  • Wizard类。
  • WizardTower接口。
  • IvoryTower类,实现了 WizardTower 接口。
  • WizardTowerProxy 类。WizardTower的静态代理类,持有一个WizardTower的引用。
  • WizardTowerProxy2 类。WizardTower 的动态代理调用处理器。
    类之间的关系如下图:
    代理模式的类图
    Wizard 类:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    *
    * Wizard
    *
    */
    public class Wizard {

    private final String name;

    public Wizard(String name) {
    this.name = name;
    }

    @Override
    public String toString() {
    return name;
    }

    }

WizardTower 接口:

1
2
3
4
5
6
7
/**
* WizardTower interface
*/
public interface WizardTower {

void enter(Wizard wizard);
}

IvoryTower类,实现了 WizardTower 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
*
* 被代理的对象
*
*/
public class IvoryTower implements WizardTower {

private static final Logger LOGGER = LoggerFactory.getLogger(IvoryTower.class);

public void enter(Wizard wizard) {
LOGGER.info("{} enters the tower.", wizard);
}

}

WizardTowerProxy 类。WizardTower的静态代理类,持有一个WizardTower的引用:

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
/**
*
* The proxy controlling access to the IvoryTower
*
*/
public class WizardTowerProxy implements WizardTower {

private static final Logger LOGGER = LoggerFactory.getLogger(WizardTowerProxy.class);

private static final int NUM_WIZARDS_ALLOWED = 3;

private int numWizards;

private final WizardTower tower;

public WizardTowerProxy(WizardTower tower) {
this.tower = tower;
}

@Override
public void enter(Wizard wizard) {
if (numWizards < NUM_WIZARDS_ALLOWED) {
tower.enter(wizard);
numWizards++;
} else {
LOGGER.info("{} is not allowed to enter!", wizard);
}
}
}

WizardTowerProxy2 是 WizardTower 的动态代理调用处理器,对WizardTower 的方法调用将会被拦截:

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
public class WizardTowerProxy2 implements InvocationHandler
{
WizardTower wizardTower;

private static final Logger LOGGER = LoggerFactory.getLogger(WizardTowerProxy.class);

private static final int NUM_WIZARDS_ALLOWED = 3;

private int numWizards;


public WizardTowerProxy2(WizardTower wizardTower)
{
this.wizardTower = wizardTower;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{

if (numWizards < NUM_WIZARDS_ALLOWED) {
method.invoke(wizardTower, args);
numWizards++;
} else {
LOGGER.info("{} is not allowed to enter!", args);
}
return null;
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class App {

/**
* Program entry point
*/
public static void main(String[] args) {

IvoryTower tower = new IvoryTower();
WizardTowerProxy proxy = new WizardTowerProxy(tower);
proxy.enter(new Wizard("Red wizard"));
proxy.enter(new Wizard("White wizard"));
proxy.enter(new Wizard("Black wizard"));
proxy.enter(new Wizard("Green wizard"));
proxy.enter(new Wizard("Brown wizard"));

WizardTower proxytower = (WizardTower) Proxy.newProxyInstance(tower.getClass().getClassLoader(), tower.getClass().getInterfaces(),new WizardTowerProxy2(tower));
proxytower.enter(new Wizard("Red wizard"));
proxytower.enter(new Wizard("White wizard"));
proxytower.enter(new Wizard("Black wizard"));
proxytower.enter(new Wizard("Green wizard"));
proxytower.enter(new Wizard("Brown wizard"));
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11:04:29.158 [main] INFO com.iluwatar.proxy.IvoryTower - Red wizard enters the tower.
11:04:29.162 [main] INFO com.iluwatar.proxy.IvoryTower - White wizard enters the tower.
11:04:29.162 [main] INFO com.iluwatar.proxy.IvoryTower - Black wizard enters the tower.
11:04:29.162 [main] INFO com.iluwatar.proxy.WizardTowerProxy - Green wizard is not allowed to enter!
11:04:29.162 [main] INFO com.iluwatar.proxy.WizardTowerProxy - Brown wizard is not allowed to enter!
11:04:29.166 [main] INFO com.iluwatar.proxy.IvoryTower - Red wizard enters the tower.
11:04:29.167 [main] INFO com.iluwatar.proxy.IvoryTower - White wizard enters the tower.
11:04:29.167 [main] INFO com.iluwatar.proxy.IvoryTower - Black wizard enters the tower.
11:04:29.167 [main] INFO com.iluwatar.proxy.WizardTowerProxy - Green wizard is not allowed to enter!
11:04:29.167 [main] INFO com.iluwatar.proxy.WizardTowerProxy - Brown wizard is not allowed to enter!

总结

本文介绍了java设计模式中的代理模式,实现方式包括静态代理和动态代理。静态代理指的是代理类和目标类实现同样的接口,并且代理类持有一个目标类的引用;动态代理指的是通过Prxoy.newInstance() 方法来创建一个代理类,并拦截目标类的方法调用。其中,动态代理在很多的框架中都有使用,Spring的AOP就是基于动态代理来实现的,构建切面拦截对于方法的调用。Mybatis 中的面向接口编程也是为每个Mpper创建一个MapperProxy,来实现具体的调用。

12
Robin Yang

Robin Yang

Only the stronger survive in the world.

20 日志
5 分类
11 标签
RSS
© 2020 Robin Yang
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4