spring高频面试题

文章详细阐述了Spring框架中的IOC(控制反转)和AOP(面向切面编程)概念,包括依赖注入的原理和动态代理实现。同时,讨论了Spring事务管理,分析了事务可能失效的多种场景,并提供了相应的解决方案。此外,还提到了检查异常与非检查异常的区别以及Spring框架中单例bean的线程安全性问题。
摘要由CSDN通过智能技术生成

什么是IOC

Spring框架提供的一种容器,用于控制对象的创建和对象之间的调用,通过IOC容器把对象的创建和调用过程交给Spring进行管理,省去了使用 new的方式创建对象。
所谓依赖注入(DI),就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中,得出依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。

什么是AOP

面向切面编程,用于将业务无关却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用降低耦合。

aop实现:主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关"方面"的代码。

spring实现AOP

1、jdk动态代理:代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理;其核心的两个类是InvocationHandler和Proxy。

2、CGLIB动态代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强;需要引入包asm.jar和cglib.jar。

aop使用场景

记录操作日志、缓存、spring事务等

记录操作日志:使用aop中的环绕通知+切点表达式(记录日志的方法),通过环绕通知的参数获取请求方法的参数(类、方法、注解、请求方式等)并保存到数据库。

spring aop

spring事务

spring事务是如何实现的

本质是通过aop实现的,对方法前后进行拦截。在执行方法之前开启事务,在执行目标方法之后根据方法的执行情况提交或回滚事务。

spring事务失效场景

情况1:异常捕获处理(try—catch)

spring事务基于aop实现,事务通知只有捕捉到目标抛出的异常,才能进行回滚处理。若目标自己处理掉异常,事务通知捕捉不到异常,自然就无法执行回滚操作。

情况2:抛出检查异常

spring默认只会回滚非检查异常(运行时异常RuntimeException)
解决方案:配置rollbackFor 属性 @Transactional(rollbackFor = Exception.class)

情况3:抛出自定义异常

如果我们自定义了一个异常直接继承了Exception
解决方案:配置rollbackFor 属性 @Transactional(rollbackFor = Exception.class)

情况4:方法内部直接调用,没有经过代理对象

在Spring的Aop代理下,只有目标方法在外部进行调用,目标方法才会由Spring生成的代理对象来进行管理,如果是其他不包含@Transactional注解的方法中调用包含@Transactional注解的方法时候,有@Transactional注解的方法的事务会被忽略,则不会发生回滚。

    public void insert() throws CustomException {
        doSomething();
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void doSomething() throws CustomException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        noticeMapper.insert(notice);
        historyMapper.insert(history);
        throw new CustomException();
    }
解决方法

解决方法1:在方法调用的最外层加上@Transactional,如下只要在insert方法上面加上@Transactional注解即可,doSomething的方法上面可以去掉了,因为同处于一个数据库链接当中。

    @Transactional(rollbackFor = Exception.class)    
    public void insert() throws CustomException {
        doSomething();
    }
 
    public void doSomething() throws CustomException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        noticeMapper.insert(notice);
        historyMapper.insert(history);
        throw new CustomException();
    }

解决方法2:使用代理对象来调用方法
一、添加依赖

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

二、配置类上加上

// 暴露代理对象
@EnableAspectJAutoProxy(exposeProxy = true)

三、使用代理对象来调用方法

/**
 * 要想事务生效,必须使用代理对象来调用
 */
// 拿到当前对象的代理对象
VoucherOrderService voucherOrderService = (VoucherOrderService) AopContext.currentProxy();
return voucherOrderService.createVoucherOrder(voucherId);

情况5:异步多线程

原因:方法使用@Async异步执行(@Async想要生效需要主启动类加上@EnableAsync)
解决方案:将 @Transactional和@Async绑定在一起进行使用,事务才能生效

    @Async
    public void save(Notice notice) throws CustomException, InterruptedException {
        System.out.println("异步任务开始...");
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        historyMapper.insert(history);
        Thread.sleep(5000);
        System.out.println("异步任务结束...");
    }
    @Transactional(rollbackFor = Exception.class)
    public void insert() throws CustomException, InterruptedException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");
 
        noticeMapper.insert(notice);
 
        myService2.save(notice);
 
        int a = 1/0;

    }
		
	-------------------解决方案-------------------
	
	  @Transactional(rollbackFor = Exception.class)
    @Async
    public void insert() throws CustomException, InterruptedException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");
 
        noticeMapper.insert(notice);
 
        myService2.save(notice);
 
        int a = 1/0;
 
    }
 
    public void save(Notice notice) throws CustomException, InterruptedException {
        System.out.println("异步任务开始...");
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        historyMapper.insert(history);
        Thread.sleep(5000);
        System.out.println("异步任务结束...");
    }
		

情况6:使用了错误的事务传播机制

先简单介绍一下Spring事务的7种传播机制

