1.什么是面向对象?
比如现在有一个洗衣服的例子,面向过程的做法是把衣服倒入洗衣机,洗衣机洗衣服,把衣服晾起来.面向对象的做法是人把衣服倒入洗衣机,把衣服拿出来,洗衣机去洗衣服.相较于面向过程,面向对象更易于复用,扩展和维护,而面向过程更加高效.
面向对象三大特性为封装,继承,多态(抽象)
封装:明确标识出允许外部使用的所有成员函数和数据项,外部调用无需修改或者关心内部实现,orm框架就是基于封装思想的,我们操作数据库无需知道连接是怎么建立的,sql是如何执行的,我们只需要引入mybatis调用相关方法就行了
继承:继承基类的方法,并且做出自己的改变和扩展
多态:(需要有继承和方法重写,父类引用指向子类对象)基于对象所属类的不同,外部对同一个方法的调用实际执行逻辑不同,无法调用子类特有的功能
抽象:我们在定义一个类的时候,实际上就是把一类事物的公有的属性和行为提取出来,形成一个物理模型,这种研究问题的方法称为抽象。
2.JVM性能调优
对于java栈:在java中一个方法对应一个栈帧,里面主要存放局部变量表,操作数栈(用于计算的一块临时内存空间),动态链接(根据动态链接jvm可以把我们写的方法对应到底层实际运行的代码),方法出口(return完之后需要连接到下一个步骤).如果说在方法里new了一个对象,那么会在栈中的表里放一个堆中对象的内存地址,具体的对象是存放在堆中的.
程序计数器:jvm会给每一个线程分配一个专属的程序计数器内存空间,会放置程序马上要运行的那一行代码的行号,前面的0-12就是所说的程序计数器数值
方法区(元空间):存放常量+静态变量+类信息,如果说new了一个对象,这个对象是静态变量会在堆中存放这个具体的对象,并且在本地放一个该对象的地址
本地方法栈:当调用到native方法时,jvm会去调用本地方法(c++代码),给这些方法分配内存空间的地方就是本地方法栈
堆:
minor gc:伊甸园区满了,通过可达性分析(GC Roots根节点:线程栈本地变量,静态变量,本地方法栈变量等),把非垃圾移动到幸存者区域,剩下的伊甸园区的垃圾全部清除,其中s0和s1区域是可以相互交换的(s0和s1区总有一个区域是空的,这个空的区域就是收集幸存者的地方,不空的区域跟着Eden一起进行minor gc),每当一个对象经过了一次交换(GC过一次),它的年龄就会+1,如果到达15,就会被丢到老年区
full gc:老年代放满了,对整个堆空间进行垃圾回收,如果回收完之后老年区还是满的就报错OOM.衍生问题(为什么会有stop the world的机制?--->如果在GC过程中线程直接结束了,原本栈中的指针也被销毁,本来不应该被认定为垃圾的对象也就被认定为垃圾了)
3.JDK,JRE,JVM三者的关系
- JDK:java开发工具
- JRE:java运行时环境
- JVM:虚拟机
三者是包含关系jdk>jre>jvm
4.==和equals的区别
==对比的是栈中的值,基本数据类型是变量值,引用类型是堆中内存对象的地址
equals:object中默认也是用==比较,通常会重写
5.简述final的作用,为什么局部内部类和匿名内部类只能访问局部final变量?
- final修饰类表示不可被继承,修饰方法表示不可被重写,修饰变量表示不可更改
- 修饰类变量(静态成员变量)只能在静态初始化代码块中指定初始值或者声明该类变量时指定初始值
- 修饰成员变量可以在非静态代块,声明类变量时或者构造器中赋值
- 修饰局部变量的时候,既可以在定义时赋值也可以在后面的代码中赋值(仅仅一次)
- 修饰基本数据类型的变量,其数值一旦初始化后就不能更改了
- 修饰的如果是引用类型的变量,在对其初始化后就不能让他指向另一个对象,但是引用的值是可以更改的
为什么局部内部类和匿名内部类只能访问局部final变量?
内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就随着方法的执行完毕而销毁,当外部类的方法结束了,局部变量就被销毁了,但是内部类对象还是存在的(稍后GC再进行回收),这时内部类对象访问了一个不存在的变量,为了解决这个问题就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡之后,内部类依然可以访问它,实际访问的是局部变量的copy,这样就延长了局部变量的生命周期
将局部变量复制为内部类的成员变量的时候,必须保证这两个变量时一样的,也就是我们在内部类中修改了成员变量,方法中的局部变量也得跟着改,所以就将局部变量设置为final,对他初始化后就不让你去修改这个变量,这就保证了两个变量的一致性,这实际上也是一种妥协.
6.String,StringBuilder和StringBuffer的区别
String:是final修饰的,不可变,每次操作都会产生新的string对象
StringBuffer:在原对象上操作,线程安全,被synchronized修饰
StringBuilder:在原对象上操作,线程不安全
性能:StringBuilder>StringBuffer>String
7.重载和重写的区别
重载:发生在一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回这和访问修饰符无所谓,发生在编译的时候
重写:发生在父子类中,方法名,参数列表必须相同,返回值范围小于父类,抛出异常范围小于等于父类,访问修饰符的范围大于父类,如果父类方法访问修饰符为private就不能重写该方法
8.接口和抽象类的区别
- 抽象类可以存放普通成员函数,接口中只能存在public abstract方法(现在可以有静态方法和默认方法)
- 抽象类的成员变量可以是各种类型的,接口中的成员变量只能是public static final类型的
- 抽象类只能继承一个,但是接口可以实现多个,因此定义抽象类的代价高
- 接口的设计目的是对类的行为进行约束,抽象类的设计目的是代码复用
- 当你关注一个事物本质的时候(蜂鸟是一种鸟)使用抽象类,当你关注一个操作的时候(蜂鸟可以像飞行器一样飞翔),使用接口
9.List和Set的区别
List:有序,按对象进入的顺序保存对象,可以重复,允许多个null元素对象,可以使用Iterator取出元素,还可以使用get(index)获取下标的元素
Set:无序不可重复最多有一个null元素对象,取元素的时候只能使用Iterator接口取出所有元素
10.hashCode和equals
hashCode的是一个对象在哈希表(key-value)中的索引.当一个对象加入到HashSet的时候,HashSet会先计算hashCode来判断该对象加入的位置,看看该位置是否有值,如果没有,HashSet就认为没有重复对象出现,如果有值,这时就会调用equals()方法检查两个对象是否相同,如果两者相同HashSet就不会让他加入成功,如果不同就会重新散列到其他的位置.这样就大大减少了equals的次数,提高了执行速度.
如果两个对象相等则hashCode一定要相同,但是hashCode相同不一定对象就是相等的
如果equals方法被重写过了那么hashCode方法也必须被重写
假设重写了equals方法但是没有重写hashCode方法,先在有一个自定义People类,两个都名叫小明的人,我们认为他们相等,此时equals方法返回true,但是由于没有重写hashCode方法,而默认的hashCode方法来自于Object类,这两个对象的hashCode一定不相等,这也就导致了hashcode和equals的矛盾,这里给出一个重写hashcode的例子
public class User {
private String id;
private String name;
private String age;
public User(){
}
public User(String id, String name, String age){
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return this.id + " " + this.name + " " + this.age;
}
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;//地址相等
}
if(obj == null){
return false;//非空性:对于任意非空引用x,x.equals(null)应该返回false。
}
if(obj instanceof User){
User other = (User) obj;
//需要比较的字段相等,则这两个对象相等
if(equalsStr(this.name, other.name)
&& equalsStr(this.age, other.age)){
return true;
}
}
return false;
}
private boolean equalsStr(String str1, String str2){
if(StringUtils.isEmpty(str1) && StringUtils.isEmpty(str2)){
return true;
}
if(!StringUtils.isEmpty(str1) && str1.equals(str2)){
return true;
}
return false;
}
//String中使用的也是31这个数字
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (name == null ? 0 : name.hashCode());
result = 31 * result + (age == null ? 0 : age.hashCode());
return result;
}
11.ArrayList和LinkedList的区别
ArrayList:基于动态数组,连续内存存储,适合下标访问,扩容机制:因为数组长度固定,超出长度存数据的时候需要新建数组,然后将老大数组数据拷贝到新的数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并且指定初始容量可以极大提升性能,其性能甚至可以超过linkedlist
LinkedList:基于链表,可以储存在分散的内存中,适合做数据插入和删除操作,不适合查询遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环通过get(i)取得某一元素的时候都会对list重新进行遍历,性能消耗极大
12.HashMap和HashTable的区别
HashTable是线程安全的,每个方法都加了Synchronized,效率低下,不允许key和value为null
HashMap是线程不安全,允许key和value为null,其底层实现为数组+链表.jdk8做出的改变:当链表高度为8,数组长度为64,链表转为红黑树,元素以内部类Node节点存在
其具体过程为:
- 计算key的hash值,二次hash然后对数组长度取模,对应到数组下标
- 如果没有产生hash冲突,则直接创建Node存入数组
- 如果产生hash冲突,先进行equals比较,相同则取代该元素,不同则判断链表高度插入链表,链表高度达到8,并且数组长度达到64的时候会转变为红黑树,红黑树高度小于6的时候就将红黑树转回链表
- key为null的时候,存放在数组下标为0的位置
- 数组扩容:默认容量是16负载因子是0.75.阈值等于容量*负载因子=12,当元素数量超过阈值的时候启用扩容,扩容为原来的两倍
13.ConcurrentHashMap在jdk7和jdk8的区别
jdk7:
- ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成,Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到的锁分离技术,而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样
- 查询,插入元素时,ConcurrentHashMap需要经过一次hash定位到Segment的位置,然后再hash定位到指定的HashEntry,遍历该HashEntry下的链表进行对比,成功就返回,不成功就返回null,读取操作由volatile保证不读到脏数据
- 锁:Segment分段锁,Segment继承了ReentrantLock,锁定操作的Segment其他的Segment不受影响,并发度(可以接受多少个线程)为Segment的个数,可以通过构造函数指定,数组扩容不会影响到其他的segment
jdk8:
- 摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本,链表+红黑树的转化和hashmap一致
- 锁:锁链表的head节点,不影响其他元素的读写,锁的粒度更细,效率更高,扩容的时候,阻塞所有的读写操作,并发扩容
- 读操作的时候没有锁.Node的val和next使用volatile修饰,保证可见性
14.如何实现一个IOC容器
1.配置文件配置包扫描路径
2.递归包扫描获取.class文件
3.反射,确定需要交给IOC管理的类,用一个Map来储存这些对象
4.遍历这个IOC容器,获取每一个类的实例,判断里面是否有其他类的实例(这里可以提一下spring面对依赖注入采用的三级缓存),对需要注入的类进行依赖注入
15.什么是字节码?采用字节码的好处是什么?
java源代码-->编译器-->jvm可执行的java字节码-->jvm-->jvm解释器-->机器可以执行的二进制机器码-->程序运行
通过字节码的方式在一定程度上解决了传统解释形语言执行效率低下的问题,同时又保留了解释形语言可移植的特点,由于字节码并不专对某一特定的机器,因此java无需重新编译就可以在多种不同的计算机上运行
16.java类加载器有哪些
- BootStrapClassLoader:Ext的父类加载器,默认负责加载JAVA_HOME/lib下的jar包和class文件
- ExtClassLoader:AppClassLoader的父类加载器,负责加载JAVA_HOME/lib/ext文件下的jar包和class文件
- AppClassLoader(线程上下文加载器,系统类加载器):自定义类加载器的父类,负责加载classpath下的类文件,我们可以继承classLoad实现自定义的类加载器
17.双亲委派机制--加载一个类的时候不会直接去加载而是去查缓存
双亲委派机制的好处:
避免用户自己编写的类替换java一些核心类,比如你自己写了一个java.lang.string类(其实编译器直接不允许了),appclassLoader向上委派发现BootStrapClassLoader中有,那么自己写的类就不会加载
避免类的重复加载,因为jvm中区分不同的类不仅仅是根据类名,相同的class文件被不同的classLoader加载就是两个不同的类
18.Java中的异常体系
19.GC如何判断对象应该回收
引用计数法(java的JVM不适用,循环引用问题无法解决):每个对象有一个引用计数属性,新增一个引用+1,是否的时候-1,计数为0就表示key回收
可达性分析:从GC Roots开始向下搜索,搜索走过的路径称为引用链,当一个对象与GC Roots没有任何路径的时候,就证明此对象不可用,GC Roots的对象有 虚拟机栈中引用的对象,方法区中静态属性引用的对象,方法区中常量引用的对象,本地方法栈中引用的对象.
如果一个对象判断不可达的时候,GC会判断该对象是否重写了finalize方法(不提倡重写),如果没有重写就直接回收否则会放入队列中,按顺序执行finalize方法,执行完毕后会再次判断该对象是否可达,不可达就回收,否则对象复活
20.线程的生命周期
线程有五种状态:创建,就绪,运行,阻塞和死亡
阻塞分为三种:
等待阻塞:运行线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入等待池中,进入这个状态后无法自动唤醒,必须依靠其他线程调用notify和notifyall才会被唤醒,wait是object类的方法
同步阻塞:运行线程在获取对象的同步锁的时候,如果该同步锁被其他线程占用,jvm就会把该线程放入锁池中.
其他阻塞:运行线程执行sleep和join方法(主线程需要子线程的结果,但是子线程运行冗长,我们在main线程里调用子线程.join就可以获得子线程的结果,在子线程调用join方法的时候,主线程会进入到无限期的阻塞状态,直到子线程被销毁),或者发出来I/O请求时线程的阻塞状态,sleep是Thread类的方法
21.sleep,wait,join,yield
锁池:所有需要竞争同步锁的线程都会放在锁池中
等待池:调用wait方法后线程会放入等待池中,wait池中的线程会被notify唤醒
区别sleep和wait:
- sleep:是Thread类的静态本地方法,sleep仅仅是把cpu的执行权放出去,不再运行此线程,当时间结束后再取回cpu资源,参与cpu的调度,如果sleep的时候该线程拥有锁,那么sleep不会释放这个锁,而是把锁带着进入了冻结的状态,其他需要这个锁的线程不可能获取到这个锁,一般用于当前线程的休眠
- wait:是object类的本地方法,wait被调用的时候会释放锁,并且会把线程加到等待队列中,wait必须在synchronized包裹的代码块或者修饰的方法中使用,在线程被唤醒之后又会重新开始竞争锁,一般用于多线程之间的通信
yield:执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依旧保留了cpu的执行资格,所以又可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行
join:执行后线程进入堵塞状态,例如在线程B中调用线程A的join,那么线程B会进入阻塞,知道线程A结束或者中断
22.说说对线程安全的理解
线程安全主要是对堆来讲的,因为堆中存放的数组和对象是线程共享的,而每个线程的栈是独立的,所以栈不存在线程不安全问题,当多个线程访问一个对象,如果不用进行额外的同步控制,调用这个对象的行为都可以获得统一正确的结果,我们就说这个对象是对线程安全的.
23.Thread和Runable的区别
Thread继承了Runnable接口,一个是类一个是接口没有可比性,在用法上如果只是简单的执行一个任务可以实现runnable,如果有复杂的线程操作就继承Thread
24.守护线程是啥
守护线程是为所有非守护线程提供服务的线程,GC垃圾回收线程就是一个经典的守护线程,当我们的程序不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做了,当垃圾回收线程是jvm上唯一的线程时,他就会自己离开.垃圾回收线程始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源.守护线程不能去访问固有资源比如读写操作或者计算逻辑,因为它随时会中断
25.ThreadLocal的原理和使用场景
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改,这就是ThreadLocal类的作用
ThreadLocal的实现原理就是ThreadLocal里面有一个静态的ThreadLocalMap
在ThreadLocal里面源码set(),此时可以看到set()的源码就是首先获取当前线程,然后利用当前线程获取一个ThreadLocalMap的对象,然后如果ThreadLocalMap对象为空,则把当前ThreadLocal当成key,set()里面的参数当成value放到ThreadLocalMap集合里面,否则创建这个ThreadLocalMap对象,然后把当前ThreadLocal当成key,set()里面的参数当成value放到ThreadLocalMap集合里面
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap由一个个Entry组成,Entry的key是一个ThreadLocal对象,并且是一个弱引用,当没有指向该key的强引用后,这个key就会被垃圾收集器回收走
- ThreadLocal申明为private static final(Private与final 尽可能不让他人修改变更引用,Static 表示为类属性,只有在程序结束才会被回收0)
- ThreadLocal使用后务必调用remove方法。
使用场景:
- 在对象进行跨层传递的时候,使用ThreadLocal可以避免多次传递
- 线程之间数据隔离
- 进行事务操作储存线程事务信息
- 数据库连接,Session会话管理
26.并发,并行和串行
串行在时间上不会发生重叠,前一个任务没做完,下一个就只能等着
并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行
并发运行两个任务彼此干扰,同一时间点只有一个任务运行,交替执行
27.并发的三大特性(原子,有序,可见)
原子性--要么全部执行要么全部失败
可见性--同volatile的可见性
有序性--禁止指令重排
28.为什么使用线程池,解释线程池的参数,解释线程池的处理流程
降低资源消耗:提高线程利用率,降低创建和销毁线程的消耗
提高响应速度:任务来了不需要创建线程
提高线程的可管理性:线程是稀缺资源,使用线程池key统一分配调优监控
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime,uint: 线程存货时间(超出核心线程数的空闲线程的存活时间),单位
- workQueue 阻塞队列
- ThreadFactory 线程工厂
- Handler 拒绝策略
流程:大量任务-->核心线程-->任务队列-->最大线程数-->拒绝策略
29.线程池中阻塞队列的作用?为什么先添加队列而不是先创建最大数量线程?
阻塞队列在任务数量超过其缓冲长度的时候,可以通过阻塞保留住当前想要入队的任务.而且当没有队列中没有任务的时候,想从队列中获取元素的操作也将被阻塞.阻塞队列自带阻塞和唤醒功能,不需要额外的处理,没有任务的时候线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于让阻塞队列一直占用cpu资源
在创建新线程的时候,是需要获取全局锁的,这个时候其他的线程就会阻塞,影响了全局效率,线程是稀缺资源,能不使用新线程就不使用.
30.线程池中线程的复用原理
线程池将任务和线程进行了解耦,线程是线程,任务是任务,摆脱了之前通过Thread创建线程时的一个线程必须对应一个任务的限制.在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对Thread进行了封装,并不是每次执行任务都会调用Thread.start()来创建新的线程,而是让每一个线程都去执行一个循环任务,在这个循环任务重不停检查是否有任务需要被执行,如果有就直接执行,也就是调用任务的run方法,将run方法当成一个普通的方法执行,通过这种方式只使用固定线程就将所有任务的run方法串联起来
31.spring是啥
32.IOC是啥?AOP是啥?
IOC:容器概念,控制反转,依赖注入
- ioc容器:实际上就是一个map,里面存的是各种对象(xml里的bean节点,@repository,@service,@controller,@component),在项目启动的时候会读取配置文件里面的所有bean节点,根据全限定类名使用反射创建对象放在map里,扫描到打上注解的类,这个时候map里就拥有各种对象了,接下里在我们需要用到的对象时候,通过DI注入(autowired,resource等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref属性根据id(对象名)注入)就可以使用对象
- 控制反转:没有引入IOC容器之前,对象A依赖于B,那么在对象A初始化或者运行到某一点的时候,自己必须去创建对象B,使用之后,A和B彻底失去联系,当A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到A需要的地方.A依赖B的过程从主动行为变成了被动行为,控制权颠倒了过来,全部对象的控制权都交给了IOC容器
- 依赖注入:控制反转之后,获得依赖对象的过程由自身管理变成了IOC容器主动注入.依赖注入是实现IOC的方法,就是IOC在运行期间,动态的将某种依赖关系注入到对象之中
AOP:将程序中的交叉业务逻辑(安全,日志,事务等)封装成一个切面,注入到目标对象(具体业务逻辑).aop可以对某个对象或者某些对象的功能进行增强并且通过反射
33.BeanFactory和ApplicationContext有什么区别?
ApplicationContext是BeanFactory的子接口,ApplicationContext提供了更加完整的功能.
- 继承了MessageSource,因此支持国际化
- 统一的资源文件访问方式
- 提供在监听器中注册bean的事件
- 同时加载多个配置文件
- 载入多个上下文,使得每个上下文都专注于一个特定的层次,比如应用的web层
具体区别:
BeanFactory采用的是延迟加载的形式来注入Bean,ApplicationContext是在容器启动的时候一次性创建了所有的bean,可以在容器启动的时候就检测到spring中配置所产生的错误
BeanFactory通常以代码的方式被创建,ApplicationContext能以声明的方式创建,比如使用ContextLoader
BeanFactory和ApplicationContext都支持BeanPostProcessor,BeanFactoryPostProcessor,两者的区别是BeanFactory需要手动注册,而ApplicationContext是自动注册
34.描述一下SpringBean的生命周期
35.解释一下spring支持的几种bean作用域
36.spring框架中的Bean是线程安全的吗?
Bean默认情况是单例的,Spring框架并没有对bean做多线程的封装处理,如果说bean是有状态的(需要存储数据)最简单的办法就是把bean的作用域改成prototype,这样每次请求Bean都是newBean也就能保证线程安全,建议是不要在bean中声明任何带有状态的实例变量或者类变量
37:spring框架中都使用到了哪些设计模式
38.spring事务的实现方式和原理以及隔离级别
有两种方式可以使用事务一种是编程式的一种是声明式的(@Transactional注解),在一个方法上声明了事务以后,spring会基于这个类生成一个代理对象,将代理对象作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻辑会先把事务的自动提交设置为fasle,然后再去执行原本的业务逻辑,如果业务逻辑没有异常,那么代理逻辑中就会将事务提交,否则回滚,可以利用@Transactional注解中的rollbackFor属性来配置对应哪些异常进行回滚
spring事务的隔离级别就是数据库的隔离级别:读未提交,读已提交,可重复度,串行化
39.spring事务传播机制
多个事务方法相互调用的时候,事务如何在这些方法之间进行传播(常用的就以下两个)
REQUIRED 当前没有事务,自己新建一个,当前有,用当前的(默认传播机制)
REQUIRES_NEW 创建一个新的事务,如果存在当前事务,就挂起该事务
40.spring事务什么时候会失效
事务的原理是AOP,进行了切面增强,失效的原因就是AOP不起作用了,原因如下
- 发生自调用(一个类中有a和b方法,其中还b是事务方法,a中调用了b方法没有使用代理对象)
- 方法不是public
- 没有被spring管理
- 异常被吃掉(try catch),事务不回滚,而且如果抛出异常类型过大(Exception)事务是不会回滚的
41.什么是bean的自动装配,有哪些方式
开启自动装配只需要在xml中定义autowired属性
42.SpringBoot Spring MVC和Spring之间有什么区别
spring是一个ioc容器用来管理Bean,使用依赖注入可以实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP代码重复问题,更方便将不同类不同方法的共同处理做成切面,自动给方法执行,比如日志和异常
springMVC是spring对web框架的一个解决方案,提供了一个总的前端控制器servlet,用来接受请求,然后定义了一套路由策略以及适配执行handle,将handle结果使用视图解析计数生成视图展现给前端
springboot是spring提供的一个快速开发工具包,能更快速方便的开发spring+springmvc应用,简化了配置,整合了一系列解决方案,redis,mongodb,es,开箱即用
43.springMVC的工作流程
44.SpringMVC的主要组件
- Handler:处理器,直接对应着MVC中的C--Controller层,具体表现形式可以是方法和类,在Controller中所有@RequestMapping标注的方法都是一个Handler
- HandlerMapping:处理器映射器,根据用户请求的uri来查找具体的Handler
- HandlerAdapter适配器,因为Handler可以是任何形式,而Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法,HandlerAdapter就是让Servlet处理Handler的中转站,Handler是用来干活的工具,HandlerMapping帮助查找到要干活的工具,HandlerAdapter则是使用工具干活的人
- HandlerExceptionResolver:作用是根据异常设置ModelandView,之后再交给render方法进行渲染
- ViewResolver:用来将String类型的视图名和Locale解析为View类型的视图,它需要找到渲染所用的模板和技术
- RequestToViewNameTranslator:ViewResolver是根据ViewName查找View,但是有的Handler处理完之后并没有设置View和VIewName,这时就需要从request中获取ViewName,这就是它做的事
- LocaleResolver:解析视图需要两个参数一个是视图名,另一个是Locale,视图名是处理器返回的,Locale是LocaleResolver从request解析出来的.有了Locale就可以对不同区域的用户显示不同结果(国际化)
- ThemeResolver:解析主题,css样式,图片等
- MultipartResolver 处理上传请求
- FlashMapManager:用来管理FlashMap,这个Map的作用是在redirect中传递参数
45.SpringBoot自动配置原理
@SpringBootApplication
- @SpringBootConfiguration 就是Configuration配置类注解
- @ComponentScan 开启组件扫描
- 最重要的是中间那个自动装配注解@EnableAutoConfiguration
@EnableAutoConfiguration
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
@AutoConfigurationPackage 注解就是将主配置类(@SpringBootConfiguration标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器中。所以说,默认情况下主配置类包及子包以外的组件,Spring 容器是扫描不到的。
@Import 注解支持导入普通 java 类,并将其声明成一个bean, AutoConfigurationImportSelector 的 selectImports 就是用来返回需要导入的组件的全类名数组,那么如何返回这些数组呢?selectImports 方法中调用了一个 getAutoConfigurationEntry() 方法,经过如下调用链getAutoConfigurationEntry() -> getCandidateConfigurations() -> loadFactoryNames()
方法传入了 EnableAutoConfiguration.class 这个参数.在MATA-INF/spring.factories文件中的EnableAutoConfiguration 下面有很多类,这些就是我们项目进行自动配置的类
loadFactoryNames() 中关键的三步(将类路径下 META-INF/spring.factories 里面配置的所有 EnableAutoConfiguration 的值加入到 Spring 容器中)
-
从当前项目的类路径中获取所有 META-INF/spring.factories 这个文件下的信息。
-
将上面获取到的信息封装成一个 Map 返回。
-
从返回的 Map 中通过刚才传入的 EnableAutoConfiguration.class 参数,获取该 key 下的所有值。
46.如何理解SpringBoot中的Starter
如果使用的是spring+springMVC,引入mybatis框架,需要在xml中定义mybatis需要的bean
starter就是定义一个starter的jar包,写一个@configuration配置类,将这些bean定义在里面,然后在starter包的META-INF/spring.factories中写入该配置类,springboot会按照约定来加载该配置类
47.什么是嵌入式服务器?为什么要使用嵌入式服务器?
节省了下载安装tomcat,应用也不需要再打war包,然后放到webapp下运行,只需要一个安装了java的虚拟机,就可以直接在上面部署应用程序了,springBoot已经内置了tomcat.jar,运行main方法的时候会去启动tomcat,并且利用tomcat的spi机制加载springMVC
48.mybatis的优缺点
优点:
- 基于sql语句编程,相当灵活,sql写在xml里,解除sql和程序代码的耦合,便于统一管理,提供xml标签,支持编写动态sql语句,可以复用
- 相比于jdbc,消除了大量的冗余代码,不需要手动开关连接
- 与数据库兼容,mybatis使用jdbc来连接数据库,所以只要jdbc支持的数据库它都支持,同时能很好的集成spring
- 提供映射标签,支持对象和数据库的ORM字段关系映射,提供对象关系映射标签,支持对象关系组件维护
缺点:
- sql语句的编写工作量大,尤其是字段多,关联表多的时候,对开发人员编写的sql语句的功底有一定要求
- sql语句依赖于数据库,导致数据库可移植性能差,不能随意更改数据库
49.#{}和${}的区别
- #{}是预编译处理,是一个占位符,在处理的时候会替换为?,调用preparedStatement来赋值,在变量替换后会自动加上单引号,有效防止sql注入问题
- ${}是字符串替换,是一个拼接符,调用statement来赋值,在变量替换后不会加上单引号
50.简述mybatis的插件运行原理,如何编写一个插件?