引言:
本文对多个平台的面试题进行了汇总、分类、概括、整理,对重点进行了标出,更便于阅读和记忆。
【黑马Java笔记+踩坑汇总】JavaSE+JavaWeb+SSM+SpringBoot+瑞吉外卖+SpringCloud+黑马旅游+谷粒商城+学成在线+牛客面试题
目录
Spring
简单介绍Spring
说说你对IoC的理解
说说你对AOP的理解
说说Bean的生命周期
说说Bean的作用域,以及默认的作用域
说说BeanFactory和FactoryBean的区别
说说@Autowired和@Resource注解的区别
说说Spring事务管理
SpringBoot
说说你对Spring Boot的理解,以及它和Spring的区别?
说说Soring Boot的起步依赖
说说Spring Boot的启动流程
说说Spring Boot的自动装配
说说Spring Boot常用的注解
SpringMVC
说说你对MVC的理解
介绍一下Spring MVC的执行流程
Mybatis
介绍一下MyBatis的缓存机制
在MyBatis中$和#有什么区别
Spring
简单介绍Spring
得分点:
IOC、Bean、DI、AOP、事务
Spring是一个通过IOC、AOP思想实现简化web、微服务、分布式开发的框架,可以进行事务处理和整合其他框架。
IOC控制反转思想:创建对象的控制权由内部(即new实例化)反转到外部(即IOC容器)。
Bean:IOC容器中存放的一个个对象
DI依赖注入:绑定IOC容器中bean与bean之间的依赖关系。例如将dao层对象注入到service层对象。
AOP面向切面编程:在不改原有代码的前提下对代码进行增强。抽取共性代码(即通知),植入到待增强的方法(即切入点)。通知和切入点的关系即切面,增强的方法是创建原始对象增强后的代理对象。
事务:保证数据层或业务层一系列的数据库操作同成功同失败
标准回答
Spring框架包含众多模块,如Core、Testing、Data Access、Web Servlet等,其中Core是整个Spring框架的核心模块。
Core模块提供了IoC容器、AOP功能、数据绑定、类型转换等一系列的基础功能,而这些功能以及其他模块的功能都是建立在IoC和AOP之上的,所以IoC和AOP是Spring框架的核心。
IoC 控制反转
创建对象的控制权由内部(new实例化)反转到外部(即IOC容器),此思想称为控制反转。使用对象时,由主动new产生对象转换为由外部提供对象。
IoC(Inversion of Control)是控制反转的意思,这是一种面向对象编程的设计思想。在不采用这种思想的情况下,我们需要自己维护对象与对象之间的依赖关系,很容易造成对象之间的耦合度过高,在一个大型的项目中这十分的不利于代码的维护。IoC则可以解决这种问题,它可以帮我们维护对象与对象之间的依赖关系,降低对象之间的耦合度。
DI依赖注入
说到IoC就不得不说DI(Dependency Injection),DI是依赖注入的意思,它是IoC实现的实现方式,就是说IoC是通过DI来实现的。由于IoC这个词汇比较抽象而DI却更直观,所以很多时候我们就用DI来代替它,在很多时候我们简单地将IoC和DI划等号,这是一种习惯。而实现依赖注入的关键是IoC容器,它的本质就是一个工厂。
AOP 面向切面编程
AOP(Aspect Oriented Programing)是面向切面编程思想,这种思想是对OOP的补充,它可以在OOP的基础上进一步提高编程的效率。简单来说,它可以统一解决一批组件的共性需求(如权限检查、记录日志、事务管理等)。在AOP思想下,我们可以将解决共性需求的代码独立出来,然后通过配置的方式,声明这些代码在什么地方、什么时机调用。当满足调用条件时,AOP会将该业务代码植入到我们指定的位置,从而统一解决了问题,又不需要修改这一批组件的代码。
参考:
Spring基础1——Spring(配置开发版),IOC和DI_vincewm的博客-CSDN博客
Spring基础3——AOP,事务管理_spring aop事务管理_vincewm的博客-CSDN博客
说说你对IoC的理解
得分点
控制反转与依赖注入含义
标准回答
IOC控制反转思想:创建对象的控制权由内部(即new实例化)反转到外部(即IOC容器)。
Bean:IOC容器中存放的一个个对象
DI依赖注入:绑定IOC容器中bean与bean之间的依赖关系。例如将dao层对象注入到service层对象。
IoC是控制反转的意思,是一种面向对象编程的设计思想。在不采用这种思想的情况下,我们需要自己维护对象与对象之间的依赖关系,很容易造成对象之间的耦合度过高,在一个大型的项目中这十分的不利于代码的维护。IoC则可以解决这种问题,它可以帮我们维护对象与对象之间的依赖关系,并且降低对象之间的耦合度。
说到IoC就不得不说DI,DI是依赖注入的意思,它是IoC实现的实现方式。由于IoC这个词汇比较抽象而DI比较直观,所以很多时候我们就用DI来代替它,在很多时候我们简单地将IoC和DI划等号,这是一种习惯。实现依赖注入的关键是IoC容器,它的本质就是一个工厂。
加分回答
IoC是Java EE企业应用开发中的就偶组件之间复杂关系的利器。
在以Spring为代表的轻量级Java EE开发风行之前,实际开发中是使用更多的是EJB为代表的开发模式。在EJB开发模式中,开发人员需要编写EJB组件,这种组件需要满足EJB规范才能在EJB容器中运行,从而完成获取事务,生命周期管理等基本服务,Spring提供的服务和EJB并没有什么区别,只是在具体怎样获取服务的方式上两者的设计有很大不同:Spring IoC提供了一个基本的JavaBean容器,通过IoC模式管理依赖关系,并通过依赖注入和AOP切面增强了为JavaBean这样的POJO对象服务于事务管理、生命周期管理等基本功能;而对于EJB,一个简单的EJB组件需要编写远程/本地接口、Home接口和Bean的实体类,而且EJB运行不能脱离EJB容器,查找其他EJB组件也需要通过诸如JNDI的方式,这就造成了对EJB容器和技术规范的依赖。也就是说Spring把EJB组件还原成了POJO对象或者JavaBean对象,以此降低了用用开发对于传统J2EE技术规范的依赖。
在应用开发中开发人员设计组件时往往需要引用和调用其他组件的服务,这种依赖关系如果固化在组件设计中,会造成依赖关系的僵化和维护难度的增加,这个时候使用IoC把资源获取的方向反转,让IoC容器主动管理这些依赖关系,将这些依赖关系注入到组件中,这就会让这些依赖关系的适配和管理更加灵活。
说说你对AOP的理解
得分点
AOP概念、AOP作用、AOP的实现方式
标准回答
AOP面向切面编程:抽取共性代码(即通知),植入到待增强的方法(即切入点)。
作用:在不改原有代码的前提下对代码进行增强。
切面:通知和切入点的关系。
AOP有两种实现方式:
JDK动态代理(默认):运行时创建接口的代理实例。
CGLib代码生成库动态代理:采用底层的字节码技术,当目标对象不存在接口时,创建子类代理的实例。
应用场景:日志和事务。
AOP是一种编程思想,是通过预编译方式和运行期动态代理的方式实现不修改源代码的情况下给程序动态统一添加功能的技术。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块。AOP技术利用一种称为“横切”的技术,剖解开封装对象的内部,将影响多个类的公共行为封装到一个可重用的模块中,并将其命名为切面。所谓的切面,简单来说就是与业务无关,却为业务模块所共同调用的逻辑,将其封装起来便于减少系统的重复代码,降低模块的耦合度,有利用未来的可操作性和可维护性。
利用AOP可以对业务逻辑各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率。
AOP可以有多种实现方式,而Spring AOP支持如下两种实现方式。
- JDK动态代理:这是Java提供的动态代理技术,可以在运行时创建接口的代理实例。Spring AOP默认采用这种方式,在接口的代理实例中织入代码。
- CGLib(Code Generation Library)动态代理:采用底层的字节码技术,在运行时创建子类代理的实例。当目标对象不存在接口时,Spring AOP就会采用这种方式,在子类实例中织入代码。
加分回答-应用场景
在应用场景方面,Spring AOP为IoC的使用提供了更多的便利,一方面,应用可以直接使用AOP的功能,设计应用的横切关注点,把跨越应用程序多个模块的功能抽象出来,并通过简单的AOP的使用,灵活地编制到模块中,比如可以通过AOP实现应用程序中的日志功能。另一方面,在Spring内部,例如事务处理之类的一些支持模块也是通过Spring AOP来实现的。
AOP不能增强的类:
1. Spring AOP只能对IoC容器中的Bean进行增强,对于不受容器管理的对象不能增强。
2. 由于CGLib采用动态创建子类的方式生成代理对象,所以不能对final修饰的类进行代理。
说说Bean的生命周期
得分点
Bean生命周期的四大部分以及详细步骤
标准回答
Bean 定义:
- 配置开发版:xml中bean标签
- 注解开发版:bean注解@Component/@Controller/@Service/@Repository,配置类注解@ComponentScan扫描bean包
Bean 的初始化:
- Spring查找、加载、实例化Bean;
- 将Bean的引用和值注入到Bean的属性中;
- 如果Bean实现了一些接口,Spring将调用Bean的一些方法对Bean初始化。例如Bean实现了InitializingBean或者内部有方法注解了@PostConstruct,将会先执行实现方法内的代码。
Bean的生存期:在应用上下文被销毁前,Bean被将一直驻留在应用上下文中,可以被使用。
Bean 的销毁:如果bean实现了DisposableBean接口或者有方法注解了@PreDestroy,将会在应用上下文销毁前执行方法里代码。
Bean 生命周期大致分为 Bean 定义、Bean 的初始化、Bean的生存期和 Bean 的销毁4个部分。具体步骤如下
1. Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
2. Bean实例化后对将Bean的引用和值注入到Bean的属性中
3. 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
4. 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
5. 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
6. 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
7. 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
8. 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
9. 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
10. 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。
加分回答-自定义初始化和销毁方法
这个过程是由Spring容器自动管理的,其中有两个环节我们可以进行干预。
1. 我们可以自定义初始化方法,并在该方法前增加@PostConstruct注解,届时Spring容器将在调用SetBeanFactory方法之后调用该方法。
2. 我们可以自定义销毁方法,并在该方法前增加@PreDestroy注解,届时Spring容器将在自身销毁前,调用这个方法。
说说Bean的作用域,以及默认的作用域
得分点
singleton、prototype、request、session、globalSession、自定义作用域
标准回答
五种作用域:
在Bean上注解@Scope,设置作用域为singleton(默认)、prototype、request、session、globalSession。
自定义作用域:
- 创建MyScope,实现Scope接口,并重写get()和remove()方法;
- Bean上注解@Scope("MyScope")
在默认情况下,Bean在Spring容器中是单例的,但我们可以通过@Scope注解来修改Bean的作用域。这个注解有五个不同的取值,代表了Bean的五种不同类型作用域
singleton单例(默认) :在Spring容器中仅存在一个实例,即Bean以单例的形式存在。
prototype原型:每次调用getBean()时,都会执行new操作,返回一个新的实例。
request :每次HTTP请求都会创建一个新的Bean。
session :同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean。
globalSession:同一个全局的Session共享一个Bean,一般用于Portlet环境。
加分回答-自定义作用域:
使用Spring的注解方式自定义Bean作用域很简单,您可以通过实现org.springframework.beans.factory.config.Scope接口并结合Spring框架中提供的@Scope注解来实现。
具体步骤如下:
- 实现Scope接口,并重写get()和remove()方法。
- 在实现类上添加@Scope注解,并指定其值为自定义Scope的名称。
get()方法用于从当前作用域中获取给定名称的对象实例。如果找不到该对象,则应返回null。 remove()方法用于从当前作用域中删除指定名称的对象实例。 如果找不到该对象,则无动作。
举个例子:假设您需要实现一个MyScope的自定义作用域,可以按照以下步骤:
定义MyScope类,该类实现Scope接口,如下所示:
public class MyScope implements Scope { private final Map<String, Object> scopedObjects = new HashMap<>(); @Override public Object get(String name, ObjectFactory<?> objectFactory) { if (!scopedObjects.containsKey(name)) { scopedObjects.put(name, objectFactory.getObject()); } return scopedObjects.get(name); } @Override public Object remove(String name) { return scopedObjects.remove(name); } // 其他方法省略 }
说说BeanFactory和FactoryBean的区别
得分点
BeanFactory定义、FactoryBean定义
标准回答
BeanFactory就是IOC容器,它有一些方法可以判断容器里Bean是否存在、是否单例等。
FactoryBean是一个用来创建bean的接口。新建一个类实现FactoryBean<MyBean>,实现三个方法,创建Bean对象、返回字节码文件、设置单例模式。
它们在拼写上非常相似,一个是Factory,也就是IoC容器或对象工厂,一个是Bean。
在Spring中,所有的Bean都是由BeanFactory(也就是IoC容器)来进行管理的。
但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能产生或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。用户使用容器时,可以使用转义符“&”来得到FactoryBean本身,用来区分获取FactoryBean产生的对象和获取FactoryBean本身。举例来说,如果alphaObject是一个FactoryBean,那么使用&alphaObject得到 的是FactoryBean,而不是alphaObject这个FactoryBean产生出来的对象。其中,alphaObject是定义Bean的时候所指定的名字。
public class MyFactoryBean implements FactoryBean<MyBean> { //创建并返回了MyBean的实例 @Override public MyBean getObject() throws Exception { // 创建MyBean对象 return new MyBean(); } @Override public Class<?> getObjectType() { //返回的是MyBean的Class类型 return MyBean.class; } //指示了MyBean是否为单例模式 @Override public boolean isSingleton() { return true; } }
加分回-BeanFactory定义方法:
- getBean(String name):
Spring容器中获取对应Bean对象的方法,如存在,则返回该对象
- contnsBean(String name):Spring容器中是否存在该对象
- isSingleton(String name):通过beanName是否为单例对象
- isPrototype(String name):判断bean对象是否为多例对象
- isTypeMatch(String name, ResolvableType typeToMatch):判断name值获取出来的bean与typeToMath是否匹配
- getType(String name):获取Bean的Class类型
- getAliases(String name):获取name所对应的所有的别名
FactoryBean方法:
- T getObject():返回实例
- Class getObjectType();:返回该装饰对象的Bean的类型
- default boolean isSingleton():Bean是否为单例
说说@Autowired和@Resource注解的区别
得分点
注解来源、注入方式、@Resource装配顺序
标准回答
注解来源:@Autowired是Spring提供的注解,@Resource是JDK提供的注解。
注入方式:@Autowired是只能按类型注入,@Resource默认按名称注入,也支持按类型注入。
@Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。
@Resource有两个中重要的属性:name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。
加分回答-@Resource装配顺序
1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
说说Spring事务管理
得分点
两种事务编程模型、@Transactional原理、隔离级别、传播机制、非事务方法调用事务方法
标准回答
两种事务编程模型
Spring支持编程式事务(注入TransactionTemplate对象,execute方法里编写事务)和声明式事务(方法或类注解@Transactional)。
@Transactional原理
原理是AOP,生成代理对象,在业务方法执行前后自动开启和提交/回滚事务。
@Transactional的属性可以设置事务的超时时间、隔离级别、传播机制、回滚异常、不会滚异常。
事务隔离级别
多个事务同时操作同一个数据库时,一个事务读取到其他事务已经提交但未持久化到数据库中的数据所带来的影响。
- 读取未提交内容:隔离级别最低,事务可以读取到其他未提交事务的数据;
- 读取已提交内容:保证其他事务提交后才能读取数据。解决脏读。
- 可重复读:保证在一个事务内多次查询相同数据时,结果是一致的。解决脏读、不可重复读
- 序列化:隔离级别最高,在整个事务过程中将所有操作串行化执行。解决脏读、不可重复读和幻读,但性能差
- 与数据库默认设置保持一致(默认):数据库的默认隔离级别
脏读:因为延迟等原因,第一个事务在更新数据库前,第二个事务已经更新了数据库,导致最终数据库内容是第一个事务改的情况。
不可重复读:同一次事务前后查询结果不一致;
幻读:同义词事务前后数据量发生变化,像幻觉一样。
事务的传播行为:多个事务方法互相调用时,事务如何在这些方法之间传播。
默认传播行为:如果当前没有事务,则开启一个新的事务;如果已存在事务,就加入到该事务中。
非事务方法调用事务方法
非事务方法调用事务方法必须用代理对象调用,也就是注入自己再调用事务方法。
Spring为事务管理提供了一致的模板,在高层次上建立了统一的事物抽象。
Spring支持两种事务编程模型:
1. 编程式事务
Spring提供了TransactionTemplate模板,利用该模板我们可以通过编程的方式实现事务管理,而无需关注资源获取、复用、释放、事务同步及异常处理等操作。相对于声明式事务来说,这种方式相对麻烦一些,但是好在更为灵活,我们可以将事务管理的范围控制的更为精确。
@Service public class MyService { @Autowired private TransactionTemplate transactionTemplate; public void doSomething() { transactionTemplate.execute(new TransactionCallback<Void>() { public Void doInTransaction(TransactionStatus status) { try { // 业务逻辑代码 return null; } catch (Exception ex) { status.setRollbackOnly(); throw ex; } } }); } }
2. 声明式事务
Spring事务管理的亮点在于声明式事务管理,它允许我们通过声明的方式,在IoC配置中指定事务的边界和事务属性,Spring会自动在指定的事务边界上应用事务属性。相对于编程式事务来说,这种方式十分的方便,只需要在需要做事务管理的方法上,增加@Transactional注解,以声明事务特征即可。
声明式事务管理是Spring事务管理的亮点,它允许我们通过声明的方式,在IoC配置中指定事务的边界和事务属性,Spring会自动在指定的事务边界上应用事务属性。相对于编程式事务来说,这种方式十分的方便,只需要在需要做事务管理的方法上,增加@Transactional注解,以声明事务特征即可。
当我们在业务方法上添加@Transactional注解时,Spring会根据该注解生成一个代理对象,并将该代理对象包装成一个事务增强器,在业务方法执行前后自动开启和提交/回滚事务。
加分回答
事务的打开、回滚和提交是由事务管理器来完成的,我们使用不同的数据库访问框架,就要使用与之对应的事务管理器。在Spring Boot中,当你添加了数据库访问框架的起步依赖时,它就会进行自动配置,即自动实例化正确的事务管理器。
对于声明式事务,是使用@Transactional进行标注的。这个注解可以标注在类或者方法上。
- 当它标注在类上时,代表这个类所有公共(public)非静态的方法都将启用事务功能。
- 当它标注在方法上时,代表这个方法将启用事务功能。
另外,在@Transactional注解上,我们可以使用isolation属性声明事务的隔离级别,使用propagation属性声明事务的传播机制。
Spring的事务隔离级别
Spring的事务隔离级别(Transaction Isolation Level)是指当多个事务同时操作同一个数据库时,一个事务读取到其他事务已经提交但未持久化到数据库中的数据所带来的影响。Spring提供了五种标准的事务隔离级别:
TRANSACTION_READ_UNCOMMITTED(读取未提交内容):隔离级别最低,事务可以读取到其他未提交事务的数据,可能出现脏读、不可重复读和幻读等问题。
TRANSACTION_READ_COMMITTED(读取已提交内容):保证其他事务提交后才能读取数据,解决了脏读问题,但依然存在不可重复读和幻读问题。
TRANSACTION_REPEATABLE_READ(可重复读):保证在一个事务内多次查询相同数据时,结果是一致的。解决了脏读、不可重复读问题,但仍会出现幻读问题。
TRANSACTION_SERIALIZABLE(序列化):隔离级别最高,在整个事务过程中将所有操作串行化执行,解决了以上所有问题,但对性能有较大影响。
TRANSACTION_DEFAULT(与数据库默认设置保持一致):使用数据库的默认隔离级别,该级别完全取决于数据库的实现。
选择相应的隔离级别需要考虑多方面的因素,包括应用程序的访问模式、数据的重要性、并发度等。默认情况下,Spring使用数据库默认的隔离级别设置。在编程或声明式事务管理时,可以通过@Transactional注解或相关配置来指定所需的事务隔离级别。
事务的传播行为
事务的传播行为(Transaction Propagation Behavior)是指在多个事务处理过程中,各个事务之间获取资源、释放资源和执行操作的相关规则。Spring提供了七种标准的事务传播行为:
PROPAGATION_REQUIRED:默认传播行为,如果当前没有事务,则开启一个新的事务;如果已存在事务,就加入到该事务中。
PROPAGATION_SUPPORTS:支持当前事务。如果当前有事务,则使用该事务;没有则非事务执行。
PROPAGATION_MANDATORY:强制必须存在当前事务中,否则抛出异常。
PROPAGATION_REQUIRES_NEW:不管当前是否存在事务,都会新开一个事务执行当前方法,并挂起其他已存在的事务。
PROPAGATION_NOT_SUPPORTED:当前方法直接以非事务方式执行,如果当前存在事务,则先将事务挂起,待当前函数执行完毕后恢复已经挂起的事务。
PROPAGATION_NEVER:当前方法必须以非事务方式执行,如果当前有事务,则抛出异常。
PROPAGATION_NESTED:如果当前事务存在,则开启一个嵌套事务并执行每一个任务,如果嵌套事务成功,则与当前事务共同提交;嵌套事务失败时,仅回滚嵌套事务,而当前事务则继续运行。
以上所有传播行为,只要组成操作链时出现异常,并且该异常没有得到多层级捕获和处理,则整个过程会触发回滚操作。
选择哪种传播行为需要结合具体业务场景进行全面考虑,遵循最小化的管理原则,在保证数据正确性的前提下,尽可能地减少锁表、死锁、超时等因事务而带来的额外开销。默认情况下,Spring使用PROPAGATION_REQUIRED作为应用程序中所有有@Transactional注解的方法的传播行为设置。
SpringBoot
说说你对Spring Boot的理解,以及它和Spring的区别?
得分点
Spring Boot与Spring 的关系 、Spring Boot的主要功能 、Spring Boot的优点
标准回答
与Spring 的关系:Spring Boot是Spring的脚手架框架。
优点:快速构建项目、对主流框架的无配置集成、项目可独立运行,无需外部依赖Servlet容器、提供运行时的应用监控
主要功能:自动配置、起步依赖(一组关联度较高的依赖集合,并自动配置)、监控项目
其实从本质上来说,Spring Boot就是Spring,它帮你完成了一些Spring Bean配置。Spring Boot使用“习惯优于配置”的理念让你的项目快速地运行起来,使用Spring Boot能很快的创建一个能独立运行、准生产级别、基于Spring框架的项目。
但Spring Boot本身不提供Spring的核心功能,而是作为Spring的脚手架框架,达到快速构建项目,预设第三方配置,开箱即用的目的。Spring Boot有很多优点,具体如下:
- 可以快速构建项目
- 可以对主流开发框架的无配置集成
- 项目可独立运行,无需外部依赖Servlet容器
- 提供运行时的应用监控
- 可以极大地提高开发、部署效率
- 可以与云计算天然集成
加分回答
Spring Boot 的核心功能:
1. 自动配置
针对很多Spring应用程序常见的应用功能,Spring Boot能自动提供相关配置。
2. 起步依赖
Spring Boot通过起步依赖为项目的依赖管理提供帮助。起步依赖其实就是特殊的Maven依赖和Gradle依赖,利用了传递依赖解析,把常用库聚合在一起,组成了几个为特定功能而定制的依赖。
3. 端点监控 Spring Boot 可以对正在运行的项目提供监控。
说说Soring Boot的起步依赖
得分点
starter配置,约定大于配置
标准回答
起步依赖:一组关联度较高的依赖集合,用于快速搭建应用程序环境。
作用:简化依赖jar包的导入、提供自动配置功能。
一些starter依赖的版本信息是由 spring-boot-starter-parent(版本仲裁中心) 统一控制,例如spring-boot-starter不用指定版本。
约定大于配置:springboot会使用一些约定俗成的配置作为默认配置。例如自动扫描启动类所在包的bean、自动加载类路径下的配置类、自动装配starter依赖(例如redis默认端口6379)。
Spring Boot的起步依赖是一种特殊的依赖关系,它可以简化项目中依赖jar包的导入过程,并提供了许多自动配置的功能。
所谓"起步依赖",指的是一组预先定义好的、常用的、关联度较高的依赖集合,通过引入这些起步依赖,就能方便地快速搭建一个特定类型的应用程序环境。
这个依赖集合包含了许多与Web开发有关的jar包(如Spring MVC、Tomcat等),并且还会自动进行一些默认的配置(如设置视图解析器、注册消息转换器等),从而使得我们无需手动集成这些jar包和进行这些配置操作,就能够快速地启动一个基础的Web应用程序。
Spring Boot提供了多种起步依赖,覆盖了不同的应用程序场景,开发人员可以根据实际需要来选择引入不同的依赖集合。同时,Spring Boot也支持自定义起步依赖,开发人员可以基于现有的依赖集合进行扩展,从而快速搭建出适合自己特定应用场景的开发环境。
Spring Boot 将日常企业应用研发中的各种场景都抽取出来,做成一个个的 starter(启动器),starter 中整合了该场景下各种可能用到的依赖,用户只需要在 Maven 中引入 starter 依赖,SpringBoot 就能自动扫描到要加载的信息并启动相应的默认配置。starter 提供了大量的自动配置,让用户摆脱了处理各种依赖和配置的困扰。所有这些 starter 都遵循着约定成俗的默认配置,并允许用户调整这些配置,即遵循“约定大于配置”的原则。
约定大于配置
Spring Boot的核心设计理念就是"约定大于配置"(Convention Over Configuration),即通过一些默认的配置和约定来减少开发人员在项目中进行复杂的配置操作,从而提高开发效率。
具体来讲,Spring Boot在很多地方都使用了约定大于配置的思想:
启动类的位置。Spring Boot在启动时会自动扫描@SpringBootApplication注解所在的包及其子包,并自动注册所有标注了@Bean注解的Bean实例。
默认配置文件。Spring Boot默认支持application.properties或application.yml格式的配置文件,并且只要这些文件放置到了classpath下,就会被自动识别并加载进来。
自动装配。Spring Boot提供了自动配置功能,只需要引入相应的starter依赖,就可以由Spring Boot自动装配相关的依赖项、创建必要的Bean实例等。
命名规范。Spring Boot通过一些常见的命名规范,如目录布局规范、类/接口命名规范,使得程序的结构更加清晰,易于维护。
总之,Spring Boot通过对常见应用场景的默认设置和约定,将大量的配置工作隐藏起来,提高了开发人员的生产力和开发效率。同时,如果需要进行定制化的配置,Spring Boot也提供了丰富的扩展机制和自定义配置选项。
那么我们看构建的项目的pom.xml文件中的starter配置。
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency>
以 spring-boot-starter-web 为例,它能够为提供 Web 开发场景所需要的几乎所有依赖,因此在使用 Spring Boot 开发 Web 项目时,只需要引入该 Starter 即可,而不需要额外导入 Web 服务器和其他的 Web 依赖。
加分回答
有时在引入starter时,我们并不需要指明版本(version),这是因为starter版本信息是由 spring-boot-starter-parent(版本仲裁中心) 统一控制的。
说说Spring Boot的启动流程
得分点
调用run方法,run方法执行流程
标准回答
Spring Boot的启动流程:
- 创建Spring应用程序上下文;
- 加载Spring组件(如SpringCloud、SpringSecurity)和第三方库;
- 加载配置类;
- 进行scheduled task、initializers等相关操作;
- 使用Tomcat或者Jetty服务器启动服务。
Spring Boot的启动流程
创建Spring应用程序上下文(ApplicationContext):Spring Boot使用内嵌的Tomcat或者Jetty服务器,所以启动时会自动创建一个applicationContext。首先使用
Application.class
引导应用程序并创建Spring应用程序上下文。加载Spring组件和第三方库:Spring Boot自动加载了大量常见依赖项,并对它们进行了管理。当应用程序启动时,Spring Boot会通过基于条件的类加载机制(根据classpath路径、JVM系统属性和环境变量判断是否加载某个类)加载所有需要的组件和第三方库。
执行auto-configuration:Spring Boot通过查找classpath中的META-INF/spring.factories文件中的相关配置信息来实现自动配置,自动配置的类本质上是实现了Spring的
Condition
接口,该接口判断某些条件是否满足从而执行自动配置脚本,在初始化时检查条件后,将所判断的Bean注入到Spring容器中。进行scheduled task、initializers等相关操作:通过在SpringBootConfig类中加入@EnableScheduling来开启scheduled task,在应用程序上下文加载时,我们还可以添加一些ApplicationContextInitialize类来进行必要的初始化操作。
启动服务:最后,Spring Boot使用内嵌的Tomcat或者Jetty服务器来启动正在运行的应用程序。
当Spring Boot项目创建完成后会默认生成一个Application的入口类,这个类中的main方法可以启动Spring Boot项目,在main方法中,通过SpringApplication的静态方法,即run方法进行SpringApplication的实例化操作,然后再针对实例化对象调用另外一个run方法来完成整个项目的初始化和启动。SpringApplication调用的run方法重点做了以下操作:
- 获取监听器参数配置
- 打印Banner信息
- 创建并初始化容器
- 监听器发送通知
加分回答
SpringApplication实例化过程中相对重要的配置:
- 项目启动类 SpringbootDemoApplication.class 设置为属性存储起来 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
- 设置应用类型是 SERVLET 应用还是 REACTIVE 应用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
- 设置初始化器(Initializer),最后会调用这些初始化器
- 所谓的初始化器就是 org.springframework.context.ApplicationContextInitializer 的实现类,在 Spring 上下文被刷新之前进行初始化的操作
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
- 设置监听器(Listener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
- 初始化 mnApplicationClass 属性:用于推断并设置项目 mn()方法启动的主程序启动类 this.mnApplicationClass = deduceMnApplicationClass();
说说Spring Boot的自动装配
得分点
自动装配概念,自动装配流程
标准回答
自动装配:springboot会自动把第三方组件Starter的 Bean 装载到IOC 器里面, 并配置初始化参数。
实现方式:启动类注解@SpringBootApplication里的@EnableAutoConfiguration。
过程:
- @EnableAutoConfiguration开启自动配置,加载spring.factories中注册的AutoConfiguration类
- 实例化满足@Conditional指定生效条件的AutoConfiguration类的bean。
使用Spring Boot时,我们需要引入对应的Starters,Spring Boot启动时便会自动加载相关依赖,配置相应的初始化参数,以最快捷、简单的形式对第三方软件进行集成,这便是Spring Boot的自动配置功能。
整个自动装配的过程是:Spring Boot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各种AutoConfiguration类,当某个AutoConfiguration类满足其注解@Conditional指定的生效条件(Starters提供的依赖、配置或Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入Spring容器,就可以完成依赖框架的自动配置。
加分回答
@EnableAutoConfiguration 作用
从classpath中搜索所有META-INF/spring.factories配置文件然后,将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration key对应的配置项加载到spring容器 只有spring.boot.enableautoconfiguration为true(默认为true)的时候,才启用自动配置 @EnableAutoConfiguration还可以根据class来排除(exclude),或是根据class name(excludeName)来排除 其内部实现的关键点有
1. ImportSelector 该接口的方法的返回值都会被纳入到spring容器管理中
2. SpringFactoriesLoader 该类可以从classpath中搜索所有META-INF/spring.factories配置文件,并读取配置
说说Spring Boot常用的注解
得分点
Spring Boot常用注解的作用
标准回答
启动、配置相关注解:
- @SpringBootApplication 里的@EnableAutoConfiguration开启自动配置
- @Import是自动配置的核心实现者
- @Conditional指定AutoConfiguration类里bean实例化的生效条件;
- @Configuration标注当前类是配置类
- @ComponentScan 组件扫描和自动装配
Controller 相关注解:
- @Controller标注类为控制器,处理http请求。
- @RequestMapping将请求路径映射到方法;
- @RestController相当于@ResponseBody+@Controller;
- @ResponseBody将方法返回的对象相映成JSON格式;
- @RequestBody接收请求体JSON数据,并转为对象或对象数组。
- @PathVariable获取请求路径中的数据;
- @RequestParam根据value获取同名请求参数的值、获取集合类型参数;
- @ControllerAdvice 统一处理异常;
- @RestControllerAdvice注解Rest风格统一处理异常;
- @ExceptionHandler 声明捕获异常类型;
Spring相关:
- @Component注解为Bean;
- @Scope设置Bean的作用域;
- @Autowired 自动注入
- @Transactional事务注解
导入配置文件:
- @PropertySource导入properties文件;
- @ImportResource导入xml配置文件;
- @Import 导入配置类;
关于Spring Boot常用注解:
@SpringBootApplication注解:
在Spring Boot入口类中,唯一的一个注解就是@SpringBootApplication。它是Spring Boot项目的核心注解,用于开启自动配置,准确说是通过该注解内组合的@EnableAutoConfiguration开启了自动配置。
@EnableAutoConfiguration注解:
@EnableAutoConfiguration的主要功能是启动Spring应用程序上下文时进行自动配置,它会尝试猜测并配置项目可能需要的Bean。自动配置通常是基于项目classpath中引入的类和已定义的Bean来实现的。在此过程中,被自动配置的组件来自项目自身和项目依赖的jar包中。
@Import注解:
@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。从源代码得知@Import(AutoConfigurationImportSelector.class)是@EnableAutoConfiguration注解的组成部分,也是自动配置功能的核心实现者。
@Conditional注解:
@Conditional注解是由Spring 4.0版本引入的新特性,可根据是否满足指定的条件来决定是否进行Bean的实例化及装配,比如,设定当类路径下包含某个jar包的时候才会对注解的类进行实例化操作。总之,就是根据一些特定条件来控制Bean实例化的行为。
SpringMVC
说说你对MVC的理解
得分点
mvc概念,model、view、controller模块功能
MVC是一种设计模式,把软件分为模型(数据)、视图(用户界面)、控制器(处理模型和视图间请求和响应)三层。
springMVC是在Servlet 基础上构建并且使用 MVC 模式设计的一个 Web 框架。
- 控制器Controller拆分成前端控制器DispatcherServlet 和后端控制器 Controller;
- 把模型Model拆分成业务层 Service 和数据访问层 Repository
- 支持JSP、Freemark等视图;
标准回答
MVC是一种设计模式,在这种模式下软件被分为三层,即Model(模型)、View(视图)、Controller(控制器)。Model代表的是数据,View代表的是用户界面,Controller代表的是数据的处理逻辑,Controller是Model和View这两层的桥梁。将软件分层的好处是,可以将对象之间的耦合度降低,便于代码的维护。
Model:指从现实世界中抽象出来的对象模型,是应用逻辑的反应;它封装了数据和对数据的操作,是实际进行数据处理的地方(模型层与数据库才有交互)。在MVC的三个部件中,模型拥有最多的处理任务。被模型返回的数据是中立的,模型与数据格式无关,这样一个模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。
View:负责进行模型的展示,一般就是我们见到的用户界面。
Controller:控制器负责视图和模型之间的交互,控制对用户输入的响应、响应方式和流程;它主要负责两方面的动作,一是把用户的请求分发到相应的模型,二是把模型的改变及时地反映到视图上。
加分回答-表现层、业务层、数据访问层、SpringMvc
为了解耦以及提升代码的可维护性,服务端开发一般会对代码进行分层,服务端代码一般会分为三层:表现层、业务层、数据访问层。在浏览器访问服务器时,请求会先到达表现层
最典型的MVC就是jsp+servlet+javabean模式。
以JavaBean作为模型,既可以作为数据模型来封装业务数据,又可以作为业务逻辑模型来包含应用的业务操作。
JSP作为视图层,负责提供页面为用户展示数据,提供相应的表单(Form)来用于用户的请求,并在适当的时候(点击按钮)向控制器发出请求来请求模型进行更新。
Serlvet作为控制器,用来接收用户提交的请求,然后获取请求中的数据,将之转换为业务模型需要的数据模型,然后调用业务模型相应的业务方法进行更新,同时根据业务执行结果来选择要返回的视图。
当然,这种方式现在已经不那么流行了,SpringMVC框架已经成为了MVC模式的最主流实现。
SpringMVC框架是基于Java实现了MVC框架模式的请求驱动类型的轻量级框架。前端控制器是DispatcherServlet接口实现类,映射处理器是HandlerMapping接口实现类,视图解析器是ViewResolver接口实现类,页面控制器是Controller接口实现类
介绍一下Spring MVC的执行流程
得分点
浏览器、DispatcherServlet、Controller、ViewResolver
SpringMvc所有组件不能直接互相发送信息,都要经过前端控制器 DispatcherServlet统一转发。
Spring MVC的执行流程:
- 浏览器发请求到DispatcherServlet,DispatcherServlet分发给后端控制器Controller;
- Controller 里面处理完业务逻辑之后,把ModelAndView对象分发给DispatcherServlet,DispatcherServlet分发给视图解析器 ViewResolver 。
- 视图解析器把ModelAndView对象解析成视图View,发给DispatcherServlet,DispatcherServlet将模型填充到视图,分发给浏览器。
标准回答
SpringMVC 的执行流程如下。
1. 用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到前端控制器(DispatcherServlet);
2. 由 DispatcherServlet 请求一个或多个处理器映射器(HandlerMapping),并返回一个执行链(HandlerExecutionChn)。
3. DispatcherServlet 将执行链返回的 Handler 信息发送给处理器适配器(HandlerAdapter);
4. HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
5. Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
6. HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
7. DispatcherServlet 接收到 ModelAndView 对象后,会请求视图解析器(ViewResolver)对视图进行解析;
8. ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
9. DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
10. 视图负责将结果显示到浏览器(客户端)。
加分回答
Spring MVC 涉及到的组件有 DispatcherServlet(前端控制器)、HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)、Handler(处理器)、ViewResolver(视图解析器)和 View(视图)。
下面对各个组件的功能说明如下:
DispatcherServlet
DispatcherServlet 是前端控制器,从图 1 可以看出,Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。
HandlerMapping
HandlerMapping 是处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。
HandlerAdapter
HandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)。
Handler(Controller)
Handler 是处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中。
View Resolver
View Resolver 是视图解析器,其作用是进行解析操作,通过 ModelAndView 对象中的 View 信息将逻辑视图名解析成真正的视图 View(如通过一个 JSP 路径返回一个真正的 JSP 页面)。
View
View 是视图,其本身是一个接口,实现类支持不同的 View 类型(JSP、FreeMarker、Excel 等)。
以上组件中,需要开发人员进行开发的是处理器(Handler,常称Controller)和视图(View)。通俗的说,要开发处理该请求的具体代码逻辑,以及最终展示给用户的界面。
Mybatis
介绍一下MyBatis的缓存机制
得分点
一、二级缓存概念
两级缓存都不是线程安全的,推荐分布式场景下使用Redis等分布式缓存。
一级缓存:SqlSession级别的缓存。SqlSession将查询结果缓存在内存中,再次相同查询时MyBatis 会从缓存中获取数据。增删改或SqlSession改变时,一级缓存会失效。
二级缓存:SqlSessionFactory级别的缓存,要手动开启。查询时会缓存,增删改时会刷新缓存,最近最少使用的缓存会被回收。
标准回答
SqlSession 负责管理持久层的一次数据库操作会话。在使用 MyBatis 进行数据库操作时,每个线程都应该拥有自己的 SqlSession 实例。SqlSession 对象不是线程安全的。
List<User> users = sqlSession.selectList("namespace.selectAll");
一级缓存也称为本地缓存,它默认启用且不能关闭。一级缓存存在于SqlSession的生命周期中,即它是SqlSession级别的缓存。在同一个 SqlSession 中查询时,MyBatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个Map对象中。如果同一个SqlSession 中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当Map缓存对象中己经存在该键值时,则会返回缓存中的对象。
SqlSessionFactory是用于创建 SqlSession 对象的工厂。
SqlSession sqlSession = sqlSessionFactory.openSession();
二级缓存存在于SqlSessionFactory 的生命周期中,即它是SqlSessionFactory级别的缓存。若想使用二级缓存,需要在如下两处进行配置:
-在MyBatis 的全局配置settings 中有一个参数cacheEnabled,这个参数是二级缓存的全局开关,默认值是true ,初始状态为启用状态。
- MyBatis 的二级缓存是和命名空间绑定的,即二级缓存需要配置在Mapper.xml 映射文件中。
配置二级缓存:
mybatis-config.xml
<setting name="cacheEnabled" value="true"/>
Mapper.xml
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
其中,eviction 属性用来指定缓存淘汰策略,flushInterval 属性表示缓存刷新时间间隔,size 属性表示缓存的大小,readOnly 属性表示缓存是否只读等等。
二级缓存具有如下效果:
- 映射语句文件中的所有SELECT 语句将会被缓存。
- 映射语句文件中的所有INSERT 、UPDATE 、DELETE 语句会刷新缓存。
- 缓存会使用Least Recently Used ( LRU ,最近最少使用的)算法来收回。
- 根据时间表(如no Flush Interval ,没有刷新间隔),缓存不会以任何时间顺序来刷新。
- 缓存会存储集合或对象(无论查询方法返回什么类型的值)的1024 个引用。
- 缓存会被视为read/write(可读/可写)的,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
加分回答-Mybatis 一级缓存失效的四种情况:
- sqlsession变了 缓存失效
- sqlsession不变,查询条件不同,一级缓存失效
- sqlsession不变,中间发生了增删改操作,一级缓存失败
- sqlsession不变,手动清除缓存,一级缓存失败
两级缓存不是线程安全的
MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
MyBatis在多表查询时,极大可能会出现脏数据,这导致安全使用二级缓存的条件比较苛刻。 由于默认的MyBatis Cache实现都是基于本地的,这导致在分布式环境下,一定会出现读取到脏数据的情况,且开发成本较高,所以开发环境中一般都会直接使用Redis,Memcached等分布式缓存.
在MyBatis中$和#有什么区别
得分点
使用传参方式MyBatis创建SQL的不同,安全和效率问题
标准回答
使用$设置参数时,MyBatis会创建普通的SQL语句,然后在执行SQL 语句时将参数拼入SQL;
使用#设置参数时,MyBatis会创建预编译的SQL语句,然后在执行SQL时MyBatis会为预编译SQL中的占位符赋值。
优缺点:
预编译的SQL语句执行效率高,并且可以防止注入攻击,效率和安全性都大大优于前者。
但在解决一些特殊问题,如在一些根据不同的条件产生不同的动态列中,我们要传递SQL的列名,根据某些列进行排序,或者传递列名给SQL就只能使用$了。