面试知识点梳理及相关面试题(十五)-- spring

Spring相关:

1. spring框架中的单例bean是线程安全的嘛?

1.1 bean单例多例配置:

bean可以手动设置单例或者多例:

@Service
@Scope("singleton")
public class UserServicelmpl implements UserService {
}
  • singleton:bean在每个Spring lOC容器中只有一个实例。
  • prototype:一个bean的定义可以有多个实例。

1.2 分析:

在这里插入图片描述
其中UserService是没有可变的状态(即是不能被修改的),所以是线程安全的;
但是对于自定义的成员变量,就会有线程安全问题出现。

1.3 问题回答:

在这里插入图片描述
标准回答:
在这里插入图片描述

2. 什么是AOP,你们项目中有没有使用过AOP?

2.1 AOP概念解析:

AOP称为面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面” (Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。

常见的AOP使用场景

  • 记录操作日志
  • 缓存处理
  • Spring中内置的事务处理
  • 接口入参校验

2.2 记录操作日志举例分析

在这里插入图片描述

  • 例如,如下请求,我们可以记录日志:
    在这里插入图片描述
  • 其中对于新增用户,我们可以添加环绕通知,来记录一些日志:
    在这里插入图片描述
  • 可以通过自定义注解,在新增用户的方法上加上我们的注解,来给此方法添加环绕通知。
    在这里插入图片描述
  • 接着通过pointcut注解,关联到我们添加了注解的类,调用环绕通知,执行环绕通知中的内容,来完成参数等信息的获取及日志的收集
    在这里插入图片描述

2.2 spring中的事务是如何实现的?

Spring支持编程式事务管理和声明式事务管理两种方式:

  • 编程式事务控制:需使用TransactionTemplate来进行实现,对业务代码有侵入性,项目中很少使用
  • 声明式事务管理:声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
    在这里插入图片描述

2.3 问题解答:

在这里插入图片描述

3. 事务失效的场景:

  • 异常捕获处理
  • 抛出检查异常
  • 非public方法

3.1 异常捕获处理

在这里插入图片描述

3.2 抛出检查异常

其中非检查异常即RuntimeException
在这里插入图片描述

3.3 非public方法:

在这里插入图片描述

3.4 问题回答

  1. 异常捕获处理,自己处理了异常,没有抛出,解决:手动抛出
  2. 抛出检查异常,配置rollbackFor属性为Exception
  3. 非public方法导致的事务失效,改为public

4. Spring的bean生命周期?Spring容器是如何管理和创建bean实例的?

4.1 BeanDefinition:创建Bean对象的基础对象

在这里插入图片描述

4.2 对象的创建过程:

  1. 构造函数:调用bean的构造函数实例化bean对象(创建对象,但是并没有初始化)
  2. 依赖注入:注入@value或者@Autowired的属性
  3. Aware接口:如果bean实现了此结构,就需要重写这些方法
  4. Bean后置处理器BeanPostProcessor:初始化方法之前的后置处理器,用于增强bean的功能
  5. 调用初始化方法:
  6. 后置处理器BeanPostProcessor:在初始化之后的后置处理器,也是用于增强bean的功能,比如如果这个bean用到了AOP,那就是用后置处理器来实现AOP的

在这里插入图片描述

4.3 代码演示:

@Component
public class User implements BeanNameAware, BeanFactoryAware, ApplicationontextAware, InitializingBean {
	// 第一步:构造函数
	public User(){ System.out.println("User的构造方法执行了.........");}
	private String name ;
	// 第二步:依赖注入
	@Value("张三")
	public void setName(String name) {
		System.out.println("setName方法执行了.........");
	}
	// 第三步:aware接口
	@Override
	public void setBeanName(String name) { 	
		System.out.println("setBeanName方法执行了.........");
	}
	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		System.out.println("setBeanFactory方法执行了.........");
	}
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		System.out.println("setApplicationContext方法执行了........");
	}
	// 第五步:初始化方法
	@PostConstruct
	public void init() {System.out.println("init方法执行了............")}
	
	@Override
	public void afterPropertiesSetO throws Exception {
		System.out.println("afterPropertiesSet方法执行了........");
	}
	// 容器销毁,bean被回收
	@PreDestroy
	public void destory(){ System.out.println("destory方法执行了。。。")}
}
4.3.1 前置后置示例:

