spice and wolfspice and wolf Be the One you wanna Be

Spring面试题

Bean的生命周期

Bean的生命周期指的是bean从创建到销毁的过程,主要分为以下4个阶段:

  1. 实例化。
    • 通过反射推断构造函数进行实例化。
    • 实例工厂、静态工厂。
  2. 属性赋值。
    • 解析自动装配(依赖注入的过程)。
    • 循环依赖。
  3. 初始化。
    • 调用XXXAware回调方法。
    • 调用初始化生命周期回调
    • 实现了AOP,创建动态代理
  4. 销毁。
    • 在spring容器关闭时进行调用
    • 调用销毁生命周期回调

spring如何解决依赖注入的循环依赖问题

依赖注入就是在bean的生命周期时成环依赖可能会导致的程序死循环现象,具体来说如果A依赖注入了B,并且B也依赖注入了A时,加入A单例先创建,A的Bean生命周期的属性赋值阶段会去遍历并且创建属性对应的实例,这时会去创建B实例,而B创建的Bean生命周期时会去创建A实例,这样就导致了程序死循环的发生。

Spring解决循环依赖问题的方法是三级缓存机制,对应的三级缓存:

  1. singletonObjects(一级缓存)。用来存储完整走完Bean生命周期中实例化、属性赋值和初始化三个阶段的单例。一级缓存主要用来缓存单例对象,spring基于它实现了单例模式。
  2. earlySingletonObjects(二级缓存)。用来存储还未完全走完Bean生命周期的Bean对象(可能是普通Bean或普通Bean的代理对象)。二级缓存为了保证Bean对象的单例特性,因为最终创建的单例可能是普通Bean对象也可能是普通Bean的代理对象。
  3. singleFactories(三级缓存)。用来存储基于bean的拉姆达表达式。

循环依赖如何解决的?

循环依赖问题出现的主要原因是:在获取bean单例时没有判断是否已经创建了单例对象。所以解决问题的方法也很简单,即一个if语句判断是否存在已经创建的bean单例和缓存已经创建的单例的数据结构,这个spring的确也是这样实现的,并且有三个缓存结构结构来存储bean。

第一级缓存的作用?

第一级缓存存储的是完整走完bean生命周期前三个阶段bean对象,它主要有以下几方面的作用:

  • 表明当前缓存中的bean是可用的,拿到当前bean后可以直接实现相关的方法。
  • 实现了spring的单例模式。
  • 作为三级缓存的最终状态。

为什么要设计三级缓存,只有俩级缓存不行吗?

之所以设计第二级缓存和第三级缓存,而不是只有第二级缓存,是因为有以下几个原因:

  • 减小bean生命周期期间的锁粒度,提高并发性能。如果只有第二级缓存,只有在bean完成了生命周期初始化中代理对象创建后才能填充第二级缓存,所以在bean实例化到初始化阶段都可能出现并发问题,即多个线程同时创建同一个bean的单例,为此我们必须在bean实例化到初始化阶段加锁,这个锁的粒度很大,会严重影响性能,所以为了提高bean创建过程中的并发性能,spring加入了第三级缓存,这样多个线程同时创建bean时,只需要判断三级缓存是否存在(如果存在二级缓存则直接取用),如果存在,则会加锁并根据三级缓存生成二级缓存(这里发生了AOP前置)。这样将覆盖几乎整个生命周期的锁粒度缩小到二三级缓存转换时的锁粒度,大大提高了并发性能。
  • 三级缓存保存的是拉姆达表达式,能更灵活地自定义代码行为。

Spring Boot自动装配

自动装配就是自动将第三方的Bean装配到IOC容器中,不需要开发人员再去写相关配置。

在启动类上加上@SpringBootApplication就能实现自动装配,而SpringBootApplication是一个复合注解,真正实现自动装配的注解是@EnableAutoConfiguration,这个注解会导入AutoConfigurationImportSelector类,而其中的selectImports()方法会调用SpringFactoriesLoader.loadFactoryNames()方法扫描所有包含META-INF/spring.factories的jar包,并将文件中包含的所有类装配到springIOC容器中。

Spring Boot如何解决跨域请求

跨域问题是指在浏览器执行javascript代码时发起请求的域和接受请求的域破坏了同源策略的现象。

解决同源策略的方法就是在不破坏同源策略的基础上,实现实现数据的共享和交互:

  • jsonp。
  • CORS。是在服务器后端解决跨域问题的方案。 跨域访问时,浏览器会发送Options请求,根据请求头中Access-Control-Allow-Origin的值判断是否允许跨域请求,所以我们只需在接受请求时在这个请求头中配置允许跨域的域名就行了,在spring boot中有两种方式进行配置:
    • @CrossOrigin注解配置
    • 使用WebMvcConfigurer接口重写addCorsMapping()方法

