Java
- int可以用于泛型吗?
- Integer类型的List里可以有String类型的值吗?
- 使用一个静态内部类的静态属性,该外类是否会加载?
- 抽象类和接口是否有构造方法,能否被实例化,普通方法是否可以调用抽象方法?
- 抽象类和接口的区别
- 泛型类型可以被反射获取吗?
- 使用类加载器去加载类是否会造成类的初始化?反射呢?
- 重载的方法传入null怎么办?
- +=小知识与i++小坑
- try catch final系列问题,与throw,throws
- 重载和重写的区别
- 内部类和枚举
- Integer内部类IntegerCache和其他
- new String(“a”)+new String(“b”)会创建几个对象问题
- str.intern()方法
- 默认生成的hashcode与什么有关
- 类加载器之间的关系,Tomcat自定义类加载器
Mysql
Jvm
Spring系列
- Spring七大事务传播机制
- Spring三种注解注入方法
- Spring中有两个ID相同的Bean会报错吗?
- Spring注册Bean的几种方式
- Spring事务实现机制
- Spring事务失效原理
- Spring的AOP三种使用方式
- cglib动态代理,jdk动态代理,AspectJ动态代理
- SpringMVC执行流程
Mybatis系列
Redis
RabbitMQ
SpringCloud系列
计算机网络和操作系统
Java
1. int可以作为泛型吗?
int是基本数据类型,泛型需要为对象,可以使用其包装类Integer。另外int[]数组是对象,可用于泛型。
2. Integer类型的List里可以有String类型的值吗?
可以看到,引用重新指向就可以插入其他类型的值。泛型只在编译阶段做检查,后面就会进行擦除。
可以使用Collections.checkedList(new ArrayList(), Integer.class)生成运行阶段也可检查泛型的集合,若类型不一致会报Cast异常。
3. 使用一个静态内部类的静态属性,该外类是否会加载?
不会进行加载,静态内部类编译后算是一个单独的类。另外的:
- 通过子类来使用其父类的静态字段只会触发父类的初始化(执行静态代码块等,不会实例化)。如B继承A,A中有静态属性a,通过B.a使用该属性不会触发B的初始化。
- 常量在编译阶段会通过编译传播优化存入调用类的常量池中,使得使用时不会造成被调用类加载。如C调用D的static final String的属性,就不会进行D的初始化。
- 通过数组定义来引用类,不会造成该类的初始化,而是会触发虚拟机自动生成的一个类的初始化,该类代表了一个元素类型为原类的数组。如E类,new E[10]; 时不会使E初始化。
4. 抽象类和接口是否有构造方法,能否被实例化,普通方法是否可以调用抽象方法?
- 抽象类可以有构造方法,接口无法有构造方法。
- 抽象类虽有构造方法,但和接口一样不能进行实例化,而是提供给子类实现调用的。
- 普通方法可以调用抽象方法,因为抽象方法最终会实现,且抽象类和接口不能实例化调用方法。
5. 抽象类和接口的区别
- 抽象类以abstract class修饰,被类获得方式为继承extends。接口以interface修饰,被类获得方式为实现implements。
- 抽象类也是类,一个类只能继承一个类,可以实现多个接口,同时使用顺序为先继承类再实现接口。接口不能继承类,可以继承多个接口(接口与接口之间的实现方式为继承extends)。
- 抽象类可以有构造方法,但是是给子类用的不能用来实例化对象。接口没有构造方法也不能用来实例化对象。
- 抽象类在类的基础上多出了抽象方法,被类继承时需要进行实现。接口则全是抽象的,只有抽象方法和默认为public static final的常量(以及接口中的default方法和static方法,和类方法一样可以被继承重写和直接调用)。
- 抽象类中的抽象方法需要用abstract修饰,权限符只能为public或者protected,默认为public。接口中的抽象方法默认为public abstract修饰。
6. 泛型类型可以被反射获取吗?
类成员中的有类型泛型可以被反射获取,而局部声明的类通常需自己传入指定类型,是不能被获取的。
反射获取泛型是访问类模板(类信息),如果类模板中是没有类型的泛型则不能被获取。
其中:有类型泛型是ArrayList< Integer >这样的,无类型泛型即ArrayList< T >等这样子的。
7. 使用类加载器去加载类是否会造成类的初始化?反射呢?
类加载器去加载类不会造成类初始化,而反射需要反射已经加载好的类模板,经过了初始化。关于初始化可以查看类加载的过程。
8. 重载的方法传入null怎么办?
重载的方法传入null,会优先使用类型更明确的那个方法。比如一个需要传入Integer一个需要传入Object就会调用Integer这个,此时如果还有需要传入为String的则会报错。同理动态参数列表int…其他方法优先。
9. +=小知识与i++小坑
+=会隐式进行转换,看此图。
而 i=i++ ,i++为先值备份后自增,后面把备份给i,此时i重新赋为了原值。
10. try catch final系列问题,与throw,throws
- final最终都会被执行,除非前面代码使程序终止。
- try或catch里面return了a变量,值会被临时存储,如果final改变了a的值但是没有return,临时保存的值是不会改变的,照样return那个临时存储的值。如果final改变了a变量并且进行return那么会直接返回改变后的值。
- catch捕获了A异常,后续再尝试捕获A异常的子类时会报错。catch捕获了B异常的子类,后续再尝试捕获B异常的父类不会报错但是不会进行捕获。
- thorw用于在方法体显式的抛出一个异常,throws用于在方法处抛出可能会发生的异常给调用者处理。throw一次抛出一个异常,throws可以用逗号隔开抛出多个异常。
11. 重载和重写的区别
- 重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重载对返回类型没有特殊的要求,不能根据返回类型进行区分。
- 重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型(本身或其子类),比父类被重写方法更好访问(权限要更高)。
12. 内部类和枚举
内部类可分为:成员内部类,局部内部类,静态内部类,匿名内部类。
- 成员内部类
作为外类的类成员存在,初始化方式为 new外类().new 成员内部类();
成员内部类访问外类的属性方式为 外类.this.属性,访问外类的方法可直接调用。 - 局部内部类
存在外类的方法中,和局部变量一样不能过多修饰,只能用final和abstract对其修饰。 - 静态内部类
静态初始化类和静态成员类,编译后相当于一个额外的类。 - 匿名内部类
此类写出来相当于一个实现了InterfaceA接口的无名类,通常用作传参。
new InterfaceA(){
@Override
}
// --------------------------
而如果接口里只有一个方法,可以简化为
()->{}
()里为那个方法的参数,{}里为方法体
- 枚举类默认继承了Enum,不能再继承其他类,可以实现接口。
- 枚举类Enum重写了Object的很多方法并且final了,所以我们在自己写枚举类时只能重写toString()方法。
- 枚举类的构造方法需要为private,并且写了任意方法后列举枚举值时在最后需要加冒号。
- 枚举类都有默认的ordinal()和name()方法,来获取枚举值列举时的属性。
- 枚举类在创建单例时有不错的安全性,在有反射干扰其时会直接报错。
13. Integer内部类IntegerCache和其他
- 我们都知道,在Integer a=1;赋值时会进行默认的valueOf(1)装箱操作,会判断该数是否在-128~127之间,如果在则会return IntegerCache内部类中的缓存的Integer数组cache中的Integer值。
- 如果Integer a=1;Integer b=1;使得a和b是一样的都是cache中同一个引用。如果Integer a=new Integer(1);Integer b=new Integer(1); 则a和b都会新建引用。
- 另外的,其它包装类也有这样的缓存机制,但是只有Integer包装类可以使用JVM参数改变缓存数组最大值,可以使得127之外的数也进缓存。
14. new String(“a”)+new String(“b”)会创建几个对象问题
- 我们先知道,new String(“a”)时,如果字符串常量池中已经存在"a"这个对象,则只会创建new String()值为"a"的对象在堆中。如果字符串常量池没有"a",则会产生一个"a"在字符串常量池和一个new String(“a”)在堆中。
- new String(“a”)+new String(“b”)需要借助StringBuilder来完成拼接再转化回去,则会产生:
new StringBuilder(),new String(“a”),字符串常量池中的"a",new String(“b”),字符串常量池中的"b",和一个new String(“ab”)共六个对象。 - StringBuilder在toString时有new String(“ab”,0,cont)会创建一个new String(“ab”)类似的对象,但是在字符串常量池里不会生成"ab"对象。另外String.valueOf(int a)等一些方法也不会在字符串常量池中创建字符串值对象。
15. str.intern()方法
str.intern() 方法的作用为返回str在字符串常量池中的值。在不同的jdk版本会有一些坑。jdk1.7把字符串常量池从永久代放到了堆区(永久代是堆中的非堆),jdk1.8去除了永久代变成了元空间。
如以下代码片段,这三个对象是否相等捏:
String strA = String.valueOf(8);
String strB = strA.intern();
String strC = "8";
在jdk1.7之后:
- String.valueOf(8)字符串常量池并不会创建"8"。
- strA.intern()会发现字符串常量池没有"8"而创建调用者strA的引用放入池中。
- 在strC引用字符串常量池中值为"8"的对象时,寻找到的则是strA的引用。则strA,strB,strC三个对象相等。
在jdk1.7版本之前:
- String.valueOf(8)字符串常量池并不会创建"8"。
- strA.intern()会发现字符串常量池没有"8"而创建"8"对象放入字符串常量池中,可以理解为非堆不会存储引用只能存值。
- 在strC引用字符串常量池中值为"8"的对象时,寻找到的则是"8"对象。则strB,strC相等,strA与它们不一样。
16. 默认生成的hashcode与什么有关
hashcode的默认生成,可通过Object追溯到本地方法,找到了生成hash的最终函数 get_next_hash,这个函数提供了6种生成hash值的方法:
0. A randomly generated number. // 随机数
1. A function of memory address of the object. // 内存地址做移位再和一个随机数做异或
2. A hardcoded 1 (used for sensitivity testing.) // 固定值1
3. A sequence. // 自增序列的当前值
4. The memory address of the object, cast to int. // 转换成int的内存地址
5. Thread state combined with xorshift (https://en.wikipedia.org/wiki/Xorshift) // 当前线程相关状态+运用xorshift随机数算法得到的一个随机数
根据globals.hpp中所述,OpenJDK8默认采用序号为5的方法,OpenJDK7和OpenJDK6都是使用序号为0的方法。
由此可知:
- 默认的hashcode生成是和对象地址无关的,但可以通过在JVM启动参数中添加-XX:hashCode=4,改变默认的hashCode计算方式。
- 且一个对象创建了哈希码之后会将值保存到对象的对象头中(对象大致分为:对象头、实例数据、对齐填充字节),避免下次创建,且在垃圾回收过程中,哈希码也不会改变。
- 我们可以试想,如果hashcode和内存地址相关,在垃圾回收的过程中改变了对象内存地址,但是hashcode又不变,其他分在旧地址上的新对象的hashcode就和该对象一样了。
另外的,调用默认的hashCode方法和System.identityHashCode(o)会占用偏向锁记录使偏向锁失效,如果想使用偏向锁,最好重写hashCode方法。
17. 类加载器之间的关系,Tomcat自定义类加载器
类加载器有以下几种,有不同的叫法:
- 根类加载器:负责加载<JAVA_HOME>\jre\lib路径下的核心类库
- 扩展类加载器:负责加载<JAVA_HOME>\jre\lib\ext目录下的类库。
- 系统类加载器:负责加载classpath环境变量所指定的类库,是用户自定义类的默认类加载器。
其中:
- 它们之间是父子关系,系统类加载器的父加载器是扩展类加载器,扩展类加载器的父加载器是根加载器。
- 注意它们是父子关系,不是父子类关系,即三者之间不是继承关系,是不同层次的关系。
- 双亲委派机制见类加载的机制。
- 如果我们想加载其它位置的类或jar和逻辑,可以继承重写ClassLoader的findClass等方法,自定义自己的逻辑。
一些框架、容器、OSGi、线程上下文类加载器都有自定义的类加载器,拿Tomcat举例,它实现自定义类加载器的好处如下,同理其他:
- 实现类的独立加载管理和隔离,防止类冲突(如同名类和不同程序的不同版本包的同一类)。
- 实现热加载,运行时动态更新代码资源(如JSP的编译的.class文件)。检测到文件改变则类加载器对其重新加载,而一般类加载器实现需要重启从而把所有类加载一遍。
- 安全隔离,防止一个程序内类加载器内存泄漏导致全体内存泄漏。
Mysql
1. 一张表里面有ID自增主键,当insert了17条记录之后,删除了第15,16,17条记录,再把mysql重启,再insert一条记录,这条记录的ID是多少?
innodb会把最大id记录在内存中,myisam把最大id记录在数据文件中。重启导致内存丢失,则innodb会从15开始,而myisam数据文件的最大id未丢失,从18开始。
Mysql索引失效问题
索引失效无非是使用错误或者使用得让mysql觉得不开心给你踢出群聊,下面一些失效原因:
- 不符合最左匹配。
- 以%开头的like模糊匹配 。
- or条件前后没有同时使用索引,或都有范围查询。
- 索隐列数据类型出现隐式转换。
- 索引列参与运算 。
- 索引列使用了函数 。
- 两列作比较 。
- 普通索引不等号查询的结果集太大索引会失效 。如:name!=‘丁’。id!=2 则会走索引,id是主键。
- is not null 失效 。is null 则会走索引
- not in和not exists失效。
- 当查询条件为大于等于、in等范围查询时,根据查询结果占全表数据比例的不同,优化器有可能会放弃索引,进行全表扫描。
- 优化器的其他优化策略,比如优化器认为在某些情况下,全表扫描比走索引快,则它就会放弃索引。
最左前缀、索引覆盖、索引下推
最左前缀
Mysql索引在建立时会根据索引排列顺序对数据进行排列,在第一个字段排序的基础上,再对联合索引中第二个字段进行排序,再对后面进行排序。 大致意思是,在第一个索引字段都相同的基础上,第二个索引字段数据是有序的,可推在第二个索引字段相同的情况下,第三个索引字段是有序的。(有一些联合索引的B+树的图可以查一下来理解。)
此时:
- 建立(a,b,c)三索引,在(b,c,a)(c,a,b)各种顺序下均可使用索引,优化器会优化这个顺序来查找。
- 建立(a,b,c)三索引,在a=1 and b>1 and c=1时,a,b可以用到索引,mysql会匹配到范围查询为止停止匹配,因为b>1会查出许多不相等的数据,此时c是无序的。如果索引是(a,c,b)则可以全部用到索引,a和c的顺序会被优化。
- 建立(a,b,c)索引,同理使用(b,a)和(a,b)效果是相同的可用索引,使用(a,c)和(c,a)也可用索引但只能用到a,使用(b,c)则失效。
覆盖索引指的是:如果要查询的结果都是索引,那么MySQL可以直接通过遍历索引取得数据,而无需回表,这减少了很多的随机io操作。减少io操作,特别的随机io其实是dba主要的优化策略。
索引下推
MySQL服务层负责SQL语法解析、生成执行计划等,并调用存储引擎层去执行数据的存储和检索。索引下推的下推其实就是指将部分上层(服务层)负责的事情,交给了下层(引擎层)去处理。
例如:select * from t_user where name like ‘张%’ and age=10
- 未使用ICP:根据name索引找到匹配到张某的id,根据多个id回表多次查出数据再过滤出age=10的记录。
- 使用ICP:根据name索引找到匹配到张某的id,此时再过滤出age=10的id再回表查询。
索引下推的目的是为了减少回表次数,也就是要减少IO操作,提升性能。
MySQL的执行流程
- 连接器,与mysql建立连接,判断一些用名密码权限之类的。
- 分析器,语法分析,分析语法有无错误。词法分析,分析要干什么。
- 优化器,根据一些成本计算,决定去走哪个索引或者联表的顺序,以及一些其他的优化,生成执行计划。
- 执行器,根据执行计划,调用存储引擎层的API接口。存储引擎层是可插拔的设计,不同存储引擎(如Innodb,Myisam)实现一套统一的API接口,调用的API不变,切换存储引擎方便。
几个范式
- 每个表只做一件事(对应的就是每行数据都代表一个事)。比如商品表,你在里面搞一些用户信息,就不好区分,操作数据也不方便。
- 每个字段不可再分。比如商品名字段,你在里面还搞一些简介也放里面,那我查询修改的时候查出来害得分割一下,修改时害得拼接一下再放进去,也会导致其他类似的问题会降低开发效率增加维护成本,得不偿失。
- 要有主键,每个字段和主键相关联。没有主键难以定位数据区分唯一数据,也不好搞索引、子查询、联表之类的。
但是一味的按照范式约定来也会不利于扩展或者在这基础上增加效率,例如提高业务查询效率增加一些冗余表和冗余字段,只是说表见多了而且知道这些范式会更容易看出这个表设计成这样的原因或问题,更好的理解表。
Jvm
1. 已知内存中没有连续的20M内存,但是不连续的内存有很多。有一个长度为20的数组,每个下标对应对象大小为1M,问内存能否放得下这个数组?
数组下标存储对象的引用,即下标对应对象引用分配在连续的空间,而引用对应的对象可以在其他不连续的空间,所以大概率放得下。
2. 类加载的过程
->加载 :类加载器把.class文件加载到内存中,创建Class对象模板。
->验证 :文件格式验证,元数据验证,字节码验证,符号引用验证。
->准备 :给引用分配内存空间,并且赋默认值(final static不会赋值,后续可能会用静态代码块改变,而final使其不能变)。
->解析 :将符号引用替换为直接引用。
->初始化:为类的静态变量赋真正的值。
->使用 :
->卸载 :
3. 类加载的机制
- 全盘负责:如果该加载器负责加载该Class,则该Class依赖引用的其他类也由该加载器加载。
- 双亲委派:向上向下直到。遇到类加载请求先给父加载器看是否加载过,加载过则直接使用(大部分核心类已经加载了),没加载则再递给父加载器看是否加载过直到顶级。然后尝试看是否能加载,不在负责加载的目录范围内则加载失败,再往下传递给子加载器,谁能加载就给谁加载。可以防止上级的类被篡改,如自己写一个String就没用,会往上就会被根加载器加载。
- 缓存:缓存保存所有加载过的类,当需要在缓存中找,没有就加载。
4. JVM内存模型
其中:
1、 程序计数器(PC寄存器):存储下一条指令的地址。线程私有,不会内存溢出。
- 线程私有,使线程上下文切换时仍能保持运行顺序。
- 占用空间极小,唯一一个不会内存溢出的区域。
- 寄存器是cpu单元读取速度最快的单位,jvm使用寄存器做程序计数器。
- 计数器中的指令地址给到解释器解释成机器码并给cpu执行,同时存入下一个命令地址。
2、方法区:静态常量,常量,类模板,运行时常量池在此。
3、 栈:方法的栈帧(存局部变量表’引用’,操作数栈’值’,方法出口)。
- 方法栈帧:比如int a=0;在局部变量表分配存a,然后0入操作数栈,弹出赋值给a。对象引用也是一样。
- 线程私有。
4、堆:字符串常量池等。
- 新生代:伊甸园区存放新生对象 和 幸存区survivor(from和to区)存放经过轻GC后仍然存活的对象。
- 老年代:存放幸存区多过来的对象。
- 元空间:非堆,有方法区。存放jdk自带的东西比如类模板、接口元数据。没有垃圾回收。
5. 如何判断对象是垃圾以及出现的问题
- 引用计数法
给对象添加引用计数器,记录对象的引用情况,引用为0可回收。
缺点是对象相互依赖难以判断是否回收,且需要额外空间记录。
- 可达性分析(主要采用此方法)
以一些对象为GC root,从根节点开始去遍历对象引用(会STW,防止对象新增或销毁导致遍历链路不稳定持续产生垃圾,导致效率降低),若有对象没有在图中大概率为垃圾。
- 可达性分析出现的问题:
1、 初始标记可能会导致被标记为非垃圾而又解除了引用,变为浮动垃圾。
2、 也可能导致被标记的非垃圾又引用了别人放弃的垃圾,而此引用还是垃圾被清除,造成对象消失。
- 对应解决方法:
1、 原始快照:标记还是按原来的快照走,记录下被删除的引用,并发标记结束后再从这些引用扫描是否有被引用重新鉴定垃圾。
2、 增量更新:被标记对象A重新关联其他引用时时记录下来变灰色,在标记完成之后再次从A开始标记一次。重新标记也需要stw。
6. 垃圾回收算法和g1收集器垃圾回收
1、标记复制(新生代使用)
标记垃圾,将非垃圾复制到to区,然后将from区都清理,然后from为空变to区,to区变from区。
没有内存碎片,但效率较低。
2、标记清除(老年代使用)
将标记的垃圾清除。
会导致内存碎片,但是效率高。
3、 标记整理(老年代使用)
将垃圾标记清除并整理空间,使其空间连续。
没有内存碎片,效率较低。
除此之外,g1收集器的垃圾回收流程为:
- 初始标记:标记GC root能直接关联到的对象。
- 并发标记:可达性分析,GC线程和用户线程并发执行。
- 最终标记:修正并发标记阶段出现的问题重新标记。
- 筛选回收:对每个区域回收价值和成本进行排序,根据用户期望的回收时间来指定回收计划。划分区域为了更方便的控制回收时间。
7. 垃圾回收器
常见好用虚拟机一般有三种:HotSpot,JRockit,J9 VM三种虚拟机。
常见的垃圾回收器有:
- 新生代收集器(都是标记复制算法):serial(单线程),ParNew(serial的多线程版本),Parallel Scavenge(自适应策略,多线程)
- 老年代收集器:CMS(标记清除算法),Serial Old(标记整理算法),Parallel(标记整理算法)
- 整堆收集器:g1收集器,堆上划分很多区域。JDK 9以后的默认垃圾收集器,取代了CMS 回收器。
对新生代进行回收称为:minor GC
对老年代:major GC
一起进行即:full GC
Full GC触发的时机:
- 当准备要触发一次Minor GC时,如果发现统计数据说之前Minor GC的平均晋升大小比目前老年代剩余的空间大,则不会触发Minor GC而是转为触发Full GC(因为HotSpot VM的GC里,除了CMS的concurrent collection之外,其它能收集老年代的GC都会同时收集整个GC堆,包括新生,所以不需要事先触发一次单独的Minor GC);
- 如果有永久代的话,要在永久代分配空间但已经没有足够空间时,也要触发一次Full GC;(Java虚拟规范中并不要求虚拟机在方法区实现垃圾收集,而且在方法区中进行垃圾收集的“性价比”较低。在大量使用频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。)
- System.gc()、heap dump带GC,默认也是触发Full GC。
7. 新生代晋升老年代的方式
1、担保机制
- JVM在内存分配的时候,新生代内存不足时,把新生代的存活的对象搬到老年代,然后新生代腾出来的空间用于为分配给最新的对象,这里老年代是担保人。
在不同的GC机制下,也就是不同垃圾回收器组合下,担保机制也略有不同:
- 在Serial+Serial Old的情况下,发现放不下就直接启动担保机制;
- 在Parallel Scavenge+Serial Old的情况下,却是先要去判断一下要分配的内存是不是>=Eden区大小的一半,如果是那么直接把该对象放入老生代,否则才会启动担保机制。
2、大对象进入老年代
-XX:PretenureSizeThreshold=对象大小(单位:byte)
参数的默认值为0时所有的对象创建出来之后默认都是分配到新生代的。
指定了大小之后,只要创建出来的对象超过设定值,那么这个对像就会直接晋升到老年代。
3、 长期存活的对象
在堆中分配内存的对象,其内存布局的对象头中(Header)包含了GC分代年龄标记信息。
如果对象在eden区出生,那么它的GC分代年龄会初始值为 1,每熬过一次 Minor GC 而不被回收,这个值就会增加 1 岁。
当它的年龄到达一定的数值时(jdk1.7 默认是 15 岁),就会晋升到老年代中。
4、 动态年龄判断
当 Survivor 空间中相同年龄所有对象的大小总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,而不需要达到默认的分代年龄。
Spring系列
1. Spring七大事务传播机制(例子为A调用B)
TransactionDefinition定义了Spring的七大事务传播机制。PROPAGATION_
- REQUIRED(Spring默认的事务传播类型 required:需要有):如果A有事务B有事务,则B加入A一起。如果A没有事务B有事务,则B自己创建事务执行。
- SUPPORTS(supports:支持别人): 支持上层做法。A有事务B有事务B就加入A事务执行,A没事务B有事务B就以非事务执行。
- MANDATORY(mandatory:强制性的): 该事务需要在可回滚事务中进行。A没事务B有事务就报错。
- REQUIRES_NEW(requires_new:启新事务): 无论上层有没有事务,该事务都会新创建执行。A事务调用B事务,A事务挂起,且各回滚各的。
- NOT_SUPPORTED(not supported:不支持):以非事务方式运行,若在事务中调用,则外层事务挂起。
- NEVER(never:从不): 上下层都不允许有事务,抛异常。
- NESTED(nested:嵌套的): A有事务B就嵌套在里面执行,A没事务B就自己创建执行。
REQUIRED和NESTED的区别就是:NESTED嵌套时A回滚B也回滚,B回滚A可以捕获异常而不回滚。REQUIRED加入时A回滚B也回滚,B回滚不能捕获异常而A也会回滚。
2. Spring三种注解注入方法
-
在属性上@Autowired等实现注入
优点:简洁明了
缺点:注入对象不能用final属性,可能导致循环依赖。 -
在构造方法上@Autowired等实现注入 (推荐)
优点:强制注入防空指针异常,注入对象可以final,可能出现三级缓存不能解决的循环依赖(生命周期在第一步卡住,第三级缓存无法生效)
缺点:注入过多很不好看 -
在set方法上@Autowired实现注入
优点:注入对象可以为null,可以在类构造后重新注入
缺点:注入对象不能final
其中,@Autowired优先通过byType进行注入,可通过@Qualifier辅助指定byName进行注入。@Resource为默认优先byName进行注入,可用其提供的属性指定byType或byName进行注入。在xml中也有使用pc命名空间来控制注入位置,使用autowired标签来控制byName,byType的注入。
3. Spring中有两个ID相同的Bean会报错吗?
- 在同一个xml文件里不能存在相同ID的Bean,在把xml解析成BeanDefinition时会验证ID唯一性而报错。在不同xml文件里可以存在相同ID的Bean,但是根据解析顺序会进行覆盖。
- 在@Configuration中使用@Bean注册时,只会保留第一个被注册的实例,后续不会生效。
4. Spring注册Bean的几种方式
- 在xml中注册。
- @Configuration的类中方法上@Bean然后return对象注册。
- 在@Configuration上@Import({类.class})注册该类。
- 开启包扫描,扫描下的@Component等的类注册。
- 已注册的一个A类实现FactoryBean<B类>接口重写三个方法,(getObject,getObjectType,isSingleton)分别return B对应的类型属性,
使用getBean(“A”)来获得getObject提供的对象也就是B,getBean(“&A”)则是获得A。 - 在注册的A类实现ImportSelector接口重写方法后return一个String数组,里面放的要注册的类的全限定名。
5. Spring事务实现机制
- @EnableTransactionManagement后使用@Tranceactional。
- @EnableTransactionManagement会注册配置类,配置类定义了对@Tranceactional使用通知,创建动态代理。
- 动态代理事务管理器新建数据库连接,通过ThreadLocal<Map<Datasource,connection>>将Map<Datasource,connection>放在当前Thread的ThreadLocalMap中,设置自动提交为false,执行事务时会使用自己的数据源当做key在获得的Map<Datasource,connection>中获取连接,使用同一个连接统一提交或回滚事务。
6. Spring事务失效原理
前面提到,执行事务需要动态代理和同一数据源,大部分失效的原因就在于此。(分布式事务在Cloud系列说)
- 方法是非public
spring事务默认生效的方法权限都必须为public - 类没有为bean
spring事务生效的前提是,service必须是一个bean对象 - 自己捕获了异常
spring事务只有捕捉到了业务抛出去的异常,才能进行后续的处理,如果业务自己捕获了异常,则事务无法感知 - 内部调用本类方法导致传播失效
事务方法由代理对象调用控制,内部调用是本普通对象调用 - 抛出受检查异常
spring默认只会回滚非检查异常和error异常 - 切面优先级不当
spring事务切面的优先级顺序最低,但如果自定义的切面优先级和他一样,且自定义的切面没有正确处理异常,则会同业务自己捕获异常的那种场景一样。 - 父子容器问题
子容器扫描范围过大,将未加事务配置的service扫描进来 - 方法final,static修饰
因为spring事务是用动态代理实现,因此如果方法使用了final,static修饰,则代理类无法对目标方法进行重写,植入事务功能 - 多线程调用
因为spring的事务是通过数据库连接来实现,而数据库连接spring是放在当前Thread成员变量里里面。同一个事务,只能用同一个数据库连接。而多线程场景下,拿到的数据库连接是不一样的,即是属于不同事务 - 错误的传播行为
使用的传播特性不支持事务,未覆盖到该事务 - 使用了不支持事务的存储引擎
使用了不支持事务的存储引擎。比如mysql中的MyISAM - 数据源没有配置事务管理器
SpringBoot一般会自动配置 - 被代理的类过早实例化
当代理类的实例化早于AbstractAutoProxyCreator后置处理器,就无法被AbstractAutoProxyCreator后置处理器增强
7. Spring的AOP三种使用方式
- 定义一个类并注册,在里面写好对应的before,after增强方法。在xml中可以指定这个类为切面,去对自己想增强的切点进行指定aop:before,aop:after等的增强。
- 定义n个类并注册,实现MethodBeforeAdvice,AfterReturningAdvice等接口重写其中的方法。在xml中通知到对应的切点里,通过接口来识别增强的时机进行方法增强。
- 定义一个类@Aspect并注册,在里面的方法上@Pointcut(),@Before(),@After(),@Around()对匹配到的切点进行方法增强。
8. cglib动态代理,jdk动态代理,AspectJ动态代理
- jdk动态代理:实现被代理类接口,方法里反射调原方法进行前后增强。
- cglib动态代理:继承被代理类,方法里直接调用原方法进行前后增强。(如果final不能被继承就代理不了)
- AspectJ代理:编译时在字节码层面对类方法前后进行增强,需要它的编译器。
9. SpringMVC执行流程
-
DispatcherServlet调用注册过的BeanNameUrlHandlerMapping(处理映射器)根据请求去查找Handler,并把解析后执行链的信息返回给DispatcherServlet
-
DispatcherServlet调用注册过的SimpleControllerHandlerAdapter(处理适配器)找到对应的Handler去执行并获得它的信息,将信息ModeAndView返回给DispatcherServlet
-
DispatcherServlet调用注册过的InternalResourceViewResolver(视图解析器)对ModelAndView中的数据进行处理,如获得数据和获得视图名并拼接地址,然后把信息返回给DispatcherServlet
-
DispatcherServlet根据视图解析器的结果调用视图渲染呈现
如下图所示
其中组件:
- DispatcherServlet
DispatcherServlet 是前端控制器,Spring MVC的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。 - BeanNameUrlHandlerMapping
BeanNameUrlHandlerMapping 是处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。 - SimpleControllerHandlerAdapter
SimpleControllerHandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)。 - Handler
Handler 是处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中。 - InternalResourceViewResolver
InternalResourceViewResolver 是视图解析器,其作用是进行解析操作,通过 ModelAndView 对象中的 View 信息将逻辑视图名解析成真正的视图 View(如拼接地址通过一个路径返回一个真正的对应页面)。 - ModelAndView
Model是模型,对数据进行存储。View 是视图,其本身是一个接口,实现类支持不同的 View 类型(JSP、FreeMarker、Excel 等)。
Mybatis系列
Redis
RabbitMQ
SpringCloud系列
计算机网络和操作系统