写点什么

程序员面试大厂,一定要警惕的 10 道 SSM 框架技能题,别被虐哭了

发布于: 2021 年 06 月 11 日
程序员面试大厂,一定要警惕的10道SSM框架技能题,别被虐哭了

今日分享开始啦,请大家多多指教~

本篇总结 SSM 三大框架相关面试题~

1、什么是有状态登录和无状态登录?

  • 有状态登录:

当客户端第一次请求服务器时(请求登录),服务器创建 Session ,然后将登录用户身份信息保存到 Session 中,并将用户身份信息作为 “门卡”,响应回客户端,客户端将服务器响应的 “门卡” 信息保存在本地 Cookie 中。

当下一次客户端再次请求服务器时,这时候就直接将客户端的 Cookie 中存放的 “门卡” 带到服务器端,服务器端从 Session 中拿出数据和 “门卡” 进行对比,判断是否可以同行。

  • 无状态登录的缺点:

服务端保存大量用户身份标识,增加服务端压力。

客户端请求依赖服务端,多次请求必须访问同一台服务器(如果是集群,相当于启动了多个 Tomcat,这时候无法在多个 Tomcat 直接共享 Session 数据)。

  • 无状态登录:

服务器不保存任何客户端用户的登录信息!

客户端的每次请求服务器必须自己具备身份信息标识(jwt),服务器端通过身份信息标识识别客户端身份。

  • 无状态登录的好处:

客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务器。

减小服务端存储压力。

如何实现无状态登录?

如图所示:

  • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)。

  • 认证通过,将用户身份信息(不包含密码)进行加密形成 token,返回给客户端,作为登录凭证。

  • 以后每次请求,客户端都携带认证的 token。

  • 服务的对 token 进行解密,判断是否有效。

2、过滤器,拦截器,Aop 区别?

过滤器和拦截器均体现了 AOP 的编程思想,都可以实现诸如日志记录、登录鉴权等功能,但二者的不同点也是比较多的。

1、实现原理不同

过滤器和拦截器 底层实现方式大不相同,过滤器是基于函数回调的,拦截器则是基于 Java 的反射机制(动态代理)实现的。

2、使用范围不同

我们看到过滤器实现的是 javax.servlet.Filter 接口,而这个接口是在 Servlet 规范中定义的,也就是说过滤器 Filter 的使用要依赖于 Tomcat 等容器,导致它只能在 web 程序中使用。

而拦截器(Interceptor) 它是一个 Spring 组件,并由 Spring 容器管理,并不依赖 Tomcat 等容器,是可以单独使用的。不仅能应用在 web 程序中,也可以用于 Application、Swing 等程序中。

3、触发时机不同

过滤器 Filter 是在请求进入容器后,但在进入 servlet 之前进行预处理,请求结束是在 servlet 处理完以后。

拦截器 Interceptor 是在请求进入 servlet 后,在进入 Controller 之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

4、拦截的请求范围不同

5、注入 Bean 情况不同

在实际的业务场景中,应用到过滤器或拦截器,为处理业务逻辑难免会引入一些 service 服务。

6、控制执行顺序不同

实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。

3、什么是 SpringMvc,说一说它的几个核心组成?

① 前端控制器(DispatcherServlet):主要用于接收客户端发送的 HTTP 请求、响应结果给客户端。

② 处理器映射器(HandlerMapping):根据请求的 URL 来定位到对应的处理器(Handler)。

③ 处理器适配器(HandlerAdapter):在编写处理器(Handler)的时候要按照处理器适配器(HandlerAdapter) 要求的规则去编写,通过适配器可以正确地去执行 Handler。

④ 处理器(Handler):就是我们经常写的 Controller 层代码,例如:UserController。

⑤ 视图解析器(ViewResolver):进行视图的解析,将 ModelAndView 对象解析成真正的视图(View)对象返回给前端控制器。

⑥ 视图(View):View 是一个接口, 它的实现类支持不同的视图类型(JSP,FreeMarker,Thymleaf 等)。

4、Springmvc 执行流程?

① 首先,用户发送 HTTP 请求给 SpringMVC 前端控制器 DispatcherServlet。

② DispatcherServlet 收到请求后,调用 HandlerMapping 处理器映射器,根据请求 URL 去定位到具体的处理器 Handler,并将该处理器对象返回给 DispatcherServlet 。

③ 接下来,DispatcherServlet 调用 HandlerAdapter 处理器适配器,通过处理器适配器调用对应的 Handler 处理器处理请求,并向前端控制器返回一个 ModelAndView 对象。

④ 然后,DispatcherServlet 将 ModelAndView 对象交给 ViewResoler 视图解析器去处理,并返回指定的视图 View 给前端控制器。

⑤ DispatcherServlet 对 View 进行渲染(即将模型数据填充至视图中)。View 是一个接口, 它的实现类支持不同的视图类型(JSP,FreeMarker,Thymleaf 等)。

⑥ DispatcherServlet 将页面响应给用户。

5、什么是 MyBatis 一、二级缓存?

