IO&NIO
Java 中 IO 流?
按照流的流向分,可以分为输入流和输出流;
按照操作单元划分,可以划分为字节流和字符流;
按照流的角色划分为节点流和处理流。
Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流
的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
Java IO与 NIO的区别
NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,
所以NIO的效率要比IO高很多。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。
常用io类有那些
File
FileInputSteam,FileOutputStream
BufferInputStream,BufferedOutputSream
PrintWrite
FileReader,FileWriter
BufferReader,BufferedWriter
ObjectInputStream,ObjectOutputSream
字节流与字符流的区别
以字节为单位输入输出数据,字节流按照8位传输 以字符为单位输入输出数据,字符流按照16位传输
阻塞 IO 模型
最传统的一种 IO 模型,即在读写数据过程中会发生阻塞现象。当用户线程发出 IO 请求之后,内核会去**查看数据是否就
绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数
据拷贝到用户线程**,并返回结果给用户线程,用 户线程才解除block 状态。典型的阻塞 IO 模型的例子为: data =
socket.read();如果数据没有就绪,就会一直阻塞在read 方法。
非阻塞 IO 模型
当用户线程发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。 如果结果是一个error时,它就知道数据
还没有准备好,于是它可以再次发送 read 操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么
它马上就将数据拷贝到了用户线程,然后返回。所以事实上,在非阻塞 IO 模型中,用户线程需要不断地询问内核数据是
否就绪,也就说非阻塞 IO不会交出 CPU,而会一直占用 CPU。 典型的非阻塞 IO 模型一般如下:
while(true){
data = socket.read();
if(data!= error){
//处理数据
break;
}
}
但是对于非阻塞 IO 就有一个非常严重的问题, 在 while 循环中需要不断地去询问内核数据是否就绪,这样会导致 CPU
占用率非常高,因此一般情况下很少使用 while 循环这种方式来读取数据。
多路复用 IO 模型
多路复用 IO 模型是目前使用得比较多的模型。 Java NIO 实际上就是多路复用 IO。在多路复用 IO模型中,会有一个线程
不断去轮询多个 socket 的状态,只有当 socket 真正有读写事件时,才真正调用实际的 IO 读写操作。因为在多路复用 IO
模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,
并且只有在真正有socket 读写事件进行时,才会使用 IO 资源,所以它大大减少了资源占用。在 Java NIO 中,是通过
selector.select()去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这种方式会导致用户线程的阻
塞。多路复用IO 模式,通过一个线程就可以管理多个 socket,只有当 socket 真正有读写事件发生才会占用资源来进行实
际的读写操作。因此,多路复用 IO 比较适合连接数比较多的情况。另外多路复用 IO 为何比非阻塞 IO 模型的效率高是因
为在非阻塞 IO 中,不断地询问 socket 状态时通过用户线程去进行的,而在多路复用 IO 中,轮询每个 socket 状态是内核
在进行的,这个效率要比用户线程要高的多。不过要注意的是,多路复用 IO 模型是通过轮询的方式来检测是否有事件到
达,并且对到达的事件逐一进行响应。因此对于多路复用 IO 模型来说, 一旦事件响应体很大,那么就会导致后续的事件
迟迟得不到处理,并且会影响新的事件轮询。
信号驱动 IO 模型
在信号驱动 IO 模型中,当用户线程发起一个 IO 请求操作,会给对应的 socket 注册一个信号函数,然后用户线程会继续
执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用 IO 读写操作来进
行实际的 IO 请求操作。
异步 IO 模型
异步 IO 模型才是最理想的 IO 模型,在异步 IO 模型中,当用户线程发起 read 操作之后,立刻就可以开始去做其它的
事。而另一方面,从内核的角度,当它受到一个 asynchronous read 之后,它会立刻返回,说明 read 请求已经成功发起
了,因此不会对用户线程产生任何 block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完
成之后,内核会给用户线程发送一个信号,告诉它 read 操作完成了。也就说用户线程完全不需要实际的整个 IO 操作是如
何进行的, 只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,可以直接去使用数据了。也就
说在异步 IO 模型中, IO 操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知
用户线程操作已完成。用户线程中不需要再次调用 IO 函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号
驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用 IO 函数进行实际的读写操作;而在异步
IO 模型中,收到信号表示 IO 操作已经完成,不需要再在用户线程中调用 IO 函数进行实际的读写操作。注意,异步 IO 是
需要操作系统的底层支持,在 Java 7 中,提供了 Asynchronous IO。
JAVA NIO
NIO 主要有三大核心部分: Channel(通道), Buffer(缓冲区), Selector。传统 IO 基于字节流和字符流进行操作, 而 NIO
基于 Channel 和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。 Selector(选择
区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。 NIO 和传统 IO 之
间第一个最大的区别是, IO 是面向流的,NIO 是面向缓冲区的。
NIO 的缓冲区
Java IO 面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前
后移动流中的数据。如果需要前后移动从流中读取的数据, 需要先将它缓存到一个缓冲区。 NIO 的缓冲导向方法不同。
数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检
查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的
数据。
NIO 的非阻塞
IO 的各种流是阻塞的。这意味着,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据
完全写入。该线程在此期间不能再干任何事情了。 NIO 的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅
能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读
取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全
写入,这个线程同时可以去做别的事情。 线程通常将非阻塞 IO 的空闲时间用于在其它通道上执行 IO 操作,所以一个单
独的线程现在可以管理多个输入和输出通道(channel)。
Channel
首先说一下 Channel,国内大多翻译成“通道”。 Channel 和 IO 中的 Stream(流)是差不多一个等级的。只不过 Stream 是
单向的,譬如: InputStream, OutputStream, 而 Channel 是双向的,既可以用来进行读操作,又可以用来进行写操作。
NIO 中的 Channel 的主要实现有:
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel
这里看名字就可以猜出个所以然来:分别可以对应文件 IO、 UDP 和TCP(Server 和 Client)
Buffer
Buffer,故名思意, 缓冲区,实际上是一个容器,是一个连续数组。 Channel 提供从文件、网络读取数据的渠道,但是读
取或写入的数据都必须经由 Buffer。
上面的图描述了从一个客户端向服务端发送数据,然后服务端接收数据的过程。客户端发送数据时,必须先将数据存入
Buffer 中,然后将 Buffer 中的内容写入通道。服务端这边接收数据必须通过 Channel将数据读入到 Buffer 中,然后再从
Buffer 中取出数据来处理。在 NIO 中, Buffer 是一个顶层父类,它是一个抽象类,常用的 Buffer 的子类有:ByteBuffer、
IntBuffer、 CharBuffer、 LongBuffer、 DoubleBuffer、 FloatBuffer、ShortBuffer
Selector
Selector 类是 NIO 的核心类, Selector 能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后
针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得
只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一
个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。
JavaWeb&序列化
说下原生 jdbc 操作数据库流程?
第一步: Class.forName()加载数据库连接驱动;
第二步: DriverManager.getConnection()获取数据连接对象;
第三步:根据 SQL 获取 sql 会话对象,有 2 种方式 Statement、 PreparedStatement ;
第四步:执行 SQL 处理结果集,执行 SQL 前如果有参数值就设置参数值 setXXX();
第五步:关闭结果集、关闭会话、关闭连接
什么要使用 PreparedStatement?
1、 PreparedStatement 接口继承 Statement, PreparedStatement 实例包含已编译的 SQL 语句,所以其执行速度要快
于 Statement 对象。
2 、 作为Statement 的子类 , PreparedStatement 继承了Statement 的所有功能 。三 种方法 execute、 executeQuery
和 executeUpdate 已被更改以使之不再需要参数。
3、在 JDBC 应用中,在任何时候都不要使用 Statement,原因如下:
一、代码的可读性和可维护性Statement 需要不断地拼接,而 PreparedStatement 不会。
二、 PreparedStatement 尽最大可能提高性能DB 有缓存机制,相同的预编译语句再次被调用不会再次需要编译。
三、最重要的一点是极大地提高了安全性Statement 容易被 SQL 注入,而 PreparedStatementc 传入的内容不会和 sql 语
句发生任何匹配关系。
关系数据库中连接池的机制是什么?
前提:为数据库连接建立一个缓冲池。
1:从连接池获取或创建可用连接
2:使用完毕之后,把连接返回给连接池
3:在系统关闭前,断开所有连接并释放连接占用的系统资源
4:能够处理无效连接,限制连接池中的连接总数不低于或者不超过某个限定值。
其中有几个概念需要大家理解:
最小连接数是连接池一直保持的数据连接。如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪
费掉。最大连接数是连接池能申请的最大连接数。如果数据连接请求超过此数,后面的数据连接请求将被加入到等待队列
中,这会影响之后的数据库操作。如果最小连接数与最大连接数相差太大,那么,最先的连接请求将会获利,之后超过最
小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释
放,它将被放到连接池中等待重复使用或是空闲超时后被释放。上面的解释,可以这样理解:数据库池连接数量一直保持
一个不少于最小连接数的数量,当数量不够时,数据库会创建一些连接,直到一个最大连接数之后连接数据库就会等待。
http 的长连接和短连接
HTTP 协议有 HTTP/1.0 版本和 HTTP/1.1 版本。 HTTP1.1 默认保持长连接(HTTP persistentconnection,也翻译为持久
连接),数据传输完成了保持 TCP 连接不断开(不发 RST 包、不四次握手),等待在同域名下继续用这个通道传输数
据;相反的就是短连接。在 HTTP/1.0 中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次 HTTP 操作,就
建立一次连接,任务结束就中断连接。 从 HTTP/1.1 起,默认使用的是长连接, 用以保持连接特性。
HTTP/1.1 与 HTTP/1.0 的区别
1 可扩展性
a) HTTP/1.1 在消息中增加版本号,用于兼容性判断。
b) HTTP/1.1 增加了 OPTIONS 方法,它允许客户端获取一个服务器支持的方法列表。
c) 为了与未来的协议规范兼容, HTTP/1.1 在请求消息中包含了 Upgrade 头域,通过该头域,客户端可以让服务器知道它
能够支持的其它备用通信协议,服务器可以据此进行协议切换,使用备用协议与客户端进行通信。
2 缓存
在 HTTP/1.0 中,使用 Expire 头域来判断资源的 fresh 或 stale,并使用条件请求(conditionalrequest)来判断资源是否
仍有效。 HTTP/1.1 在 1.0 的基础上加入了一些 cache 的新特性,当缓存对象的 Age 超过 Expire 时变stale 对象, cache
不需要直接抛弃 stale 对象,而是与源服务器进行重新激活(revalidation)。
3 带宽优化
HTTP/1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了。例
如,客户端只需要显示一个文档的部分内容,又比如下载大文件时需要支持断点续传功能,而不是在发生断连后不得不重
新下载完整的包。HTTP/1.1 中在请求消息中引入了 range 头域,它允许只请求资源的某个部分。在响应消息中 Content-
Range 头域声明了返回的这部分对象的偏移值和长度。如果服务器相应地返回了对象所请求范围的内容,则响应码为
206(Partial Content),它可以防止 Cache 将响应误以为是完整的一个对象。另外一种情况是请求消息中如果包含比较
大的实体内容,但不确定服务器是否能够接收该请求(如是否有权限),此时若贸然发出带实体的请求,如果被拒绝也会
浪费带宽。HTTP/1.1 加入了一个新的状态码 100(Continue)。客户端事先发送一个只带头域的请求,如果服务器因为
权限拒绝了请求,就回送响应码 401(Unauthorized);如果服务器接收此请求就回送响应码 100,客户端就可以继续发
送带实体的完整请求了。注意, HTTP/1.0 的客户端不支持 100 响应码。但可以让客户端在请求消息中加入 Expect头
域,并将它的值设置为 100-continue。节省带宽资源的一个非常有效的做法就是压缩要传送的数据。 Content-Encoding
是对消息进行端到端(end-toend)的编码,它可能是资源在服务器上保存的固有格式(如 jpeg 图片格式);在请求消息
中加入 Accept-Encoding头域,它可以告诉服务器客户端能够解码的编码方式
4 长连接
HTTP/1.0 规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个 TCP 连接,服务器完成请
求处理后立即断开 TCP 连接,服务器不跟踪每个客户也不记录过去的请求。此外,由于大多数网页的流量都比较小,一
次 TCP 连接很少能通过 slow-start 区,不利于提高带宽利用率。HTTP 1.1 支持长连接(PersistentConnection)和请求
的流水线(Pipelining)处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延
迟。例如:一个包含有许多图像的网页文件的多个请求和应答可以在一个连接中传输,但每个单独的网页文件的请求和应
答仍然需要使用各自的连接。HTTP 1.1 还允许客户端不用等待上一次请求结果返回,就可以发出下一次请求,但服务器
端必须按照接收到客户端请求的先后顺序依次回送响应结果,以保证客户端能够区分出每次请求的响应内容,这样也显著
地减少了整个下载过程所需要的时间。
5 消息传递
HTTP 消息中可以包含任意长度的实体,通常它们使用 Content-Length 来给出消息结束标志。但是,对于很多动态产生的
响应,只能通过缓冲完整的消息来判断消息的大小,但这样做会加大延迟。如果不使用长连接,还可以通过连接关闭的信
号来判定一个消息的结束。HTTP/1.1 中引入了 Chunkedtransfer-coding 来解决上面这个问题,发送方将消息分割成若干
个任意大小的数据块,每个数据块在发送时都会附上块的长度,最后用一个零长度的块作为消息结束的标志。这种方法允
许发送方只缓冲消息的一个片段,避免缓冲整个消息带来的过载。在 HTTP/1.0 中,有一个 Content-MD5 的头域,要计算
这个头域需要发送方缓冲完整个消息后才能进行。而HTTP/1.1 中,采用 chunked 分块传递的消息在最后一个块(零长
度)结束之后会再传递一个拖尾(trailer),它包含一个或多个头域,这些头域是发送方在传递完所有块之后再计算出值
的。发送方会在消息中包含一个 Trailer 头域告诉接收方这个拖尾的存在。
6 Host 头域
在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随
着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed WebServers),并且它们共享一个
IP 地址。HTTP1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误
(400Bad Request)。此外,服务器应该接受以绝对路径标记的资源请求。
7 错误提示
HTTP/1.0 中只定义了 16 个状态响应码,对错误或警告的提示不够具体。 HTTP/1.1 引入了一个Warning 头域,增加对错
误或警告信息的描述。此外,在 HTTP/1.1 中新增了 24 个状态响应码,如 409(Conflict)表示请求的资源与资源的当前
状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
http 常见的状态码有哪些?
200 OK //客户端请求成功
301 Moved Permanently(永久移除),请求的 URL 已移走。 Response 中应该包含一个 Location
URL, 说明资源现在所处的位置
302 found 重定向
400 Bad Request //客户端请求有语法错误, 不能被服务器所理解
401 Unauthorized //请求未经授权,这个状态代码必须和 WWW-Authenticate 报头域一起使用
403 Forbidden //服务器收到请求,但是拒绝提供服务
404 Not Found //请求资源不存在, eg:输入了错误的 URL
500 Internal Server Error //服务器发生不可预期的错误
503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
GET 和 POST 的区别?
GET 请求的数据会附在 URL 之后(就是把数据放置在 HTTP 协议头中),以?分割 URL 和传输数据,参数之间以&
相连,如: login.action?name=zhagnsan&password=123456。POST 把提交的数据则放置在是 HTTP 包的包体中。
GET 方式提交的数据最多只能是 1024 字节,理论上 POST 没有限制,可传较大量的数据。其实这样说是错误的,不
准确的: GET 方式提交的数据最多只能是 1024 字节”,因为 GET 是通过 URL 提交数据,那么 GET 可提交的数据
量就跟URL 的长度有直接关系了。而实际上, URL 不存在参数上限的问题, HTTP 协议规范没有对 URL 长度进行
限制。这个限制是特定的浏览器及服务器对它的限制。IE对URL长度的限制是2083 字节(2K+35)。对于其他浏览器,
如 Netscape、FireFox 等,理论上没有长度限制,其限制取决于操作系统的支持。
POST 的安全性要比 GET 的安全性高。注意:这里所说的安全性和上面 GET 提到的“安全”不是同个概念。上面“安
全”的含义仅仅是不作数据修改,而这里安全的含义是真正的 Security 的含义,比如:通过GET 提交数据,用户名和
密码将明文出现在 URL 上,因为(1)登录页面有可能被浏览器缓存, (2)其他人查看浏览器的历史纪录,那么别人就可
以拿到你的账号和密码了,除此之外,使用 GET 提交数据还可能会造成 Cross-site request forgery 攻击。Get 是向
服务器发索取数据的一种请求,而 Post 是向服务器提交数据的一种请求,在 FORM(表单)中, Method默认
为”GET”,实质上, GET 和 POST 只是发送机制不同,并不是一个取一个发!
http 中重定向和请求转发的区别?
本质区别: 转发是服务器行为,重定向是客户端行为。
重定向特点:两次请求,浏览器地址发生变化,可以访问自己 web 之外的资源,传输的数据会丢失。
请求转发特点:一次强求,浏览器地址不变,访问的是自己本身的 web 资源,传输的数据不会丢失。
Cookie 和 Session 的区别
Cookie 是 web 服务器发送给浏览器的一块信息,浏览器会在本地一个文件中给每个 web 服务器存储cookie。以后浏览器
再给特定的 web 服务器发送请求时,同时会发送所有为该服务器存储的 cookie。Session 是存储在 web 服务器端的一块
信息。 session 对象存储特定用户会话所需的属性及配置信息。当用户在应用程序的 Web 页之间跳转时,存储在 Session
对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。
Cookie 和 session 的不同点:
1、无论客户端做怎样的设置, session 都能够正常工作。当客户端禁用 cookie 时将无法使用cookie。
2、在存储的数据量方面: session 能够存储任意的 java 对象, cookie 只能存储 String 类型的对象
session 共享怎么做的(分布式如何实现 session 共享)
问题描述:一个用户在登录成功以后会把用户信息存储在 session 当中,这时 session 所在服务器为server1,那么用户在
session 失效之前如果再次使用 app,那么可能会被路由到 server2,这时问题来了, server没有该用户的session,所以
需要用户重新登录,这时的用户体验会非常不好,所以我们想如何实现多台 server 之间共享 session,让用户状态保存。
1、服务器实现的 session 复制或 session 共享,这类型的共享 session 是和服务器紧密相关的,比如webSphere或
JBOSS 在搭建集群时候可以配置实现 session 复制或 session 共享,但是这种方式有一个致命的缺点,就是不好扩展和
移植,比如我们更换服务器,那么就要修改服务器配置。
2、利用成熟的技术做session复制,比如12306使用的gemfire,比如常见的内存数据库如redis或memorycache,这类方
案虽然比较普适,但是严重依赖于第三方,这样当第三方服务器出现问题的时候,那么将是应用的灾难。
3、将 session 维护在客户端,很容易想到就是利用 cookie,但是客户端存在风险,数据不安全,而且可以存放的 数据量
比较小,所以将 session 维护在客户端还要对 session 中的信息加密。我们实现的方案可以说是第二种方案和第三种方案
的合体,可以利用 gemfire 实现 session 复制共享,还可以将session 维护在 redis 中实现 session 共享,同时可以将
session 维护在客户端的 cookie 中,但是前提是数据要加密。
这三种方式可以迅速切换,而不影响应用正常执行。我们在实践中,首选 gemfire 或者 redis 作为session 共享的载体,一
旦 session 不稳定出现问题的时候,可以紧急切换 cookie 维护 session 作为备用,不影响应用提供服务。这里主要讲解
redis 和 cookie 方案, gemfire 比较复杂大家可以自行查看 gemfire 工作原理。利用redis 做session 共享,首先需要与业
务逻辑代码解耦,不然 session 共享将没有意义,其次支持动态切换到客户端 cookie 模式。 redis 的方案是重写服务器中
的 HttpSession和 HttpServletRequest,首先实现HttpSession 接口,重写 session的所有方法,将 session 以 hash 值的
方式存在 redis 中,一个session 的 key 就是 sessionID, setAtrribute 重写之后就是更新 redis 中的数据, getAttribute 重
写之后就是获取 redis 中的数据,等等需要将 HttpSession 的接口一一实现实现了 HttpSesson,那么我们先将该 session
类叫做 MySession(当然实践中不是这么命名的),当MySession出现之后问题才开始,怎么能在不影响业务逻辑代码的
情况下,还能让原本的 request.getSession()获取到的是MySession,而不是服务器原生的 session。这里,我决定重
写服务器的HttpServletRequet,这里先称为 MyRequest,但是这可不是单纯的重写,我需要在原生的 request 基础上重
写,于是我决定在 filter 中,实现 request 的偷梁换柱,我的思路是这样的, MyRequest 的构建器,必须以 request 作为
参数,于是我在 filter 中将服务器原生的 request(也有 可能 是框 架封装过的 request ) ,当 做参数 new 出 来一 个
MyRequest ,并 且 MyRequest 也 实现 了HttpServletRequest 接口,其实就是对原生 request 的一个增强,这里主要重
写了几个 request 的方法,但是最重要的是重写了 request.getSession(),写到这里大家应该都明白为什么重写这个方法
了吧,当然是为了获取 MySession,于是这样就在filter中,偷偷的将原生的request换成MyRequest了,然后再将替换过
的request传入chan.doFilter(),这样 filter 时候的代码都使用的是 MyRequest 了,同时对业务代码是透明的,业务代码获
取 session 的方法仍然request.getSession(),但其实获取到的已经是 MySession 了,这样对 session 的操作已经变成
了对 redis 的操作。这样实现的好处有两个,第一开发人员不需要对 session 共享做任何关注, session 共享对用户是透
明的;第二, filter是可配置的,通过 filter 的方式可以将 session 共享做成一项可插拔的功能,没有任何侵入性。这个时
候已经实现了一套可插拔的 session 共享的框架了,但是我们想到如果 redis 服务出了问题,这时我们该怎么办呢,于是
我们延续 redis 的想法,想到可以将 session 维护在客户端内(加密的 cookie),当然实现方法还是一样的,我们重写
HttpSession 接口,实现其所有方法,比如 setAttribute 就是写入cookie, getAttribute 就是读取cookie,我们可以将重写
的 session 称作 MySession2,这时怎么让开发人员透明的获取到 MySession2 呢,实现方法还是在 filter 内偷梁换柱,在
MyRequest 加一个判断,读取 sessionType 配置,如果 sessionType 是 redis 的,那么 getSession 的时候获取到的是
MySession,如果 sessionType 是 coolie 的,那么 getSession 的时候获取到的是MySession2,以此类推,用同样的方法
就可以获取到 MySession 3,4,5,6 等等。这样两种方式都有了,那么我们怎实现两种 session 共享方式的快速切换呢,刚
刚我提到一个sessionType,这是用来决定获取到session的类型的,只要变换sessionType就能实现两种session共享方式
的切换,但是sessionType必须对所有的服务器都是一致的,如果不一致那将会出现比较严重的问题,我们目前是将
sessionType 维护在环境变量里,如果要切换 sessionType 就要重启每一台服务器,完成 session共享的转换,但是当服
务器太多的时候将是一种灾难。而且重启服务意味着服务的中断,所以这样的方式只适合服务器规模比较小,而且用户量
比较少的情况,当服务器太多的时候,务必需要一种协调技术,能够让服务器能够及时获取切换的通知。基于这样的原
因,我们选用zookeeper 作为配置平台,每一台服务器都会订阅 zookeeper 上的配置,当我们切换 sessionType 之后,
所有服务器都会订阅到修改之后的配置,那么切换就会立即生效,当然可能会有短暂的时间延迟,但这是可以接受的。
在单点登录中,如果 cookie 被禁用了怎么办?
单点登录的原理是后端生成一个 session ID,然后设置到 cookie,后面的所有请求浏览器都会带上cookie,然后服务端从
cookie 里获取 session ID,再查询到用户信息。所以,保持登录的关键不是 cookie,而是通过cookie 保存和传输的
session ID,其本质是能获取用户信息的数据。除了 cookie,还通常使用 HTTP请求头来传输。但是这个请求头浏览器不
会像 cookie 一样自动携带,需要手工处理。
什么是 jsp,什么是 Servlet? jsp 和 Servlet 有什么区别?
jsp 本质上就是一个 Servlet,它是 Servlet 的一种特殊形式(由 SUN 公司推出),每个 jsp 页面都是一个 servlet实例。
Servlet 是由 Java 提供用于开发 web 服务器应用程序的一个组件,运行在服务端,由 servlet 容器管理,用来生成动态内
容。一个 servlet 实例是实现了特殊接口 Servlet 的 Java 类,所有自定义的 servlet 均必须实现 Servlet 接口。
区别:
jsp 是 html 页面中内嵌的 Java 代码,侧重页面显示;
Servlet 是 html 代码和 Java 代码分离,侧重逻辑控制, mvc 设计思想中 jsp 位于视图层, servlet 位于控制层Jsp 运行机
制:如下图
JVM 只能识别 Java 类,并不能识别 jsp 代码! web 容器收到以.jsp 为扩展名的 url 请求时,会将访问请求交给tomcat 中
jsp 引擎处理,每个 jsp 页面第一次被访问时, jsp 引擎将 jsp 代码解释为一个 servlet 源程序,接着编译servlet 源程序生
成.class 文件,再有 web 容器 servlet 引擎去装载执行 servlet 程序,实现页面交互。
jsp 有哪些域对象和内置对象及他们的作用?
四大域对象: (1) pageContext page 域-指当前页面,在当前 jsp 页面有效,跳到其它页面失效 (2) request request 域-指一次请求范围内有效,从 http 请求到服务器处理结束,返回响应的整个过程。在这个过程中使用 forward(请求转发)方式跳转多个 jsp,在这些页面里你都可以使用这个变量 (3) session session 域-指当前会话有效范围,浏览器从打开到关闭过程中,转发重定向均可以使用 (4) application context 域-指只能在同一个 web 中使用,服务器未关闭或者重启,数据就有效
九大内置对象:
生命中周期 | 作用域 | 使用情况 | |
---|---|---|---|
Request | 一次请求 | 只在Jsp页面内有效 | 用于接受通过 HTTP 协议传送到服务器 的数据(包括头信息、系统信息、请 求方式以及请求参数等)。 |
Reponse | 一次请求 | 只在Jsp页面内有效 | 表示服务器端对客户端的回应。主要 用于设置头信息、跳转、 Cookie |
Session | 从存入数据开始,默认闲置 30 分钟后失 效 | 会话内有效 | 用于存储特定的用户会话所需的信息 |
Out | 用于在 Web 浏览器内输出信息,并且 管理应用服务器上的输出缓冲区 | ||
PageContext | 用于存取其他隐含对象,如 request、reponse、 session、 application 等对 象。实际上, pageContext 对象提供了对JSP 页面所有的对象及命名空间的访问 | ||
Page | page 对象代表 JSP 本身(对应 this) , 只有在 JSP 页面内才是合法的 | ||
Exception | 显示异常信息, 必须在 page 指令中设 定%@ page isErrorPage=”true” %>才能 使用,在一般的 JSP 页面中使用该对象 将无法编译JSP 文件 | ||
Application | 服务器启动发送第一个请求时就产生了 Application 对象,直到服务器关闭。 | 用于存储和访问来自任何页面的变量 所有的用户分享一个 Application 对象 | |
Config | 取得服务器的配置信息 |
什么是 xml,使用 xml 的优缺点, xml 的解析器有哪几种,分别有什么区别?
xml 是一种可扩展性标记语言,支持自定义标签(使用前必须预定义)使用 DTD 和 XML Schema 标准化XML结构优点:
用于配置文件,格式统一,符合标准;用于在互不兼容的系统间交互数据,共享数据方便;缺点: xml 文件格式复杂,数
据传输占流量,服务端和客户端解析 xml 文件占用大量资源且不易维护Xml 常用解析器有 2 种,分别是: DOM 和 SAX;
主要区别在于它们解析 xml 文档的方式不同。使用 DOM 解析, xml 文档以 DOM树形结构加载入内存,而 SAX 采用的是
事件模型。
谈谈你对 ajax 的认识
Ajax 是一种创建交互式网页应用的的网页开发技术; Asynchronous JavaScript and XML”的缩写。Ajax 的优势:通过异
步模式,提升了用户体验。优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用。Ajax 引擎在客
户端运行承担了一部分本来由服务器承担的工作,从而减少了大用户量下的服务器负载。
Ajax 的最大特点:可以实现局部刷新,在不更新整个页面的前提下维护数据,提升用户体验度
jsonp 原理
JavaScript 是一种在 Web 开发中经常使用的前端动态脚本技术。在 JavaScript 中,有一个很重要的安全性限制,被称
为“Same-Origin Policy”(同源策略)。这一策略对于 JavaScript 代码能够访问的页面内容做了很重要的限制,即
JavaScript 只能访问与包含它的文档在同一域下的内容。JavaScript 这个安全策略在进行多 iframe 或多窗口编程、以及
Ajax 编程时显得尤为重要。根据这个策略,在 baidu.com 下的页面中包含的 JavaScript 代码,不能访问在 google.com 域
名下的页面内容;甚至不同的子域名之间的页面也不能通过 JavaScript 代码互相访问。对于 Ajax 的影响在于,通过
XMLHttpRequest 实现的Ajax 请求,不能向不同的域提交请求,例如,在 abc.example.com 下的页面,不能向
def.example.com 提交Ajax 请求,等等。然而,当进行一些比较深入的前端编程的时候,不可避免地需要进行跨域操作,
这时候“同源策略”就显得过于苛刻。 JSONP 跨域 GET 请求是一个常用的解决方案,下面我们来看一下 JSONP 跨域是如
何实现的,并且探讨下 JSONP 跨域的原理。jsonp 的最基本的原理是:动态添加一个标签, 使用 script 标签的 src 属性
没有跨域的限制的特点实现跨域。首先在客户端注册一个 callback, 然后把 callback 的名字传给服务器。此时,服务器先
生成 json数据。 然后以 javascript 语法的方式,生成一个 function , function 名字就是传递上来的参数 jsonp。最后将json
数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。客户端浏览器,解析
script 标签,并执行返回的 javascript 文档,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里。
谈谈你对 restful 的理解以及在项目中的使用?
一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的
软件。 REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful。它结构清晰、
符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
什么是 webService?
WebService 是一种跨编程语言和跨操作系统平台的远程调用技术。所谓跨编程语言和跨操作平台,就是说服务端程序采
用 java 编写,客户端程序则可以采用其他编程语言编写,反之亦然!跨操作系统平台则是指服务端程序和客户端程序可
以在不同的操作系统上。
常见的远程调用技术
RMI 是 java 语言本身提供的远程通讯协议,稳定高效,是 EJB 的基础。但它只能用于 JAVA 程序之间的通讯。Hessian
和 Burlap 是 caucho 公司提供的开源协议,基于 HTTP 传输,服务端不用开防火墙端口。协议的规范公开,可以用于任
意语言。跨平台有点小问题。Httpinvoker 是 SpringFramework 提供的远程通讯协议,只能用于 JAVA 程序间的通讯,且
服务端和客户端必须使用 SpringFramework。Web service 是连接异构系统或异构语言的首选协议,它使用 SOAP 形式通
讯,可以用于任何语言,目前的许多开发工具对其的支持也很好。效率相比: RMI > Httpinvoker >= Hessian >> Burlap
>> web service。
什么是java序列化,如何实现java序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,
也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。序列化的实现:将需
要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被
序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用
ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输
入流。
保存(持久化)对象及其状态到内存或者磁盘
Java 平台允许我们在内存中创建可复用的 Java 对象,但一般情况下,只有当 JVM 处于运行时,这些对象才可能存在,
即,这些对象的生命周期不会比 JVM 的生命周期更长。 但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久
化)指定的对象,并在将来重新读取被保存的对象。Java 对象序列化就能够帮助我们实现该功能。
序列化对象以字节数组保持-静态成员不保存
使用 Java 对象序列化, 在保存对象时,会把其状态保存为一组字节,在未来, 再将这些字节组装成对象。必须注意地
是, 对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
序列化用户远程对象传输
除了在持久化对象时会用到对象序列化之外,当使用 RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列
化。 Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。
Serializable 实现序列化
在 Java 中, 只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化。ObjectOutputStream 和
ObjectInputStream 对对象进行序列化及反序列化通过 ObjectOutputStream和 ObjectInputStream 对对象进行序列化及反
序列化。
writeObject 和 readObject 自定义序列化策略
在类中增加 writeObject 和 readObject 方法可以实现自定义序列化策略。
序列化 ID
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致
就是 private static final long serialVersionUID
序列化并不保存静态变量
序列化子父类说明 要想将父类对象也序列化,就需要让父类也实现 Serializable 接口。
Transient 关键字阻止该变量被序列化到文件中
在变量声明前加上 Transient 关键字,可以阻止该变量被序列化到文件中,在被反序列 化后, transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串 等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在 客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的 数据安全。
序列化(深 clone 一中实现)
在 Java 语言里深复制一个对象,常常可以先使对象实现 Serializable 接口,然后把对象(实际上只是对象的一个拷贝)
写到一个流里,再从流里读出来,便可以重建对象。