解释型语言和编译型语言的区别?Java是解释型语言还是编译型语言?
编译型语言:把做好的源程序全部编译成二进制代码的可运行程序。然后,可直接运行这个程序。
解释型语言:把做好的源程序翻译一句,然后执行一句,直至结束!
编译型语言,执行速度快、效率高;依靠编译器、跨平台性差些。
解释型语言,执行速度慢、效率低;依靠解释器、跨平台性好。
个人认为,java是解释型的语言,因为虽然java也需要编译,编译成.class文件,但是并不是机器可以识别的语言,而是字节码,最终还是需要 jvm的解释,才能在各个平台执行,这同时也是java跨平台的原因。所以可是说java即是编译型的,也是解释型,但是假如非要归类的话,从概念上的定义,恐怕java应该归到解释型的语言中。
JIT是什么
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lKmKwpFE-1640407809272)(D:\Ego\Java\面试\20191015222132356.png)]
java编译为字节码文件后,如果jvm发现某段代码为“热点代码”,那么就会用JIT直接编译并进行优化。不是热点代码的话jvm就用解释器去解释。
热点代码有两类:1.被多次调用的方法 2.被多次循环执行的方法体
jvm识别热点代码需要进行热点探测,探测算法有两种:
基于采样
jvm会周期性对各个线程栈顶进行检查,如果某些方法经常出现在栈顶,它就是热点方法。实现简单高效,但容易收到线程阻塞或其他外因干扰
基于计数器
为每个方法或代码块建立计数器,超过一定阈值就认为是热点方法。结果严谨,但实现麻烦
HotSpot采用第二种,并且有两类计数器:
方法调用计数器
调用一次次数加一,超过一定时间(半衰周期)没有调用的话,次数会减半,称为“热度衰减”。
回边计数器
统计循环体执行的次数。字节码中遇到控制流向后跳转的指令称为回边,遇到回边就加一
正则表达式和Java?
正则表达式就是一种字符串的匹配模式,可以将一些复杂的规则用一个表达式来描述。
Java的String类提供了支持正则表达式操作的方法:matches() replaceAll() replaceFirst() split()
此外也可以用Pattern类表示正则表达式对象
Java如何跳出多重嵌套循环?
在最外层循环前加一个标记,比如a: 然后break a即可跳出,但是不建议这样用,会让代码可读性变差
java也有关键字goto,但是没有用
int和Integer的区别?
Java是面向对象语言,所以为了能将基本数据类型当对象操作,Java为他们各自提供了包装类型,Integer就是int的包装类型,从Java5开始引入了自动装箱/拆箱,二者可以相互转换
如何输出一个某种编码的字符串
String a = "abcde"; //将a以UTF-8的编码方式获得字节数组,再以GBK的编码方式编码为b String b = new String(a.getBytes("UTF-8"), "GBK"); System.out.println(b);
请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?
Array声明的时候就要确定长度,而且长度不可变,只能存储同一数据类型,可以存储基本数据类型
ArrayList是一个集合,长度可变,可以存放不同数据类型,不可以存放基本数据类型(可以存他们的包装类)。
当能确定数据类型和个数时可以用Array
什么是自动拆装箱?
Java八种基本数据类型都有各自对应的包装类,基本类型和对应的包装类在很多场景下可以自动转换,不需要手动去转换。
实现原理:
哪些地方自动拆装箱:
为什么会出现4.0-3.6=0.40000001?
计算机计算十进制时要先转换成二进制,而二进制无法精确表示十进制小数,所以会出现误差
可以用BigDecimal来解决这个问题。
十进制的数在内存中是怎么存的?
补码的形式
Java8新特性?
对HashMap的数据结构进行优化
HashMap1.8以前是数组+链表,1.8以后是数组+链表/红黑树
Lambda表达式,本质是一段匿名内部类
函数时接口,给Lambda提供更好的支持
Stream API:创建Stream,中间操作,终止操作
Optional类:用来解决空指针异常
接口中可以定义m默认实现方法和静态方法
日期API
Object若不重写hashCode()的话,hashCode()如何计算出来的?
Object的hashCode是本地方法,用c/c++实现的,直接返回该对象的内存地址
请你解释为什么重写equals还要重写hashcode?
提升效率
向HashMap中put时,首先会计算对象的哈希码,然后看看对应的位置上有没有元素,如果没有的话就可以直接插入了,就不需要从头到尾和每个元素都进行equals判断了。
还有为了保证HashMap和HashSet中的去重性。因为equals相同的对象,hashcode必须相同,如果只重写equals的话,两个属性相同的对象按照规则hash值也一样,但是没有hashcode的话会调用Object默认的的hashcode,这样值就不相同了。比如两个相同的属性的对象,hashcode值不同,HashMap认为是两个不同的对象,都会存进去,但是我们想要的结果是只存一个,这就违反了HashMap的唯一性了。
请你谈谈关于Synchronized和lock
synchronized | Lock |
---|---|
Java关键字 | 一个接口 |
线程执行完或发生异常会释放锁,不会出现死锁 | 需要在finally中手动释放锁,不然容易造成死锁 |
不可以中断等待锁的线程 | 可以中断等待锁的线程 |
不可以判断锁的状态 | 可以判断锁的状态 |
大量线程竞争时性能低 | 大量线程竞争时性能高 |
请你介绍一下volatile?
volatile可以用来保证可见性和有序性。
先说下内存模型:计算机执行程序时,每条指令都是在CPU上执行,那就涉及到了数据的读写。CPU的运行速度很快,而向主存读写数据的速度很慢,所以就在两者之间加了高速缓存。先把主存的数据复制一份到高速缓存中,然后CPU就可以从高速缓存中进行读写,最后高速缓存再将值刷新到主存中。那么多线程就会出现问题,比如主存中有一个数为0,现在有两个线程想对它加一,那么结果应该为2。但是可能第一个线程计算为1之后还没来得及写入主存,第二个线程就进行运算,也就是读取的也是0,那么最后结果为1。所以就提供了两种办法:
这就是可见性问题。
有序性问题就是说,JVM在执行时会对程序执行顺序进行优化,比如int a = 1, int b = 2。在实际执行时可能会将他们的顺序交换,但不会造成影响,因为这两个变量没有依赖关系,这就是指令重排序。单线程下是不会造成问题,但是多线程就可能出现问题。比如第一个线程先定义了某个变量,然后定义了一个标志位为true,第二个线程中假设标志位为true的话就使用第一个变量,那么重排序后,第一个线程可能设置标志位为true提前了,第二个线程认为可以使用了,但其实变量还没定义,那么就会出错。Java内存模型具备一定的有序性,即happens-before原则
在Java中,Java模型为了获得更好的性能,允许处理器使用高速缓存,也允许编译器进行指令重排序,所以也会出现这两个问题。
那么volatile修饰的变量有两个作用:
但是volatile不可以保证原子性,比如要对a = 0 自增,开启两个线程,每个线程循环100次a++,那么结果可能是小于200的。因为a++不是原子操作,它分为三个步骤,先读取a,再加一,再写会,那么第一个线程第一次加的时候,可能刚读取完,然后被阻塞了,第二个线程再读的时候还是原来的值,加完之后写了回去,这时第一个线程阻塞完毕继续再原来的基础上加1,然后写回,那么两次操作其实只加了1。
volatile底层原理是,在生成汇编代码时会多出一个lock前缀指令,这个指令相当于一个内存屏障,它提供了3个功能:
重载和重写的区别?
两个都是实现多态的方式。重载是编译时多态,重写是运行时多态。重载发生在同一个类中,要求方法名一样,参数列表不一样,对返回值没有要求;重写发生在子类与父类中,子类重写父类的方法要求方法名,参数列表一样,返回值一样或者为父类返回值的字类,访问修饰符不能小于父类,不能抛出新的异常或更宽泛的异常
面向对象的六原则一法则?
在try块中可以抛出异常吗?
可以,比如IO流读取File的时候,外层try catch包含创建流的代码,内层try catch来操作流,这样如果流创建失败直接抛异常,就不用关闭流了。
抽象类和接口的区别?
语法上:
应用上:
接口是横向的,抽象类是纵向的,接口约定了一个共同的行为,而抽象类是把一些子类的共性抽取出来,可以帮他们完成一部分方法的实现。所以需要横向扩展就用接口,纵向扩展就用抽象类。
举个例子,比如某个项目的所有Servlet都需要进行权限判断,记录日志、异常等操作,就可以定义一个抽象类,定义一个抽象方法,里面写具体的业务逻辑,然后定义一个非抽象的方法,去完成权限判断,记录日志的操作,然后去调用这个抽象方法,那么子类去继承他的时候只需要重写业务逻辑的抽象方法就可以了,别的操作就自动帮他完成了。
请说明一下final, finally, finalize的区别?
final:
修饰属性表示这个属性不可变,是一个常量。
修饰方法表示这个方法不可以被重写。
修饰类表示这个类不可以被继承。
finally:
用于异常处理,无论是否抛出异常,finally中的代码一定会执行,所以一般用于资源的关闭。
finalize:
是Object类中的一个方法,当垃圾收集器回收时,被回收的对象会调用此方法,以供其他资源回收
请说明面向对象的特征有哪些方面?
封装
就是要做到高内聚低耦合,把一个对象的属性和行为都封装到一个类中,把成员变量定义为私有。
继承
就是把父类的属性和方法继承过来,然后添加一些自己需要的新的东西,做到了可重用性和扩展性
多态
就是父类引用指向子类对象。分为编译时多态和运行时多态,重载实现了编译时多态,重写实现了运行时多态
请说明Comparable和Comparator接口的作用以及它们的区别?
两个接口都是来定义排序规则的。
Comparable:
相当于内部比较器,比如对集合进行排序一般会用到Collections.sort()方法,但是集合中的这个类必须实现Comparable接口,并重写他的compareTo方法,才可以直接用Collections进行排序
Comparator:
相当于外部比较器,如果一个类我们没办法对他进行扩展,也就是无法继承Comparable,那就可以使用Comparator,在用Collections.sort()方法时,里面传入集合以及一个匿名内部类,这个内部类去实现Comparator接口,并重写compare()方法。比如String类型中,默认的比较规则是按照字典序排序,如果我们想忽略大小写进行排序的话,就可以使用Comparator
请你讲讲什么是泛型?
泛型就是参数化类型,就是将操作的数据类型指定为一个参数,可以在类、接口、方法中使用。
泛型提高了代码的重用率,编译时可以检查类型安全,消除了强制转换,减少了出错的机会。
请解释一下extends 和super 泛型限定符?
extends用来定义上界,super定义下界。
上界的list只能get,不能add。
因为extends表示它和它的子类,但是具体add哪一个子类是不确定的,所以干脆就不让它add
但是get的话,无论是哪一个子类,都可以向上转型成上界
下界的list只能add,不能get。
super表示它和它的父类,父类那么多不知道add哪个,所以只能add他和他的子类,虽然不知道add哪个,但是都可以向上转型成下界。
而get的话,那么多父类是不能向下转型的,除非用Object来接收。
所以如果想取数据就使用extends,想存数据就用super
请说明”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?
static表明让成员变量或成员方法所属于一个类,而不是对象,被static修饰的成员变量和方法可以直接通过类名.的方式被访问,独立于对象。
static还可以当作静态代码块,在类第一次被加载的时候执行,只会被执行一次,一般用作初始化,提高性能。
Java不可以覆盖private或static修饰的方法。private只用本类能访问到,子类是访问不到的,更别说覆盖了。
而static修饰的静态方法,跟任何实例无关,在编译时就绑定了,但覆盖是在运行时动态绑定,所以概念上不适用。
请列举你所知道的Object类的方法并简要说明
类和对象的区别?
类是一个抽象的概念,是对具有共同特征的实体的集合。
对象是类的实例,是真实的个体,和真实世界一一对应的。
String为什么不可变?为什么要这样设计?
因为String的本质是char数组,而char数组是被final修饰的,所以不可以变。
为什么要这样设计:
字符串常量池的需要
Java堆内存中有一块区域是字符串常量池。当创建一个String对象时,如果常量池已经有这个字符串了,那就不会再创建,而是直接将引用指向它。如果有好几个引用都指向了一个常量池中的对象,这个时候如果有一个引用想要改变它的话,那所有的引用指向的就都被改变了,显然不合理。
效率
String中有hash来保存它的哈希码,String设置为不可变的话,每次使用的时候就不需要重复计算哈希码,提高了效率
安全
Java很多类都使用了String,比如网络连接,反射等等,如果String是可变的话h会引起安全隐患
List、Map、Set三个接口,存取元素时,各有什么特点?
List和Set都是单列集合,Map是双列集合。
List有先后顺序,Set和Map是无序的。
List:
存的时候调用add()方法。
取的时候用get()或者用Iterator接口遍历
Set:
不能有重复的元素。存的时候调用add()方法,返回值为boolean,如果为true说明集合中没有这个元素,成功存进去,如果为false说明已经存在了。
取的时候用Iterator接口遍历
Map:
以键值对形式存储,不能有重复的键。存的时候调用put()方法
取的时候可以调用get(),根据key来找相应的value;也可以通过keySet()获取所有key的集合;也可以通过values()获取所有value的集合;也可以通过entrySet()获取所有键值对的集合
Set和Map都有哈希存储的版本和排序树存储的版本,哈希存储版本存取非常高效,而排序树版本可以自定义排序规则
ArrayList、LinkedList、Vector的区别和实现原理
存储结构
ArrayList和Vector基于数组实现,LinkedList基于双向链表实现。
线程安全
ArrayList和LinkedList不是线程安全的,可以用Collections中的静态方法synchronizedList()把他们变成线程安全的。Vector是线程安全的,大部分方法都包含synchronized,所以效率比较低,已经被遗弃了。
扩容机制
ArrayList如果元素个数超过数组长度,会产生一个新的数组,容量是原来的1.5倍,然后将原来的数据复制过来,再加上新的数据。
效率
ArrayList和Vector查询效率是O(1),平均增删的效率是O(n)
LinkedList平均查询效率是O(n),增删效率是O(1)
请判断List、Set、Map是否继承自Collection接口?
List和Set继承自Collection,Map不是
请讲讲你所知道的常用集合类以及主要方法
List、Set、Map
List:具体实现有ArrayList和LinkedList,常用方法有get(),add(),remove(),contains(),size()
Set:具体实现有HashSet和TreeSet,常用方法有add(),remove(),contains(), size()
Map:具体实现有HashMap和TreeMap,常用方法有get(),put(),remove(),containsKey(),containsValue(),keySet(),values(),entrySet()
Collection和Collections的区别?
Collection是集合类的父接口,实现有List和Set
Collections是集合类的一个帮助类,提供了一系列静态方法来操作集合类,主要方法有:
请说说快速失败(fail-fast)和安全失败(fail-safe)的区别?
快速失败:当用迭代器遍历一个集合时,如果这个集合被修改,那么就会抛出异常,所以不能在多线程下并发修改。原理:迭代时会用一个modeCount变量,如果集合遍历时被修改,那么modeCount的值就会改变,迭代器每次调用hasNext和next前都会判断modeCount和expectedmodeCount是否一样,如果不一样就会抛出异常。
安全失败:迭代器遍历集合时,集合被修改也不会抛出异常。原理:迭代前会对原有集合进行一次拷贝,迭代器对拷贝的集合进行迭代,所以就算原有集合被修改,拷贝的集合也没有被修改,也就不会抛出异常
请你说说Iterator和ListIterator的区别
两者都是迭代器。
Iterator:可以应用于List和Set,只能向后遍历,而且只能获取和删除。
ListIterator:
只能应用于List,是对Iterator的一个增强。
不仅可以向后遍历,也可以向前遍历,即多了hasPrevious()和previous()。
可以获取当前索引的位置,即nextIndex()和previousIndex()。
可以对当前对象进行修改,即set()
可以插入对象,即add()
请你说明一下ConcurrentHashMap的原理?
1.7:使用了分段锁Segment,Segment就类似于HashMap,也就是相当于一个二级哈希表。在put时,先根据key找到对应的Segment,然后在对应的Segment里面尝试获取锁,获取到锁之后就进行插入,和HashMap类似,最后再释放锁。在get时就很简单,因为它的value被volatile修饰了,所以保证了可见性,这样就不需要加锁了,效率就得到了提高。
1.8:数据结构和1.8的HashMap类似,不使用Segment,而是采用了CAS和synchronized来实现并发安全的。put时,先尝试用CAS写入,如果失败就通过自旋保证成功。如果hashcode==-1就需要进行扩容。如果都不满足的话,就用synchronized进行写入。最后判断是否要转成红黑树。
请说明ArrayList是否会越界?
会发生。因为ArrayList是线程不安全的,所以多线程情况下可能会发生越界。
HashMap的容量为什么是2的n次幂?
为了散列更均匀,减小哈希碰撞。因为在计算key对应的下标时,计算方法是(n-1)&hash值,这其实就相当于取模的位运算形式,如果容量是2的n次幂的话,减去1之后就是低位全是1的形式,这样和hash进行与运算时会大大减小hash冲突。
如果一直在list的尾部添加元素,用哪种方式的效率高?
在尾部添加元素的时间复杂度都是O(1),但是ArrayList需要扩容,而LinkedList需要new结点,所以在千万级别一下的时候,扩容的优势还不明显,LinkedList效率更高,但在千万级别以上ArrayList效率更高
请你解释一下hashMap具体如何实现的?
jdk1.7以前是通过数组+链表实现的,jdk1.8改为了数组+链表/红黑树。使用时,先初始化HashMap,可以指定容量大小n,它会自动调用tabSizefor()函数来得到第一个大于等于n且为2的整数次幂的数,如果不指定容量的话,就默认为16。然后插入时,会先根据key计算得到hash值,然后通过hash值得到索引。拿到索引后先判断那个位置上有没有结点,如果没有的话直接插入,如果有的话,判断和头节点的key是否相等,如果相等的话直接覆盖,如果不相等的话,如果头节点是红黑树结点,就按照红黑树结点的查找方式遍历,如果是链表结点的话,按链表结点方式遍历,如果在遍历中找到key相同的结点就覆盖,如果没有的话,就插入到尾部,链表的话插入完判断结点是否超过8个而且数组长度超过64,如果超过的话就转为红黑树。最后判断是否需要扩容。
HashMap扩容机制
首先判断老表的容量是否超过上限,如果超过上限的话,将扩容阈值修改为Integer最大值,如果没超过的话,将容量和阈值都扩大为2倍,并指向新数组,然后遍历老数组,如果当前索引只有一个结点,则重新计算索引位置然后放到新数组;如果当前索引不止一个结点,则计算e.hash&oldCap,如果为0,则直接放到原索引位置,如果为1,则放到原索引+oldCap的位置。因为计算索引时,用的是(n-1)&hash,那么扩容为2倍之后,n-1和原来相比,就是在高位多出来一个1,低位还是一样的,所以只用判断这一位即可
如何实现线程安全?
线程安全主要体现在原子性、可见性、有序性
实现原子性:使用原子类、synchronized、lock
实现可见性:volatile、synchronized、lock
实现有序性:volatile、happens-before原则
线程的基本状态以及状态之间的关系?
基本状态:新建、就绪、运行、阻塞、死亡
什么是守护线程?
线程分为用户线程和守护线程,守护线程就是在后台提供一种通用服务的线程,比如垃圾回收器。当所有用户线程结束后,守护线程就会结束,因为没有存在的意义了。可以用过thread.setDaemon(true)来设置一个线程为守护线程,必须在start之前设置。守护线程中产生的新线程也是守护线程。守护线程中不可以有读写操作和计算逻辑,因为守护线程随时都有可能停止。
线程池的状态和状态之间的关系?
5种状态: running、shutdown、stop、tidying、terminated
如何停止线程池?
Java锁?
并发编程中,为了避免共享数据产生数据不一致的问题,使用锁来锁定代码块、方法。
锁分为:悲观锁、乐观锁、公平锁、非公平锁、独占锁、共享锁、自旋锁、可重入锁、偏向锁/轻量级锁/重量级锁
什么是死锁?原因和必要条件?怎么解决?
死锁就是多个进程因为争夺资源而陷入的一种僵局,如果无外力作用就会一直保持下去。
原因:竞争资源和推进顺序非法
必要条件:
怎么解决:
预防死锁:
破坏4个必要条件之一即可:
避免死锁:银行家算法
检测死锁
解除死锁
什么是活锁和饥饿?
活锁跟死锁相反,死锁是因为争夺资源而陷入僵局,活锁是都可以获取到资源,但是都相互谦让陷入的一种僵局,活锁有可能自行解开。比如让线程休眠一段时间,让别的线程先获取到资源。
饥饿是一个线程一直获取不到资源,资源一直被别的线程获取,导致他可能永远等待。可能的原因是这个线程的优先级低,所以一直被高优先级的线程争夺到资源。
无锁技术
什么是happens-before?
JVM会对代码进行优化,进行指令重排序,happens-before就保证了重排序后依然可以正确执行代码。他的原则是如果一个操作happens-before另一个操作,那么第一个操作的结果对第二天操作可见。
有6条规则:
sleep()和wait()的区别?
sleep是Thread类的静态方法,wait是Object类的成员方法
sleep可以在任何地方使用,wait只能在同步代码块或同步方法中使用
sleep释放CPU资源,但不释放锁,休眠时间到后继续执行,wait释放锁,进入等待队列,被唤醒后才有机会获取锁
notify和notifyAll的区别?
一个线程中调用了一个对象的wait方法,该线程就会释放该对象的锁,进入该对象的等待池,等待池中的线程不会去竞争该对象的资源。一个对象还有一个锁池,只有在锁池中的线程才会去竞争该对象的锁。notify就是随机唤醒等待池中的一个线程进入锁池,notifyAll将等待池中所有的线程唤醒进入锁池
线程池中submit()和execute()方法有什么区别?
submit参数为runnable或callable,execute参数为runnable
submit有返回值,execute没有返回值
ThreadLocal有什么作用?有哪些使用场景?
ThreadLocal是本地线程存储,为每个线程创建一个该变量的副本,可以做到数据间的隔离。
JDBC连接connection就使用到了,为每个线程创建一个自己的连接,这样就保证了他们是在各自的连接上进行数据库操作,不会出现A线程关了连接,而B线程还在用的情况
如何保证多个线程同时启动?
使用countDownLatch。run方法中调用countDownLatch的await(),就会将线程阻塞在此处。然后调用start。
在需要同时启动的地方调用countDownLatch的countDown()
说说对于sychronized同步锁的理解
每个java对象都有一个内置锁,当线程运行到非静态的synchronized方法上时,就会自动获取该实例对象的锁,此时别的线程无法获得它的锁,当方法执行完毕后,会释放锁。
BIO、NIO、AIO有什么区别?
BIO:同步阻塞,线程发起IO操作,不管内核是否准备好IO操作,线程都会一直阻塞直到完成。适用于连接数少的架构。
NIO:同步非阻塞,发送的请求会注册到多路复用器上,多路复用器轮询到连接有IO请求时才会开启一个线程。适用于连接数多且连接比较短的架构。
AIO:异步非阻塞,线程发起IO请求立即返回,内核做完IO操作后才会通知线程。适用于连接数多且连接比较长的架构。
如何读取文件a.txt中第10个字节?
FileInputStream fis = new FileInputStream("a.txt"); fis.skip(9); int b = fis.read();
节点流和处理流区别?
节点流:可以从某个节点读数据或写数据的流
处理流:对已有的流进行封装,提供更丰富的处理,构造方法必须是其他流的对象,比如BufferedReader
缓冲流的优缺点?
不带缓存的流读一个字节或字符就写一个字节或字符,缓冲流读取到字节或字符先放入缓冲区,当缓冲区满了再一次性写出去。
优点:减少了写的次数,提高效率
缺点:接受端无法即时接收到数据
spring的好处
什么是IOC?
IOC就是控制反转,是一种设计思想,把对象的创建交给spring来处理,可以解耦合,提高程序的复用性
什么是AOP?
AOP就是面向切面编程,可以将一些与业务无关,但是被业务共同调用的逻辑封装起来,增加代码的复用性,降低模块间的耦合
spring注入方式
接口注入,setter注入,构造器注入
spring的bean是线程安全吗?
spring不能保证bean是线程安全的,因为默认bean是单例的,这样就可能存在竞争,就会造成线程不安全。
可以用ThreadLocal或锁来解决这个问题。
spring自动装配bean有哪些?
XML方式:
注解方式:
Bean的生命周期
分为4个阶段:实例化,属性赋值,初始化,销毁。
实例化的时候,可以通过InstantiationAwareBeanPostProcessor接口来进行扩展,可以在实例化之前调用它的方法来替换原本的bean作为代理,这也是AOP能实现的关键点。可以在实例化之后,属性赋值之前调用它的方法来阻断属性填充。
初始化阶段,可以调用BeanPostProcessor接口,一系列Aware接口,InitializingBean接口,来进行扩展。BeanPostProcessor接口作用在初始化的前后,Aware接口可以拿到Spring的一些资源,比如BeanName,BeanFactory,ApplicationContext,InitializingBean接口可以自定义一些初始化操作。初始化时调用init-method指定的方法。
销毁阶段,可以通过DisposableBean接口来进行扩展,最后调用destroy-method指定的方法进行销毁
BeanFactory 和 FactoryBean 的区别
BeanFactory 是一个工厂类,用来管理Bean的,最核心的功能就是加载Bean,也就是getBean()方法。
FactoryBean 是一个特殊的Bean,实现该接口的类可以实现它的getObject方法来自定义创建Bean实例
BeanFactory 和 ApplicationContext 的区别
BeanFactory 是一个基础的IOC容器,提供了完整的IOC功能。
ApplicationContext是BeanFactory的一个子接口,是一个高级的IOC容器,提供了更多的功能。
二者加载Bean的时机也不同。BeanFactory 是延迟加载,调用getBean()时才加载,而ApplicationContext在启动后就预加载所有单实例的Bean,到时候可以直接拿来使用
Spring 的 AOP 有哪几种创建代理的方式?
有JDK动态代理和Cglib代理。
JDK动态代理只能代理实现了接口的类,因为通过JDK动态代理生成的类已经实现了Proxy类,所以不能继承别的类了。
而Cglib代理没有这个限制,但是他不可以代理被final修饰的类或方法,因为他的本质是通过继承实现的
Spring是如何解决的循环依赖?
Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。
为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?
如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。
Spring 的事务传播行为有哪些?
什么是SpringMVC ?简单介绍下你对springMVC的理解?
SpringMVC 是Spring框架的一部分,把model,view,controller层分离,把复杂的web应用分成逻辑清晰的几部分,可以做到解耦,简化开发,方便开发人员之间的配合
SpringMVC的流程?
如何解决POST请求中文乱码问题,GET的又如何处理呢?
POST:在web.xml里配置CharacterEncodingFilter 过滤器
GET:两种方法
@RequestMapping的作用是什么?
用来标识HTTP请求地址与Controller之间的映射关系。
可以加在类上,也可以加在方法上,完整的路径是类上的@RequestMapping的value加上方法上的RequestMapping的value
SpringMvc的控制器是不是单例模式?如果是,有什么问题?怎么解决?
是,在多线程访问时会出现线程不安全的问题。
解决方法:控制器里对可变状态量使用ThreadLocal,为每个线程生成一个副本,互不影响
Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
@ComponentScan:Spring组件扫描。
SpringBoot自动装配原理
Springboot通过启动类的@SpringBootApplication注解来实现自动装配。该注解是一个组合注解。包含三个注解