Spring中Bean的作用域

Bean不同的作用域对应于Bean不同的生命周期,SpringIOC容器通过管理Bean的生命周期来达到实现不同作用域的效果。一般来说Bean有以下几种作用域:

  • singleton(单例)。默认作用域,IOC容器中之存在一个实例,每次请求都会返回同一个Bean实例。
  • prototype(原型)。每次获取Bean时都会返回一个新的Bean实例。
  • request(请求)。每次http请求都会返回一个新的Bean实例。
  • session(会话)。一个会话内都会返回同一个Bean实例。
  • application(应用上下文)。servletContext下只会存在一个Bean实例。
  • websocket。在websocket的生命周期内只会存在一个Bean实例。

java web中的过滤器

Filter是servlet规范中用于拦截、处理和修改请求与响应的组件。

触发时机

在servlet执行前后执行,用于在请求到达servlet前和生成response响应后执行自定义行为。

应用场景

  • 日志记录。可以用于记录资源访问的日志信息,如访问者IP地址、访问时间、访问资源等。
  • 字符编码处理。统一不同客户端的字符编码,避免乱码问题。
  • 用户身份验证。可以用作登陆验证和资源访问权限控制,如果未登录或者权限不够可以重定向到登录页面或错误页面。
  • 请求转发。根据业务要求进行请求转发。
  • 响应内容处理。对请求信息进行压缩,或者在响应中添加版权、版本等额外信息。
  • 跨域资源共享。filter可以在服务器端设置响应头信息,允许来自特定源的请求,从而解决跨域问题。
  • 敏感词过滤。

过滤器使用

实现Filter接口
@Component
@Order(1)
public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("过滤器测试");
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}
  • init。init用于初始化过滤器资源,只会在过滤器创建时调用一次。
  • doFilter。只要请求与过滤器匹配就会执行当前方法。
  • destroy。destroy方法用于在销毁前执行自定义操作,只会在过滤器销毁前调用一次。

Spring的拦截器

intercepter是spring框架提供的请求处理组件

触发时机

请求到达servlet后,controller接受请求前。

应用场景

  • 登陆校验,权限验证。
  • 日志记录。
  • 响应内容处理。

拦截器类型

常见拦截器类型有:

  • HandlerInterceptor
  • MethodInterceptor

拦截器使用

实现拦截器接口
@Component
public class TestInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}
  • preHandle。在请求进入到Controller前进行拦截,返回true表示放行;返回false表示不放行,然后返回错误信息。
  • postHandle。在DispatcherServlet进行时图返回渲染之前被调用,在这个方法中对Controller处理之后的ModelAndView对象进行操作。
  • afterCompletion。在ModelAndView返回给前端渲染后执行。

过滤器和拦截器的区别

  • 实现原理不同。
    • 过滤器是基于函数回调实现的。
    • 拦截器是基于java的反射机制(动态代理)实现的。
  • 使用范围不同。
    • 过滤器是在servlet规范中定义的,它依赖于web容器,所以只能在web应用中使用。
    • 拦截器是Spring组件,并不依赖于web容器,所以其使用不仅限于在web应用中。
  • 触发时机不同。
    • 过滤器是在请求到达servlet之前和生成响应之后触发。
    • 拦截器因为是Spring组件,所以是在servlet处理请求后,请求到达controller前触发。
  • 适用的请求范围不同。
    • 过滤器可以针对基本所有请求做处理。
    • 拦截器则只会对能进入Controller的请求或static目录的资源请求起作用。
  • 注入Bean的方式不同。
    • 过滤器可以注解形式注入。
    • 拦截器需要手动注入。
  • 执行顺序不同。
    • 过滤器按照优先级顺序执行。
    • 拦截器则按洋葱圈模型执行。

Spring boot的starter

starter是一种自动化配置机制,遵循约定优于配置的思想,能以最简单快速的方式引入功能模块。它有以下特点:

  • 管理依赖组件必要的jar包和版本。以功能为维度来维护对应jar包的版本依赖,开发者可以不需要关心jar包之间的依赖和版本适配问题,starter组件会代为解决。
  • 自动引入依赖的jar包。
  • 自带自动装配机制。会将所需jar中的类自动装配到IOC容器中。
  • 组件对应的外部化配置都集成到了application.properties中。只需要在application.properties中进行配置即可完成对应功能的配置。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Press ESC to close