【Java 面试题总结 4】Java Web、网络、设计模式综合篇
1、存储位置不同
cookie 在客户端浏览器;
session 在服务器;
2、存储容量不同
cookie<=4K,一个站点最多保留 20 个 cookie;
session 没有上线,出于对服务器的保护,session 内不可存过多东西,并且要设置 session 删除机制;
3、存储方式不同
cookie 只能保存 ASCII 字符串,并需要通过编码方式存储为 Unicode 字符或者二进制数据;
session 中能存储任何类型的数据,包括并不局限于 String、integer、list、map 等;
4、隐私策略不同
cookie 对客户端是可见的,不安全;
session 存储在服务器上,安全;
5、有效期不同
开发可以通过设置 cookie 的属性,达到使 cookie 长期有效的效果;
session 依赖于名为 JESSIONID 的 cookie,而 cookie JSESSIONID 的过期时间默认为-1,只需关闭窗口该 session 就会失效,因而 session 达不到长期有效的效果;
6、跨域支持上不同
cookie 支持跨域;
session 不支持跨域;
六、如果客户端禁止 cookie 能实现 session 还能用吗?
一般默认情况下,在会话中,服务器存储 session 的 sessionid 是通过 cookie 存到浏览器里。
如果浏览器禁用了 cookie,浏览器请求服务器无法携带 sessionid,服务器无法识别请求中的用户身份,session 失效。
但是可以通过其他方法在禁用 cookie 的情况下,可以继续使用 session。
通过 url 重写,把 sessionid 作为参数追加的原 url 中,后续的浏览器与服务器交互中携带 sessionid 参数。
服务器的返回数据中包含 sessionid,浏览器发送请求时,携带 sessionid 参数。
通过 Http 协议其他 header 字段,服务器每次返回时设置该 header 字段信息,浏览器中 js 读取该 header 字段,请求服务器时,js 设置携带该 header 字段。
多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
1、session 机制
session 是服务端存储的一个对象,主要用来存储所有访问过该服务端的客户端的用户信息(也可以存储其他信息),从而实现保持用户会话状态。但是服务器重启时,内存会被销毁,存储的用户信息也就消失了。
不同的用户访问服务端的时候会在 session 对象中存储键值对,“键”用来存储开启这个用户信息的“钥匙”,在登录成功后,“钥匙”通过 cookie 返回给客户端,客户端存储为 sessionId 记录在 cookie 中。当客户端再次访问时,会默认携带 cookie 中的 sessionId 来实现会话机制。
(1)session 是基于 cookie 的。
cookie 的数据 4k 左右;
cookie 存储数据的格式:字符串 key=value
cookie 存储有效期:可以自行通过 expires 进行具体的日期设置,如果没设置,默认是关闭浏览器时失效。
cookie 有效范围:当前域名下有效。所以 session 这种会话存储方式方式只适用于客户端代码和服务端代码运行在同一台服务器上(前后端项目协议、域名、端口号都一致,即在一个项目下)
(2)session 持久化
用于解决重启服务器后 session 消失的问题。在数据库中存储 session,而不是存储在内存中。通过包:express-mysql-session。
当客户端存储的 cookie 失效后,服务端的 session 不会立即销毁,会有一个延时,服务端会定期清理无效 session,不会造成无效数据占用存储空间的问题。
2、token 机制
适用于前后端分离的项目(前后端代码运行在不同的服务器下)
请求登录时,token 和 sessionid 原理相同,是对 key 和 key 对应的用户信息进行加密后的加密字符,登录成功后,会在响应主体中将{token:“字符串”}返回给客户端。
客户端通过 cookie 都可以进行存储。再次请求时不会默认携带,需要在请求拦截器位置给请求头中添加认证字段 Authorization 携带 token 信息,服务器就可以通过 token 信息查找用户登录状态。
当客户端登录完成后,会在服务端产生一个 session,此时服务端会将 sessionid 返回给客户端浏览器。客户端将 sessionid 储存在浏览器的 cookie 中,当用户再次登录时,会获得对应的 sessionid,然后将 sessionid 发送到服务端请求登录,服务端在内存中找到对应的 sessionid,完成登录,如果找不到,返回登录页面。
网络篇
===
一、http 响应码 301 和 302 代表的是什么?有什么区别?
301 和 302 状态码都表示重定向,当浏览器拿到服务器返回的这个状态码后悔自动跳转到一个新的 URL 地址。
301 代表永久性重定向,旧地址被永久移除,客户端向新地址发送请求。
302 代表暂时性重定向,旧地址还在,客户端继续向旧地址发送请求。
303 代表暂时性重定向,重定向到新地址时,必须使用 GET 方法请求新地址。
307 代表暂时性重定向,与 302 的区别在于 307 不允许从 POST 改为 GET。
307 代表永久性重定向,与 301 的区别在于 308 不允许从 POST 改为 GET。
TCP 是传输控制协议,UDP 是用户数据表协议;
TCP 长连接,UDP 无连接;
UDP 程序结构较简单,只需发送,无须接收;
TCP 可靠,保证数据正确性、顺序性;UDP 不可靠,可能丢数据;
TCP 适用于少量数据,UDP 适用于大量数据传输;
TCP 速度慢,UDP 速度快;
因为客户端和服务端都要确认连接,①客户端请求连接服务端;②针对客户端的请求确认应答,并请求建立连接;③针对服务端的请求确认应答,建立连接;
两次无法确保 A 能收到 B 的数据;
五、get 和 post 请求有哪些区别?
get 请求参数是连接在 url 后面的,而 post 请求参数是存放在 requestbody 内的;
get 请求因为浏览器对 url 长度有限制,所以参数个数有限制,而 post 请求参数个数没有限制;
因为 get 请求参数暴露在 url 上,所以安全方面 post 比 get 更加安全;
get 请求只能进行 url 编码,而 post 请求可以支持多种编码方式;
get 请求参数会保存在浏览器历史记录内,post 请求并不会;
get 请求浏览器会主动 cache,post 并不会,除非主动设置;
get 请求产生 1 个 tcp 数据包,post 请求产生 2 个 tcp 数据包;
在浏览器进行回退操作时,get 请求是无害的,而 post 请求则会重新请求一次;
浏览器在发送 get 请求时会将 header 和 data 一起发送给服务器,服务器返回 200 状态码,而在发送 post 请求时,会先将 header 发送给服务器,服务器返回 100,之后再将 data 发送给服务器,服务器返回 200 OK;
xss(Cross Site Scripting),即跨站脚本攻击,是一种常见于 web 应用程序中的计算机安全漏洞。指的是在用户浏览器上,在渲染 DOM 树的时候,执行了不可预期的 JS 脚本,从而发生了安全问题。
XSS 就是通过在用户端注入恶意的可运行脚本,若服务端对用户的输入不进行处理,直接将用户的输入输出到浏览器,然后浏览器将会执行用户注入的脚本。 所以 XSS 攻击的核心就是浏览器渲染 DOM 的时候将文本信息解析成 JS 脚本从而引发 JS 脚本注入,那么 XSS 攻击的防御手段就是基于浏览器渲染这一步去做防御。只要我们使用 HTML 编码将浏览器需要渲染的信息编码后,浏览器在渲染 DOM 元素的时候,会自动解码需要渲染的信息,将上述信息解析成字符串而不是 JS 脚本,这就是我们防御 XSS 攻击的核心想法。
预防:
1、获取用户的输入,不用 innerHtml,用 innerText.
2、对用户的输入进行过滤,如对 & < > " ' /等进行转义;
跨站请求伪造(英语:Cross-site request forgery),也被称为?one-click attack?或者?session riding,通常缩写为?CSRF?或者?XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS?利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
1、攻击细节
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
例子
假如一家银行用以运行转账操作的 URL 地址如下:http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
那么,一个恶意攻击者可以在另一个网站上放置如下代码: <img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">
如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金。
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成信息的网站中。这意味着如果服务端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
透过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义运行操作。
2、防御措施
检查 Referer 字段
HTTP 头中有一个 Referer 字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer 字段应和请求的地址位于同一域名下。以上文银行操作为例,Referer 字段地址通常应该是转账按钮所在的网页地址,应该也位于 www.examplebank.com 之下。而如果是 CSRF 攻击传来的请求,Referer 字段会是包含恶意网址的地址,不会位于 www.examplebank.com 之下,这时候服务器就能识别出恶意的访问。
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的 Referer 字段。虽然 http 协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其 Referer 字段的可能。
3、添加校验 token
由于 CSRF 的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在 cookie 中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再运行 CSRF 攻击。这种数据通常是窗体中的一个数据项。服务器将其生成并附加在窗体中,其内容是一个伪随机数。当客户端通过窗体提交请求时,这个伪随机数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪随机数,而通过 CSRF 传来的欺骗性攻击中,攻击者无从事先得知这个伪随机数的值,服务端就会因为校验 token 的值为空或者错误,拒绝这个可疑请求。
2、最流行的跨域方案 cors
cors 是目前主流的跨域解决方案,跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的 Web 应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
3、最方便的跨域方案 Nginx
nginx 是一款极其强大的 web 服务器,其优点就是轻量级、启动快、高并发。
现在的新项目中 nginx 几乎是首选,我们用 node 或者 java 开发的服务通常都需要经过 nginx 的反向代理。
反向代理的原理很简单,即所有客户端的请求都必须先经过 nginx 的处理,nginx 作为代理服务器再讲请求转发给 node 或者 java 服务,这样就规避了同源策略。
WebSocket 是一个允许 Web 应用程序(通常指浏览器)与服务器进行双向通信的协议。HTML5 的 WebSocket API 主要是为浏览器端提供了一个基于 TCP 协议实现全双工通信的方法。
WebSocket 优势: 浏览器和服务器只需要要做一个握手的动作,在建立连接之后,双方可以在任意时刻,相互推送信息。同时,服务器与客户端之间交换的头信息很小。
发送方需要等缓冲区满才能发送出去,造成粘包;
接收方不及时接收缓冲区的包,造成粘包;
设计模式篇
=====
一、请列举出在 JDK 中几个常用的设计模式?
1、单例模式
作用:保证类只有一个实例。
JDK 中体现:Runtime 类。
2、静态工厂模式
作用:代替构造函数创建对象,方法名比构造函数清晰。
JDK 中体现:Integer.valueOf、Class.forName
3、抽象工厂
作用:创建某一种类的对象。
JDK 中体现:Java.sql 包。
4、原型模式
clone();
原型模式的本质是拷贝原型来创建新的对象,拷贝是比 new 更快的创建对象的方法,当需要大批量创建新对象而且都是同一个类的对象的时候考虑使用原型模式。
一般的克隆只是浅拷贝(对象的 hash 值不一样,但是对象里面的成员变量的 hash 值是一样的)。
有些场景需要深拷贝,这时我们就要重写 clone 方法,以 ArrayList 为例:
5、适配器模式
作用:使不兼容的接口相容。
JDK 中体现:InputStream、OutputStream。
6、装饰器模式
作用:为类添加新的功能,防止类继承带来的类爆炸。
JDK 中体现:io 类、Collections、List。
7、外观模式
作用:封装一组交互类,一直对外提供接口。
JDK 中体现:logging 包。
8、享元模式
作用:共享对象、节省内存。
JDK 中体现:Integer.valueOf、String 常量池。
9、代理模式
作用:
(1)透明调用被代理对象,无须知道复杂实现细节;
(2)增加被代理类的功能;
JDK 中体现:动态代理。
10、迭代器模式
作用:将集合的迭代和集合本身分离。
JDK 中体现:Iterator
11、命令模式
作用:封装操作,使接口一致。
JDK 中体现:Runable、Callable、ThreadPoolExecutor。
二、什么是设计模式?你是否在你的代码里面使用过任何设计模式?
1、什么是设计模式?
设计模式是解决软件开发某些特定问题而提出的一些解决方案,也可以理解为解决问题的一些固定思路。
通过设计模式可以帮助我们增强代码的可复用性、可扩展性、灵活性。
我们使用设计模式的最终目的是实现代码的高内聚、低耦合。
2、设计模式的七大原则
单一职责原则
接口隔离原则
依赖倒转原则
里式替换原则
开闭原则
迪米特法则
合成复用原则
3、你是否在你的代码里面使用过任何设计模式?
(1)单例模式
JDK 种的 runtime,Spring 种的 singeton。
(2)简单工厂模式
Spring 的 BeanFactory,根据传入一个唯一标识来获得 bean 对象。
(3)原型模式
clone()
(4)代理模式
Spring 的 AOP 中,Spring 实现 AOP 功能的原理就是代理模式,①JDK 动态代理。②CGLIB 动态代理,使用 Advice(通知)对类进行方法级别的切面增强。
(5)装饰器模式
为类添加新的功能,防止类爆炸;
IO 流、数据源包装,Spring 中用到的装饰器模式表现在 Wrapper。
三、Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
保证程序只有一个对象的实例,叫做单例模式;
内部类的方式实现单例模式,是线程安全的;
双重验证方式实现单例模式也是线程安全的;
评论