JVM、JRE、JDK的区别:
JVM:java虚拟机,是java实现跨平台的最核心部分,能够运行java语言所开发的程序
JRE:java运行环境,是运行java程序所必须的环境的集合,包括JVM+java系统类库
JDK:java开发工具包,是java的核心、包括JRE+编译、运行等命令工具
java的8种基本数据类型是什么(简述java的8种基本数据类型)
byte:字节型,用于存储整数的,占用1个字节,范围-128到127
short:短整型,用于存储整数的,占用2个字节,范围-32768到32767
int:最常用的整数,用于存储整数的,占用4个字节,范围-2^31到2^31-1
long:长整型,用于存储较大的整数,占用8个字节,范围-2^63到2^63-1
float:单精度浮点型,用于存储小数的,占用4个字节,不能表示精确的值
double:双精度浮点数,最常用的存储小数的类型,占用8个字节,不能表示精确的数
boolean:布尔型,存储true或false,占用1个字节
char:字符型,采用Unicode字符编码格式,存储单个字符,占用2个字节
switch可以作用于那些数据类型上?
答:byte、short、int、char、String、枚举,其余类型都不允许
重写(override)与重载(overload)的区别:
重写:发生在父子类中,是子类对父类方法的重新实现,方法名相同,参数列表相同
重载:发生在同一类或子类中,方法名相同,参数列表不同
重写的原则(两同两小一大):
两同:方法名相同,参数列表相同
两小:派生类方法的返回值类型小于或等于超类方法的返回类型
void和基本类型时,必须相等
引用类型时,小于或等于
派生类方法抛出的异常小于或等于超类方法的
一大:派生类方法的访问权限大于或等于超类方法的
实例变量与静态变量的区别:
实例变量:不使用static定义的成员变量,实例变量属于对象,在创建对象时存储在内存堆中,创建多少个对象,则实例变量就会在内存堆中存在多少份,通过对象来访问
静态变量:使用static声明的变量,静态变量属于类,在类被加载时存储在内存方法区中,无论创建多少个对象,静态变量在内存中都只有一份,可以被类的全体实例共享,通过类名点来访问
java是值传递还是引用传递?
在java中,无论是基本类型还是引用类型,都是值传递
对于基本类型而言,传递的是具体的值得副本
对于引用类型而言,传递的是具体的地址的副本
抽象类与接口的区别?
抽象类:
由abstract修饰
派生类通过extends来继承
可以包含变量、常量、构造方法、普通方法、静态方法、抽象方法
只能继承一个(单继承)
抽象类中的成员,任何访问权限都可以
接口:
由interface定义
实现类通过implements实现
只能包含常量和抽象方法
可以实现多个(多实现)
java中继承的原则是单继承,但是接口继承接口可以多继承
接口中的成员,访问权限只能是public
String s=new String("java");创建了几个对象?
答:两个,一个是java字面量对象,另一个是new出来的对象
==和equals()的区别:
==可以作用于基本类型,也可以作用于引用类型:
若为基本类型,则是在比较值是否相同
若为引用类型,则是在比较地址值是否相等
equals()只能作用于引用类型:
Object类的equals()默认比较的还是==(即地址值),但是没有参考意义,所有需要重写
equals()比较对象的属性值是否相同
Java7开始提供了工具方法Objects.equals用于实现equals方法
注:String类重写了equals()来比较字符串内容是否相同
包装类也重写了equals()方法比较字符串内容
String常用的方法有哪些?
length():获取字符串的长度(字符串个数)
trim():去掉两边的空白字符
charAt():根据下标找字符
substring():截取字符串
静态方法valueOf():将其他类型的数据转换为字符串
toUpperCase()/toLowerCase():将英文部分给转换为全大写/全小写字母
startsWith()/endsWith():判断是否是以???开始/结束的
indexOf()/lastIndexOf():查找字符串第一次出现/最后一次出现的下标
matches():验证匹配(正则表达式)
replaceAll():替换(正则表达式)
split():拆分(正则表达式)
String、StringBuilder、StringBuffer的区别:
String:
由final修饰,不能被继承。底层封装的是final的字符数组,所以为不变对象。每次修改String引用变量的值,都是在创建新的对象。适合查看,但是不适合频繁修改。在实际应用中对于字符串大部分情况下都是在查看,所以String的应用率最高
StringBuilder:
底层封装的是字符数组,所做操作就是在该数组之上的,修改效率高。
非线程安全的,并发处理的,性能稍快------一般都是用StringBuilder
StringBuffer:
底层封装的是字符数组,所做操作就是在该数组之上的,修改效率高
线程安全的,同步处理的,性能稍慢-----应用率不高
Integer和int的区别
Integer是int的包装类是引用类型,int是基本数据类型
Integer类型的变量是引用类型变量,引用Integer类型对象
Integer对象是不变对象,其中-128到127被缓存复用
基本类型运算性能好于包装类型
i++和++i的区别
运算时都可以将变量值增加1
作为表达式使用时,表达式值不同:
i++:先表达式返回i的值,后加1
++i:先加1,后表达式返回i+1的值
| 和 || 的区别(&和&&)
||是短路或运算,左侧表达式为true时,不再计算右侧表达式的值,直接得到结果(true)
|是非短路逻辑运算,两侧表达式都运算,再得到结果
|也是2进制按位或运算
final,finally,finalize的区别
final修饰符(关键字):
被final修饰的类,不能被继承
被final修饰的方法,不能被重写
被fianl修饰的变量,不能被修改
finally:
是异常处理时的finally块,不能单独存在,必须配合try或try-catch使用,不管有没有异常被抛出、捕获,finally块都会被执行
finalize:
是Object类中的一个方法,该方法是在垃圾收集器删除对象之前对这个对象调用的
使用过的for循环
计数循环
for迭代器循环
for死循环
foreach循环
forEach方法
静态方法能不能重写?
静态方法不能被重写,重写是建立在动态执行基础上的,Java静态方法是静态绑定执行的,不能重写
对构造方法的理解?
构造器即构造函数或构造方法,其本质是一个特殊的方法,用来初始化对象
构造器的名称要与类的名称一致
如果没有定义构造器,则会自动添加一个无参构造函数
构造方法可以重载
面向对象的特征
面向对象的三个基本特征是:封装、继承、多态
封装:对实体的属性和功能实现进行访问控制,向信任的实体开放,对不信任的实体隐藏
继承:使得低层级的类可以延用高层级类的特征和方法
多态性:对象的多态,通过向上造型体现;方法的多态,通过方法的重写体现
解释一下面向对象
面向对象编程是一种软件设计思想,其核心是通过以对象为抽象的程序单元,抽象设计出类、属性、方法。逐步细化设计出程序代码。
堆和栈的区别
什么是堆内存?
堆内存是Java内存区域,它的作用是用于存储Java中的对象,当我们new一个对象时候就会在堆内存中开辟一段空间存储对象。需要垃圾回收器回收对内存中无用的垃圾对象。多个线程共享同一个堆内存。
什么是栈内存
栈内存是Java的内存区域,每个线程都有一个独立的栈内存,主要是用来存储方法执行的局部变量,方法参数和this变量就在栈内存。随着调用方法开辟栈帧,方法调用结束就销毁对应的栈帧,方法中分配的局部变量也随栈帧销毁。
float和double的区别
float单精度浮点数,占4个字节,用32位二进制描述。精度不高,不常用。
double双精度浮点数,占8个字节,用64位二进制描述。精度是float的两倍,顾称为双倍精度浮点数。因为精度高,便于保持运算的精确性,是最常用的浮点数类型。
Java浮点数采用IEEE-754标注
值传递和引用传递的区别?
Java只有一种参数传递方式:值传递(值得赋值)
基本类型变量作为方法参数时,将变量的值复制一份传递到方法中
引用类型变量作为方法参数时,也是将变量的值(地址值)复制一份传递到方法中。所以基本类型参数传递和引用类型参数传递是没有区别的。
HashMap底层原理是什么?
HashMap底层是一个散列桶数组,每个散列桶是一个单向链表结构
散列桶容量从16开始,每次扩容为2倍
当出现散列冲突时,散列桶中元素形成链表结构。当散列表容量大于64,散列桶中元素超过8个时,会转换为红黑树,来提升查询性能。
查找时,利用散列算法,计算到散列桶位置,直接定位到散列桶,定位速度快。
散列表加载因子是0.75,最多75%充满率,出现散列冲突机会少,严重散列冲突还有红黑树优化,在散列桶中查询性能快。
Java7 Java8中的HashMap区别?
Java7采用:散列桶数组+链表方式实现。Java7以前很少管理超过2GB的内存,散列数组很少会达到数组上限,利用适当数组扩容就可以减少因为散列冲突出现的链表长度问题,就可以解决链表查询性能问题。
Java8采用:散列桶数组+链表/红黑树方式实现。Java8时代是大数据时代,Java面对的内存经常2GB内存,理论上,散列桶数组完全可能达到Java数组的上限2G,不能通过扩容解决散列冲突问题,Java8的优化方法就是引入红黑树,利用红黑树解决链表查询问题。
在链表长度超过8个节点时转换为红黑树
HashMap扩容机制是什么?
实例化的HashMap默认内部类数组是null。第一调用put方法初始化,长度为16
创建HashMap时设定了扩容阈值,默认的散列数组容量的75%
添加元素时先添加元素,然后判断是否需要扩容,如果容量达到阈值,则容量变为原来的2倍,阈值也变为原来的2倍,作为下次扩容的阈值
扩容后数组长度始终是2的整次幂,这个是散列算法的要求
ArrayList和LinkedList的区别?
ArrayList:基于数组实现,容量不够时采用复制方式扩容。LinkedList:使用链接实现
随机访问效率:ArrayList比LinkedList在随机访问时效率高,因为LinkedList是链表结构,需要依次查找元素,性能不高。
增加和删除效率:LinkedList首尾操作效率高,ArrayList头部性能稍差
线程安全:ArrayList和LinkedList都是非线程安全的
需要频繁读取集合中的元素时,推荐使用ArrayList;首尾增删操作较多时,推荐使用LinkedList。综合性能方面,ArrayList更为优秀
手写HashMap的几种遍历方式
Map<String, Integer> map = new java.util.HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
map.put("D", 4);
/*entrySet遍历*/
//获取Entry对象,以键值对的形式,保存在Set集合中
Set<Map.Entry<String, Integer>> entries = map.entrySet();
//输出entries集合
System.out.println(entries);
//遍历并输出entries集合
entries.forEach(System.out::println);
/*keySet遍历*/
//遍历map中的所有key值,保存在Set集合中
Set<String> set = map.keySet();
//输出set集合
System.out.println(set);
//遍历并输出set集合
set.forEach(System.out::println);
/*values遍历*/
//遍历map中的所有value值,保存在Collection集合中
Collection<Integer> c = map.values();
//输出c集合
System.out.println(c);
//遍历并输出c集合
c.forEach(System.out::println);
/*forEch遍历*/
map.forEach((String key, Integer value) -> {
System.out.println(key + "=" + value);
});
Collection和Collections的区别
Collection是集合类的一个顶级父类接口,定义了集合通用方法
Collection接口直接继承接口有List和Set
Collections是集合类的一个工具类,它提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
HashMap和Hashtable的区别
线程是否安全:HashMap是非线程安全的,Hashtable是线程安全的。(线程安全可以使用ConcurrentHashMap)
效率:因为线程安全问题,HashMap比Hashtable效率高。(Hashtable基本被淘汰不再使用)
对Null的支持:HashMap可以存储null的key和value,但是null作为key只能有一个,null作为值可以有多个;Hashtable不允许有null键和null值,否知抛出NullPointerException。
Map、List和Set的区别
List:线性表,元素有序可重复,可以根据下标访问元素,可以存储null
Set:元素不重复,只能存储一个null
Map:元素按照key:value键值对的形式存储,可以根据key查找value,查询性能好,key不能重复,只能有一个null键
HashMap、LinkedHashMap、TreeMap区别?底层数据结构?
HashMap:内部是一个散列表,查找数据性能很高
LinkedHashMap:继承自HashMap,基于HashMap和双向链表来实现的
TreeMap:实现了红黑树的结构,形成二叉树,按照key进行排序
Hash碰撞是什么?怎么解决?
Hash碰撞:当两个不同的值,经过散列计算后,得到相同的散列值的现象(哈希碰撞)
HashMap中的解决办法是利用链表结构存储,超过8各元素时转换为红黑树。
元素数量超过阈值时,扩容也可以减少散列冲突
开放寻址法:指当前数组位置被占用时,就放到下一位去,如果下一位也被占用了,就继续往下找,直到找到空位置
拉链法:采用链表的方式,不再只存放Entry,此时的Entry还要额外保存一个next指针,指向数组外的另一个位置。
开放寻址法和拉链法都是想办法找到下一个空位置来存放发生冲突的值
双向链表/双向循环链表
双向链表:指在单链表的基础上增加了一个可以保存某节点的上一个节点指针域的数据结构。相对单链表而言,双向链表在查找数据的时候所花费的时间是要小于单链表的,因为其可进可退。缺点就是增删节点比单链表麻烦,而且在创建节点的时候也需要多申请一个指针存储空间。
双向循环链表:指在双向链表的基础上,让头节点的前驱指针指向链表的尾节点,让尾节点的后驱指针指向头节点。它和双向链表的不同是,其判断节点是否遍历完毕的条件不再是是否为空,而是是否回到了头节点,优点是可以在任意位置插入数据。
创建线程的几种方式
继承Thread类创建线程类,重写run方法,调用start()方法启动
步骤:创建一个继承于Thread类的子类
重写Thread类的run()方法
创建此子类对象
调用start()方法:两个作用:1.启动当前线程。2.调用当前线程的run()
实现Runnable接口,实现run方法,通过Thread类的对象调用start()方法
创建一个实现了Runnable接口的类
实现类实现Runnable接口中的抽象方法:run()
创建实现类的对象
将此实现类作为参数传递到Thread类的构造器中,创建Thread类的对象
通过Thread类的对象调用start()
实现Callable接口
创建一个实现Callable接口的实现类
实现call()方法,将此线程需要执行的操作声明在call()中
创建Callable接口实现类的对象
将此Callable接口实现类的的对象作为参数传递到FutureTask构造器中,创建FutureTask对象
将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start
可以通过FutureTask对象的get()方法获取Callable中call()的返回值
sleep()和wait()有什么区别
sleep方法是Thread类的静态方法;wait方法是Object类的成员方法
---------------------------------------------------------
Spring的核心组件
IoC容器(Inversion of Control):实现了对象的创建、管理和装配的功能,负责对象的实例化和依赖注入,它可以将我们编写的业务逻辑代码和控制流程解耦,提高代码的可重用性和可维护性。
AOP框架(Aspect-Oriented Programming):基于动态代理实现,可以实现对系统中各个模块的横向逻辑进行统一处理,如事务管理、异常处理等。
MVC框架(Model-View-Controller):Spring提供了一套轻量级的MVC框架,可以帮助我们更加方便地实现视图层、控制层和模型层的解耦,实现Web应用程序的快速开发。
JDBC模块:Spring提供了一套JDBC模块,它封装了JDBC操作中的冗余代码,使得数据库操作变得更加简单和高效。
集成模块:Spring提供了一些集成模块,可以帮助我们更加方便地将Spring集成到其他框架中,如与Struts、Hibernate等框架进行整合。
Spring MVC的核心组件
DispatcherServlet:Spring MVC最核心的组件,基于Servlet容器实现。它是整个Spring MVC中的请求派发器,负责接收请求并将请求分发给不同的处理器进行处理。
HandlerMapping:用来映射请求和处理器,DispatcherServlet会根据请求信息来查找合适的HandlerMapping,并将请求分发给相应的Handler进行处理。
HandlerAdapter:指定如何执行处理器方法并将其结果返回给DispatcherServlet。一个处理器可能有多个HandlerAdapter,用来支持不同的方法返回类型。
ViewResolver:负责将逻辑视图名称解析为View对象,Spring MVC框架默认提供了多种ViewResolver实现,如InternalResourceViewResolver、JstlView等。
ModelAndView:包含处理器返回结果以及视图名称等信息,被DispatcherServlet使用来选择对应的视图渲染器进行渲染。
Spring两大核心技术是什么
IOC和AOP
简单描述对IOC和AOP的理解
IOC/DI:即控制反转,是一种设计模式,它可以通过将对象之间的依赖关系交给容器来管理,实现松耦合和模块化,使得代码的可维护性和可测试性得到大幅度提升。
AOP:即面向切面编程,是一种编程思想,它能够将程序中的横切关注点(如日志、事务、安全等)与业务逻辑进行有效的解耦,提高代码的可维护性、可拓展性和可重用性。
松耦合是指组件之间的关系程度较低,相互之间的耦合度小,彼此之间的影响较少。在程序设计中,松耦合可以提高系统的灵活性、可扩展性和可维护性。
具体来说,松耦合可以表现为组件之间接口简单、互相独立、修改某一个组件不会影响到其他组件的运行等特点。相反,紧耦合则表示组件之间关系紧密,一旦其中一个组件发生改变,就会直接影响到其他的组件,导致系统的可维护性、可扩展性较差。
AOP能解决那些问题
可以解决程序中的横切关注点问题,如日志、事务、安全等。
AOP的优点
横向抽取机制:AOP能够将与业务无关的横切关注点(如日志、安全等)从业务逻辑中分离出来,形成独立的模块。这样可以降低代码的冗余度,提高代码的可维护性和可测试性。
松耦合:使用AOP可以让各模块之间实现松耦合,即模块之间的依赖关系较弱,修改某一个模块不会影响到其他模块的运行,从而提高了系统的灵活性和可扩展性。
集中处理:通过AOP,可以将相同的逻辑集中在一起处理,避免了代码的分散和重复,减少了代码的工作量和复杂度。
代码重用:AOP中的切面可以被多个对象共享,这样就可以减少代码的复制和粘贴,提高代码的重用性和可维护性。
总的来说,AOP可以使程序的设计更加模块化和可维护,提高代码的可重用性和可扩展性,对于大型复杂的应用程序设计非常有帮助。
AOP是如何实现的
动态代理实现,AOP是通过将程序的关注点切面化,并在运行时动态地织入切面来实现的。这种方式使得程序各模块之间的耦合度降低,代码的可维护性和可扩展性提高。
AOP是通过将程序的关注点切面化来实现的。具体来说,AOP将程序中的关注点分为两个维度:横向关注点和纵向关注点。横向关注点是指程序中多个模块都会用到的功能,如日志、事务、安全等;纵向关注点是指程序中某一个模块内部的逻辑关系,如业务逻辑、数据权限等。
AOP通过给程序添加一组切面来解决横向关注点的问题。切面是一组横切逻辑,与程序的其他逻辑相互独立,可以被多个程序模块共享。在AOP中,切面可以被动态地织入weave)到程序的不同模块中,从而实现对横向关注点的统一处理。
AOP的实现方式有多种,其中基于动态代理和字节码增强的方式比较常见。在这些实现方式中,程序的字节码会被修改,在运行时生成一个代理对象或修改原对象的字节码,从而在不修改源代码的情况下实现切面的织入。
织入(weave)是AOP中的一个术语,指将横向关注点(如日志、事务、安全等)的处理逻辑动态地添加到程序的纵向逻辑中。织入可以发生在编译时、类加载时或运行时,其中运行时织入最为常见。
在AOP中,织入是通过代理对象或修改字节码实现的,它可以在目标对象的方法执行前后插入特定操作,比如记录日志、进行安全检查或开启事务等。因此,织入使得不同模块之间的关注点得到了统一处理,提高了系统的可维护性和可扩展性。
对于代理对象,织入通常发生在被代理对象的方法调用前后;对于通过修改字节码实现的增强,则是在目标对象的字节码中插入新的指令以实现织入逻辑。在织入时,需要注意不影响目标对象原有的业务逻辑,并且确保织入的逻辑和顺序正确,否则可能会引起意外错误。
织入是AOP的核心概念,使得AOP框架能够透明地处理系统的各种横向关注点,提高了系统的可读性、可维护性和可拓展性。
简述Spring中常用的几种Advice注解
@Around:唯一可以使ProceedingJoinPoint参数来控制流程的advice,在方法执行前拦截,可以在切面逻辑中手动释放拦截,且可以在其后加入逻辑代码,该代码段会在方法执行后执行。
@Before:在目标方法执行前执行,可以用于实现权限控制、事务管理等。
@After:在目标方法在目标方法执行后无论是否抛出异常都执行,可以用于释放资源、清理缓存等。
@AfterReturning:在目标方法执行后返回结果时执行,可以用于修改返回值,记录日志等
@AfterThrowing:在目标方法抛出异常时执行,可以用于统一处理异常,进行资源回收等。
CGLIB代理和JDK代理的区别
JDK代理只能对实现接口的类生成代理,JDK代理使用的是反射机制实现AOP动态代理。
CGLIB是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式。CGLIB代理使用字节码处理框架ASM,通过修改字节码生成子类。
@Autowired和@Resource注解的区别
@Autowired属于Spring框架,默认使用类型(byType)进行注入,按照类型匹配失败,在按照名字ByName匹配;
@Resource是Java的注解,Spring支持@Resource。而@Resource首选按byName自动注入,如果匹配失败再按照类型byType匹配注入。
@Resource的作用相当于@Autowired。 @Autowired首选按byType自动注入,而@Resource首选按byName自动注入。
Spring如何解决循环依赖问题
Spring解决循环依赖的机制基于两个核心原理,提前暴露对象和三级缓存机制。这些机制可以帮助Spring在Bean的创建过程中暴露尚未完成实例化的Bean实力,并通过缓存机制协助完成Bean实例创建的过程,从而解决循环依赖问题。具体处理流程如下:
1.Spring IoC容器在创建Bean实例时,如果发现存在循环依赖关系,则将正在创建的Bean实例暴露给提前暴露对象的机制,从而提前暴露还未完成实例化的Bean实例
2.当Bean实例被暴露后,Spring会将其放入三级缓存中的ObjectFactory对象集合中的第一级缓存中
3.如果依赖于当前Bean实例的其他Bean已经创建,且已经完成属性注入,则Spring会将当前Bean实例的ObjectFactory返回给需要该Bean实例的其他Bean,在它们的初始化过程中使用
4.如果存在需要往当前Bean实例中注入属性,但是还没有创建的其他Bean,则Spring会将当前Bean实例放入第二级缓存中,表示它已经被创建过,但是这个Bean实例还没有完成完全的初始化
5.Spring会递归地执行上述操作,知道所有Bean实例都完成创建的过程
通过以上机制,Spring可以在Bean实例的创建过程中解决循环依赖问题。需要注意的是,如果循环依赖关系比较复杂或者出现了死循环,则Spring可能无法解决循环依赖问题,此时需要重新设计业务逻辑或Spring组件的结构
Spring事务
Spring框架为事务管理提供了一个一致的抽象,在不同的事务API(JDBC、JPA、MyBatis)中具有一致的编程模型
支持声明式事务管理,与编程式事务相比,编程式事务管理的API更简单。往往只需要在类或者方法上标注事务注解即可。
Spring框架的声明式事务管理是通过Spring面向切面编程(AOP)实现的,底层依赖数据库的事务功能
事务管理有两种用法:编程式事务和声明式事务
编程式事务是通过硬编码的方式使用Spring提供的事务相关类来控制事务。主要分为两种用法:
通过PlatformTransactionManager控制事务;
通过TransactionTemplate控制事务。
其中,PlatformTransactionManager是最原始的方式,代码量较大,后面其他方式都是针对这种方式的封装。在代码中需要手动进行事务管理,即明确指定哪些方法需要事务支持,需要在方法中使用try-catch语句捕获异常并手动回滚事务。
声明式事务则是通过AOP技术实现事务管理。主要分为两种用法:
基于XML配置;
基于注解配置。
在声明式事务中,事务管理的细节被抽象出来形成了一个横切面,通过AOP将事务管理代码注入到应用程序的业务逻辑中。在代码中不需要手动进行事务管理,只需要标注哪些方法需要事务支持,事务管理的工作由Spring框架在运行时自动完成。
两种方式的区别在于,编程式事务需要手动实现事务管理,代码量较大,但相对灵活,适用于一些特殊情况。而声明式事务则是通过AOP自动实现事务管理,代码量较少,但相对不够灵活,适用于大多数情况。
事务注解以及作用(@Transactional)
为单个方法或类上添加事务管理
标注事务注解的类,其全部方法都有事务
标注了事务注解的方法,默认情况会开启事务,方法中全部功能执行完成以后,会自动提交事务,默认情况下,如果方法出现了RuntimeException(含子类型)则会回退事务
Spring框架的声明式事务管理是通过Spring面向切面编程(AOP)实现的,底层依赖数据库的事务功能
Spring常用注解
声明bean的注解
@Component组件,没有明确的角色
@Service在业务逻辑层使用(service层)
@Repository在数据访问层使用(dao/mapper层)
@Controller在展现层使用,控制器的声明(controller层)
注入bean注解
@Autowired:由Spring提供
@Resource:由JSR-250提供
java配置类相关注解
@Configuration声明当前类为配置类
@Bean注解在方法上,声明当前方法的返回值为一个bean
@ComponentScan用于对Component进行扫描
@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持
@Scope设置Bean的作用域
@PostConstruct由JSR-250提供,在构造函数执行完之后执行
@PreDestory由JSR-250提供,在Bean销毁前执行
@Value为属性注入值
@PropertySource加载配置文件
@EnableScheduling在配置类上使用,开启计划任务的支持
切面(AOP)相关注解
@Aspect声明一个切面。AspectJ的注解
@Before在方法执行之前执行(方法上)
@After 无论是否有异常之后都会执行(方法上)
@AfterThrowing 在方法执行出现异常之后执行(方法上)
@AfterReturing 在方法正常执行之后执行(方法上)
@PointCut声明切点
@Order指定多个切面的优先级,数值越小越先执行;
测试相关注解
@SpringBootTest
JSR-250,全称为Java Specification Request 250,是Java平台通用注解的一部分,旨在定义一组注解来表达常见的语义概念,以避免在Java EE和Java SE组件之间出现冗余的重复注解。JSR-250广泛应用于Java EE技术栈中的各种组件,如EJB3、Servlet、JSP等。常见的JSR-250注解包括:
- @PostConstruct:在依赖注入完成后执行,用于初始化对象;
- @PreDestroy:在对象销毁前执行,用于清理资源;
- @Resource:用于依赖注入,可以指定注入的名称和类型等信息;
- @RolesAllowed:用于声明访问方法所需要的权限角色。
关注业务逻辑而非底层细节。除此之外,JSR-250还提供了一些注解用于管理事务、处理异常等方面的功能。
@PropertySource是Spring框架中的一个注解,用于引入外部的.properties或者.yml文件,将其中的属性值注入到Spring容器中的Environment对象中。
具体来说,我们可以在一个Java配置类上添加@PropertySource注解,指定外部属性文件的位置。例如:
@Configuration
@PropertySource
("classpath:/com/example/app.properties") public class AppConfig { // ... }
这段代码表示在classpath中搜索名为
app.properties
的文件,并将其中的属性值加载到Spring容器的Environment对象中。需要注意的是,如果在多个@PropertySource注解中使用同名的属性,最后一个注解中定义的属性值将被使用覆盖之前的值。
@PropertySource注解还有一些其他的属性,例如:
value
:外部属性文件的位置,支持读入多个文件,可以使用数组的形式;name
:用于设置properties文件的名称,与value
属性二选一;encoding
:指定properties文件的编码方式,默认为UTF-8;ignoreResourceNotFound
:是否忽略找不到的资源文件,默认为false。
@
EnableScheduling
是@Spring框架中的一个注解,用于开启定时任务的支持。当我们在Spring Boot应用中使用@Scheduled
注解定义定时任务时,必须在配置类上添加@EnableScheduling
注解才能使定时任务生效。具体来说,@
EnableScheduling
注解会开启一个后台线程池,用于执行被@Scheduled注解的方法。需要注意的是,它仅适用于当前的应用程序上下文,如果有多个应用程序上下文,则需要在每个上下文中重新声明@EnableScheduling
注解以启用定时任务。以下是一个示例代码:
@
Configuration
@
EnableScheduling
public class AppConfig { // ... }
依赖注入的三种方式
属性注入:通过直接给字段赋值来设置依赖对象,即先创建实例后再直接赋值。此方法存在被误用或滥用的风险,不建议使用。
构造器注入:通过构造函数参数传递依赖对象,即将所有必需的依赖关系都声明在构造函数参数中。此方法使得创建实例时就已经建立了完整的依赖关系,从而确保了依赖的完整性和一致性。
setter方法注入:通过提供Setter方法来设置依赖对象,即先创建实例后再通过Setter方法来设置依赖对象。此方法使得对依赖对象的更新更加灵活,缺点是存在可能某个依赖未被设置的情况。
Spring中Bean作用域
singleton:单例模式,全局有且仅有一个实例
prototype:原型模式,每次获取Bean时都会创建一个新的实例
request:Web请求作用域,每个HTTP请求都会创建一个新的实例
session:Web会话作用域,同一个HTTP Session共享一个实例
globalSession:全局会话作用域,只在基于portlet的Web应用中使用,它表示全局唯一的Portlet会话
在使用@Scope注解时,常用的属性包括value和proxyMode。其中,Value
属性用于指定Bean的作用域范围,而proxyMode属性则用于指定使用哪种作用域代理。
@Scope
注解是Spring IoC容器中的一个作用域,可以用于指定Bean在容器中的生命周期。具体来说,在 Spring IoC 容器中,@Scope
注解提供了以下几种作用域范围:singleton:单例模式,全局有且仅有一个实例;
prototype:原型模式,每次获取Bean时都会创建一个新的实例;
request:Web请求作用域,每个HTTP请求都会创建一个新的实例;
session:Web会话作用域,同一个HTTP Session共享一个实例;
globalSession:全局会话作用域,只在基于portlet的Web应用中使用,它表示全局唯一的Portlet会话。
除了以上标准的作用域外,我们还可以通过自定义作用域来扩展
@Scope
注解的使用范围。在使用
@Scope
注解时,常用的属性包括value
和proxyMode
。其中,value
属性用于指定Bean的作用域范围,而proxyMode
属性则用于指定使用哪种作用域代理。
Spring MVC、Spring Cloud、Spring Boot的区别
Spring MVC是Spring的一个模块,一个web框架,它提供了丰富的功能和灵活性,包括:请求分发、请求参数解析、视图渲染等。Spring MVC的目标是为了实现Web应用程序的开发,使得开发者可以快速构建灵活的Web应用程序。
Spring Cloud是一个开源的微服务框架,主要用于构建分布式架构中的微服务应用。它提供了一系列的工具和组件,帮助开发者解决了很多分布式开发中的常见问题,例如:服务注册与发现、配置中心、负载均衡、断路器、网关等。Spring Cloud支持多种开发语言和服务注册中心,如Eureka、Consul、ZooKeeper等
Spring Boot是一个可以快速构建独立应用程序的框架。它为Spring框架的使用者提供了一种更快速、更简单的方式来构建Spring应用程序。它利用自动配置和约定优于配置的方式,帮助开发者省去了很多繁琐的配置工作。Spring Boot配合Spring Cloud使用可以快速搭建微服务架构。
Spring MVC主要用于Web应用程序开发;Spring Cloud是构建分布式微服务应用的框架;Spring Boot是一个快速构建独立应用程序的框架,也可以用来与Spring Cloud搭配构建微服务。
Spring和Spring Boot的区别
Spring:
Spring 框架提供了企业软件的各种基础组件,有了这些基础组件的帮助,开发者就可以更多的聚焦在软件的业务功能,大大提升开发效率。
Spring 的核心功能包括:IoC/DI(依赖注入、控制反转) 和 AOP(面向切面编程)
Spring 还提供了:事务管理、MVC、测试、数据访问等组件
SpringBoot:
Spring Boot是以Spring为基础,帮助快速搭建Spring应用程序。开箱即用,几乎不用配置
Spring Boot提供了统一的依赖管理,可以很少代码就能整合各种资源,包括JDBC、MyBatis等
Spring Boot提供了内嵌服务器、安全健康检查等开箱即用的功能
Spring Boot还提供了打包工具,可以将应用打成Fat Jar,然后一个命令就能启动
Spring和Spring Boot的区别如下:
- Spring是一个综合性的框架,提供了很多核心特性和扩展功能,如IoC、AOP、MVC、JDBC等,并可以与其他框架集成使用,如Hibernate、MyBatis、Struts等。
- Spring Boot是在Spring基础之上构建的快速开发框架,通过自动化配置、内嵌服务器等特性,简化了Spring应用程序的初始化配置、开发以及部署过程。相比于Spring,Spring Boot更加轻量级,注重约定优于配置,提供了更加便捷和快速的开发方式。
总的来说,Spring Boot是在Spring基础之上提供了更加便捷、快速、轻量级的开发方式,能够大大提高开发效率和代码质量。
Spring Boot自动配置原理
@EnableAutoConfiguration会读取工厂配置 /META-INF/spring.factories
找到标注@Configuration的自动配置类
自动配置类中使用@ConditionalXXX注解,进行条件配置,根据条件初始化Bean组件
可以根据属性文件、Bean对象、Bean类型自动配置
Spring Boot会在classpath下查找META-INF/spring.factories文件,并将其中所有EnableAutoConfiguration指定的配置类读取到spring容器中。
在应用启动时,Spring Boot会通过SpringBootApplication注解中的@ComponentScan扫描所有的 @Configuration 配置类 。
当Spring Boot需要某个服务(例如数据库),但用户没有配置时,Spring Boot会根据依赖关系,自动添加默认的配置。
自动配置类通常使用@Conditional注解来判断特定的条件是否成立,从而决定是否启用该自动配置类。
用户可以通过自定义的配置覆盖默认的自动配置,只需提供自己的@Configuration类即可。
综上,Spring Boot自动配置的原理就是,在程序启动时,根据classpath下的META-INF/spring.factories文件中的配置和用户配置的内容,判断应用所需的服务是否已经配置,如果没有则自动添加默认的配置,然后将所有@Configuration类加载到Spring容器中以供使用。这种自动化的配置方式,大大降低了应用的配置复杂度,并且可以使开发者更加专注于业务逻辑的实现
Spring MVC的设计理念
MVC是一种使用MVC设计创建Web应用程序的模式
Model表示应用程序核心,处理应用程序数据逻辑的部分
View显示数据
Controller处理输入与交互流程
Spring MVC的处理流程
1.用户发送请求至前端控制器DispatcherServlet
2.DispatcherServlet收到请求调用处理器映射器HandlerMapping
3.处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet
4.DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,如:参数封装、数据格式转换,数据验证等操作
5.执行处理器Handler(Controller,也叫页面控制器)
6.Handler执行完成返回ModeAndView
7.HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
8.DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9.ViewReslover解析后返回具体View
10.DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)
11.DispatcherServlet响应用户
Spring MVC常用注解
@Controller:用于配合组件扫描(@ComponentScan)创建控制器对象,常与@RequestMapping注解结合使用,其元注解包括@Component
@RestController:一个方便的注解,它元注解有@Controller和@ResponseBody注解
@ResponseBody:表明控制器方法的返回值绑定到HTTP响应体
@RequestMapping:使用@RequestMapping注解来映射请求到控制器方法
@PostMapping:用于将HTTP POST请求映射到特定的处理方法的注解
@GetMapping:用于将HTTP GET请求映射到特定的处理方法的注解
@RequestBody:标注在方法参数上,表示网络请求正文映射到丰富参数
@RequestParam:将请求参数映射到控制器的方法参数上
@PathVariable:将请求路径上“URL模板”映射到控制器的方法参数上
@ResponseStatus:设定HTTP响应状态码
@RequestHeader:映射请求头到控制器方法参数
如何处理get请求
@GetMapping:用于将HTTP GET请求映射到特定的处理方法的注解
@RequestMapping:使用@RequestMapping注解来映射请求到控制器方法,添加请求方式属性
基于Spring Security与JWT的单点登录
具体实现:Spring Security框架是根据SecurityContext
中的Authentication
对象来处理认证的,所以,要想Spring Security识别用户的身份,必须将相关信息创建为Authentication
对象,然后存入到SecurityContext
中;JWT与传统的Session不同,它本身可以解析出有意义的数据;在开发时,应该在验证用户登录成功后,将用户的相关信息用于生成JWT数据,然后响应到客户端,并且,用户在后续的访问过程中,应该携带JWT数据,服务器端就可以使用Filter
组件接收到JWT数据,并解析出用户身份相关的信息,用于创建Authentication
对象,最终将此对象存入到SecurityContext
中。
MyBatis #{}和${}的区别
#{}是预处理,${}是字符串替换
MyBatis处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法赋值
MyBatis处理${}时,就是把${}替换成变量的值,也就是字符串拼接,字符串拼接存在SQL注入风险
在MyBatis中,
#{}
和${}
都是用于占位符的语法。(语法分析、语义分析、编译、执行)
#{}是预编译。
会将参数值作为一个占位符传入SQL语句,MyBatis底层会使用PreparedStatement的setXXX方法来设置参数值,可以有效防止SQL注入攻击;
${}不是预编译的。会
将参数值直接拼接到SQL语句中,可能存在SQL注入风险。如果需要表示某个部分只是字段的值,需要使用一对单引号将它框住('${}')。
#{}
可用于任何SQL语句中,包括动态SQL中的if、choose等标签,而${}
仅仅适用于普通SQL语句。
在MyBatis中,当需要拼接的变量不能带单引号时,必须使用${},比如:
当SQL中表名或列名是通过参数传递进来时,使用${}
在order by排序语句中,因为order by后面必须跟字段名,而字段名不能带引号
在动态SQL语句中,如果需要将参数直接拼接到SQL语句中,则也需要使用${}
MyBatis如何防止SQL注入
使用预处理语句(#{}):MyBatis底层使用JDBC进行数据库连接和操作,在进行SQL语句的预编译时,会自动将输入参数中的特殊字符进行转义,从而避免SQL注入攻击的发生。
使用OGNL表达式:MyBatis内置了一种表达式语言OGNL,用于对Java对象进行操作。在使用OGNL表达式时,MyBatis会自动将输入参数中的特殊字符进行转义,从而保证SQL语句的安全性。
使用参数映射:MyBatis可以将输入参数映射为Java对象,并使用其属性值作为SQL语句的参数,从而避免手动拼接字符串的风险。
使用动态SQL:MyBatis的动态SQL语句可以根据不同的条件拼接出不同的SQL语句,从而避免手动拼接字符串的风险。同时,MyBatis可以自动进行参数类型转换和转义,保证SQL语句的安全性。
MyBatis的一级缓存二级缓存
MyBatis一级缓存是SqlSession级别的缓存,当用户发起查询时,MyBatis先在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。MyBatis的一级缓存是默认开启的,作用是提高查询效率,减少对数据库的访问,从而提升系统的性能。
MyBatis二级缓存是全局缓存,被多个SqlSession共享数据,减少重复查询的开销,提高系统的性能。二级缓存默认是不开启的,需要手动配置。通过在Mapper.xml文件中添加<cache>标签开启二级缓存。开启二级缓存后,数据查询执行的流程就是二级缓存->一级缓存->数据库。二级缓存的生命周期是应用程序的整个运行周期,它会将查询结果缓存到内存中,以减少对数据库的访问。
两者的区别
作用范围不同:一级缓存的作用范围是在同一个SqlSession中共享数据,而几乎缓存的作用范围是在多个SqlSession之间共享数据
存储位置不同:一级缓存是存储在SqlSession对象中的,而二级缓存是存储在SessionFactory对象中的。
缓存数据的有效性不同:一级缓存的有效性仅限于同一个SqlSession当中,而二级缓存的有效性可以跨多个SqlSession,因此需要保证缓存数据的一致性和及时性。
总的来说,MyBatis的一级缓存主要是为了减少数据库的访问次数,提高查询效率;而二级缓存是为了实现数据的共享,在多个SqlSession之间共享数据,以提高系统的性能。
MyBatis如何不走一级缓存
禁用一级缓存:MyBatis没有提供一级缓存的启用、禁用开关,但是在Mapper文件对应的语句中增加flushCache="true",表示执行该操作时清空缓存,同时还会加上userCache="false",表示禁用该操作的缓存
简述MyBatis的单个参数,多个参数如何声明
单个参数,直接在SQL语句中使用#{参数名}的占位符即可,在执行时会把参数值替换到占位符中。
多个参数:
1.匿名参数:按照参数在方法中出现的顺序,一次加上#{0},#{1}等占位符来引用参数值
2.使用@Param注解:在方法参数前使用@Param("参数名")注解来为参数命名,然后在SQL语句中使用#{参数名}占位符来引用参数值
3.实体类封装参数:将参数封装成一个实体类,在SQL语句中使用#{实体类属性名}占位符来引用参数值
4.Map集合:将参数封装成一个Map,在SQL语句中使用#{Map键名}占位符来引用参数值
MyBatis如何完成批量操作,举例说明
基于Java代码的批量操作:通过SqlSession提供的insert()、update()、delete()方法来实现批量操作。这些方法有两个重载版本,一个是只接受单个参数,另一个是接受Collection类型的参数。
基于Mapper XML的批量操作:借助MyBatis提供的foreach标签,讲一个集合中的元素动态地拼接成多个SQL语句进行批量操作
MyBatis缓存优先级
二级缓存->一级缓存->数据库
MyBatis和MyBatis plus的区别
MyBatis是一个开源的基于Java的持久化框架,主要用于数据库访问,在SQL映射配置文件或注解中配置SQL语句,可以灵活地实现SQL查询、插入、更新、删除等操作,还支持高级映射、存储过程和自定义SQL等特性。
MyBatis-plus则是在MyBatis框架的基础上进行了二次封装,提供了一些更加便捷的操作方式和内置的常用功能模块。比如分页查询、条件构造器、自动填充、性能分析器等。
MyBatis更注重灵活性和自由度,需要自行编写SQL语句,MyBatis-plus则更注重便捷性和开发效率,提供了许多实用的功能模块,适合开发速度较快、业务逻辑简单的项目。
MyBatis如何实现分页
limit实现分页:使用SQL语句的limit关键字指定查询结果的偏移量和限制数量。
RowRounds实现分页:在查询SQL后添加limit限制语句,通过RowRounds参数来指定limit的偏移量和取值个数,实现分页查询
MyBatis分页插件PageHelper实现分页:通过拦截器机制,拦截SQL语句中的select操作,对其进行重写,从而实现分页查询功能
PageHelper 在拦截 SQL 语句时,会解析 SQL 语句中的参数,获取分页相关的信息(如当前页码、每页记录数等),并基于这些信息生成重写后的 SQL 语句。在生成 SQL 语句时,PageHelper 会将 select 操作的结果集限定在指定的分页范围内,并且将总记录数作为返回值传递给应用程序。
物理分页:通过在SQL语句中添加ROWNUM、LIMIT或TOP等关键字进行分页
逻辑分页:通过Java代码中根据分页参数手动处理分页逻辑来实现
游标分页、滚动分页、哈希分页、内容分页、时间分页、基于数据量的分页、基于搜索的分页、基于位置的分页、基于标记的分页、基于主键范围的分页
写过原生的分页吗?用过什么插件吗?分页插件的原理?
原生分页就是利用MySQL的limit实现分页
通过MyBatis分页插件PageHelper实现分页
PageHelper通过MyBatis拦截器实现的分页,核心类是PageInterceptor
MyBatis延迟加载
MyBatis的延迟加载就是按需查询,在需要的时候进行查询。
在MyBatis的属性中配置:将lazyLoadingEnable设置为true表示开启延迟加载,没认为false。将aggressiveLazyLoading设置为false表示按需加载,默认为true
JDBC和MyBatis的区别
JDBC是java提供的操作数据库的API,JDBC的弊端就是编程工作量相对较大且繁琐
MyBatis是支持普通SQL查询,存储过程和高级映射的持久层的框架。MyBatis封装了JDBC API,能够通过反射将JDBC与Java对象映射,基本不用JDBC代码
由于采用反射进行映射,MyBatis性能比原生JDBC API略慢
(草稿--------------)