(正常的代理就是在后置的后置处理器中完成的)。

bean对象处理配置类实现BeanPostProcessor接口,并重写其中的方法,完成后置处理器处理:

查看对User增强及增强的效果:目的是生成User的代理对象:
在这里插入图片描述
结果可以看到是个代理对象:
在这里插入图片描述

4.4 答案回答:

在这里插入图片描述

5. Spring的Bean的循环依赖问题:

5.1 问题示例:

在这里插入图片描述

5.2 死循环的产生:

在这里插入图片描述

5.3 三级缓存解决循环依赖问题:

其中二级缓存存放的是半成品对象即只是创建了对象并没有初始化的对象。

5.3.1 三级缓存示例:

在这里插入图片描述

5.3.2 一级缓存无法解决循环依赖问题:

在这里插入图片描述

5.3.3 一级和二级缓存解决循环依赖问题:
  1. 到了创建B需要注入A的步骤,此时二级缓存中有对象A和对象B
    在这里插入图片描述
  2. 将二级缓存中的半成品A对象注入到B中就能创建B对象了,有了B对象,A对象也创建成功,此时单例池中就有A对象和B对象了。
    在这里插入图片描述
  3. 还存在的问题
    如果A是个代理对象,存入代理池的就会是代理对象,所以一二级缓存只能解决一般的循环依赖:

在这里插入图片描述

5.3.4 三级缓存解决循环依赖过程演示:
  1. 到了创建B对象需要A对象,此时通过三级缓存存储了原始对象A和原始对象B生成的工厂对象,对象工厂是专门用来创建对象的:
    在这里插入图片描述
  2. 注入A需要从三级缓存中拿到A的工厂对象,生成一个A对象或者A的代理对象放到二级缓存中(还是一个半成品),接着从二级缓存中拿到这个半成品对象就可以把A的半成品注入B,B就可以创建成功
    在这里插入图片描述
  3. B创建了放到单例池,A也被创建了:
    在这里插入图片描述
5.3.5 有了三级缓存为什么还需要二级缓存:

因为我们的对象时单例的,对象工厂创建的对象都是单例的,在B中注入A的时候,我们生成了一个A的半成品对象放到了二级缓存中;创建完B对象,我们在生成A对象的时候,再次从二级缓存中获取到A的半成品对象,基于此半成品对象来生成A。

如果没有二级缓存,在注入B的时候,我们生了一个半成品A对象,到了创建A的时候,我们又会生成一个半成品A对象,出现了多例,此时就会有问题。

5.4 构造方法注入产生了循环依赖如何解决:

因为上面的三级缓存是处理初始化过程中的循环依赖问题,如果是构造函数(构造函数被调用在初始化之前)中的循环依赖如何解决?

5.4.1 问题出现:

在这里插入图片描述

5.4.2 解决:@lazy延迟加载

什么时候需要,什么时候再加载
在这里插入图片描述

5.5 问题回答:

5.5.1 spring中循环依赖:
  • 循环依赖:循环依赖其实就是循环用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A
  • 循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖
    • 一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
    • 二级缓存:缓存早期的bean对象(生命周期还没走完)
    • 三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的
5.5.2 构造方法中的循环依赖:

A依赖于B,B依赖于A,注入的方式是构造函数

  • 原因:由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入
  • 解决方案:使用@Lazy进行懒加载,什么时候需要对象再进行bean对象的创建

6. springMVC执行流程:

  • 前端控制器:调度中心
  • 处理器映射器:找到具体的处理器
  • 处理器适配器:调用具体的方法
  • 处理器handler:controller中的具体方法

6.1 视图阶段:

在这里插入图片描述

6.2 前后端分离:

在这里插入图片描述

6.3 问题回答:

6.3.1 jsp版本:
  1. 用户发送出请求到前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping (处理器映射器)
  3. HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet
  4. DispatcherServlet调用HandlerAdapter (处理器适配器)
  5. HandlerAdapter经过适配调用具体的处理器 (Handler/Controller)
  6. Controller执行完成返回ModelAndView对象
  7. HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewReslover (视图解析器)
  9. ViewReslover解析后返回具体View (视图)
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)
  11. DispatcherServlet响应用户
