前言
共25题。
项目中为什么选择SpringBoot?
SpringBoot简化了Spring,开发效率更高。
它的主要优点如下:
-
版本锁定:在父工程中进行了大量常见依赖的版本锁定,我们就不用去找了;
-
起步依赖 :将依赖进行组装,并且允许程序员以
starter的方式进行引入; -
默认配置:实现了大量依赖框架的默认配置项,不需要自己配置;
-
内置
Tomcat。
缺点:SpringBoot可能会装载大量用不到的对象,会浪费资源空间。
SpringBoot的核心注解是哪个?
SpringBoot的核心注解是@SpringBootApplication(自动装配),将其标记在启动类上,表示这是一个SpringBoot应用。
该注解主要包含了以下3个注解:
@SpringBootConfiguration:实现配置文件的功能;@EnableAutoConfiguration:打开或关闭某个自动配置;@ComponentScan:默认扫描启动类所在的包。
SpringBoot中的starter是干什么的?
每个starter都可以为我们提供某个服务场景所需要的一系列依赖。在导入starter之后,SpringBoot主要帮我们完成了两件事情:
-
相关组件的自动导入;
-
相关组件的自动配置。
SpringBoot可以有哪些方式加载配置?
常见的有三种:
-
配置文件 :比如
properties、yaml、yml(常用); -
系统环境变量:但不推荐;
-
命令行参数:一般用于临时修改配置。
bootstrap.yml和application.yml有何区别?
都是SpringBoot支持的核心配置文件,区别在于:
-
boostrap比applicaton优先加载,在应用程序上下文的引导阶段生效,且里面的属性不能被覆盖。一般来说我们在SpringCloud Config中会用到它(Nacos),一般用于多环境配置; -
application用于SpringBoot项目的自动化配置,一般来说我们会将自己项目的业务配置项写在这里面。
SpringBoot读取配置的方式有几种?
常见的有两种:
-
使用
@Value配合EL表达式(@Value("${name}")) 直接注入对应的值; -
使用
@ConfigurationProperties(prefix=" ")把对应的值绑定到一个配置对象,然后将配置对象注入到需要的地方。
推荐使用使用第二种方式,在配置比较多的情况下,操作简单,可读性好。
SpringBoot项目如何热部署?
使用Spring Boot的开发工具(DevTools)模块实现。当开发人员更改文件后,该模块就会自动部署修改到服务器并自动重启服务器。
SpringBoot项目如何实现方法的异步调用?
异步调用指的是两个方法a与b,a调用b的时候,不用等b执行完毕就可以继续向下执行,一般用在a方法不需要使用b方法返回结果的场景,可提高运行效率。
在SpringBoot只需要做两个操作就可以实现异步调用:
-
在启动类上添加
@EnableAsync注解,开启异步调用支持; -
在被调用的方法上添加
@Async注解。
SpringBoot中如何实现定时任务?
主要有两种方式:
-
使用第三方框架
Quartz; -
使用
SpringTask:主要是通过@Scheduled注解来实现定时任务触发。
@Scheduled注解主要属性如下:
fixedRate:按一定的频率执行任务,参数类型为long,单位ms;fixedDelay:上一次任务执行完后多久再执行,参数类型为long,单位ms;initialDelay:延迟多久再第一次执行任务,参数类型为long,单位ms;cron:使用cron表达式指定任务在特定时间执行。
集群部署用
xxl-job来实现定时任务。涉及到集群部署,SpringTask定时任务不支持,会造成同一个任务重复执行多份,Redis分布式锁需要手动修改比较麻烦。xxl-job可以在后台随时调整时间规则,统一管理所有定时任务。
cron表达式?
cron表达式其实就是一个字符串,通过cron表达式可以定义任务的触发时间。SpringTask支持的cron表达式分为6个域,由空格分隔开,每个域代表一个含义:秒 分 时 日 月 周。每个域都支持精准数值的写法,也支持一些具有特殊意义的字符,主要的有:
(1) *:表示任意
(2) ?:表示忽略,只能用在日和周两个域
(3) -:表示区间,
(4) /:表示起始时间开始触发,然后每隔固定时间触发一次
(5) ,:表示列出枚举值,例如在分域使用5,20则意味着在5和20分触发一次
(6) #: 用于确定每个月第几个星期几
SpringBoot中如何解决跨域问题?
跨域问题指的是,前后端出现域名、端口、协议任意一个不同,都属于跨域。
解决方法 :添加一个配置类,实现WebMvcConfigurer接口,然后重写addCorsMappings方法即可,最后都是可以采用跨域资源共享CORS来解决跨域问题。
如何理解拦截器?
拦截器是Spring提供的一种拦截机制,实现对指定请求路径进行拦截:
-
单体项目中拦截器校验
token,拦截用户信息存入ThreadLocal; -
微服务在网关校验,获取用户
ID放到请求头中传入后面微服务,微服务中拦截器拦截请求获取用户信息,将其放入ThreadLocal中。
自定义一个拦截器,需要实现
HandlerInterceptor接口,并重写3个方法:
preHandle: 这个方法在Controller处理请求之前被调用,通过方法的返回值可以确定是否放行请求;postHandle:这个方法在Controller处理请求之后被调用;afterCompletion:在整个请求结束之后被调用,此方法主要用于进行资源清理。
拦截器和过滤器的区别是什么?
都可以实现请求的拦截处理,不同点有4个:
-
技术栈所属 不同:过滤器属于
JavaWeb技术,依赖Servlet容器;而拦截器是属于Spring的技术; -
实现原理 不同:拦截器是基于
Java的反射机制,而过滤器是基于函数回调; -
拦截范围 不同:过滤器可以拦截所有请求,而拦截器主要是针对发往
controller请求; -
拦截位置不同:过滤器在前端控制器前拦截,而拦截器在前端控制器后拦截。
SpringBoot中properties文件如何配置文件上传上限?
ini
# 设置单个文件上传大小限制为10MB
spring.servlet.multipart.max-file-size=10MB
# 设置总上传文件大小限制为20MB
spring.servlet.multipart.max-request-size=20MB
SpringBoot如何实现缓存?
SpringCache是Spring提供的一个缓存框架,它可以通过简单的注解实现缓存的操作,常用的注解有下面几个:
-
@EnableCaching: 开启基于注解的缓存; -
@CachePut: 一般用在查询方法上,表示将方法的返回值放到缓存中; -
@Cacheable: 一般用在查询方法上,表示在方法执行前先查看缓存中是否有数据,如果有直接返回;如果没有,再调用方法体查询数据并将返回结果放到缓存中;他有两个关键属性:value: 缓存的名称,每个缓存名称下面可以有多个keykey: 缓存的key,支持Spring的表达式语言SPEL语法
-
@CacheEvict: 一般用在增删改方法上 ,用于清理指定缓存,可以根据key清理,也可以清理整个value下的缓存。
SpringCache还有一个优点:就是可以随意切换底层的缓存软件,比如:Redis、内存等等。
项目中是如何进行异常处理的?
项目中是使用全局异常处理器来实现异常处理的,核心是两个注解:
- @RestControllerAdvice:标注在类上,声明该类是处理异常的类;
- @ExceptionHandler:标注在方法上,声明该方法是处理什么异常。
在全局异常处理器中一般定义三种异常:
-
指定异常 :指定异常指的是用户操作产生的与程序设计相关的异常,比如字段重复异常、
Validation校验异常等等,这类异常捕获之后,我们会根据异常的消息提示,给前端一个确定的返回结果; -
业务异常:业务异常是由于用户不正当操作产生的与业务相关的的异常,这种异常往往需要我们自定义,指定异常提示信息,然后在程序的相关位置手动抛出。然后异常处理器捕获之后,直接将异常提示消息返回给前端;
-
异常时兜底异常:此处主要捕获的是不属于上面两种异常的异常,一般是一些程序员代码不够严谨引发的运行时异常,对于这些异常,我们处理方案是首先要把错误记录到日志系统中,然后给前端一个类似于服务器开小差了之类的统一提示。
项目中是如何存储文件的?
主要有三类存储方式:
-
第一种是直接将文件保存到服务到硬盘,这种方式操作方便,但是扩容困难,而且安全保障不高,现在基本不再使用;
-
分布式文件存储系统:如果文件是隐私性比较高,建议使用自己搭建的分布式文件存储系统,安全性也比较高;
-
第三方服务:如果文件隐私性不高,可以考虑使用第三方服务,比如阿里云或者七牛云。
项目中是如何进行参数校验的?
项目中的参数校验是使用validation来实现的,它有一些特定的注解,这些注解主要标注在请求参数或者是参数对象对应类的属性上,每个注解都有自己的校验规则。
如果我们输入的请求参数不符合对应的校验规则,系统就会抛出异常,此时我们只需要在全局异常处理器中捕获异常,然后给前端提示即可。
常用的注解有下面这些:
-
@Null:可以标注在任意类型元素上,被标注的元素必须为null -
@NotEmpty:可以标注在字符串,集合,数组,map上,被标注的元素必须不能为null,也不能是空串 -
@Range:标注在数值类型上,数值的大小必须在指定的范围内,对于null无效 -
@Digits(integer(数值的位数) =3 , fraction(小数的位数)=2),被注释的元素必须是一个数字,其值必须在可接受的范围内 -
@size(min=,max=):可标注在字符串,数组,集合,map用于控制长度 -
@Email:邮箱 -
@URL:合法的地址
如何理解分组校验?
比如我们在新增和修改一个用户对象时,都会接收User对象作为请求参数,但是新增要求对象的id为空,而修改则要求id字段不能为空。这个时候就需要使用到分组校验。分组校验其实就是定义多套校验规则,对于指定的功能,我们按照要求指定它使用哪套规则即可。
SpringBoot自动装配的过程是可以被干预的吗?
可以的。在实践之中,如果我想要排除一个自动装配类,就会利用@SpringBootApplication注解的exclude属性,比如:exclude={DataSourceAutoConfiguration.class},即可。
或者在yml配置文件中使用spring.autoconfigure.exclude=DataSourceAutoConfiguration.class来排除自动配置。
比如SpringBoot内置tomcat,现在我想使用netty,怎么办?
SpringBoot内置tomcat作为web服务器,如果想要更换为netty,需要如下步骤:
- 更换依赖,在
pom.xml中引入netty,然后在spring-boot-starter-web依赖除移除tomcat。 - 如果有需要的话,可以创建一个配置类修改
netty配置,然后启动应用即可
@Controller和@Service交换会发生什么?
完整问题:现有控制层类StudentController和业务层类StudentService,将这两个类的@Controller注解和@Service注解进行交换,应用程序还能正常启动吗,StudentController类还可以处理请求吗?
可以正常启动。因为@Controller注解和@Service注解都继承了@Component注解,在Spring容器眼里都是一样的,只是方便程序员区分。真正决定能否匹配url、处理请求的是@RequestMapping等注解。
SpringBoot中都有哪些场景会导致事务失效?
我知道的有6种场景:
- 事务方法不是public修饰的 ,因为
Transaction注解基于SpringAOP的动态代理,而SpringAOP的动态代理只适用于public方法 - 当前类没有交给IOC容器管理
- 抛出异常类型不是运行时异常
- 方法执行失败时,抛出的异常被捕获,导致事务没有发现异常无法回滚
- 同一个类中有事务方法也有非事务方法,非事务方法调用事务方法,因为非事务方法没有
@transaction注解,相当于直接调用普通方法 - 事务传播行为使用有误 :如一个事务方法
A内部调用另一个事务方法B,A的传播机制是默认的,B是必须新事务。进入A时,A使用默认事务,调用B时,B又创建了一个新事务,A与B不是一个事务了。当A抛出异常时,A就会回滚,因为B与A不是一个事务,就不会回滚。
如何自定义一个注解?
比如:
Java
public @interface MyAnnotation {
public String name();
int age();
String sex() default "女";
}
- 访问修饰符必须为
public,不写默认为public; - 该元素的类型只能是基本数据类型、
String、Class、枚举类型、注解类型以及一维数组; - 该元素的名称一般定义为名词,如果注解中只有一个元素,名字起为
value最好; ()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;default代表默认值,值必须定义的类型一致;- 如果没有默认值,代表后续使用注解时必须给该类型元素赋值。
什么是元注解?
配置注解需使用元注解,元注解是专门修饰注解的注解,常见的有@Target、@Retention、@Documented和@Inherited。
@Target
@Target是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的,包含一个ElementType[] value,ElementType常见的有:
TYPE:类、接口、枚举等METHOD:方法CONSTRUCTOR:构造方法
@Retention
@Retention注解,用来修饰自定义注解的生命力。
- 如果一个注解被定义为
RetentionPolicy.SOURCE,则它将被限定在Java源文件中,那么这个注解既不会参与编译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到; - 如果一个注解被定义为
RetentionPolicy.CLASS,则它将被编译到Class文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到,是默认的; - 如果一个注解被定义为
RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发 中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME。
@Documented
@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中
@Inherited
@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类(继承关系)的声明部分也能自动拥有该注解。该注解只对@Target被定义为ElementType.TYPE的自定义注解起作用。
SpringBoot常用的starter?
spring-boot-starter-web:用于构建web应用spring-boot-starter-test:用于测试spring-boot-starter-data-redis:redis相关spring-boot-starter-validation:数据验证