一级缓存:作用域是 SqlSession,同一个 SqlSession 中执行相同的 SQL 查询(相同的 SQL 和参数),第一次会去查询数据库并写在缓存中,第二次会直接从缓存中取。

  • 一级缓存是基于 PerpetualCache 的 HashMap 本地缓存,默认打开一级缓存。

  • 失效策略:当执行 SQL 时候两次查询中间发生了增删改的操作,即 insert、update、delete 等操作 commit 后会清空该 SqlSession 缓存。

二级缓存:作用域是 NameSpace 级别,多个 SqlSession 去操作同一个 NameSpace 下的 Mapper 文件的 sql 语句,多个 SqlSession 可以共用二级缓存,如果两个 Mapper 的 NameSpace 相同,(即使是两个 Mapper,那么这两个 Mapper 中执行 sql 查询到的数据也将存在相同的二级缓存区域中)。

  • 二级缓存也是基于 PerpetualCache 的 HashMap 本地缓存,可自定义存储源,如 Ehcache/Redis 等。默认是没有开启二级缓存。

  • 操作流程:第一次调用某个 NameSpace 下的 SQL 去查询信息,查询到的信息会存放该 Mapper 对应的二级缓存区域。第二次调用同个 NameSpace 下的 Mapper 映射文件中的相同的 sql 去查询信息时,会去对应的二级缓存内取结果。

  • 失效策略:执行同个 NameSpace 下的 Mapepr 映射文件中增删改 sql,并执行了 commit 操作,会清空该二级缓存。

注意:实现二级缓存的时候,MyBatis 建议返回的 POJO 是可序列化的, 也就是建议实现 Serializable 接口。

如图所示:

当 Mybatis 调用 Dao 层查询数据库时,先查询二级缓存,二级缓存中无对应数据,再去查询一级缓存,一级缓存中也没有,最后去数据库查找。

6、为什么说 Mybatis 是半自动 ORM 映射工具?它与全自动的区别在哪里?

ORM 是什么?

ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单 Java 对象(POJO)建立映射关系的技术。

为什么说 Mybatis 是半自动 ORM 映射工具?它与全自动的区别在哪里?

  • 首先,像 Hibernate、JPA 这种属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。

  • 而 Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。

  • 换句话来解释就是说 MyBatis 是 半自动 ORM 最主要的一个原因是,它需要在 XML 或者注解里通过手动或插件生成 SQL,才能完成 SQL 执行结果与对象映射绑定。

7、能否简单说下 Mybatis 加载的流程?

  • MyBatis 是以一个 SqlSessionFactory 实例为核心,SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。

  • SqlSessionFactoryBuilder 可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

  • SqlSessionFactory 实例工厂可以生产 SqlSession ,它里面提供了在数据库执行 SQL 命令所需的所有方法。

具体流程:

① 加载配置文件:需要加载的配置文件包括全局配置文件(mybatis-config.xml)和 SQL(Mapper.xml) 映射文件,其中全局配置文件配置了 Mybatis 的运行环境信息(数据源、事务等),SQL 映射文件中配置了与 SQL 执行相关的信息。

② 创建会话工厂:MyBatis 通过读取配置文件的信息来构造出会话工厂(SqlSessionFactory),即通过 SqlSessionFactoryBuilder 构建 SqlSessionFactory。

③ 创建会话:拥有了会话工厂,MyBatis 就可以通过它来创建会话对象(SqlSession)。会话对象是一个接口,该接口中包含了对数据库操作的增删改查方法。

④ 创建执行器:因为会话对象本身不能直接操作数据库,所以它使用了一个叫做数据库执行器(Executor)的接口来帮它执行操作。

⑤ 封装 SQL 对象:执行器(Executor)将待处理的 SQL 信息封装到一个对象中(MappedStatement),该对象包括 SQL 语句、输入参数映射信息(Java 简单类型、HashMap 或 POJO)和输出结果映射信息(Java 简单类型、HashMap 或 POJO)。

⑥ 操作数据库:拥有了执行器和 SQL 信息封装对象就使用它们访问数据库了,最后再返回操作结果,结束流程。

8、什么是 Spring 三级缓存?

所谓三级缓存,其实就是 org.springframework.beans.factory 包下 DefaultSingletonBeanRegistry 类中的三个成员属性:

  • Spring 一级缓存:用来保存实例化、初始化都完成的对象

Key:beanName

Value: Bean 实例

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

  • Spring 二级缓存:用来保存实例化完成,但是未初始化完成的对象

Key:beanName

Value: Bean 实例

和一级缓存一样也是保存 BeanName 和创建 bean 实例之间的关系,与 singletonObjects 不同之处在于,当一个单例 bean 被放在里面后,那么 bean 还在创建过程中,就可以通过 getBean 方法获取到了,其目的是用来循环检测引用!

private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

  • Spring 三级缓存:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象

Key:beanName

Value: Bean 的工厂

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

如图所示,除了三级缓存是一个 HashMap,其他两个都是 ConcurrentHashMap:

Spring 之所以引入三级缓存,目的就是为了解决循环依赖问题!

除了上面三个 Map 集合,还有另一个集合这里也说一下:

