项目一后模拟面试题
文章目录
- **1、谈谈你对SpringIOC的理解**
- **2、Spring的常用注解有哪些**
- **3、SpringMVC的常用注解有哪些**
- **4、SpringBoot的自动装配原理**
- **5、HashMap的底层原理**
- **6、HashMap是怎么解决哈希冲突的**
- **7、HashMap的扩容机制是怎样的**
- **8、说一说集合类的体系结构**
- **9、创建线程有几种方式**
- **10、线程池的执行流程**
- **11、线程池的核心参数**
- 12、Mybatis中#{}和${}的区别
- **13、Spring中的bean线程安全吗**
- **14、Redis中有哪些数据类型**
- **15、git在工作中的使用流程**
- **16、前端小程序的微信登录流程**
- **17、ThreadLocal在项目中的应用**
- **18、项目是如何进行异常处理的**
- **19、微信支付流程**
- **20、介绍下苍穹外卖中的套餐模块**
1、谈谈你对SpringIOC的理解
IOC(inversion of control),也叫控制反转,是Spring用来解耦的一种设计思想,它的做法就是将对象的控制权由程序员手中反转到Spring手中。具体来说呢就是,在没有IOC之前,对象都是程序员在类中主动去创建,需要哪个创建哪个;有了IOC之后,对象会交给Spring容器创建和管理,如果哪个对象中需要其它对象属性,Spring也会自动完成依赖注入。
? 总之一句话,IOC可以将对象的创建和对象之间依赖关系的维护交给Spring自动完成。
2、Spring的常用注解有哪些
我们常用的Spring注解主要分类下面几大类:
1、创建对象:@Component、@Controller、@Service、@Repository
? 它们都可以标注在自己开发的类上,Spring会使用注解标注的类创建出对象,然后放入容器
2、依赖注入:@Autowired
? 标注在属性或者属性对应的set方法上,Spring会根据被标注属性的类型自动对属性进行赋值
3、依赖注入:@Qualifier
? 和@Autowired一块使用,在同一类型的bean有多个的情况下Spring会根据name进行选择注入
4、配置类:@Configuration、@Bean
? 主要标注在配置类中,用于声明配置类和向Spring容器中放入一些配置有关的对象
5、当然还有一些平时用的不是特别多的
? 比如:声明注解扫描的@ComponentScan,声明Bean的作用域的@Scope,用于切面编程的@Around,@Pointcut等等
3、SpringMVC的常用注解有哪些
我们常用的Springmvc注解主要分类下面几大类:
1、用于声明Bean到Springmvc容器:@Controller、@RestController
? 区别在于后者还可以将返回的集合或对象转换为JSON直接返回
2、设置请求路径:@RequestMapping、@GetMapping、@PostMapping 、@PutMapping、@DeleteMapping
? 第一个是通用的,可以接收各种类型的请求;后面四个只能直接对应类型的请求
3、接收请求参数:
? @RequestBody: 接收请求体中的json数据
? @PathViriable:接收请求路径中的参数
? @RequestHeader:接收请求头中的参数
? @RequestParam:一般用于给参数设置默认值或者完成请求参数和controller方法参数的映射
4、SpringBoot的自动装配原理
Springboot自动装配主要是基于注解编程和约定优于配置的思想来进行设计的
自动装配就是自动地把其他组件中的Bean装载到IOC容器中,不需要开发人员再去配置文件中添加大量的配置
我们只需要在SpringBoot的启动类上添加一个@SpringBootApplication的注解,就可以开启自动装配
SpringBootApplication底层最重要的一部分是@EnableAutoConfiguration这个注解来实现的,它作用是:
读取所有jar包中两个指定配置文件中的所有自动配置类(xxxxAutoConfiguration)
这些值必须声明为Spring的配置类,也就是在类中需要向Spring容器放入对象
为了防止非当前所需的组件进入到容器,配置类中需要使用@Conditional注解来声明配置成立的必要条件
5、HashMap的底层原理
HashMap底层数据结构是哈希表,哈希表在JDK1.8之前是数组+链表实现,在JDK1.8之后是数组+链表+红黑树实现的
下面我以map中存储对象的流程给您说一下它的实现原理把
当我们创建一个HashMap的时候,JDK就会在内存中创建一个长度为16的数组
当我们调用put方法像HashMap中保存一个元素的时候,它会先调用key的hashCode方法计算出key的hash值
然后使用得到hash值对数组长度取余,找出当前对象的元素在数组中的位置
接下来,它会判断算出的位置上是否有元素,如果没有,就会将此元素直接存储到当前位置上
如果算出的位置上有元素或者是有链表,它会再调用key的equals方法跟存在元素的key做比较
如果有一个比较得到的结果为true,则会进行值的覆盖,如果都为false,则会将元素追加在链表的末尾
当然,为了降低Hash冲突和链表长度,HashMap还做了一些优化
当元素的数量超过数组大小与加载因子的乘积的时候,就会执行扩容,扩容为原来的2倍,并将原来数组中的键重新进行hash运算,然后分配到新数组中
当链表的长度>8,并且数组长度>=64的时候,链表就会转换为红黑树,当红黑树结点数小于6时将再次转回为链表。
6、HashMap是怎么解决哈希冲突的
首先,HashMap的底层有一个数组,它在保存元素的时候,会对元素的key进行hash运算,得到hash值,然后再使用hash值对数组长度取余,得到元素在数组中的位置,这样的话,不同的元素计算完毕之后,就可能会被分配到数组中的同一个位置上,这就是所谓的哈希冲突。
? 解决hash冲突最常用的方式有链表法和开放地址法,而HashMap就是采用了链表法。具体做法就是当哈希冲突出现之后,HashMap会在发生冲突的位置上创建一个链表来保存元素,当然在JDK1.8之后,又对此做出了改进,那就是当链表的长度>8,并且数组长度>=64的时候,链表就会转换为红黑树,使得效率更高。
7、HashMap的扩容机制是怎样的
HashMap的扩容机制是指当HashMap中的元素个数超过数组长度乘以负载因子时,就会重新分配一个更大的数组,并将原来的元素重新计算哈希值并插入到新的数组中。
? 在JDK1.8中,底层是调用resize方法实现扩容的,它的默认做法是:当元素个数超过数组长度的0.75倍时触发扩容,每次扩容的时候,都是扩容为原来的2倍, 扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。
8、说一说集合类的体系结构
我们常见的集合主要有两大类,分别是单列集合和双列集合
单列集合的顶级接口是Collection,它下面有两个主要的子接口分别是List和Set
List的特点是元素有序的,可以重复的;Set的特点是元素无序的,不可重复的
List下我们常用的类有ArrayList、LinkedList等,Set下我们常用的类有HashSet、LinkedHashSet、TreeSet等
双列集合的顶级接口是Map,它的特点是每个元素都有键和值两部分组成,而且键不能重复
Map接口下我们常用的类有:HashMap、LinkedHashMap、TreeMap等
9、创建线程有几种方式
我知道的创建线程的方式大体上可以分为四种:
继承Thread类并重写run方法创建线程,这种方式实现简单但线程类不可以再继承其他类
实现Runnable接口并重写run方法,这种方式避免了单继承局限性,编程更加灵活,实现解耦
实现Callable 接口并重写call方法,这种方式可以获取线程执行结果的返回值,并且可以抛出异常
使用线程池创建
10、线程池的执行流程
当我们提交一个任务到线程池中,线程池的处理流程如下:
首先判断线程池里的核心线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务。
如果核心线程都在执行任务,则判断工作队列是否已满,如果没满,则将新提交的任务存储在这个工作队列里。
如果工作队列满了,则判断线程数是否小于最大线程数,如果是,则创建临时线程直接执行任务
如果线程数已经到达了最大线程数,则会执行对应的拒绝策略逻辑
11、线程池的核心参数
线程池在创建的时候最大支持传入7个参数,分别是:
核心线程数
最大线程数
临时线程的空闲时间:临时线程会在空闲这段时间后
临时线程的空闲时间单位
工作队列:用来保存等待执行的任务的
threadFactory:设置创建线程的工厂
handler:线程池的拒绝策略
12、Mybatis中#{}和${}的区别
在Mybatis中#{}
和${}
都可以用于在sql语句中拼接参数,但是在使用方面有很多的区别
1、处理方式不同: ${}
表示的是字符串拼接,Mybatis在处理它时,会直接将${}
替换成变量的值
? 而#{}
是预编译处理,Mybatis在处理它时,会将sql中的#{}替换为?号,然后底层使用JDBC的预编译对象来赋值
2、安全性不同:${}
存在SQL注入问题,#{}
可以有效的防止SQL注入
3、效率不同:${}
处理的sql到数据库每次都要重新编译,而#{}
处理的sql只需要编译一次
总之,在实际使用过程中尽量使用#{}
,而避免使用${}
,当然这也不是说${}
就没有使用场景
比如:如果sql中需要动态传递表名或者字段名,那就只能使用${}
了
13、Spring中的bean线程安全吗
Spring中的Bean主要分为单例和多例
多例对象每次获取都会创建新实例,也就是说线程之间不存在Bean共享问题,也就不存在线程安全问题
单例对象是所有线程共享一个实例,因此就可能会存在线程安全问题。但是单例对象又分为无状态和有状态。
无状态Bean是指只对对象的成员变量进行查询操作,不会修改成员变量的值,因此不存在线程安全问题
有状态Bean需要对Bean中的成员变量进行数据更新操作,因此就可能存在线程安全问题
所以,最终我们得出结论,在Spring中,只有有状态的单例Bean才会存在线程安全问题
处理有状态单例Bean的线程安全问题有以下两种方法:
将Bean的作用域由单例改为多例
将需要的可变成员变量保存在ThreadLocal中, ThreadLocal本身就具备线程隔离的特性,这就相当于为每个线程提供了一个独立的变量副本,每个线程只需要操作自己的线程副本变量,从而解决线程安全问题。
14、Redis中有哪些数据类型
Redis是一个基于内存的键值对数据库,它的键都是字符串类型,而值的部分支持5种数据类型,每种类型特点不一样
string:字符串类型,可以存储普通字符串、JSON字符串,也可以存储对象系列化之后的字符串
hash:哈希类型,类似于Java中的HashMap,比较适合存储对象
list:列表类型,底层是一个顺序链表,可以从两端添加或移除元素,元素是有序的,可重复的
set:无序集合,没有重复元素
zset:有序集合,没有重复元素,且集合中每个元素关联一个分数,可以根据分数进行排序
15、git在工作中的使用流程
我在工作中对于git的使用,可分为以下几个步骤:
(1)首先,每天上班之后,我会从公司远程仓库中进行拉取(pull),以保证本地项目和远程仓库项目进度一致
(2)然后,在本地的开发分支上新建一个当天的分支,进行代码开发
开发过程中一般在完成某功能或某一模块时,进行本地提交(commit)
(3)结束一天工作后,先将本地新分支的代码合并到开发分支
(4)最后,提交(push)到远程仓库前,先进行拉取(pull),如果有冲突,就先进行冲突解决,解决完毕之后,再push
16、前端小程序的微信登录流程
微信登录的核心是通过微信小程序提供的临时凭证code换取永久凭证openid的过程
(1)首先微信小程序会向微信官方申请一个临时登录code
(2)然后,小程序带着code向后台服务发送请求
(3)后台接收到code后,会调用微信官方接口验证code是否合法,如果合法,官方会返回一个openid;这个openid就是此用户在我们系统中的唯一标识,同时也代表用户身份合法
(4)后台服务接收到来着微信的openid之后,会去数据库查询一下是否存在此账户;如果存在,代表这是一个老用户,如果不存在,则代表这是一个新用户首次使用我们的系统,我们需要将其信息保存到用户表中
(5)登录成功之后,需要生成一个标识用户身份的token,返回给前端,前端会将token保存起来
(6)用户后面访问系统的时候,需要携带着这个token,而我们后端需要编写一个拦截器,用于拦截请求,校验token
(7)校验通过,则放行请求,正常访问;校验失败,则禁止通行,返回提示
17、ThreadLocal在项目中的应用
(1)ThreadLocal 称为线程局部变量,可以为每个线程单独提供一份存储空间,它的特点是:线程之内,数据共享;线程之间,数据隔离。
(2)在我们的项目中经常使用ThreadLocal来存储用户的登录信息,具体的做法是:
每次用户访问后台都需要经过拦截器鉴权,如果鉴权通过,我们就将登录用户的信息保存到ThreadLocal中
接下来,在项目的各个代码中就可以轻松的从ThreadLocal中获取用户信息了
最后,当请求访问完服务离开的时候,还会再次经过拦截器,这个时候就可以清理掉ThreadLocal中的内容了
18、项目是如何进行异常处理的
在我们的项目中,异常处理都是通过spring的全局异常处理器来实现的,核心是两个注解:
(1)一个是@RestControllerAdvice,标注在类上,可以定义全局异常处理类对异常进行拦截
(2)一个是@ExceptionHandler,标注在异常处理类中的方法上,可以声明每个方法能够处理的异常类型
在我们的项目中,将异常分为了三大类:
在苍穹外卖项目的全局异常处理器中一般定义三种异常:
(1)第一类是指定异常,指定异常指的是用户操作产生的与程序设计相关的异常,比如说字段重复异常、Validation校验异常等等,这类异常捕获之后,我们会根据异常的消息提示,给前端一个确定的返回结果
(2)第二类是业务异常处理,业务异常是由于用户不正当操作产生的与业务相关的的异常,这种异常往往需要我们自定义,然后在程序的相关位置手动抛出,在抛出的时候还会指定异常提示信息。然后异常处理器捕获之后,直接将异常提示消息返回给前端
(3)第三种异常时兜底异常,此处主要捕获的是不属于上面两种异常的异常,一般是一些程序员代码不够严谨引发的运行时异常,对于这些异常,我们处理方案是首先要把错误记录到日志系统中,然后给前端一个类似于服务器开小差了之类的统一提示
19、微信支付流程
整个微信支付流程涉及到三个角色:微信小程序、服务端、微信平台
(1)首先,由小程序发起下单请求到服务端,服务端生成订单保存到数据库后,将订单号返给前端
(2)然后,小程序会向服务端发起支付请求,这个请求中会携带着订单号
(3)服务端根据订单号查询到订单信息后,开始调用微信下单接口从微信平台获取预支付交易标识
(4)服务端需要将预支付交易标识进行签名之后组装成支付参数,回传给小程序,小程序就会弹出支付窗口
(5)用户通过小程序向微信平台付款,并可以获取到支付结果,进行显示
(6)微信平台还会将订单支付结果推送给我们的后台程序,后台程序需要修改订单状态
20、介绍下苍穹外卖中的套餐模块
套餐新增:本质是对套餐表和套餐菜品关系表进行新增操作
(1)首先将前端传过来的套餐基本信息保存到套餐表中,并返回主键的id
(2)然后为前端传过来的套餐菜品设置套餐id
(3)最后将套餐包含的菜品添加到套餐菜品关系表中
套餐修改:本质是对套餐表进行修改,再对套餐菜品关系表进行增删操作
(1)首先根据前端传过来的套餐基本信息更新到套餐表中
(2)然后根据套餐的id删除所有套餐菜品关系表中包含的菜品信息
(3)最后遍历前端传过来的菜品的列表,设置好套餐的id后重新保存到套餐菜品关系表中
套餐删除:本质是对套餐表和套餐菜品关系表进行删除操作
(1)首先遍历前端传过来套餐id的集合得到每一个套餐的信息
(2)然后根据id查询套餐,判断套餐的状态是否为启售状态,如果是启售状态,则不能删除
(3)如果是在禁售状态,就可以通过套餐的id进行套餐菜品关系表的删除操作
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!