PROPAGATION_REQUIRED如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
PROPAGATION_SUPPORTS如果当前存在事务,则加入该事务;如果没有事务,则以非事务方式继续运行
PROPAGATION_MANDATORY必须运行在已存在的事务中,否则抛出异常
PROPAGATION_REQUIRES_NEW创建一个新事务,如果已经存在一个事务,则把当前事务挂起
PROPAGATION_NOT_SUPPORTED以非事务方式运行,如果当前存在事务,则把当前事务挂起
PROPAGATION_NEVER以非事务方式运行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则等同于`PROPAGATION_REQUIRED

这里使用的是PROPAGATION_REQUIRES_NEW的传播机制(两个独立的事务,如果insert方法出现异常,事务回滚时,不会影响到save方法)

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public void save(Notice notice){
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        historyMapper.insert(history);
    }

	@Transactional(rollbackFor = Exception.class)
    public void insert() throws CustomException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");
 
        noticeMapper.insert(notice);
        
        myService2.save(notice);
 
        throw new CustomException();
    }

情况7:方法被private或者final修饰

原因:spring为方法创建代理、添加事务通知的前提是该方式是public的

情况8:当前类没有注册到Spring容器

情况9:数据库不支持事务

比如Mysql的Myisam存储引擎是不支持事务,只有innodb存储引擎才支持。 这个问题出现的概率极其小,了解一下

本节参考于: Spring事务常见的8种失效场景

检查异常和非检查异常的区别

Java中的异常分为两种:检查异常和非检查异常 / 运行时异常,具体的区别如下:

检查异常是指需要在编译时使用try-catch或者throws声明的异常,如果不处理将无法编译通过。如IOException、ClassNotFoundException等。

非检查异常 / 运行时异常是指在编译时不需要try-catch或者throws声明的异常,在程序运行期间会抛出。如NullPointerException、ArrayIndexOutOfBoundsException等

spring框架中的单例bean时线程安全的吗?

不是线程安全的。

spring框架中有一个@Scope注解,默认的值是singleton(单例的)

因为一般注入IOC容器中的bean都是无状态的对象,没有线程安全问题,若bean中定义了可修改的成员变量,那么就要考虑线程安全问题,可以用prototype(多例)或者加锁来解决。

Bean的生命周期图示
在这里插入图片描述
Bean的生命周期流程

1、 通过BeanDefinition获取bean的定义信息

2、调用构造函数实例化bean

3、bean的依赖注入

4、处理Aware接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware)

5、Bean的后置处理器BeanPostProcessor-前置

6、初始化方法(InitializingBean、init-method)

7、Bean的后置处理器BeanPostProcessor-后置

8、销毁bean

bean的生命周期

spring的循环依赖

spring的循环依赖及其解决方案

Spring MVC的执行流程

视图阶段(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传给ViewResolver(视图解析器)
  9. ViewResolver解析后返回具体的View(视图)
  10. DispatcherServlet根据View渲染视图(将模型视图填充到视图中)
  11. DispatcherServlet响应View给用户

前后端分离阶段(接口开发,异步请求)

在这里插入图片描述执行流程

  1. 用户发送请求到DispatcherServlet(前端控制器)
  2. DispatcherServlet收到请求后调用HandlerMapping(处理器映射器)
  3. HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),一起返回给DispatcherServlet
  4. DispatcherServlet调用HandlerAdapter(处理器适配器)
  5. HandlerAdapter经过适配调用具体的处理器(Handler/Controller)
  6. 方法上添加了@ResponseBody
  7. 通过HttpMessageConverter来返回结果并响应

SpringBoot自动配置原理

@SpringBootApplication

// @SpringBootApplication里面包含的注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

@SpringBootConfiguration :此注解作用与@Configuration注解相同,用来表明当前类是一个配置类

@ComponentScan :组件扫描,默认扫描当前引导类及其子包

△@EnableAutoConfiguration:Springboot实现自动配置的核心注解
在这里插入图片描述面试回答:在springboot项目中的引导类上有个注解@SpringBootApplication,这个注解封装了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan等注解,其中@EnableAutoConfiguration注解是Springboot实现自动配置的核心注解,该注解通过@Import注解导入对应的配置选择器(@Import({AutoConfigurationImportSelector.class}))

内部读取了org.springframework.boot:spring-boot-autoconfigure包下的classpath路径下META-INF/spring.factories文件中的配置类的全类名。在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到IOC容器中

条件注解如@ConditionalOnClass等,这个注解的作用判断是否有对应的class文件,若有则加载此类,将这个配置类所有的Bean放入IOC容器中。

过滤器(filter)和拦截器(intercept)的区别

  • filter基于函数回调,intercept基于反射机制
  • filter依赖于servlet容器,intercept不依赖于servlet容器
  • filter几乎对所有请求起作用,intercept只对action请求起作用
  • intercept可以访问action上下文、值栈里的对象;而filter不能
  • 在action的生命周期中,intercept可以多次被调用,而filter只能在容器初始化时被调用一次
  • intercept可以获取IOC容器中的各个bean,而filter就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑
  • filter和intercept的触发时机不一样,filter是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是一样,是在servlet处理完后,返回给前端之前。

总结:过滤器包裹住servlet,servlet包裹住拦截器。

在这里插入图片描述
1、过滤器的触发时机是容器后,servlet之前,所以过滤器的doFilter方法,的入参是ServletRequest ,而不是httpservletrequest。因为过滤器是在httpservlet之前。

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("before...");
        //这个方法的调用作为分水岭。事实上调用Servlet的doService()方法是在chain.doFilter(request, response);这个方法中进行的。
        chain.doFilter(request, response);
        System.out.println("after...");
    }

过滤器是JavaEE标准,采用函数回调的方式进行。是在请求进入容器之后,还未进入Servlet之前进行预处理,并且在请求结束返回给前端这之间进行后期处理。

2、拦截器是被包裹在过滤器之中的。

    // preHandle方法过滤器的chain.doFilter(request, response)方法之前执行的
    // preHandle()方法之后,在return ModelAndView之前进行,可以操控Controller的ModelAndView内容
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

   // afterCompletion方法过滤器的chain.doFilter(request, response)方法之后执行的
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }

3、SpringMVC的机制是由同一个Servlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的。所以过滤器、拦截器、service()方法,dispatc()方法的执行顺序应该是这样的,大致画了个图:其实非常好测试,自己写一个过滤器,一个拦截器,然后在这些方法中都加个断点,一路F8下去就得出了结论。

4、SpringMVC的机制是由同一个Servlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的。

5、SpringMVC的机制是由同一个Servlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的。

6、SpringMVC的机制是由同一个Servlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的。

7、过滤器是servlet中定义的;而拦截器是spring容器的,是spring支持的

filter和intercept的执行顺序

在这里插入图片描述总结:拦截器功在对请求权限鉴定方面确实很有用处,在我所参与的这个项目之中,第三方的远程调用每个请求都需要参与鉴定,所以这样做非常方便,而且他是很独立的逻辑,这样做让业务逻辑代码很干净。和框架的其他功能一样,原理很简单,使用起来也很简单,大致看了下SpringMVC这一部分的源码,其实还是比较容易理解的。

面试回答:拦截器与过滤器的区别

Spring的拦截器与Servlet 的Filter有相似之处,比如二者都是AOP编程思想的体现,都能实现权限检查、日志记录等。不同的是:

  • 使用范围不同:Filter是Servlet规范规定的,只能用于Web程序中。而拦截器既
    可以用于Web程序,也可以用于Application、Swing程序中。
  • 规范不同:Filter是在 Servlet 规范中定义的,是 Servlet容器支持的。而拦截器是
    在 Spring容器内的,是 Spring框架支持的。
  • 使用的资源不同:同其他的代码块一样,拦截器也是一个Spring的组件,归Spring
    管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如 Service对象、数据源、事务管理等,通过 loC注入到拦截器即可;而Filter则不能。
  • 深度不同:Filter在只在Servlet前后起作用。而拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。所以在 Spring 构架的程序中,要优先使用拦截器。

本节搬运于:https://www.cnblogs.com/panxuejun/p/7715917.html

spring框架的常用注解

spring的常用注解

注解说明
@Component、@Controller、@Service、@Repository使用在类上用于实例化Bean
@Autowired使用在属性上根据类型进行依赖注入
@Qualifier结合@Autowired一起使用,根据名称进行依赖注入
@Scope标注bean的作用域
@Configuration指定当前类为一个配置类
@ComponentScan用于spring在初始化容器时扫描的包
@Bean标注在方法上,表示将该bean添加到spring容器
@Import使用@Import导入的类会被加载到spring容器
@Aspect、@Before、@after、@Around、@Pointcut用于aop 详见

① bean的作用域

作用域描述
singleton(默认)将单个 bean 定义限定为每个 Spring IoC 容器的单个对象实例。
prototype将单个 bean 定义限定为任意数量的对象实例
request将单个 bean 定义限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有自己的 bean 实例,该实例是在单个 bean 定义的后面创建的。仅在web类型的 ApplicationContext有效。
session将单个 bean 定义限定为 HTTP 会话的生命周期。仅在web类型的 ApplicationContext有效。
application将单个 bean 定义限定为 ServletContext 的生命周期。仅在web类型的 ApplicationContext有效。
websocket将单个 bean 定义限定为 WebSocket 的生命周期。仅在web类型的 ApplicationContext有效。

spring mvc常用注解

注解说明
@RequestMapping用于请求映射路径,可以用在方法或类上。用在类上表示当前类中所有的方法都以此路径为父路径
@RequestBody接收http请求的JSON数据,将JSON转换成java对象
@RequestParam指定请求参数的名称,如:/user?name=hh 请求中的name属性@RequestParam(“name”)
@PathViriable从请求路径下获取请求参数,如:/user/{id} 使用@PathViriable("id)
@ResponseBody将返回对象转换成JSON数据并响应给前端
@RequestHeader获取指定的请求头
@RestController等价于@Controller + @ResponseBody

springboot常用注解

注解说明
@SpringBootConfiguration组合了@Configuration注解,实现文件配置功能
@EnableAutoConfiguration打开或关闭自动配置功能
@ComponentScanspring组件扫描
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值