用来保存当前所有已注册的 Bean

private final Set<String> registeredSingletons = new LinkedHashSet<>(256);

9、什么是 Spring 循环依赖问题?如何解决?

首先,Spring 解决循环依赖有两个前提条件:

  • Setter 方式注入造成的循环依赖(构造器方式注入不可以)

  • 必须是单例

本质上解决循环依赖的问题就是依靠三级缓存,通过三级缓存提前拿到未初始化的对象。下面我们来看一个循环依赖的例子:

A 对象的创建过程:

1.创建对象 A,实例化的时候把 A 对象工厂放入三级缓存

2.A 注入属性时,发现依赖 B,转而去实例化 B

3.同样创建对象 B,注入属性时发现依赖 A,一次从一级到三级缓存查询 A,从三级缓存通过对象工厂拿到 A,把 A 放入二级缓存,同时删除三级缓存中的 A,此时,B 已经实例化并且初始化完成,把 B 放入一级缓存。

4.接着继续创建 A,顺利从一级缓存拿到实例化且初始化完成的 B 对象,A 对象创建也完成,删除二级缓存中的 A,同时把 A 放入一级缓存。

5.最后,一级缓存中保存着实例化、初始化都完成的 A、B 对象。

从上面 5 步骤的分析可以看出,三级缓存解决循环依赖是通过把实例化和初始化的流程分开了,所以如果都是用构造器的话,就没法分离这个操作(因为构造器注入实例化和初始是一起进行的)。因此构造器方式注入的话是无法解决循环依赖问题的。

解决循环依赖为什么必须要要三级缓存?二级不行吗?

答案:不可以!

使用三级缓存而非二级缓存并不是因为只有三级缓存才能解决循环引用问题,其实二级缓存同样也能很好解决循环引用问题。

使用三级而非二级缓存并非出于 IOC 的考虑,而是出于 AOP 的考虑,即若使用二级缓存,在 AOP 情形下,往二级缓存中放一个普通的 Bean 对象,BeanPostProcessor 去生成代理对象之后,覆盖掉二级缓存中的普通 Bean 对象,那么多线程环境下可能取到的对象就不一致了。

  • 一句话总结就是,在 AOP 代理增强 Bean 后,会对早期对象造成覆盖,如果多线程情况下可能造成取到的对象不一致~

10、说说 Spring 里用到了哪些设计模式?

单例模式:Spring 中的 Bean 默认情况下都是单例的。无需多说。

工厂模式:工厂模式主要是通过 BeanFactory 和 ApplicationContext 来生产 Bean 对象。

代理模式:最常见的 AOP 的实现方式就是通过代理来实现,Spring 主要是使用 JDK 动态代理和 CGLIB 代理。

单例模式

单例模式使⽤场景:

  • 业务系统全局只需要⼀个对象实例,⽐如发号器、 redis 连接对象等。

  • Spring IOC 容器中的 Bean 默认就是单例。

  • Spring Boot 中的 Controller、Service、Dao 层中通过 @Autowire 的依赖注⼊对象默认都是单例的。

单例模式分类:

  • 懒汉:就是所谓的懒加载,延迟创建对象,需要用的时候再创建对象。

  • 饿汉:与懒汉相反,提前创建对象。

单例模式实现步骤:

  • 私有化构造函数。

  • 提供获取单例的⽅法。

工厂模式

⼯⼚模式介绍:它提供了⼀种创建对象的最佳⽅式,在创建对象时 不会对客户端暴露创建逻辑,并且是通过使⽤⼀个共同的接⼝来指向新创建的对象。

⼯⼚模式有 3 种不同的实现⽅式:

① 简单⼯⼚模式(静态工厂):通过传⼊相关的类型来返回相应的类,这 种⽅式⽐较单 ⼀,可扩展性相对较差。

② ⼯⼚⽅法模式:通过实现类实现相应的⽅法来决定相应的返回结果,这种⽅式的可扩展性⽐较强。

③ 抽象⼯⼚模式:基于上述两种模式的拓展,且⽀持细化产品。

应⽤场景:

解耦:分离职责,把复杂对象的创建和使⽤的过程分开。

复⽤代码 降低维护成本:如果对象创建复杂且多处需⽤到,如果每处都进⾏编写,则很多重复代码,如果业务逻辑发⽣了改 变,需⽤四处修改;使⽤⼯⼚模式统⼀创建,则只要修改⼯⼚类即可, 降低成本。

代理模式

代理模式最直观的解释就是,通过代理,将被代理对象 “增强”!(即,扩展被代理对象的功能)

代理模式分为静态代理,和动态代理:动态代理的代理类是动态生成的 , 静态代理的代理类是我们提前写好的逻辑。

Java 中实现动态代理的方式有 2 种:

  • JDK 动态代理

  • CGLIB 动态代理

今日份分享已结束,请大家多多包涵和指点!

用户头像

还未添加个人签名 2021.04.20 加入

Java工具与相关资料获取等WX: pfx950924(备注来源)

评论

发布
暂无评论
程序员面试大厂,一定要警惕的10道SSM框架技能题,别被虐哭了