2019年快结束了,给你们整理了今年来最经典的面试真题100道,每一个题目都有详细的解答,收集了java基础,容器,多线程,反射,对象拷贝,Java Web,异常,网络,设计模式,Spring / Spring MVC,等专题的经典面试真题,和详细分析。没道题目都详细讲解,文章过长,你们必定要耐心的看完哦。php
具体来讲 JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了不少 java 程序调试和分析的工具。简单来讲:若是你须要运行 java 程序,只需安装 JRE 就能够了,若是你须要编写 java 程序,须要安装 JDK。html
== 解读前端
对于基本类型和引用类型 == 的做用效果是不一样的,以下所示:java
代码示例:程序员
String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true复制代码
代码解读:由于 x 和 y 指向的是同一个引用,因此 == 也是 true,而 new String()方法则重写开辟了内存空间,因此 == 结果为 false,而 equals 比较的一直是值,因此结果都为 true。web
equals 解读面试
equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。看下面的代码就明白了。正则表达式
首先来看默认状况下 equals 比较一个有相同值的对象,代码以下:算法
class Cat {
public Cat(String name) {
this.name = name;
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Cat c1 = new Cat;
Cat c2 = new Cat;
System.out.println(c1.equals(c2)); // false复制代码
输出结果出乎咱们的意料,居然是 false?这是怎么回事,看了 equals 源码就知道了,源码以下:spring
public boolean equals(Object obj) {
return (this == obj);
}复制代码
原来 equals 本质上就是 ==。
那问题来了,两个相同值的 String 对象,为何返回的是 true?代码以下:
String s1 = new String("老王");
String s2 = new String("老王");
System.out.println(s1.equals(s2)); // true复制代码
一样的,当咱们进入 String 的 equals 方法,找到了答案,代码以下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}复制代码
原来是 String 重写了 Object 的 equals 方法,把引用比较改为了值比较。
总结 :== 对于基本类型来讲是值比较,对于引用类型来讲是比较的是引用;而 equals 默认状况下是引用比较,只是不少类从新了 equals 方法,好比 String、Integer 等把它变成了值比较,因此通常状况下 equals 比较的是值是否相等。
不对,两个对象的 hashCode()相同,equals()不必定 true。
代码示例:
String str1 = "通话";
String str2 = "重地";
System.out.println(String.format("str1:%d | str2:%d", str1.hashCode(),str2.hashCode()));
System.out.println(str1.equals(str2));复制代码
执行的结果:
str1:1179395 | str2:1179395
false复制代码
代码解读:很显然“通话”和“重地”的 hashCode() 相同,然而 equals() 则为 false,由于在散列表中,hashCode()相等即两个键值对的哈希值相等,然而哈希值相等,并不必定能得出键值对相等。
等于 -1,由于在数轴上取值时,中间值(0.5)向右取整,因此正 0.5 是往上取整,负 0.5 是直接舍弃。
String 不属于基础类型,基础类型有 8 种:byte、boolean、char、short、int、float、long、double,而 String 属于对象。
操做字符串的类有:String、StringBuffer、StringBuilder。
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操做都会生成新的 String 对象,而后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 能够在原有对象的基础上进行操做,因此在常常改变字符串内容的状况下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,因此在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
不同,由于内存的分配方式不同。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String("i") 则会被分到堆内存中。
使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
示例代码:
// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("abcdefg");
System.out.println(stringBuffer.reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abcdefg");
System.out.println(stringBuilder.reverse()); // gfedcba复制代码
不须要,抽象类不必定非要有抽象方法。
示例代码:
abstract class Cat {
public static void sayHi() {
System.out.println("hi~");
}
}复制代码
上面代码,抽象类并无抽象方法但彻底能够正常运行。
不能,定义抽象类就是让其余类继承的,若是定义为 final 该类就不能被继承,这样彼此就会产生矛盾,因此 final 不能修饰抽象类,以下图所示,编辑器也会提示错误信息:
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。
经常使用容器的图录:
对于在Map中插入、删除和定位元素这类操做,HashMap是最好的选择。然而,假如你须要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供全部可选的映射操做,并容许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
HashMap的数据结构: 在java编程语言中,最基本的结构就是两种,一个是数组,另一个是模拟指针(引用),全部的数据结构均可以用这两个基本结构来构造的,HashMap也不例外。HashMap其实是一个“链表散列”的数据结构,即数组和链表的结合体。
当咱们往Hashmap中put元素时,首先根据key的hashcode从新计算hash值,根绝hash值获得这个元素在数组中的位置(下标),若是该数组在该位置上已经存放了其余元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最早加入的放入链尾.若是数组中该位置没有元素,就直接将该元素放到数组的该位置上。
须要注意Jdk 1.8中对HashMap的实现作了优化,当链表中的节点数据超过八个以后,该链表会转为红黑树来提升查询效率,从原来的O(n)到O(logn)
最明显的区别是 ArrrayList底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。
poll() 和 remove() 都是从队列中取出一个元素,可是 poll() 在获取元素失败的时候会返回空,可是 remove() 失败的时候会抛出异常。
迭代器是一种设计模式,它是一个对象,它能够遍历并选择序列中的对象,而开发人员不须要了解该序列的底层结构。迭代器一般被称为“轻量级”对象,由于建立它的代价小。
Java中的Iterator功能比较简单,而且只能单向移动:
(1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。
(2) 使用next()得到序列中的下一个元素。
(3) 使用hasNext()检查序列中是否还有元素。
(4) 使用remove()将迭代器新返回的元素删除。
Iterator是Java迭代器最简单的实现,为List设计的ListIterator具备更多的功能,它能够从两个方向遍历List,也能够从List中插入和删除元素。
因此并发编程的目标是充分的利用处理器的每个核,以达到最高的处理性能。
简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程当中拥有独立的内存单元,而多个线程共享内存资源,减小切换次数,从而效率更高。线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间能够并发执行。
守护线程(即daemon thread),是个服务线程,准确地来讲就是服务其余的线程。
①. 继承Thread类建立线程类
②. 经过Runnable接口建立线程类
③. 经过Callable和Future建立线程
有点深的问题了,也看出一个Java程序员学习知识的广度。
线程一般都有五种状态,建立、就绪、运行、阻塞和死亡。
sleep():方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其余线程,等到休眠时间结束后,线程进入就绪状态和其余线程一块儿竞争cpu的执行时间。由于sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,可是对象的机锁没有被释放,其余线程依然没法访问这个对象。
wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其余线程可以访问,能够经过notify,notifyAll方法来唤醒等待的线程。
每一个线程都是经过某个特定Thread对象所对应的方法run()来完成其操做的,方法run()称为线程体。经过调用Thread类的start()方法来启动一个线程。
start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,能够直接继续执行下面的代码; 这时此线程是处于就绪状态, 并无运行。 而后经过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。而后CPU再调度其它线程。
run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 若是直接调用run(),其实就至关因而调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,因此执行路径仍是只有一条,根本就没有线程的特征,因此在多线程执行时要使用start()方法而不是run()方法。
①. newFixedThreadPool(int nThreads)
建立一个固定长度的线程池,每当提交一个任务就建立一个线程,直到达到线程池的最大数量,这时线程规模将再也不变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。
②. newCachedThreadPool()
建立一个可缓存的线程池,若是线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增长时,则能够自动添加新线程,线程池的规模不存在任何限制。
③. newSingleThreadExecutor()
这是一个单线程的Executor,它建立单个工做线程来执行任务,若是这个线程异常结束,会建立一个新的来替代它;它的特色是能确保依照任务在队列中的顺序来串行执行。
④. newScheduledThreadPool(int corePoolSize)
建立了一个固定长度的线程池,并且以延迟或定时的方式来执行任务,相似于Timer。
线程池有5种状态:Running、ShutDown、Stop、Tidying、Terminated。
线程池各个状态切换框架图:
线程安全在三个方面体现:
在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争状况逐渐升级。锁能够升级但不能降级。
锁升级的图示过程:
死锁是指两个或两个以上的进程在执行过程当中,因为竞争资源或者因为彼此通讯而形成的一种阻塞的现象,若无外力做用,它们都将没法推动下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。是操做系统层面的一个错误,是进程死锁的简称,最先在 1965 年由 Dijkstra 在研究银行家算法时提出的,它是计算机操做系统乃至整个并发程序设计领域最难处理的问题之一。
死锁的四个必要条件:
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不知足,就不会发生死锁。
理解了死锁的缘由,尤为是产生死锁的四个必要条件,就能够最大可能地避免、预防和 解除死锁。
因此,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确 定资源的合理分配算法,避免进程永久占据系统资源。
此外,也要防止进程在处于等待状态的状况下占用资源。所以,对资源的分配要给予合理的规划。
线程局部变量是局限于线程内部的变量,属于线程自身全部,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。可是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别当心,在这种状况下,工做线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工做完成后没有释放,Java 应用就存在内存泄露的风险。
synchronized能够保证方法或者代码块在运行时,同一时刻只有一个方法能够进入到临界区,同时它还能够保证共享变量的内存可见性。
Java中每个对象均可以做为锁,这是synchronized实现同步的基础:
synchronized是和if、else、for、while同样的关键字,ReentrantLock是类,这是两者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,能够被继承、能够有方法、能够有各类各样的类变量,ReentrantLock比synchronized的扩展性体如今几点上:
另外,两者的锁机制其实也是不同的:ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操做的应该是对象头中mark word。
Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操做时,具备排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程能够向自旋锁同样,继续尝试,一直等到执行成功。
Atomic系列的类中的核心方法都会调用unsafe类中的几个本地方法。咱们须要先知道一个东西就是Unsafe类,全名为:sun.misc.Unsafe,这个类包含了大量的对C代码的操做,包括不少直接内存分配以及原子操做的调用,而它之因此标记为非安全的,是告诉你这个里面大量的方法调用都会存在安全隐患,须要当心使用,不然会致使严重的后果,例如在经过unsafe分配内存的时候,若是本身指定某些区域可能会致使一些相似C++同样的指针越界到其余进程的问题。
反射主要是指程序能够访问、检测和修改它自己状态或行为的一种能力
Java反射:
在Java运行时环境中,对于任意一个类,可否知道这个类有哪些属性和方法?对于任意一个对象,可否调用它的任意一个方法
Java反射机制主要提供了如下功能:
简单说就是为了保存在内存中的各类对象的状态(也就是实例变量,不是方法),而且能够把保存的对象状态再读出来。虽然你能够用你本身的各类各样的方法来保存object states,可是Java给你提供一种应该比你本身好的保存对象状态的机制,那就是序列化。
什么状况下须要序列化:
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想经过RMI传输对象的时候;
动态代理:
当想要给实现了某个接口的类中的方法,加一些额外的处理。好比说加日志,加事务等。能够给这个类建立一个代理,故名思议就是建立一个新的类,这个类不只包含原来类方法的功能,并且还在原来的基础上添加了额外处理的新类。这个代理类并非定义好的,是动态生成的。具备解耦意义,灵活,扩展性强。
动态代理的应用:
首先必须定义一个接口,还要有一个InvocationHandler(将实现接口的类的对象传递给它)处理类。再有一个工具类Proxy(习惯性将其称为代理类,由于调用他的newInstance()能够产生代理对象,其实他只是一个产生代理对象的工具类)。利用到InvocationHandler,拼接代理类源码,将其编译生成代理类的二进制码,利用加载器加载,并将其实例化产生代理对象,最后返回。
想对一个对象进行处理,又想保留原有的数据进行接下来的操做,就须要克隆了,Java语言中克隆针对的是类的实例。
有两种方式:
1). 实现Cloneable接口并重写Object类中的clone()方法;
2). 实现Serializable接口,经过对象的序列化和反序列化实现克隆,能够实现真正的深度克隆,代码以下:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class MyUtil {
private MyUtil() {
throw new AssertionError();
}
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
// 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
// 这两个基于内存的流只要垃圾回收器清理对象就可以释放资源,这一点不一样于对外部资源(如文件流)的释放
}
}复制代码
下面是测试代码:
import java.io.Serializable;
/**
* 人类
* @author nnngu
*
*/
class Person implements Serializable {
private static final long serialVersionUID = -9102017020286042305L;
private String name; // 姓名
private int age; // 年龄
private Car car; // 座驾
public Person(String name, int age, Car car) {
this.name = name;
this.age = age;
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
}
}/**
* 小汽车类
* @author nnngu
*
*/
class Car implements Serializable {
private static final long serialVersionUID = -5713945027627603702L;
private String brand; // 品牌
private int maxSpeed; // 最高时速
public Car(String brand, int maxSpeed) {
this.brand = brand;
this.maxSpeed = maxSpeed;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
}
}复制代码
class CloneTest {
public static void main(String[] args) {
try {
Person p1 = new Person("郭靖", 33, new Car("Benz", 300));
Person p2 = MyUtil.clone(p1); // 深度克隆
p2.getCar().setBrand("BYD");
// 修改克隆的Person对象p2关联的汽车对象的品牌属性
// 原来的Person对象p1关联的汽车不会受到任何影响
// 由于在克隆Person对象时其关联的汽车对象也被克隆了
System.out.println(p1);
} catch (Exception e) {
e.printStackTrace();
}
}
}复制代码
注意:基于序列化和反序列化实现的克隆不只仅是深度克隆,更重要的是经过泛型限定,能够检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来老是好过把问题留到运行时。
JSP有9个内置对象:
JSP中的四种做用域包括page、request、session和application,具体来讲:
其实session是一个存在服务器上的相似于一个散列表格的文件。里面存有咱们须要的信息,在咱们须要用的时候能够从里面取出来。相似于一个大号的map吧,里面的键存储的是用户的sessionid,用户向服务器发送请求的时候会带上这个sessionid。这时就能够从中取出对应的值了。
Cookie与 Session,通常认为是两个独立的东西,Session采用的是在服务器端保持状态的方案,而Cookie采用的是在客户端保持状态的方案。但为何禁用Cookie就不能获得Session呢?由于Session是用Session ID来肯定当前对话所对应的服务器Session,而Session ID是经过Cookie来传递的,禁用Cookie至关于失去了Session ID,也就得不到Session了。
假定用户关闭Cookie的状况下使用Session,其实现途径有如下几种:
Struts2是类级别的拦截,每次请求就会建立一个Action,和Spring整合时Struts2的ActionBean注入做用域是原型模式prototype,而后经过setter,getter吧request数据注入到属性。Struts2中,一个Action对应一个request,response上下文,在接收参数时,能够经过属性接收,这说明属性参数是让多个方法共享的。Struts2中Action的一个方法能够对应一个url,而其类属性却被全部方法共享,这也就没法用注解或其余方式标识其所属方法了,只能设计为多例。
SpringMVC是方法级别的拦截,一个方法对应一个Request上下文,因此方法直接基本上是独立的,独享request,response数据。而每一个方法同时又何一个url对应,参数的传递是直接注入到方法中的,是方法所独有的。处理结果经过ModeMap返回给框架。在Spring整合时,SpringMVC的Controller Bean默认单例模式Singleton,因此默认对全部的请求,只会建立一个Controller,有应为没有共享的属性,因此是线程安全的,若是要改变默认的做用域,须要添加@Scope注解修改。
Struts2有本身的拦截Interceptor机制,SpringMVC这是用的是独立的Aop方式,这样致使Struts2的配置文件量仍是比SpringMVC大。
Struts2采用Filter(StrutsPrepareAndExecuteFilter)实现,SpringMVC(DispatcherServlet)则采用Servlet实现。Filter在容器启动以后即初始化;服务中止之后坠毁,晚于Servlet。Servlet在是在调用时初始化,先于Filter调用,服务中止后销毁。
Struts2是类级别的拦截,每次请求对应实例一个新的Action,须要加载全部的属性值注入,SpringMVC实现了零配置,因为SpringMVC基于方法的拦截,有加载一次单例模式bean注入。因此,SpringMVC开发效率和性能高于Struts2。
spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高。
XSS攻击又称CSS,全称Cross Site Script (跨站脚本攻击),其原理是攻击者向有XSS漏洞的网站中输入恶意的 HTML 代码,当用户浏览该网站时,这段 HTML 代码会自动执行,从而达到攻击的目的。XSS 攻击相似于 SQL 注入攻击,SQL注入攻击中以SQL语句做为用户输入,从而达到查询/修改/删除数据的目的,而在xss攻击中,经过插入恶意脚本,实现对用户游览器的控制,获取用户的一些信息。 XSS是 Web 程序中常见的漏洞,XSS 属于被动式且用于客户端的攻击方式。
XSS防范的整体思路是:对输入(和URL参数)进行过滤,对输出进行编码。
CSRF(Cross-site request forgery)也被称为 one-click attack或者 session riding,中文全称是叫跨站请求伪造。通常来讲,攻击者经过伪造用户的浏览器的请求,向访问一个用户本身曾经认证访问过的网站发送出去,使目标网站接收并误觉得是用户的真实操做而去执行命令。经常使用于盗取帐号、转帐、发送虚假消息等。攻击者利用网站对请求的验证漏洞而实现这样的攻击行为,网站可以确认请求来源于用户的浏览器,却不能验证请求是否源于用户的真实意愿下的操做行为。
如何避免:
1. 验证 HTTP Referer 字段
HTTP头中的Referer字段记录了该 HTTP 请求的来源地址。在一般状况下,访问一个安全受限页面的请求来自于同一个网站,而若是黑客要对其实施 CSRF攻击,他通常只能在他本身的网站构造请求。所以,能够经过验证Referer值来防护CSRF 攻击。
2. 使用验证码
关键操做页面加上验证码,后台收到请求后经过判断验证码能够防护CSRF。但这种方法对用户不太友好。
3. 在请求地址中添加token并验证
CSRF 攻击之因此可以成功,是由于黑客能够彻底伪造用户的请求,该请求中全部的用户验证信息都是存在于cookie中,所以黑客能够在不知道这些验证信息的状况下直接利用用户本身的cookie 来经过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,而且该信息不存在于 cookie 之中。能够在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端创建一个拦截器来验证这个 token,若是请求中没有token或者 token 内容不正确,则认为多是 CSRF 攻击而拒绝该请求。这种方法要比检查 Referer 要安全一些,token 能够在用户登录后产生并放于session之中,而后在每次请求时把token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址以后,这样 URL 就变成 http://url?csrftoken=tokenvalue。而对于 POST 请求来讲,要在 form 的最后加上 <input type="hidden" name="csrftoken" value="tokenvalue"/>,这样就把token以参数的形式加入请求了。
4. 在HTTP 头中自定义属性并验证
这种方法也是使用 token 并进行验证,和上一种方法不一样的是,这里并非把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。经过 XMLHttpRequest 这个类,能够一次性给全部该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,经过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担忧 token 会透过 Referer 泄露到其余网站中去。
throws是用来声明一个方法可能抛出的全部异常信息,throws是将异常声明可是不处理,而是将异常往上传,谁调用我就交给谁处理。而throw则是指抛出的一个具体的异常类型。
答:catch 能够省略
缘由:
更为严格的说法实际上是:try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。也就是说,若是你只用try去处理普通异常却不加以catch处理,编译是通不过的,由于编译器硬性规定,普通异常若是选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,因此catch能够省略,你加上catch编译器也以为无可厚非。
理论上,编译器看任何代码都不顺眼,都以为可能有潜在的问题,因此你即便对全部代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。可是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。若是是普通异常,编译器要求必须用catch捕获以便进一步处理;若是运行时异常,捕获而后丢弃而且+finally扫尾处理,或者加上catch捕获以便进一步处理。
至于加上finally,则是在无论有没捕获异常,都要进行的“扫尾”处理。
答:会执行,在 return 前执行。
代码示例1:
/* * java面试题--若是catch里面有return语句,finally里面的代码还会执行吗? */public class FinallyDemo2 { public static void main(String[] args) { System.out.println(getInt()); } public static int getInt() { int a = 10; try { System.out.println(a / 0); a = 20; } catch (ArithmeticException e) { a = 30; return a; /* * return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就造成了 * 可是呢,它发现后面还有finally,因此继续执行finally的内容,a=40 * 再次回到之前的路径,继续走return 30,造成返回路径以后,这里的a就不是a变量了,而是常量30 */ } finally { a = 40; }// return a; }}复制代码
执行结果:30
代码示例2:
package com.java_02;/* * java面试题--若是catch里面有return语句,finally里面的代码还会执行吗? */public class FinallyDemo2 { public static void main(String[] args) { System.out.println(getInt()); } public static int getInt() { int a = 10; try { System.out.println(a / 0); a = 20; } catch (ArithmeticException e) { a = 30; return a; /* * return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就造成了 * 可是呢,它发现后面还有finally,因此继续执行finally的内容,a=40 * 再次回到之前的路径,继续走return 30,造成返回路径以后,这里的a就不是a变量了,而是常量30 */ } finally { a = 40; return a; //若是这样,就又从新造成了一条返回路径,因为只能经过1个return返回,因此这里直接返回40 }// return a; }}复制代码
执行结果:40
答:301,302 都是HTTP状态的编码,都表明着某个URL发生了转移。
区别:
Forward和Redirect表明了两种请求转发方式:直接转发和间接转发。
直接转发方式(Forward),客户端和浏览器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,保存的对象对于每一个信息资源是共享的。
间接转发方式(Redirect)实际是两次HTTP请求,服务器端在响应第一次请求的时候,让浏览器再向另一个URL发出请求,从而达到转发的目的。
举个通俗的例子:
直接转发就至关于:“A找B借钱,B说没有,B去找C借,借到借不到都会把消息传递给A”;
间接转发就至关于:"A找B借钱,B说没有,让A去找C借"。
为了实现可靠数据传输, TCP 协议的通讯双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程便是通讯双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤。
若是只是两次握手, 至多只有链接发起方的起始序列号能被确认, 另外一方选择的序列号则得不到确认。
①. 发送方产生粘包
采用TCP协议传输数据的客户端与服务器常常是保持一个长链接的状态(一次链接发一次数据不存在粘包),双方在链接不断开的状况下,能够一直传输数据;但当发送的数据包过于的小时,那么TCP协议默认的会启用Nagle算法,将这些较小的数据包进行合并发送(缓冲区数据发送是一个堆压的过程);这个合并过程就是在发送缓冲区中进行的,也就是说数据发送出来它已是粘包的状态了。
②. 接收方产生粘包
接收方采用TCP协议接收数据时的过程是这样的:数据到底接收方,从网络模型的下方传递至传输层,传输层的TCP协议处理是将其放置接收缓冲区,而后由应用层来主动获取(C语言用recv、read等函数);这时会出现一个问题,就是咱们在程序中调用的读取数据函数不能及时的把缓冲区中的数据拿出来,而下一个数据又到来并有一部分放入的缓冲区末尾,等咱们读取数据时就是一个粘包。(放数据的速度 > 应用层拿数据速度)
方式一:图片ping或script标签跨域
图片ping经常使用于跟踪用户点击页面或动态广告曝光次数。
script标签能够获得从其余来源数据,这也是JSONP依赖的根据。
方式二:JSONP跨域
JSONP(JSON with Padding)是数据格式JSON的一种“使用模式”,可让网页从别的网域要数据。根据 XmlHttpRequest 对象受到同源策略的影响,而利用 <script>元素的这个开放策略,网页能够获得从其余来源动态产生的JSON数据,而这种使用模式就是所谓的 JSONP。用JSONP抓到的数据并非JSON,而是任意的JavaScript,用 JavaScript解释器运行而不是用JSON解析器解析。全部,经过Chrome查看全部JSONP发送的Get请求都是js类型,而非XHR。
缺点:
方式三:CORS
Cross-Origin Resource Sharing(CORS)跨域资源共享是一份浏览器技术的规范,提供了 Web 服务从不一样域传来沙盒脚本的方法,以避开浏览器的同源策略,确保安全的跨域数据传输。现代浏览器使用CORS在API容器如XMLHttpRequest来减小HTTP请求的风险来源。与 JSONP 不一样,CORS 除了 GET 要求方法之外也支持其余的 HTTP 要求。服务器通常须要增长以下响应头的一种或几种:
Access-Control-Allow-Origin: *Access-Control-Allow-Methods: POST, GET, OPTIONSAccess-Control-Allow-Headers: X-PINGOTHER, Content-TypeAccess-Control-Max-Age: 86400复制代码
跨域请求默认不会携带Cookie信息,若是须要携带,请配置下述参数:
"Access-Control-Allow-Credentials": true// Ajax设置"withCredentials": true复制代码
方式四:window.name+iframe
window.name经过在iframe(通常动态建立i)中加载跨域HTML文件来起做用。而后,HTML文件将传递给请求者的字符串内容赋值给window.name。而后,请求者能够检索window.name值做为响应。
每一个iframe都有包裹它的window,而这个window是top window的子窗口。contentWindow属性返回<iframe>元素的Window对象。你能够使用这个Window对象来访问iframe的文档及其内部DOM。
<!-- 下述用端口 10000表示:domainA 10001表示:domainB--><!-- localhost:10000 --><script> var iframe = document.createElement('iframe'); iframe.style.display = 'none'; // 隐藏 var state = 0; // 防止页面无限刷新 iframe.onload = function() { if(state === 1) { console.log(JSON.parse(iframe.contentWindow.name)); // 清除建立的iframe iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } else if(state === 0) { state = 1; // 加载完成,指向当前域,防止错误(proxy.html为空白页面) // Blocked a frame with origin "http://localhost:10000" from accessing a cross-origin frame. iframe.contentWindow.location = 'http://localhost:10000/proxy.html'; } }; iframe.src = 'http://localhost:10001'; document.body.appendChild(iframe);</script><!-- localhost:10001 --><!DOCTYPE html>...<script>window.name = JSON.stringify({a: 1, b: 2});</script></html>复制代码
方式五:window.postMessage()
HTML5新特性,能够用来向其余全部的 window 对象发送消息。须要注意的是咱们必需要保证全部的脚本执行完才发送 MessageEvent,若是在函数执行的过程当中调用了它,就会让后面的函数超时没法执行。
下述代码实现了跨域存储localStorage
<!-- 下述用端口 10000表示:domainA 10001表示:domainB--><!-- localhost:10000 --><iframe src="http://localhost:10001/msg.html" name="myPostMessage" style="display:none;"></iframe><script> function main() { LSsetItem('test', 'Test: ' + new Date()); LSgetItem('test', function(value) { console.log('value: ' + value); }); LSremoveItem('test'); } var callbacks = {}; window.addEventListener('message', function(event) { if (event.source === frames['myPostMessage']) { console.log(event) var data = /^#localStorage#(\d+)(null)?#([\S\s]*)/.exec(event.data); if (data) { if (callbacks[data[1]]) { callbacks[data[1]](data[2] === 'null' ? null : data[3]); } delete callbacks[data[1]]; } } }, false); var domain = '*'; // 增长 function LSsetItem(key, value) { var obj = { setItem: key, value: value }; frames['myPostMessage'].postMessage(JSON.stringify(obj), domain); } // 获取 function LSgetItem(key, callback) { var identifier = new Date().getTime(); var obj = { identifier: identifier, getItem: key }; callbacks[identifier] = callback; frames['myPostMessage'].postMessage(JSON.stringify(obj), domain); } // 删除 function LSremoveItem(key) { var obj = { removeItem: key }; frames['myPostMessage'].postMessage(JSON.stringify(obj), domain); }</script><!-- localhost:10001 --><script> window.addEventListener('message', function(event) { console.log('Receiver debugging', event); if (event.origin == 'http://localhost:10000') { var data = JSON.parse(event.data); if ('setItem' in data) { localStorage.setItem(data.setItem, data.value); } else if ('getItem' in data) { var gotItem = localStorage.getItem(data.getItem); event.source.postMessage( '#localStorage#' + data.identifier + (gotItem === null ? 'null#' : '#' + gotItem), event.origin ); } else if ('removeItem' in data) { localStorage.removeItem(data.removeItem); } } }, false);</script>复制代码
注意Safari一下,会报错:
Blocked a frame with origin “http://localhost:10001” from accessing a frame with origin “http://localhost:10000“. Protocols, domains, and ports must match.
避免该错误,能够在Safari浏览器中勾选开发菜单==>停用跨域限制。或者只能使用服务器端转存的方式实现,由于Safari浏览器默认只支持CORS跨域请求。
方式六:修改document.domain跨子域
前提条件:这两个域名必须属于同一个基础域名!并且所用的协议,端口都要一致,不然没法利用document.domain进行跨域,因此只能跨子域
在根域范围内,容许把domain属性的值设置为它的上一级域。例如,在”aaa.xxx.com”域内,能够把domain设置为 “xxx.com” 但不能设置为 “XXX.com Porn - Free PORNO videos” 或者”com”。
如今存在两个域名aaa.xxx.com和bbb.xxx.com。在aaa下嵌入bbb的页面,因为其document.name不一致,没法在aaa下操做bbb的js。能够在aaa和bbb下经过js将document.name = 'xxx.com';设置一致,来达到互相访问的做用。
方式七:WebSocket
WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通讯,同时容许跨域通信,是server push技术的一种很棒的实现。相关文章,请查看:WebSocket、WebSocket-SockJS
须要注意:WebSocket对象不支持DOM 2级事件侦听器,必须使用DOM 0级语法分别定义各个事件。
方式八:代理
同源策略是针对浏览器端进行的限制,能够经过服务器端来解决该问题
DomainA客户端(浏览器) ==> DomainA服务器 ==> DomainB服务器 ==> DomainA客户端(浏览器)
来源:blog.csdn.net/ligang2585116/article/details/73072868
jsonp 即 json+padding,动态建立script标签,利用script标签的src属性能够获取任何域下的js脚本,经过这个特性(也能够说漏洞),服务器端不在返货json格式,而是返回一段调用某个函数的js代码,在src中进行了调用,这样实现了跨域。
参考:经常使用的设计模式汇总,超详细!
简单工厂模式:
这个模式自己很简单并且使用在业务较简单的状况下。通常用于小项目或者具体产品不多扩展的状况(这样工厂类才不用常常更改)。
它由三种角色组成:
来用类图来清晰的表示下的它们之间的关系:
抽象工厂模式:
先来认识下什么是产品族: 位于不一样产品等级结构中,功能相关联的产品组成的家族。
图中的BmwCar和BenzCar就是两个产品树(产品层次结构);而如图所示的BenzSportsCar和BmwSportsCar就是一个产品族。他们均可以放到跑车家族中,所以功能有所关联。同理BmwBussinessCar和BenzBusinessCar也是一个产品族。
能够这么说,它和工厂方法模式的区别就在于须要建立对象的复杂程度上。并且抽象工厂模式是三个里面最为抽象、最具通常性的。抽象工厂模式的用意为:给客户端提供一个接口,能够建立多个产品族中的产品对象。
并且使用抽象工厂模式还要知足一下条件:
来看看抽象工厂模式的各个角色(和工厂方法的一模一样):
1.简介
简单来讲,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
2.轻量
从大小与开销两方面而言Spring都是轻量的。完整的Spring框架能够在一个大小只有1MB多的JAR文件里发布。而且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。
3.控制反转
Spring经过一种称做控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会经过被动的方式传递进来,而不是这个对象本身建立或者查找依赖对象。你能够认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
4.面向切面
Spring提供了面向切面编程的丰富支持,容许经过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该作的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
5.容器
Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你能够配置你的每一个bean如何被建立——基于一个可配置原型(prototype),你的bean能够建立一个单独的实例或者每次须要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不该该被混同于传统的重量级的EJB容器,它们常常是庞大与笨重的,难以使用。
6.框架
Spring能够将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了不少基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
全部Spring的这些特征使你可以编写更干净、更可管理、而且更易于测试的代码。它们也为Spring中的各类模块提供了基础支持。
AOP(Aspect-Oriented Programming,面向方面编程),能够说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来创建一种对象层次结构,用以模拟公共行为的一个集合。当咱们须要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP容许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码每每水平地散布在全部对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其余类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它致使了大量代码的重复,而不利于各个模块的重用。
而AOP技术则偏偏相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减小系统的重复代码,下降模块间的耦合度,并有利于将来的可操做性和可维护性。AOP表明的是一个横向的关系,若是说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以得到其内部的消息。而剖开的切面,也就是所谓的“方面”了。而后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特色是,他们常常发生在核心关注点的多处,而各处都基本类似。好比权限认证、日志、事务处理。Aop 的做用在于分离系统中的各类关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
IOC是Inversion of Control的缩写,多数书籍翻译成“控制反转”。
1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中,首先提出了IOC 这个概念。对于面向对象设计及编程的基本思想,前面咱们已经讲了不少了,再也不赘述,简单来讲就是把复杂系统分解成相互合做的对象,这些对象类经过封装之后,内部实现对外部是透明的,从而下降了解决问题的复杂度,并且能够灵活地被重用和扩展。
IOC理论提出的观点大致是这样的:借助于“第三方”实现具备依赖关系的对象之间的解耦。以下图:
你们看到了吧,因为引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动所有依靠“第三方”了,所有对象的控制权所有上缴给“第三方”IOC容器,因此,IOC容器成了整个系统的关键核心,它起到了一种相似“粘合剂”的做用,把系统中的全部对象粘合在一块儿发挥做用,若是没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。
咱们再来作个试验:把上图中间的IOC容器拿掉,而后再来看看这套系统:
咱们如今看到的画面,就是咱们要实现整个系统所须要完成的所有内容。这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经下降到了最低程度。因此,若是真能实现IOC容器,对于系统开发而言,这将是一件多么美好的事情,参与开发的每一成员只要实现本身的类就能够了,跟别人没有任何关系!
咱们再来看看,控制反转(IOC)到底为何要起这么个名字?咱们来对比一下:
软件系统在没有引入IOC容器以前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,本身必须主动去建立对象B或者使用已经建立的对象B。不管是建立仍是使用对象B,控制权都在本身手上。
软件系统在引入IOC容器以后,这种情形就彻底改变了,如图3所示,因为IOC容器的加入,对象A与对象B之间失去了直接联系,因此,当对象A运行到须要对象B的时候,IOC容器会主动建立一个对象B注入到对象A须要的地方。
经过先后的对比,咱们不难看出来:对象A得到依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
Spring框架至今已集成了20多个模块。这些模块主要被分以下图所示的核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。
更多信息:Spring Tutorials - HowToDoInJava
Spring经过DI(依赖注入)实现IOC(控制反转),经常使用的注入方式主要有三种:
Spring容器中的Bean是否线程安全,容器自己并无提供Bean的线程安全策略,所以能够说spring容器中的Bean自己不具有线程安全的特性,可是具体仍是要结合具体scope的Bean去研究。
当经过spring容器建立一个Bean实例时,不只能够完成Bean实例的实例化,还能够为Bean指定特定的做用域。Spring支持以下5种做用域:
其中比较经常使用的是singleton和prototype两种做用域。对于singleton做用域的Bean,每次请求该Bean都将得到相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;若是一个Bean被设置成prototype做用域,程序每次请求该id的Bean,Spring都会新建一个Bean实例,而后返回给程序。在这种状况下,Spring容器仅仅使用new 关键字建立Bean实例,一旦建立成功,容器不在跟踪实例,也不会维护Bean实例的状态。
若是不指定Bean的做用域,Spring默认使用singleton做用域。Java在建立Java实例时,须要进行内存申请;销毁实例时,须要完成垃圾回收,这些工做都会致使系统开销的增长。所以,prototype做用域Bean的建立、销毁代价比较大。而singleton做用域的Bean实例一旦建立成功,能够重复使用。所以,除非必要,不然尽可能避免将Bean被设置成prototype做用域。
Spring容器负责建立应用程序中的bean同时经过ID来协调这些对象之间的关系。做为开发人员,咱们须要告诉Spring要建立哪些bean而且如何将其装配到一块儿。
spring中bean装配有两种方式:
固然这些方式也能够配合使用。
事务隔离级别指的是一个事务对数据的修改与另外一个并行的事务的隔离程度,当多个事务同时访问相同数据时,若是没有采起必要的隔离机制,就可能发生如下问题:
Spring MVC运行流程图:
Spring运行流程描述:
1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;
2. DispatcherServlet对请求URL进行解析,获得请求资源标识符(URI)。而后根据该URI,调用HandlerMapping得到该Handler配置的全部相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
3. DispatcherServlet 根据得到的Handler,选择一个合适的HandlerAdapter;(附注:若是成功得到HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法)
4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程当中,根据你的配置,Spring将帮你作一些额外的工做:
5. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
6. 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;
7. ViewResolver 结合Model和View,来渲染视图;
8. 将渲染结果返回给客户端。
Spring MVC的核心组件:
RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的全部响应请求的方法都是以该地址做为父路径。
RequestMapping注解有六个属性,下面咱们把她分红三类进行说明。
value, method:
consumes,produces
params,headers
《@Autowired用法详解》
对于2019年100道经典面试真题的总结和解析,给你们在未来面试路上一点帮助,更多的精彩文章和面试真题请关注:微信公众号:java程序员汇集地