一、java基础
分享一份大彬精心整理的大厂面试手册,包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等高频面试题,非常实用,有小伙伴靠着这份手册拿过字节offer~【领取/点击】
2.== 和 equals 的区别是什么?
“==”比较的是两个引用在内存中的地址是否相同,也就是说在内存中的地址是否一样。
equals方法是由Object类提供的,可以由子类来进行重写。默认的实现只有当对象和自身进行比较时才会返回true, 这个时候和 “==”是等价的。
Java中很多类(String类 Date类 File类)等都对equals方法进行了重写,这里拿常见的String类举例。
1 public class Test {
2 public static void main(String[] args) {
3 String str1 = "abc";
4 String str2 = "abc";
5 System.out.println(str1==str2);//true
6
7
8 String str3 = new String("abc");
9 String str4 = new String ("abc");
10 System.out.println(str3==str4);//false
11 System.out.println(str3.equals(str4));//true
12 }
13 }
先看第3行代码,先在栈中创建一个对 String类的对象引用变量str1,然后通过引用去字符串常量池 里找有没有"abc",如果没有,则将"abc"存放进字符串常量池。这里常量池中并没有“abc”。即在编译期已经创建好(直接用双引号定义的)“abc”,存储在了常量池中。
第4行代码又创建了对String类的对象引用str2,然后通过引用去字符串常量池 里找有没有"abc",如果没有,则将"abc"存放进字符串常量池 ,并令str2指向”abc”,如果已经有”abc” 则直接令str2指向“abc”。这里我们在第三行代码中已经将“abc”这个字符串存储进了常量池。所以str2和str1指向的是同一个“abc”,返回true。
第8行和第9行代码分别创建了2个对象,str3和str4指向的是不同的对象,即上面所说的内存空间中存储位置不同。故str3 == str4 返回的肯定是false。
第11行代码 str3.equals(str4) 返回true
因为String类重写了equals方法,比较的是内存空间存放的数据是否相同。这里存放的都是字符串“abc” 故返回true。
3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?
答案肯定是不一定。同时反过来equals为true,hashCode也不一定相同。类的hashCode方法和equals方法都可以重写,返回的值完全在于自己定义。hashCode()返回该对象的哈希码值;equals()返回两个对象是否相等。
4.final在 java 中有什么作用?
根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。
final类不能被继承,没有子类,final类中的方法默认是final的。
final方法不能被子类的方法覆盖,但可以被继承。
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
final不能用于修饰构造方法。
注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。
- java 中的 Math.round(-1.5) 等于多少?
-1Math.round(1.5)的返回值是2,Math.round(-1.5)的返回值是-1。四舍五入的原理是在参数上加0.5然后做向下取整。
6.String 属于基础的数据类型吗?
String不是基本的数据类型,是final修饰的java类,java中的基本类型一共有8个,它们分别为:
1 字符类型:byte,char
2 基本整型:short,int,long
3 浮点型:float,double
4 布尔类型:boolean
- java 中操作字符串都有哪些类?它们之间有什么区别?
String : final修饰,String类的方法都是返回new String。即对String对象的任何改变都不影响到原对象,对字符串的修改操作都会生成新的对象。
StringBuffer : 对字符串的操作的方法都加了synchronized,保证线程安全。
StringBuilder : 不保证线程安全,在方法体内需要进行字符串的修改操作,可以new StringBuilder对象,调用StringBuilder对象的append、replace、delete等方法修改字符串。
8.String str="i"与 String str=new String(“i”)一样吗?
内存地址不一样
9.如何将字符串反转?
public class StringReverse {
/**
* 二分递归地将后面的字符和前面的字符连接起来。
*
* @param s
* @return
*/
public static String reverse1(String s) {
int length = s.length();
if (length <= 1)
return s;
String left = s.substring(0, length / 2);
String right = s.substring(length / 2, length);
return reverse1(right) + reverse1(left);
}
/**
* 取得当前字符并和之前的字符append起来
* @param s
* @return
*/
public static String reverse2(String s) {
int length = s.length();
String reverse = "";
for (int i=0; i<length; i++)
reverse = s.charAt(i) + reverse;
return reverse;
}
/**
* 将字符从后往前的append起来
* @param s
* @return
*/
public static String reverse3(String s) {
char[] array = s.toCharArray();
String reverse = "";
for (int i = array.length - 1; i >= 0; i--) {
reverse += array[i];
}
return reverse;
}
/**
* 和StringBuffer()一样,都用了Java自实现的方法,使用位移来实现
* @param s
* @return
*/
public static String reverse4(String s) {
return new StringBuilder(s).reverse().toString();
}
/**
* 和StringBuilder()一样,都用了Java自实现的方法,使用位移来实现
* @param s
* @return
*/
public static String reverse5(String s) {
return new StringBuffer(s).reverse().toString();
}
/**
* 二分交换,将后面的字符和前面对应的那个字符交换
* @param s
* @return
*/
public static String reverse6(String s) {
char[] array = s.toCharArray();
int end = s.length() - 1;
int halfLength = end / 2;
for (int i = 0; i <= halfLength; i++) {
char temp = array[i];
array[i] = array[end-i];
array[end-i] = temp;
}
return new String(array);
}
/**
* 原理是使用异或交换字符串
* a=a^b;
* b=b^a;
* a=b^a;
*
* @param s
* @return
*/
public static String reverse7(String s) {
char[] array = s.toCharArray();
int begin = 0;
int end = s.length() - 1;
while (begin < end) {
array[begin] = (char) (array[begin] ^ array[end]);
array[end] = (char) (array[end] ^ array[begin]);
array[begin] = (char) (array[end] ^ array[begin]);
begin++;
end--;
}
return new String(array);
}
/**
* 基于栈先进后出的原理
*
* @param s
* @return
*/
public static String reverse8(String s) {
char[] array = s.toCharArray();
Stack<Character> stack = new Stack<Character>();
for (int i = 0; i < array.length; i++)
stack.push(array[i]);
String reverse = "";
for (int i = 0; i < array.length; i++)
reverse += stack.pop();
return reverse;
}
public static void main(String[] args) {
System.out.println(reverse1("Wang Sheng"));
System.out.println(reverse2("Wang Sheng"));
System.out.println(reverse3("Wang Sheng"));
System.out.println(reverse4("Wang Sheng"));
System.out.println(reverse5("Wang Sheng"));
System.out.println(reverse6("Wang Sheng"));
System.out.println(reverse7("Wang Sheng"));
System.out.println(reverse8("Wang Sheng"));
}
}
10.String 类的常用方法都有那些?
增-删改查
删:trim, subString
改:valueOf ,toUpperCase, toLowerCase
查:length, indexOf ,charAt ,startsWith, endsWith
11.抽象类必须要有抽象方法吗?
抽象类中不一定包含抽象方法,但是包含抽象方法的类一定要被声明为抽象类。
抽象类本身不具备实际的功能,只能用于派生其子类。
抽象类中可以包含构造方法, 但是构造方法不能被声明为抽象。
抽象类不能用final来修饰,即一个类不能既是最终类又是抽象类。
抽象类可以没有抽象方法,但是如果你的一个类已经声明成了抽象类,即使这个类中没有抽象方法,它也不能再实例化,即不能直接构造一个该类的对象。
12.普通类和抽象类有哪些区别?
1.抽象类不能被实例化。
2.抽象类可以有构造函数,抽象方法不能被声明为静态。
3.抽象方法只需申明,而无需实现,抽象类中可以允许普通方法有主体
4.含有抽象方法的类必须申明为抽象类
5.抽象的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类。
14.接口和抽象类有什么区别?
接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象。
(1) 接口不能被实例化
(2) 接口只能包含方法声明
(3) 接口的成员包括方法、属性、索引器、事件
(4) 接口中不能包含常量、字段(域)、构造函数、析构函数、静态成员
- java 中 IO 流分为几种?
- BIO:线程发起请求,不管内核是否准备号IO操作,从请求发起,线程一直阻塞,直到操作完成。
- NIO:线程发起IO请求,立刻返回;内核在完成Io操作以后通过调用注册的回调函数通知线程进行IO操作。
- AIO:线程发起IO请求,立刻返回;内存做好IO操作的准备以后,做IO操作,知道操作完成或者失败,通过调用注册的回调函数通知线程做IO操作。
- BIO:是一个连接一个线程。同步阻塞,适用于连接数目较少,这种对服务器资源消耗较多
- NIO:是一个请求线程。同步非阻塞,适用于连接数目较多而且连接时间短的架构--聊天jdk1.4以后
- AIO:是一个有效的请求线程。异步非阻塞适用于连接数目多时间长的数据连接操作。jdk1.7开始
17.Files的常用方法都有哪些?
1.创建功能:
createNewFile()
mkdirs()
renameTo(File dest)
判断功能:
isFile()
isDirectory()
canRead()
canWrite()
idHideden()
获取功能:
getAbsolutepath()//绝对路径
getPath()//相对路径
getName()
length()
public long lastModified() 获取文件最后一次修改的时间(单位,毫秒)
二、容器
- java 容器都有哪些?
java容器:
数组/String/Java.util下的集合容器。
数组的限制为:Integer.MAX_VALUE。
String的限制长度:底层是char数组,长度Integer.MAX_VALUE线程安全。
List:存放有序,列表存储,元素可以重复。
Set:无序,元素不可以重复。底层实现使用hashMap=数组+链表
Map:元素不可以重复,LinkedHashMap,TreeHashMap底层用额外的链表和树进行维护
19.Collection 和 Collections 有什么区别?
- Collections相当于一个工具类,可以reverse()逆序和sort()排序
- Collection是一个集合的接口,它提提供了具体的实现方法。有ArrayList(),LinkedList()
20.List、Set、Map 之间的区别是什么?
List的元素以线性方式存储,可以存放重复,List主要有一下两个实现类。
ArrayList:长度可变数组,可以对元素进行随机访问,ArrayList中插入与删除元素的速度比较慢。扩容是采用1.5倍扩容。然后调用Arrays.copyof()对原来的数组进行复制。
LinkedList:采用链表数据结构,插入和删除速度较快基于链表。
Set(集合)
Set中的对象不按照特定(hashCode)的方式进行排序,并且没有重复对象,set主要有俩个实现类:
- HashSet:按照哈希算法来存取集合中的对象,存取速度比较快。当HashSet中的元素超过*loadFactor(默认值为0.75)时,就可以近似两倍扩容。
- TreeSet:TreeSet实现了SortedSet接口,能够对集合中的集合对象进行排序。这个是使用二叉链表实现。
Map(映射)
Map是一种把键对象和值对象映射的集合,它的每一个元素都包含一个键对象和值对象。 Map主要有以下两个实现类:
HashMap:HashMap基于散列表实现,其插入和查询<K,V>的开销是固定的,可以通过构造器设置容量和负载因子来调整容器的性能。
LinkedHashMap:类似于HashMap,但是迭代遍历它时,取得<K,V>的顺序是其插入次序,或者是最近最少使用(LRU)的次序。
TreeMap:TreeMap基于红黑树实现。查看<K,V>时,它们会被排序。TreeMap是唯一的带有subMap()方法的Map,subMap()可以返回一个子树。
22.如何决定使用 HashMap 还是 TreeMap?
TreeMap<k,v>的key值要求实现java.lang.Comparable,所以迭代的时候TreeMap默认是按照Key值升序排列的;TreeMap的实现也是基于红黑树结构。而HashMap<k,v>的Key值实现散列hashCode(),分布散列的均匀的,不支持排序;数据结构主要是通过桶(数组),链表或红黑树
大多数情况下HashMap有更好的性能,所以不需要排序的时候一般使用HashMap.
26.如何实现数组和 List 之间的转换?
List转数组:toArray(arraylist.size()方法
数组转List:Arrays的asList(a)方法
30.哪些集合类是线程安全的?
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
statck:堆栈类,先进后出
hashtable:就比hashmap多了个线程安全
enumeration:枚举,相当于迭代器
31.迭代器 Iterator 是什么?
对于collection进行迭代的类,成为迭代器。根据面向对象的方法,迭代器是专门取出集合元素的对象。但是该元素对象比较特殊,不能直接创建对象(通过New),该对象是以内部类的形式存在于每个集合类的内部。
如何获取迭代器?Collection接口定义了获取集合迭代器的方法(Iterator)所以所有Colletion体系集合都可以获取自身的迭代器。
总来的说:
Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包括了可以返回迭代器实例的迭代方法。迭代器可以在迭代过程中删除底层集合的元素,但是不可以直接调用集合的remove(Object obj)删除,可以通过迭代器的remove()方法删除。
使用方法:
public class TestIterator {
static List<String> list = new ArrayList<String>();
static {
list.add("111");
list.add("222");
list.add("333");
}
public static void main(String[] args) {
testIteratorNext();
System.out.println();
testForEachRemaining();
System.out.println();
testIteratorRemove();
}
//使用 hasNext 和 next遍历
public static void testIteratorNext() {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
System.out.println(str);
}
}
//使用 Iterator 删除元素
public static void testIteratorRemove() {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
if ("222".equals(str)) {
iterator.remove();
}
}
System.out.println(list);
}
//使用 forEachRemaining 遍历
public static void testForEachRemaining() {
final Iterator<String> iterator = list.iterator();
iterator.forEachRemaining(new Consumer<String>() {
public void accept(String t) {
System.out.println(t);
}
});
}
33.Iterator 和 ListIterator 有什么区别?
在使用的时候,list和set都有iterator来取得迭代器,对于list来说可以使用listIterator来获取迭代器,两种迭代器是在某些方面是不能通用的。
1.ListIterator有add()方法,可以向list中添加对象,但是iterator不能
2.ListIterator和Iterator可以实现hasPrevious和previous()
-
ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
-
都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
34.怎么确保一个集合不能被修改?
首先我们很容易想到使用final进行修饰,
使用关键字final可以修饰类、方法、成员变量,使用final修饰的类不可以被继承,使用final修饰的变量必须初始化值,如果这个成员变量是基本类型数据,表述这个变量的值是不可以改变的,如果说这个成员变量是引用类型数据,则表示这个引用类型的地址是不可以改变的,但是引用指向的对象的内容还是可以改变的。
因此,如果要确保一个集合不被修改,我们要清除集合(list,set,map)都是引用类型数据,所以使用final修饰集合的内容还是可以修改的。
我们可以采用Collections包下的unmodifiableMap方法,通过这个方法返回的map,是不可以修改的。他会报 java.lang.UnsupportedOperationException错。
同理:Collections包也提供了对list和set集合的方法。
Collections.unmodifiableList(List)
Collections.unmodifiableSet(Set)
多线程:
35.并行和并发有什么区别?
并发是多个事件在同一时间间隔发生。
并行是多个事件在同一时刻发生。
36.线程和进程的区别?
进程是具有一定独立功能的程序关于某个数据集合上的一次活动,进程是资源分配的最小单位。
线程是进程的一个实体。
区别:
进程有独立的资源单位,线程只是一个进程的不同的执行路径。
线程拥有自己的堆栈和局部变量,线程之间没有独立的地址空间。
1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
37.守护线程是什么?
什么是线程守护,daemon thread,是服务框架,准确的来说就是服务其他线程,这是他的作用-而其他线程只有一种作用,就是用户线程,所以java分为两种。
- 守护进程,比如垃圾回收
- 用户线程:就是应用程序自己自定义的线程
这个就是用户自定义的线程都执行完毕,main也执行完毕以后,jvm就会立即退出,当jvm也退出以后,守护线程也会停止。
38.创建线程有哪几种方式?
创建的四种方法:
1.通过继承Thread类创建接口
public class MyThread extends Thread{//继承Thread类
public void run(){
//重写run方法
}
}
public class Main {
public static void main(String[] args){
new MyThread().start();//创建并启动线程
}
}
2.实现Runnable接口创建线程
public class MyThread2 implements Runnable {//实现Runnable接口
public void run(){
//重写run方法
}
}
public class Main {
public static void main(String[] args){
//创建并启动线程
MyThread2 myThread=new MyThread2();
Thread thread=new Thread(myThread);
thread().start();
//或者 new Thread(new MyThread2()).start();
}
}
3.使用Callable和Future创建
和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。
public class Main {
public static void main(String[] args){
MyThread3 th=new MyThread3();
//使用Lambda表达式创建Callable对象
//使用FutureTask类来包装Callable对象
FutureTask<Integer> future=new FutureTask<Integer>(
(Callable<Integer>)()->{
return 5;
}
);
new Thread(task,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程
try{
System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回
}catch(Exception e){
ex.printStackTrace();
}
}
}
4.-使用线程池例如用Executor框架
1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦。要执行任务的人只需把Task描述清楚,然后提交即可。这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不用关心了。具体点讲,提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果就好了。Executor框架的内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象用Executor在构造器中。
Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。
Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类。ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程。
ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。
Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
39.说一下 runnable 和 callable 有什么区别?
Runnable和Callable的区别是:
(1)callable规定执行的方法是call(),Runable规定执行的方法是run(),其中Runnable可以提交给Thread来包装下直接启动一个线程
进行执行,但是callable一般是交给ExautorService来执行。
(2)callable的任务执行后可以返回值,但是Runnable是无返回值的
(3)call方法是可以抛出异常的,run不可以
(4)运行callable任务可以拿到一个Future对象,c表示异步计算的结果
/**
* 通过简单的测试程序来试验Runnable、Callable通过Executor来调度的时候与Future的关系
*/
package com.hadoop.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class RunnableAndCallable2Future {
public static void main(String[] args) {
// 创建一个执行任务的服务
ExecutorService executor = Executors.newFixedThreadPool(3);
try {
//1.Runnable通过Future返回结果为空
//创建一个Runnable,来调度,等待任务执行完毕,取得返回结果
Future<?> runnable1 = executor.submit(new Runnable() {
@Override
public void run() {
System.out.println("runnable1 running.");
}
});
System.out.println("Runnable1:" + runnable1.get());
// 2.Callable通过Future能返回结果
//提交并执行任务,任务启动时返回了一个 Future对象,
// 如果想得到任务执行的结果或者是异常可对这个Future对象进行操作
Future<String> future1 = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
return "result=task1";
}
});
// 获得任务的结果,如果调用get方法,当前线程会等待任务执行完毕后才往下执行
System.out.println("task1: " + future1.get());
//3. 对Callable调用cancel可以对对该任务进行中断
//提交并执行任务,任务启动时返回了一个 Future对象,
// 如果想得到任务执行的结果或者是异常可对这个Future对象进行操作
Future<String> future2 = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
try {
while (true) {
System.out.println("task2 running.");
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Interrupted task2.");
}
return "task2=false";
}
});
// 等待5秒后,再停止第二个任务。因为第二个任务进行的是无限循环
Thread.sleep(10);
System.out.println("task2 cancel: " + future2.cancel(true));
// 4.用Callable时抛出异常则Future什么也取不到了
// 获取第三个任务的输出,因为执行第三个任务会引起异常
// 所以下面的语句将引起异常的抛出
Future<String> future3 = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
throw new Exception("task3 throw exception!");
}
});
System.out.println("task3: " + future3.get());
} catch (Exception e) {
System.out.println(e.toString());
}
// 停止任务执行服务
executor.shutdownNow();
}
}
结果:
runnable1 running.
Runnable1:null
task1: result=task1
task2 running.
task2 cancel: true
Interrupted task2.
java.util.concurrent.ExecutionException: java.lang.Exception: Bad flag value!
FutureTask则是一个RunnableFuture,即实现了Runnbale又实现了Futrue这两个接口,另外它还可以包装Runnable和Callable,所以一般来讲是一个符合体了,它可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行,并且还可以通过v get()返回执行结果,在线程体没有执行完成的时候,主线程一直阻塞等待,执行完则直接返回结果。
public class FutureTaskTest {
/**
* @param args
*/
public static void main(String[] args) {
Callable<String> task = new Callable<String>() {
public String call() {
System.out.println("Sleep start.");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Sleep end.");
return "time=" + System.currentTimeMillis();
}
};
//直接使用Thread的方式执行
FutureTask<String> ft = new FutureTask<String>(task);
Thread t = new Thread(ft);
t.start();
try {
System.out.println("waiting execute result");
System.out.println("result = " + ft.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//使用Executors来执行
System.out.println("=========");
FutureTask<String> ft2 = new FutureTask<String>(task);
Executors.newSingleThreadExecutor().submit(ft2);
try {
System.out.println("waiting execute result");
System.out.println("result = " + ft2.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
结果
waiting execute result
Sleep start.
Sleep end.
result = time=1370844662537
=========
waiting execute result
Sleep start.
Sleep end.
result = time=1370844672542
40.线程有哪些状态?
线程的状态:新建--就绪--运行--阻塞--死亡 一共五种状态。
1.新建状态:
当使用new thread新建一个线程时,线程还没有运行,这个阶段属于新建状态,线程中的代码还没运行。
2.就绪状态:
创建一个新的线程并不自动运行,需要执行线程,必须调用start()方法运行。当线程调用start方法时开始运行,并且调度运行Run()方法运行,当stat()方法返回后,线程属于就绪状态。
处于就绪状态的线程不一定运行run()方法,线程必须还要同其他线程抢占资源,只有获取到cup时间片段后,线程才可以运行。因此在单cup中一个时刻只一个线程处于运行状态,对于处于多个cup的有专门的调度机构。
3.运行状态
当线程获取cup时间状态以后,他才可以进入运行状态,这个时候才处于真正的运行状态。开始执行run的方法。
4.线程阻塞:
线程阻塞一般分为三种:
- 等待阻塞:线程执行wait()方法,jvm会把该线程放到线程池中。(wait会释放持有的锁)
- 同步阻塞:线程在获取对象的同步锁时,若该同步锁被别的线程调用,则jvm会把该线程放到线程池中。
- 其他阻塞:运行时执行sleep()或者join()方法/线程调用一个在I/O上被阻塞,就是在输入或者输出之后不会返回到他的调用者。当sleep()结束 join()终止/超时,线程会重新执行。(sleep不会放弃锁)
5.死亡状态:执行完run()
41.序列化和反序列化
原理:对象序列化过程可以分为两步:
第一: 将对象转换为字节数组
第二: 将字节数组存储到磁盘
序列化就是把是对象转化成字节顺序的过程,反序序列化就是把字节转化为对象的过程。
public class FlyPig implements Serializable {
//private static final long serialVersionUID = 1L;
private static String AGE = "269";
private String name;
private String color;
transient private String car;
//private String addTip;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getCar() {
return car;
}
public void setCar(String car) {
this.car = car;
}
//public String getAddTip() {
// return addTip;
//}
//
//public void setAddTip(String addTip) {
// this.addTip = addTip;
//}
@Override
public String toString() {
return "FlyPig{" +
"name='" + name + ''' +
", color='" + color + ''' +
", car='" + car + ''' +
", AGE='" + AGE + ''' +
//", addTip='" + addTip + ''' +
'}';
}
}
/**
* 序列化测试
*
* @author lxk on 2017/11/1
*/
public class SerializableTest {
public static void main(String[] args) throws Exception {
serializeFlyPig();
FlyPig flyPig = deserializeFlyPig();
System.out.println(flyPig.toString());
}
/**
* 序列化
*/
private static void serializeFlyPig() throws IOException {
FlyPig flyPig = new FlyPig();
flyPig.setColor("black");
flyPig.setName("naruto");
flyPig.setCar("0000");
// ObjectOutputStream 对象输出流,将 flyPig 对象存储到E盘的 flyPig.txt 文件中,完成对 flyPig 对象的序列化操作
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:/flyPig.txt")));
oos.writeObject(flyPig);
System.out.println("FlyPig 对象序列化成功!");
oos.close();
}
/**
* 反序列化
*/
private static FlyPig deserializeFlyPig() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:/flyPig.txt")));
FlyPig person = (FlyPig) ois.readObject();
System.out.println("FlyPig 对象反序列化成功!");
return person;
}
}
//结果
被transient 修饰的不序列化.
被static修饰的变量应该也是不会被序列化的,因为只有堆内存会被序列化.所以静态变量会天生不会被序列化。
那这里被static修饰的变量反序列化后有值又是什么鬼 这是因为 静态变量在方法区,本来流里面就没有写入静态变量,我们打印静态变量当然会去方法区查找,我们当前 jvm 中有所以静态变量在序列化后任然有值。
分享一份大彬精心整理的大厂面试手册,包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等高频面试题,非常实用,有小伙伴靠着这份手册拿过字节offer~【领取/点击】
为什么要做序列化?
①、在分布式系统中,此时需要把对象在网络上传输,就得把对象数据转换为二进制形式,需要共享的数据的 JavaBean 对象,都得做序列化。
②、服务器钝化:如果服务器发现某些对象好久没活动了,那么服务器就会把这些内存中的对象持久化在本地磁盘文件中(Java对象转换为二进制文件);如果服务器发现某些对象需要活动时,先去内存中寻找,找不到再去磁盘文件中反序列化我们的对象数据,恢复成 Java 对象。这样能节省服务器内存。
46.线程池中 submit()和 execute()方法有什么区别?
ThreadPoolExecutor线程池发现可以有两种启动线程的方法:submit(Runnable runnable),excute(Runnable runnable)
ThreadPoolExecutor的抽象父类AbstractExecutorService实现了接口ExecutorService和Executor,而submit方法是ExecutorService中的接口,executor方法是Executor中的接口,
查看AbstractExecutorService源码,发现submit方法是在此抽象父类中实现:
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
可以看到,submit方法是先构造出一个RunnableFuture(FutureTask) 然后调用execute方法。不管你submit的时候传入的是Runnable还是Callable最后RunnableFuture(FutureTask)里面都会生成Callable对象。任务调用的时候调用RunnableFuture(FutureTask)的run方法,run方法调用Callable对象的call方法。
submit()方法,可以提供Future < T > 类型的返回值。
ThreadPoolExecutor中的executor方法:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
executor()方法,无返回值。
47.在 java 程序中怎么保证多线程的运行安全?
当多个线程要共享一个实例对象的值得时候,那么在考虑安全的多线程并发编程时就要保证下面3个要素:
原子性(Synchronized, Lock)
有序性(Volatile,Synchronized, Lock)
可见性(Volatile,Synchronized,Lock)
当然由于synchronized和Lock保证每个时刻只有一个线程执行同步代码,所以是线程安全的,也可以实现这一功能,但是由于线程是同步执行的,所以会影响效率。
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
在Java中,基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取共享变量时,它会去内存中读取新值。
更新的主要步骤:设置当前线程缓存无效,然后将新的更新到主内存,当其他线程访问时,清除本地缓存更新新的数据。
有序性:即程序执行的顺序按照代码的先后顺序执行。
48.多线程锁的升级原理是什么?
Sycronized是重量级锁(也是悲观锁)
49.什么是死锁?
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。
当然死锁的产生是必须要满足一些特定条件的:
1.互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
2.请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
3.不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
4.循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
50.怎么防止死锁?
- 加锁顺序(线程按照一定的顺序加锁)
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
- 死锁检测
51.ThreadLocal 是什么?有哪些使用场景?
ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
52.说一下 synchronized 底层实现原理?
53.synchronized 和 volatile 的区别是什么?
54.synchronized 和 Lock 有什么区别?
55.synchronized 和 ReentrantLock 区别是什么?
56.说一下 atomic 的原理?
这个写的头大了 后续更新。。。。
四 设计模式
57.说一下你熟悉的设计模式?
创建型模式,共五种:
- 工厂方法模式
- 抽象工厂模式
- 单例模式
- 建造者模式
- 原型模式。
结构型模式,共七种:
- 适配器模式
- 装饰器模式
- 代理模式
- 外观模式
- 桥接模式
- 组合模式
- 享元模式。
行为型模式,共十一种:
- 策略模式
- 模板方法模式
- 观察者模式
- 迭代子模式
- 责任链模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介者模式
- 解释器模式
58.简单工厂和抽象工厂有什么区别?
简单工厂 : 用来生产同一等级结构中的任意产品。(对于增加新的产品,无能为力)
工厂方法 :用来生产同一等级结构中的固定产品。(支持增加任意产品)
抽象工厂 :用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
简单工厂就是:一个工厂可以生产兰州拉面/黄焖鸡
工厂方法:就是一个大工厂下面有中国工厂和美国工厂,中国工厂生产兰州拉面,美国工厂生产牛肉面
抽象工厂:就是一个大工厂下面有英特尔厂和amd厂。英特尔组装英特尔主板+英特尔cup,amd工厂组装amd主板+amdcpu
五、Spring/Spring MVC
59.为什么要使用 spring?
spring是一个大容器,提供良好接口,切面编程。
60.解释一下什么是 aop?
AOP 即 Aspect Oriental Program 面向切面编程
首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。
所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
所谓的周边功能,比如性能统计,日志,事务管理等等
周边功能在Spring的面向切面编程AOP思想里,即被定义为切面
在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发
然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP
61.解释一下什么是 ioc?
我的理解是,a同学去通过中介所找对象,第一步填写资料,然后中介就根据你的标准给你找一个对象,当过一段时间想换个对象时,这时你就用再次填写资料,中介会直接给你找一个对象分配给你。
62.spring 有哪些主要模块?
Spring有七大功能模块,分别是Spring Core,AOP,ORM,DAO,MVC,WEB,Context。
1,Spring Core
Core模块是Spring的核心类库,Spring的所有功能都依赖于该类库,Core主要实现IOC功能,Sprign的所有功能都是借助IOC实现的。
2,AOP
AOP模块是Spring的AOP库,提供了AOP(拦截器)机制,并提供常用的拦截器,供用户自定义和配置。
3,ORM
Spring 的ORM模块提供对常用的ORM框架的管理和辅助支持,Spring支持常用的Hibernate,ibtas,jdao等框架的支持,Spring本身并不对ORM进行实现,仅对常见的ORM框架进行封装,并对其进行管理
4,DAO模块
Spring 提供对JDBC的支持,对JDBC进行封装,允许JDBC使用Spring资源,并能统一管理JDBC事物,并不对JDBC进行实现。(执行sql语句)
5,WEB模块
WEB模块提供对常见框架如Struts1,WEBWORK(Struts 2),JSF的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器。
6,Context模块
Context模块提供框架式的Bean访问方式,其他程序可以通过Context访问Spring的Bean资源,相当于资源注入。
7,MVC模块
WEB MVC模块为Spring提供了一套轻量级的MVC实现,在Spring的开发中,我们既可以用Struts也可以用Spring自己的MVC框架,相对于Struts,Spring自己的MVC框架更加简洁和方便。
63.spring 常用的注入方式有哪些?
配置文件,注解,构造器
64.spring 中的 bean 是线程安全的吗?
Spring 不保证 bean 的线程安全。
spring 管理的 bean 的线程安全跟 bean 的创建作用域和 bean 所在的使用环境是否存在竞态条件有关,spring 并不能保证 bean 的线程安全。
65.spring 支持几种 bean 的作用域?
- singleton:单例模式,在整个Spring IoC容器中,使用 singleton 定义的 bean 只有一个实例
- prototype:原型模式,每次通过容器的getbean方法获取 prototype 定义的 bean 时,都产生一个新的 bean 实例
66.spring 自动装配 bean 有哪些方式?
spring 配置文件中 节点的 autowire 参数可以控制 bean 自动装配的方式:
default - 默认的方式和 "no" 方式一样
no - 不自动装配,需要使用 节点或参数 spring 配置文件,使用 ref 参数注入 bean,必须要有对象的 setter 方法,这里即 Person 的 setFr 方法。
byName - 根据名称进行装配 byName 也是需要相应的 setter 方法才能注入
byType - 根据类型进行装配 byType 也是需要相应的 setter 方法才能注入
constructor - 根据构造函数进行装配 constructor 无需 setter 方法,需要通过 构造方法注入 bean
67.spring 事务实现方式有哪些?
第一种:使用 TransactionTemplate 事务模板对象
-
如果使用的是JDBC的话,就用DataSourceTransactionManager。注意需要传入一个DataSource,这样,平台才知道如何和数据库打交道。
-
第二: 为了使得平台事务管理器对我们来说是透明的,就需要使用TransactionTemplate。使用TransactionTemplat需要传入一个PlateformTransactionManager 进入,这样,我们就得到了一个TransactionTemplate,而不用关心到底使用的是什么平台了。
3.第三: TransactionTemplate 的重要方法就是 execute 方法,此方法就是调用TransactionCallback 进行处理。实际上我们需要处理的事情全部都是在 TransactionCallback 中编码的。
4.第四: 也就是 TransactionCallback 接口,我们可以定义一个类并实现此接口,然后作为TransactionTemplate.execute 的参数。把需要完成的事情放到 doInTransaction中,并且传入一个TransactionStatus 参数。此参数是来调用回滚的。 也就是说 ,PlateformTransactionManager 和 TransactionTemplate 只需在程序中定义一次,而TransactionCallback 和 TransactionStatus 就要针对不同的任务多次定义了。
步骤:
1.配置事务管理器
<!-- 配置事务管理器 ,封装了所有的事务操作,依赖于连接池 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
2.配置事务模板对象
<!-- 配置事务模板对象 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
第三种:基于Aspectj AOP开启事务
1.配置事务通知
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" rollback-for="Exception" />
</tx:attributes>
</tx:advice>
2.配置织入
<aop:config>
<!-- 扫描 cn.sys.service 路径下所有的方法,并加入事务处理 -->
<aop:pointcut id="tx" expression="execution(* cn.sys.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="tx" />
</aop:config>
这样就算是给 cn.sys.service下所有的方法加入了事务
第四种:基于注解的 @Transactional 的声明式事务管理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
<!-- 创建加载外部Properties文件对象 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:dataBase.properties"></property>
</bean>
<!-- 引入redis属性配置文件 -->
<import resource="classpath:redis-context.xml"/>
<!-- 配置数据库连接资源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" scope="singleton">
<property name="driverClassName" value="${driver}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
<property name="maxActive" value="${maxActive}"></property>
<property name="maxIdle" value="${maxIdle}"></property>
<property name="minIdle" value="${minIdle}"></property>
<property name="initialSize" value="${initialSize}"></property>
<property name="maxWait" value="${maxWait}"></property>
<property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}"></property>
<property name="removeAbandoned" value="${removeAbandoned}"></property>
<!-- 配置sql心跳包 -->
<property name= "testWhileIdle" value="true"/>
<property name= "testOnBorrow" value="false"/>
<property name= "testOnReturn" value="false"/>
<property name= "validationQuery" value="select 1"/>
<property name= "timeBetweenEvictionRunsMillis" value="60000"/>
<property name= "numTestsPerEvictionRun" value="${maxActive}"/>
</bean>
<!--创建SQLSessionFactory对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:MyBatis_config.xml"></property>
</bean>
<!-- 创建MapperScannerConfigurer对象 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.sys.dao"></property>
</bean>
<!-- 配置扫描器 IOC 注解 -->
<context:component-scan base-package="cn.sys" />
<!-- 配置事务管理 ,封装了所有的事务操作,依赖于连接池 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务模板对象 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<!-- 配置事务增强 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<!-- aop代理事务 -->
<aop:config>
<aop:pointcut id="tx" expression="execution(* cn.sys.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="tx" />
</aop:config>
</beans>
68.说一下 spring 的事务隔离?
@Transactional(propagation=Propagation.REQUIRED)
如果有事务就新添加一条事务。没有默认添加一个
@ Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不为方法开启事务
@ Transactional(propagation=Propagation.REQUIRES_NEW)
不管事务是否存在直接新建一个事务,如果都创建事务,原来的事务挂起,当第二个事务执行完毕后,在执行挂起的事务
@ Transactional(propagation=Propagation.MANDATORY)
必须在一个事务执行完毕否则抛出异常
@Transactional(propagation=Propagation.NEVER)
必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@ Transactional(propagation=Propagation.SUPPORTS)
如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
事务隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED)
读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ)
可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE)
串行化
MYSQL: 默认为REPEATABLE_READ级别
SQLSERVER: 默认为READ_COMMITTED
脏读 : 一个事务读取到另一事务未提交的更新数据
不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说,
后续读取可以读到另一事务已提交的更新数据. 相反, "可重复读"在同一事务中多次
读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
幻读 : 一个事务读到另一个事务已提交的insert数据
69.说一下 spring mvc 运行流程?
70.spring mvc 有哪些组件?
前端控制器(DispatcherServlet)
处理器映射器(HandlerMapping)
处理器适配器(HandlerAdapter)
拦截器(HandlerInterceptor)
语言环境处理器(LocaleResolver)
主题解析器(ThemeResolver)
视图解析器(ViewResolver)
文件上传处理器(MultipartResolver)
异常处理器(HandlerExceptionResolver)
数据转换(DataBinder)
消息转换器(HttpMessageConverter)
请求转视图翻译器(RequestToViewNameTranslator)
页面跳转参数管理器(FlashMapManager)
处理程序执行链(HandlerExecutionChain)
71.@RequestMapping 的作用是什么?
@RequestMapping 是一个注解,用来标识 http 请求地址与 Controller 类的方法之间的映射。
72.@Autowired 的作用是什么?
- @Autowired可以对成员变量、方法和构造函数进行标注,来完成自动装配的工作,这里必须明确:@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Qualifier使用;
- @Autowired标注可以放在成员变量上,也可以放在成员变量的set方法上。前者,Spring会直接将UserDao类型的唯一一个bean赋值给userDao这个成员变量;后者,Spring会调用setUserDao方法来将UserDao类型的唯一一个bean装配到userDao这个属性。
- Spring 2.5 引入了 @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired的使用来消除 set ,get方法。
六、 Spring Boot/Spring Cloud
73.什么是 spring boot?
用来简化搭建spring应用,嵌套容器tomcat,jetty,简化xml配置。
74.为什么要用 spring boot?
- 支持Quartz调度程序
- 简化安全自动配置
- 至此嵌入式Netty,tomcat
- 全新框架,支持springMVC,webFlux
- 为各种组件的响应式编程提供自动化配置
75.spring boot 核心配置文件是什么?
76.spring boot 配置文件有哪几种类型?它们有什么区别?
Spring boot核心配置的两种形式:
1、.properties: 键值对的配置方式
2、.yml:值与前面的问号必须存在一个问好
server:
port: 8080
context-path: spring-webproject
77.spring boot 有哪些方式可以实现热部署?
- 使用 Spring Loaded
- 使用 spring-boot-devtools
spring loaded:
//引入依赖
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.6.RELEASE</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
//运行方式
运行的时候不能直接点击main运行,需要使用
mvn:spring-boot:run启动
spring-boot-devtools
//引入以来
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
</dependency>
//然后还是使用 mvn spring-boot:run 启动项目,随意更改代码即可看到效果。
如果我们想指定让 devtools 监听指定文件夹,那么可以在 application.yml 配置
spring.devtools.restart.additional-paths=your path,注意这里需要改成 yml 文件的格式。