Spring面试题
目录
1.Spring
1.1.说一下你对 Spring 的理解
1.2.spring的核心思想说说你的理解?
1.3.Spring IoC和AOP 介绍一下
1.4.Spring的aop介绍一下
1.5.IOC和AOP是通过什么机制来实现的?
1.6.依赖倒置,依赖注入,控制反转分别是什么?
1.7.如果让你设计一个SpringIoc,你觉得会从哪些方面考虑这个设计?
1.8.SpringAOP主要想解决什么问题??
1.9.SpringAOP的原理了解吗??
1.10.动态代理是什么?
1.11.动态代理和静态代理的区别
1.12.AOP实现有哪些注解?
1.13.什么是反射?有哪些使用场景?
1.14.spring是如何解决循环依赖的?
1.15.spring三级缓存的数据结构是什么?
1.16.Spring的事务什么情况下会失效?
1.17.Spring的事务,使用this调用是否生效?
1.18.Bean的生周期说一下
1.19.Bean是否单例?
1.20.Bean的单例和非单例,生命周期是否一样??
1.21.Spring容器里存的是什么?
1.22.在Spring中,在bean加载/销毁前后,如果想实现某些逻辑,可以怎么做?
1.23.Bean注入和xml注入最终得到了相同的效果,它们在底层是怎样做的?
1.24.Spring给我们提供了很多扩展点,这些有了解吗?
SpringMVC
2.1MVC分层介绍一下
2.2.了解SpringMVC的处理流程吗?
SpringBoot
3.1.为什么使用springboot?
3.2.怎么理解SpringBoot中的约定大于配置?
3.3.过滤器和拦截器的区别?
Mybatis
4.1.Mybatis里的 # 和 $ 的区别?
1.Spring
1.1.说一下你对 Spring 的理解
Spring 是一个开源的轻量级 Java 开发框架:Spring框架核心特性包括:
- IoC容器:Spring通过控制反转实现了对象的创建和对象间的依赖关系管理。开发者只需要定义好Bean及其依赖关系,Spring容器负责创建和组装这些对象。
- AOP:面向切面编程,允许开发者定义横切关注点,例如事务管理、安全控制等,独立于业务逻辑的代码。通过AOP,可以将这些关注点模块化,提高代码的可维护性和可重用性。
- 事务管理:Spring提供了一致的事务管理接口,支持声明式和编程式事务。开发者可以轻松地进行事务管理,而无需关心具体的事务API。
- MVC框架:Spring MVC是一个基于Servlet API构建的Web框架,采用了模型-视图-控制器(MVC)架构。它支持灵活的URL到页面控制器的映射,以及多种视图技术。
1.2.spring的核心思想说说你的理解?
Spring通过这IOC、DI、AOP三大核心思想,实现了轻量级、高内聚低耦合的企业级应用开发框架,成为Java生态中不可或缺的基石。
1.3.Spring IoC和AOP 介绍一下
Spring IoC和AOP 区别:
- IoC:即控制反转的意思,它是一种创建和获取对象的技术思想,依赖注入(DI)是实现这种技术的一种方式。传统开发过程中,我们需要通过new关键字来创建对象。使用IoC思想开发方式的话,我们不通过new关键字创建对象,而是通过IoC容器来帮我们实例化对象。通过IoC的方式,可以大大降低对象之间的耦合度。
- AOP:是面向切面编程,能够将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,以减少系统的重复代码,降低模块间的耦合度。Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理。
在 Spring 框架中,IOC 和 AOP 结合使用,可以更好地实现代码的模块化和分层管理。例如:
- 通过 IOC 容器管理对象的依赖关系,然后通过 AOP 将横切关注点统一切入到需要的业务逻辑中。
- 通过 IOC 容器管理对象的依赖关系,然后通过 AOP 将横切关注点统一切入到需要的业务逻辑中。、日志记录等横切功能,使得业务逻辑更加清晰和可维护。
1.4.Spring的aop介绍一下
Spring AOP是Spring框架中的一个重要模块,用于实现面向切面编程。
我们知道,Java 就是一门面向对象编程的语言,在 OOP 中最小的单元就是“Class 对象”,但是在 AOP 中最小的单元是“切面”。一个“切面”可以包含很多种类型和对象,对它们进行模块化管理,例如事务管理。
在面向切面编程的思想里面,把功能分为两种:
- 核心业务:登陆、注册、增、删、改、查、都叫核心业务
- 周边功能:日志、事务管理这些次要的为周边业务
在面向切面编程中,核心业务功能和周边功能是分别独立进行开发,两者不是耦合的,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP。
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来便于减少系统的重复代码,降低模块间的耦合度并有利于未来的可拓展性和可维护。
在 AOP 中有以下几个概念:
- AspectJ:切面,只是一个概念,没有具体的接口或类与之对应,是 Join point,Advice 和 Pointcut 的一个统称。
- Join point:连接点,指程序执行过程中的一个点,例如方法调用、异常处理等。通俗来讲就是可以被AOP控制的方法。连接点指的是可以被aop控制的方法在 Spring AOP 中,仅支持方法级别的连接点。
- Advice:(指那些重复的逻辑,也即共性功能)通知,即我们定义的一个切面中的横切逻辑有“around”,“before”和“after”三种类型。在很多的 AOP 实现框架中,Advice 通常作为一个拦截器,也可以包含许多个拦截器作为一条链路围绕着Join point 进行处理。
- Pointcut:切点,用于匹配连接点,一个 AspectJ 中包含哪些 Join point 需要由 Pointcut 进行筛选。
Spring AOP 是基于 JDK 动态代理和 Cglib 提升实现的,两种代理方式都属于运行时的一个方式,所以它没有编译时的一个处理,那么因此 Spring 是通过 Java 代码实现的。
1.5.IOC和AOP是通过什么机制来实现的?
Spring IOC 实现机制
- 反射:Spring IOC容器利用Java的反射机制动态地加载类、创建对象实例及调用对象方法,反射允许在运行时检查类、方法、属性等信息,从而实现灵活的对象实例化和管理。
- 依赖注入:IOC的核心概念是依赖注入,即容器负责管理应用程序组件之间的依赖关系。Spring通过构造函数注入、属性注入或方法注入,将组件之间的依赖关系描述在配置文件中或使用注解。
- 设计模式 - 工厂模式:Spring IOC容器通常采用工厂模式来管理对象的创建和生命周期。容器作为工厂
- 容器实现:Spring IOC容器是实现IOC的核心,通常使用BeanFactory或ApplicationContext来管理Bean。BeanFactory是IOC容器的基本形式,提供基本的IOC功能;ApplicationContext是BeanFactory的扩展,并提供更多企业级功能。
Spring AOP 实现机制
Spring AOP的实现依赖于动态代理技术。动态代理是在运行时动态生成代理对象,而不是在编译时。它允许开发者在运行时指定要代理的接口和行为,从而实现在不修改源码的情况下增强方法的功能。
Spring AOP支持两种动态代理:
- 基于JDK的动态代理:使用java.lang.reflect.Proxy类java.lang.reflect.InvocationHandler接口实现。这种方式需要代理的类实现一个或多个接口。
- 基于CGLIB的动态代理:当被代理的类没有实现接口时,Spring会使用CGLIB库生成一个被代理类的子类作为代理。CGLIB(Code Generation Library)是一个第三方代码生成库,通过继承方式实现代理。
1.6.依赖倒置,依赖注入,控制反转分别是什么?
- 控制反转:“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序员“反转”给了框架。
- 依赖注入:依赖注入和控制反转恰恰相反,它是一种具体的编码技巧,我们不通过 new 的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类来使用。
- 依赖倒置:这条原则跟控制反转有点类似,主要用来指导框架层面的设计。高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。
1.7.如果让你设计一个SpringIoc,你觉得会从哪些方面考虑这个设计?
- Bean的生命周期管理:需要设计Bean的创建、初始化、销毁等生命周期管理机制,可以考虑使用工厂模式和单例模式来实现。
- 依赖注入:需要实现依赖注入的功能,包括属性注入、构造函数注入、方法注入等可以考虑使用反射机制和XML配置文件来实现。
- Bean的作用域:需要支持多种Bean作用域,比如单例、原型、会话、请求等可以考虑使用Map来存储不同作用域的Bean实例。
- AOP功能的支持:需要支持AOP功能,可以考虑使用动态代理机制和切面编程来实现。
- 异常处理:需要考虑异常处理机制,包括Bean创建异常、依赖注入异常等。
- 配置文件加载:需要支持从不同的配置文件中加载Bean的相关信息,可以考虑使用XML、注解或者Java配置类来实现。
工厂模式:工厂模式是一种创建对象的设计模式,它将对象的创建和使用分离,把对象创建逻辑封装在工厂类中,提高了代码的可维护性和可扩展性。
1.8.SpringAOP主要想解决什么问题??
它的目的是对于面向对象思维的一种补充。在我个人的理解下AOP更像是一种对于不支持多继承的弥补,除开对象的主要特征被抽象为了一条继承链路,对于一些“弱共性”,AOP可以统一对他们进行抽象和集中处理。
举一个简单的例子,打印日志。需要打印日志可能是许多对象的一个共性,这在企业级开发中十分常见,但是日志的打印并不反应这个对象的主要共性。而日志的打印又是一个具体的内容,它并不抽象,所以它的工作也不可以用接口来完成。而如果利用继承,打印日志的工作又横跨继承树下面的多个同级子节点,强行侵入到继承树内进行归纳会干扰这些强共性的区分。
这时候,我们就需要AOP了。AOP首先在一个Aspect(切面)里定义了一些Advice(增强),其中包含具体实现的代码,同时整理了切入点,切入点的粒度是方法。最后,我们将这些Advice织入到对象的方法上,形成了最后执行方法时面对的完整方法。
1.9.SpringAOP的原理了解吗??
Spring AOP的实现依赖于动态代理技术。动态代理是在运行时动态生成代理对象,而不是在编译时。它允许开发者在运行时指定要代理的接口和行为,从而实现在不修改源码的情况下增强方法的功能。
Spring AOP支持两种动态代理:
- 基于JDK的动态代理:使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。这种方式需要代理的类实现一个或多个接口。
- 基于CGLIB的动态代理:当被代理的类没有实现接口时,Spring会使用CGLIB库生成一个被代理类的子类作为代理。CGLIB(Code Generation Library)是一个第三方代码生成库,通过继承方式实现代理。
1.10.动态代理是什么?
Java的动态代理是一种在运行时动态创建代理对象的机制,主要用于在不修改原始类的情况下对方法调用进行拦截和增强。
Java动态代理主要分为两种类型:
- 基于接口的代理(JDK动态代理): 这种类型的代理要求目标对象必须实现至少一个接口。Java动态代理会创建一个实现了相同接口的代理类,然后在运行时动态生成该类的实例。这种代理的实现核心是
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口每一个动态代理类都必须实现InvocationHandler
接口,并且每个代理类的实例都关联到一个handler
。当通过代理对象调用一个方法时,这个方法的调用会被转发为由InvocationHandler
接口的invoke()
方法来进行调用。 - 基于类的代理(CGLIB动态代理): CGLIB(Code Generation Library)是一个强大的高性能的代码生成库,它可以在运行时动态生成一个目标类的子类。CGLIB代理不需要目标类实现接口,而是通过继承的方式创建代理类。因此,如果目标对象没有实现任何接口,可以使用CGLIB来创建动态代理。
1.11.动态代理和静态代理的区别
代理是一种常用的设计模式,目的是:为其他对象提供一个代理以控制对某个对象的访问,将两个类的关系解耦。代理类和委托类都要实现相同的接口,因为代理真正调用的是委托类的方法。
区别:
- 静态代理:由程序员创建或者是由特定工具创建,在代码编译时就确定了被代理的类是一个静态代理静态代理通常只代理一个类;
- 动态代理:在代码运行期间,运用反射机制动态创建生成。动态代理代理的是一个接口下的多个实现类。
1.12.AOP实现有哪些注解?
常用的注解包括:
- @Aspect:用于定义切面,标注在切面类上。
- @Pointcut:定义切点,标注在方法上,用于指定连接点。
- @Before:在方法执行之前执行通知。
- @After:在方法执行之后执行通知。
- @Around:在方法执行前后都执行通知。
- @AfterReturning:在方法执行后返回结果后执行通知。
- @AfterThrowing:在方法抛出异常后执行通知。
- @Advice:通用的通知类型,可以替代@Before、@After等。
1.13.什么是反射?有哪些使用场景?
反射机制是指程序在运行状态下,对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意属性和方法。也就是说,Java 反射允许在运行时获取类的信息并动态操作对象,即使在编译时不知道具体的类也能实现。
反射具有以下特性:
- 运行时类信息访问:反射机制允许程序在运行时获取类的完整结构信息,包括类名、包名、父类、实现的接口、构造函数、方法和字段等。
- 动态对象创建:可以使用反射API动态地创建对象实例。这是通过Class类的newInstance()方法或Constructor对象的newInstance()方法实现的。
- 动态方法调用:可以在运行时动态地调用对象的方法,包括私有方法。这通过Method类的invoke()方法实现,允许你传入对象实例和参数值来执行方法。
- 访问和修改字段值:反射还允许程序在运行时访问和修改对象的字段值,即使是私有的。这是通过Field类的get()和set()方法完成的。
Java反射机制在spring框架中,很多地方都用到了反射,让我们来看看Spring的IoC和AOP是如何使用反射的。
1、Spring框架的依赖注入(DI)和控制反转(IoC)
Spring 使用反射来实现其核心特性:依赖注入。
在Spring中,开发者可以通过XML配置文件或者基于注解的方式声明组件之间的依赖关系。当应用程序启动时,Spring容器会扫描这些配置或注解,然后利用反射来实例化Bean(即Java对象),并根据配置自动装配它们的依赖。
例如,当一个Service类需要依赖另一个DAO类时,开发者可以在Service类中使用@Autowired注解,而无需自己编写创建DAO实例的代码。Spring容器会在运行时解析这个注解,通过反射找到对应的DAO类,实例化它,并将其注入到Service类中。这样不仅降低了组件之间的耦合度,也极大地增强了代码的可维护性和可测试性。
2、动态代理的实现
在需要对现有类的方法调用进行拦截、记录日志、权限控制或是事务管理等场景中,反射结合动态代理技术被广泛应用。
一个典型的例子是Spring AOP(面向切面编程)的实现。Spring AOP允许开发者定义切面(Aspect),这些切面可以横切关注点(如日志记录、事务管理),并将其插入到业务逻辑中,而不需要修改业务逻辑代码。
例如,为了给所有的服务层方法添加日志记录功能,可以定义一个切面,在这个切面中,Spring会使用JDK动态代理或CGLIB(如果目标类没有实现接口)来创建目标类的代理对象。这个代理对象在调用任何方法前或后,都会执行切面中定义的代码逻辑(如记录日志),而这一切都是在运行时通过反射来动态构建和执行的,无需硬编码到每个方法调用中。
1.14.spring是如何解决循环依赖的?
循环依赖指的是两个类中的属性相互依赖对方:例如 A 类中有 B 属性,B 类中有 A属性,从而形成了一个依赖闭环,例如,Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A,这种情况下就产生了循环依赖。当 Spring 容器启动并尝试创建这些相互依赖的 Bean 时,会陷入无限循环创建的困境,最终可能导致创建失败或抛出异常。如下图。
循环依赖问题在Spring中主要有三种情况:
- 第一种:通过构造方法进行依赖注入时产生的循环依赖问题。
- 第二种:通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
- 第三种:通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。
只有【第三种方式】的循环依赖问题被 Spring 解决了,其他两种方式在遇到循环依赖问题时,Spring都会产生异常。
Spring 解决单例模式下的setter循环依赖问题的主要方式是通过三级缓存解决循环依赖。三级缓存指的是 Spring 在创建 Bean 的过程中,通过三级缓存来缓存正在创建的 Bean,以及已经创建完成的 Bean 实例,具体步骤如下:
spring 利用三级缓存来解决单例 Bean 的 setter 注入循环依赖问题,这三级缓存分别是:
- 一级缓存(singletonObjects):也称为单例池,用于存放已经完全创建好的单例 Bean 实例。
- 二级缓存(singletonFactories):存放的是创建 Bean 的工厂对象,这些 Bean 还未完全初始化完成。
- 三级缓存(earlySingletonObjects):存放的是提前暴露的早期 Bean 实例,这些 Bean 已经实例化,但还未完成属性注入和初始化操作。
具体解决流程
- 创建 Bean A:Spring 容器开始创建 Bean A,首先将 Bean A 的工厂对象放入三级缓存
singletonFactories
中,此时 Bean A 处于实例化但未初始化的状态。 - 属性注入:在对 Bean A 进行属性注入时,发现需要 Bean B,于是开始创建 Bean B。
- 创建 Bean B:同样,Spring 先将 Bean B 的工厂对象放入三级缓存
singletonFactories
中,在对 Bean B 进行属性注入时,发现需要 Bean A。 - 获取 Bean A:由于 Bean A 已经在三级缓存
singletonFactories
中,Spring 会通过 Bean A 的工厂对象获取到早期的 Bean A 实例,并将其从三级缓存移到二级缓存earlySingletonObjects
中,然后将这个早期的 Bean A 实例注入到 Bean B 中。 - 完成 Bean B 的创建:Bean B 完成属性注入和初始化后,将其放入一级缓存
singletonObjects
中。 - 完成 Bean A 的创建:接着继续完成 Bean A 的属性注入,将已经创建好的 Bean B 注入到 Bean A 中,最后将 Bean A 也放入一级缓存
singletonObjects
中。
1.15.spring三级缓存的数据结构是什么?
都是 Map类型的缓存,比如Map {k:name; v:bean}。
- 一级缓存(Singleton Objects):这是一个Map类型的缓存,存储的是已经完全初始化好的bean,即完全准备好可以使用的bean实例。键是bean的名称,值是bean的实例。
- 二级缓存(Early Singleton Objects):这同样是一个Map类型的缓存,存储的是早期的bean引用,即已经实例化但还未完全初始化的bean。这些bean已经被实例化,但是可能还没有进行属性注入等操作。
- 三级缓存(Singleton Factories):这也是一个Map类型的缓存,存储的是ObjectFactory对象,这些对象可以生成早期的bean引用。当一个bean正在创建过程中,如果它被其他bean依赖,那么这个正在创建的bean就会通过这个ObjectFactory来创建一个早期引用,从而解决循环依赖的问题。
1.16.Spring的事务什么情况下会失效?
Spring Boot通过Spring框架的事务管理模块来支持事务操作。事务管理在Spring Boot中通常是通过@Transactional 注解来实现的。事务可能会失效的一些常见情况包括:
- 未捕获异常: 如果一个事务方法中发生了未捕获的异常,并且异常未被处理或传播到事务边界之外,那么事务会失效,所有的数据库操作会回滚。
- 事务传播属性设置不当: 如果在多个事务之间存在事务嵌套,且事务传播属性配置不正确,可能导致事务失效。特别是在方法内部调用有 @Transactional 注解的方法时要特别注意。
- 跨方法调用事务问题: 如果一个事务方法内部调用另一个方法,而这个被调用的方法没有@Transactional 注解,这种情况下外层事务可能会失效。
- 事务在非公开方法中失效: 如果 @Transactional 注解标注在私有方法上或者非 public 方法上,事务也会失效。
1.17.Spring的事务,使用this调用是否生效?
不能生效。因为Spring事务是通过代理对象来控制的,只有通过代理对象的方法调用才会应用事务管理的相关规则。当使用this
直接调用时,是绕过了Spring的代理机制,因此不会应用事务设置。
1.18.Bean的生周期说一下
- Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
- Bean实例化后对将Bean的引入和值注入到Bean的属性中
- 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
- 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
- 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
- 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
- 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
- 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
- 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
- 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。
1.19.Bean是否单例?
Spring 中的 Bean 默认都是单例的。
就是说,每个Bean的实例只会被创建一次,并且会被存储在Spring容器的缓存中,以便在后续的请求中重复使用。这种单例模式可以提高应用程序的性能和内存效率。
但是,Spring也支持将Bean设置为多例模式,即每次请求都会创建一个新的Bean实例。要将Bean设置为多例模式,可以在Bean定义中通过设置scope属性为"prototype"来实现。
1.20.Bean的单例和非单例,生命周期是否一样??
不一样的,Spring Bean 的生命周期完全由 IoC 容器控制。Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype
的 Bean,Spring 在创建好交给使用者之后,则不会再管理后续的生命周期。具体区别如下:
1.21.Spring容器里存的是什么?
在Spring容器中,存储的主要是Bean对象。
Bean是Spring框架中的基本组件,用于表示应用程序中的各种对象。当应用程序启动时,Spring容器会根据配置文件或注解的方式创建和管理这些Bean对象。Spring容器会负责创建、初始化、注入依赖以及销毁Bean对象。
1.22.在Spring中,在bean加载/销毁前后,如果想实现某些逻辑,可以怎么做?
在Spring框架中,如果你希望在Bean加载(即实例化、属性赋值、初始化等过程完成后)或销毁前后执行某些逻辑,你可以使用Spring的生命周期回调接口或注解。这些接口和注解允许你定义在Bean生命周期的关键点执行的代码。
使用init-method和destroy-method
在XML配置中,你可以通过init-method和destroy-method属性来指定Bean初始化后和销毁前需要调用的方法。
然后,在你的Bean类中实现这些方法:
实现InitializingBean和DisposableBean接口
使用@PostConstruct和@PreDestroy注解
1.23.Bean注入和xml注入最终得到了相同的效果,它们在底层是怎样做的?
在Spring框架中,基于注解的Bean注入(如@Autowired
、@Resource
)和基于XML的依赖注入虽然在配置方式上不同,但在底层最终都通过Spring容器的统一机制实现依赖注入。它们的核心流程可以归纳为以下步骤:
XML 注入
使用 XML 文件进行 Bean 注入时,Spring 在启动时会读取 XML 配置文件,以下是其底层步骤:
- Bean 定义解析:Spring 容器通过
XmlBeanDefinitionReader
类解析 XML 配置文件,读取其中的<bean>
标签以获取 Bean 的定义信息。 - 注册 Bean 定义:解析后的 Bean 信息被注册到
BeanDefinitionRegistry
(如DefaultListableBeanFactory
)中,包括 Bean 的类、作用域、依赖关系、初始化和销毁方法等。 - 实例化和依赖注入:当应用程序请求某个 Bean 时,Spring 容器会根据已经注册的 Bean 定义:首先,使用反射机制创建该 Bean 的实例。然后,根据 Bean 定义中的配置,通过 setter 方法、构造函数或方法注入所需的依赖 Bean。
注解注入
使用注解进行 Bean 注入时,Spring 的处理过程如下:
- 类路径扫描:当 Spring 容器启动时,它首先会进行类路径扫描,查找带有特定注解(如
@Component
、@Service
、@Repository
和@Controller
)的类。 - 注册 Bean 定义:找到的类会被注册到
BeanDefinitionRegistry
中,Spring 容器将为其生成 Bean 定义信息。这通常通过AnnotatedBeanDefinitionReader
类来实现。 - 依赖注入:与 XML 注入类似,Spring 在实例化 Bean 时,也会检查字段上是否有
@Autowired
、@Inject
或@Resource
注解如果有,Spring 会根据注解的信息进行依赖注入。
尽管使用的方式不同,但 XML 注入和注解注入在底层的实现机制是相似的,主要体现在以下几个方面:
- BeanDefinition:无论是 XML 还是注解,最终都会生成
BeanDefinition
对象,并存储在同一个BeanDefinitionRegistry
中。 - 后处理器:Spring 提供了多个 Bean 后处理如
AutowiredAnnotationBeanPostProcessor
,用于处理注解(如@Autowired
)的依赖注入。对于 XML,Spring 也有相应的后处理器来处理 XML 配置的依赖注入。 - 在依赖注入时,Spring 容器会通过
ApplicationContext
中的 BeanFactory 方法来查找和注入依赖,无论是通过 XML 还是注解,都会调用类似的查找方法。
1.24.Spring给我们提供了很多扩展点,这些有了解吗?
Spring框架提供了许多扩展点,使得开发者可以根据需求定制和扩展Spring的功能。以下是一些常用的扩展点:
- Spring MVC中的HandlerInterceptor:用于拦截处理请求,可以在请求处理前、处理中和处理后执行特定逻辑。
- Spring Boot的自动配置:通过创建自定义的自动配置类,可以实现对框架和第三方库的自动配置。
- 自定义注解:创建自定义注解,用于实现特定功能或约定,如权限控制、日志记录等。
SpringMVC
2.1MVC分层介绍一下
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
- 视图(view): 为用户提供使用界面,与用户直接进行交互。
- 模型(model): 代表一个存取数据的对象或 JAVA POJO(Plain Old Java Object,简单java对象)。它也可以带有逻辑,主要用于承载数据,并对用户提交请求进行计算的模块。模型分为两类,一类称为数据承载 Bean,一类称为业务处理Bean。所谓数据承载 Bean 是指实体类(如:User类)专门为用户承载业务数据的;而业务处理 Bean 则是指Service 或 Dao 对象, 专门用于处理用户提交请求的。
- 控制器(controller): 用于将用户请求转发给相应的 Model 进行处理,并根据 Model 的计算结果向用户提供相应响应。它使视图与模型分离。
流程步骤:
- 用户通过View 页面向服务端提出请求,可以是表单请求、超链接请求、AJAX 请求等;
- 服务端 Controller 控制器接收到请求后对请求进行解析,找到相应的Model,对用户请求进行处理Model 处理;
- 将处理结果再交给 Controller(控制器其实只是起到了承上启下的作用);
- 根据处理结果找到要作为向客户端发回的响应View 页面,页面经渲染后发送给客户端。
2.2.了解SpringMVC的处理流程吗?
Spring MVC的工作流程如下:
- 用户发送请求至前端控制器DispatcherServlet
- DispatcherServlet收到请求调用处理器映射器HandlerMapping。
- 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
- DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作。
- 执行处理器Handler(Controller,也叫页面控制器)。
- Handler执行完成返回ModelAndView
- HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器
- ViewReslover解析后返回具体View
- DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
- DispatcherServlet响应用户。
SpringBoot
3.1.为什么使用springboot?
- 简化开发:Spring Boot通过提供一系列的开箱即用的组件和自动配置,简化了项目的配置和开发过程,开发人员可以更专注于业务逻辑的实现,而不需要花费过多时间在繁琐的配置上。
- 快速启动:Spring Boot提供了快速的应用程序启动方式,可通过内嵌的Tomcat、Jetty或Undertow等容器快速启动应用程序,无需额外的部署步骤,方便快捷。
- 自动化配置:Spring Boot通过自动配置功能,根据项目中的依赖关系和约定俗成的规则来配置应用程序,减少了配置的复杂性,使开发者更容易实现应用的最佳实践。
3.2.怎么理解SpringBoot中的约定大于配置?
约定大于配置是Spring Boot的核心设计理念,它通过预设合理的默认行为和项目规范,大幅减少开发者需要手动配置的步骤,从而提升开发效率和项目标准化程度。
理解 Spring Boot 中的“约定大于配置”原则,可以从以下几个方面来解释:
- 自动化配置:Spring Boot 提供了大量的自动化配置,通过分析项目的依赖和环境,自动配置应用程序的行为。开发者无需显式地配置每个细节,大部分常用的配置都已经预设好了。例如,引入
spring-boot-starter-web
后,Spring Boot会自动配置内嵌Tomcat和Spring MVC,无需手动编写XML。 - 默认配置:Spring Boot 为诸多方面提供大量默认配置,如连接数据库、设置 Web 服务器、处理日志等。开发人员无需手动配置这些常见内容,框架已做好决策。例如,默认的日志配置可让应用程序快速输出日志信息,无需开发者额外繁琐配置日志级别、输出格式与位置等。
- 约定的项目结构:Spring Boot 提倡特定项目结构,通常主应用程序类(含 main 方法)置于根包,控制器类、服务类、数据访问类等分别放在相应子包,如
com.example.demo.controller
放控制器类,com.example.demo.service
放服务类等。此约定使团队成员更易理解项目结构与组织,新成员加入项目时能快速定位各功能代码位置,提升协作效率。
3.3.过滤器和拦截器的区别?
- 所属规范:过滤器是 Java Servlet 规范的一部分,而拦截器是 Spring 框架提供的机制。
- 执行顺序:过滤器在请求进入 Servlet 容器后,在到达目标 Servlet 或控制器之前执行;拦截器在请求到达控制器之后,在控制器方法执行前后执行。
- 使用范围:过滤器可以对所有类型的请求进行过滤,包括静态资源请求,拦截器只能对 Spring MVC 控制器的请求进行拦截。
- 功能特性:过滤器主要用于对请求和响应进行预处理和后处理,如字符编码处理、请求日志记录等拦截器可以更细粒度地控制控制器方法的执行,如权限验证、性能监控等。
Mybatis
4.1.Mybatis里的 # 和 $ 的区别?
- Mybatis 在处理 #{} 时,会创建预编译的 SQL 语句,,将 SQL 中的 #{} 替换为 ? 号,在执行 SQL 时会为预编译 SQL 中的占位符(?)赋值,调用 PreparedStatement 的 set 方法来赋值,预编译的 SQL 语句执行效率高,并且可以防止SQL 注入,提供更高的安全性,适合传递参数值。
- Mybatis 在处理 ${} 时,只是创建普通的 SQL 语句,然后在执行 SQL 语句时 MyBatis 将参数直接拼入到 SQL 里,不能防止 SQL 注入,因为参数直接拼接到 SQL 语句中,如果参数未经过验证、过滤,可能会导致安全问题。
【另】SQL注入问题:攻击者可以通过构造特殊的输入,使 SQL 语句永远为真,例如在用户名输入框中输入' OR '1'='1' --
,这样 SQL 语句就变成了SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = 'input_password'
,由于'1'='1'
永远为真,所以无论密码是什么,都能成功登录。
使用参数化查询:这是防范 SQL 注入的最有效方法之一。使用参数化查询时,数据库会将用户输入作为参数处理,而不是将其作为 SQL 语句的一部分,这样可以避免用户输入被解析为 SQL 代码。例如,在 Java 中使用PreparedStatement
代替Statement
来执行 SQL 语句。