6.3.2 前后端分离版本:
  1. 用户发送出请求到前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping (处理器映射器)
  3. HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet
  4. DispatcherServlet调用HandlerAdapter (处理器适配器)
  5. HandlerAdapter经过适配调用具体的处理器 (Handler/Controller)
  6. 方法上添加了@ResponseBody
  7. 通过HttpMessageConverter来返回结果转换为JSON并响应

7. springboot的自动配置原理:

7.1 springboot启动类注解解释:

在这里插入图片描述

7.2 EnableAutoConfiguration注解:

通过此注解,springboot已经为我们引入了100多个配置类:
在这里插入图片描述
这里面并不是所有的配置都会加载,只有引入了的才会加载,例如redis的配置类,直接帮我们配置了RedisTemplate:
在这里插入图片描述

7.3 问题回答:

  1. 在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:
    • SpringBootConfiguration
    • EnableAutoConfiguration
    • ComponentScan
  2. 其中@EnableAutoConfiguration是实现自动化配置的核心注解。该注解通过@lmport注解导入对应的配置选择器内部就是读取了该项目和该项目引用的Jar包的的casspath路径下META-INF/spring.factories文件中的所配置的类的全类名。在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中
  3. 条件判断会有像@Conditional0nClass这样的注解,判断是否有对应的lass文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。

8. spring、springmvc和springboot常用注解有哪些?

8.1 spring常用注解:

在这里插入图片描述

8.2 springmvc相关注解:

在这里插入图片描述

8.3 springboot相关注解:

在这里插入图片描述

9. 过滤器和拦截器的区别

  1. 过滤器是基于函数回调的,而拦截器是基于java反射的
  2. 过滤器依赖于servlet容器,而拦截器不依赖与Servlet容器,拦截器依赖SpringMVC
  3. 过滤器几乎对所有的请求都可以起作用,而拦截器只能对SpringMVC请求起作用
  4. 拦截器可以访问处理方法的上下文,而过滤器不可以
  5. 触发时机不同,过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。

在这里插入图片描述

  1. 控制执行顺序不同

总结

过滤器就是筛选出你要的东西,比如requeset中你要的那部分 拦截器在做安全方面用的比较多,比如终止一些流程

  • 过滤器(Filter) :可以拿到原始的http请求,但是拿不到你请求的控制器和请求控制器中的方法的信息。
  • 拦截器(Interceptor):可以拿到你请求的控制器和方法,却拿不到请求方法的参数。
  • 切片(Aspect): 可以拿到方法的参数,但是却拿不到http请求和响应的对象

应用场景

拦截器是在DispatcherServlet这个servlet中执行的,因此所有的请求最先进入Filter,最后离开Filter。其顺序如下:
Filter->Interceptor.preHandle->Handler->Interceptor.postHandle->Interceptor.afterCompletion->Filter

拦截器应用场景

拦截器本质上是面向切面编程(AOP),符合横切关注点的功能都可以放在拦截器中来实现,主要的应用场景包括:

  • 登录验证,判断用户是否登录。
  • 权限验证,判断用户是否有权限访问资源,如校验token
  • 日志记录,记录请求操作日志(用户ip,访问时间等),以便统计请求访问量。
  • 处理cookie、本地化、国际化、主题等。
  • 性能监控,监控请求处理时长等。
  • 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现)
过滤器应用场景
  • 过滤敏感词汇(防止sql注入)
  • 设置字符编码
  • URL级别的权限访问控制
  • 压缩响应信息
总结

过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。 拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。 监听器(Listener):当一个事件发生的时候,你希望获得这个事件发生的详细信息,而并不想干预这个事件本身的进程,这就要用到监听器。

我项目中的使用:
  • 过滤器:
    • 主要是网关中进行拦截判断有没有token
    • 也可以在网关中进行跨域问题的处理
    • 由于有些接口无法携带token(有可能是早期的feign无法携带token,也可能是其他接口),所以通过过滤器来设置这些接口跳过校验(查看PrincipleFilter类)
  • 拦截器:
    • 主要用来在发送feign的请求之前给请求拼上token(查看FeignBasicAuthRequestInterceptor类)
    • 用来校验接收到的请求是不是gateway转发过来的(通过gateway转发过来的请求,会特定的拼上一些数据)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值