文章目录
- 一:常见面试题
- 1:什么是Spring框架?
- 1.1:spring官网中文
- 1.2:spring官网英文
- 2:谈谈自己对于Spring IOC和AOP的理解
- 2.1:IOC
- Spring Bean 的生命周期主要包括以下步骤:
- 2.2:AOP
- JDK 动态代理:
- CGLIB 动态代理:
- 3:Spring中的bean的作用域有哪些?
- 4:Spring中的单例bean的线程安全问题了解吗?
- 4.1:解决单例Bean的线程安全问题
- 5:说说自己对于Spring MVC的了解?
- 5.1:流程说明:
- 6:Spring框架中用到了哪些设计模式
- 7:@Component和@Bean的区别是什么
- 7.1:@Bean注解的使用示例:
- 8:将一个类声明为Spring的bean的注解有哪些?
- 9:Spring事务管理的方式有几种?
- 9.1:编程式事务:在代码中硬编码(不推荐使用)。
- 9.2:声明式事务:在配置文件中配置(推荐使用),分为基于XML的声明式事务和基于注解的声明式事务。
- 9.3:spring事务总结
- 10:高并发下数据影响
- 10.1:脏读:
- 10.2:丢失修改:
- 10.3:不可重复读:
- 10.4:幻读:
- 11:4种事务特性
- 11.1:原子性 (atomicity):
- 11.2:一致性 (consistency):
- 11.3:隔离性 (isolation):
- 11.4:持久性(durability) :
- 12:设置事务隔离级别(5种)
- 12.1:读未提交(read uncommited) :
- 12.2:读已提交 (read commited):
- 12.3:可重复读 (repeatable read) :
- 12.4:串行化的 (serializable) :
- 13:事务的传播行为
- 13.1: REQUIRED(默认):
- 13.2:SUPPORTS:
- 13.3:MANDATORY:
- 13.4.:REQUIRES_NEW:
- 13.5.:NOT_SUPPORTED:
- 13.6: NEVER:
- 13.7.:NESTED:
- 14:解释自动装配的各种模式?
- 14.1:no
- 14.2:byName
- 14.3:byType
- 14.4:constructor
- 14.5:autodetect
- 15:有哪些不同类型的IOC(依赖注入)?
- 15.1:构造器依赖注入
- 15.2:Setter方法依赖注入
- 16:循环依赖
- 16.1:Spring采用了三级缓存
- 16.2:什么是早期暴露的对象
- 16.3:三个步骤:
- 17:@Autowired 与@Resource的区别
- 17.1:来源
- 17.2. 装配方式
- 17.3. 可选性
- 17.4. 适用范围
- 18:AOP常用注解
- 18.1:AOP(面向切面编程)在Spring中常用的注解包括
一:常见面试题
1:什么是Spring框架?
1.1:spring官网中文
1.2:spring官网英文
- Spring是一种轻量级框架,旨在提高开发人员的开发效率以及系统的可维护性。
- 我们一般说的Spring框架就是Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。
- 比如Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现IOC和DI的基础,AOP组件用来实现面向切面编程。
- 核心技术:依赖注入(DI),AOP,事件(Events),资源,i18n,验证,数据绑定,类型转换,SpEL。
- 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。
- 数据访问:事务,DAO支持,JDBC,ORM,编组XML。
- Web支持:Spring MVC和Spring WebFlux Web框架。
- 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
- 语言:Kotlin,Groovy,动态语言。
2:谈谈自己对于Spring IOC和AOP的理解
2.1:IOC
- (Inversion Of Controll,控制反转 )是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由给Spring框架来管理。IOC在其他语言中也有应用,并非Spring特有。
- IOC容器是Spring用来实现IOC的载体,IOC容器实际上就是一个Map(key, value),Map中存放的是各种对象。
- 将对象之间的相互依赖关系交给IOC容器来管理,并由IOC容器完成对象的注入。
- 在没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
- 在引入IOC容器之后,这种情形就完全改变了,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
- 所以IOC也叫依赖注入(DI),IOC是控制反转“获得依赖对象的过程被反转了”。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
- 并且IOC容器还可以管理bean的生命周期
Spring Bean 的生命周期主要包括以下步骤:
- 实例化 Bean:Spring IoC 容器首先根据 Bean 的定义信息(如 XML 配置、注解或 Java 配置)创建 Bean 的实例。
- 设置 Bean 属性:Spring IoC 容器通过反射机制调用 Bean 的 setter 方法或使用构造函数来设置 Bean 的属性。
- 调用 BeanNameAware 的 setBeanName 方法:如果 Bean 实现了 BeanNameAware 接口,Spring 会调用它的 setBeanName 方法,传入 Bean 的 ID。
- 调用 BeanFactoryAware 的 setBeanFactory 方法:如果 Bean 实现了 BeanFactoryAware 接口,Spring 会调用它的 setBeanFactory 方法,传入当前的 BeanFactory。
- 调用 ApplicationContextAware 的 setApplicationContext 方法:如果 Bean 实现了 ApplicationContextAware 接口,Spring 会调用它的 setApplicationContext 方法,传入当前的 ApplicationContext。
- 调用 BeanPostProcessor 的 postProcessBeforeInitialization 方法:Spring 会调用所有注册的 BeanPostProcessor 的 postProcessBeforeInitialization 方法,这是一个扩展点,可以在 Bean 初始化前添加自定义逻辑。
- 调用 InitializingBean 的 afterPropertiesSet 方法:如果 Bean 实现了 InitializingBean 接口,Spring 会调用它的 afterPropertiesSet 方法。
- 调用自定义的 init 方法:如果在 Bean 的定义信息中配置了 init-method,Spring 会调用这个方法。
- 调用 BeanPostProcessor 的 postProcessAfterInitialization 方法:Spring 会调用所有注册的 BeanPostProcessor 的 postProcessAfterInitialization 方法,这是一个扩展点,可以在 Bean 初始化后添加自定义逻辑。
以上步骤完成后,Bean 就已经准备好了,可以被应用程序使用。
当容器关闭时,Bean 的销毁过程如下:
- 调用 DisposableBean 的 destroy 方法:如果 Bean 实现了 DisposableBean 接口,Spring 会调用它的 destroy 方法。
- 调用自定义的 destroy 方法:如果在 Bean 的定义信息中配置了 destroy-method,Spring 会调用这个方法。
2.2:AOP
- (Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。
- Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理。
JDK 动态代理:
- JDK 动态代理主要通过实现接口的方式进行代理,因此它只能代理实现了接口的类。
- JDK 动态代理的实现原理是,通过实现 InvocationHandler 接口和使用 Proxy 类的 newProxyInstance 方法生成代理对象。在 InvocationHandler 的 invoke 方法中,我们可以添加自定义的逻辑。
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在目标方法执行前后添加自定义逻辑
System.out.println("Before method");
Object result = method.invoke(target, args);
System.out.println("After method");
return result;
}
}
// 使用示例
MyInterface target = new MyInterfaceImpl();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyInvocationHandler(target)
);
proxy.myMethod();
CGLIB 动态代理:
- CGLIB 动态代理不需要实现接口,它主要通过继承的方式进行代理,因此无法代理 final 类。
- CGLIB 动态代理的实现原理是,通过继承被代理类,并在子类中覆盖父类的方法实现代理。在覆盖的方法中,我们可以添加自定义的逻辑。
public class MyMethodInterceptor implements MethodInterceptor {
private Object target;
public MyMethodInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 在目标方法执行前后添加自定义逻辑
System.out.println("Before method");
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method");
return result;
}
}
// 使用示例
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MyClass.class);
enhancer.setCallback(new MyMethodInterceptor(new MyClass()));
MyClass proxy = (MyClass) enhancer.create();
proxy.myMethod();
3:Spring中的bean的作用域有哪些?
1.singleton:唯一bean实例,Spring中的bean默认都是单例的。
2.prototype:每次请求都会创建一个新的bean实例。
3.request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
4.session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
5.global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。
4:Spring中的单例bean的线程安全问题了解吗?
- Spring的单例Bean默认是线程安全的,因为Spring容器会确保在多线程环境下正确地管理单例Bean的创建和使用。但是,如果单例Bean中包含可变状态,可能会引发线程安全问题。
4.1:解决单例Bean的线程安全问题
- 可以采取以下几种方式:
- 使用不可变对象:尽量将单例Bean设计为不可变对象,避免包含可变状态。
- 使用局部变量:在方法中使用局部变量而不是实例变量,以避免多个线程共享状态。
- 同步关键部分:如果无法避免可变状态,可以使用synchronized关键字或者ReentrantLock等机制来保护关键部分的代码,确保线程安全。
- 使用线程安全的集合:如果单例Bean需要维护集合等数据结构,可以使用ConcurrentHashMap、CopyOnWriteArrayList等线程安全的集合类。
- 使用ThreadLocal:对于需要线程本地副本的数据,可以使用ThreadLocal来保证线程安全。
通过以上方式,可以确保Spring的单例Bean在多线程环境下能够安全地被使用。
5:说说自己对于Spring MVC的了解?
- MVC是一种设计模式,Spring MVC是一款很优秀的MVC框架。Spring MVC可以帮助我们进行更简洁的Web层的开发,并且它天生与Spring框架集成。Spring MVC下我们一般把后端项目分为Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前台页面)。
5.1:流程说明:
1.客户端(浏览器)发送请求,直接请求到DispatcherServlet。
2.DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler。
3.解析到对应的Handler(也就是我们平常说的Controller控制器)。
4.HandlerAdapter会根据Handler来调用真正的处理器来处理请求和执行相对应的业务逻辑。
5.处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是逻辑上的View。
6.ViewResolver会根据逻辑View去查找实际的View。
7.DispatcherServlet把返回的Model传给View(视图渲染)。
8.把View返回给请求者(浏览器)。
6:Spring框架中用到了哪些设计模式
1.工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。
2.代理设计模式:Spring AOP功能的实现。
3.单例设计模式:Spring中的bean默认都是单例的。
4.模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。
5.包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
6.观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。
7.适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。
7:@Component和@Bean的区别是什么
- 作用对象不同。@Component注解作用于类,而@Bean注解作用于方法。
- @Component注解通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan注解定义要扫描的路径)。@Bean注解通常是在标有该注解的方法中定义产生这个bean,告诉Spring这是某个类的实例,当我需要用它的时候还给我。
- @Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册bean。比如当引用第三方库的类需要装配到Spring容器的时候,就只能通过@Bean注解来实现。
7.1:@Bean注解的使用示例:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
- 上面的代码相当于下面的XML配置:
<beans>
<bean id="transferService" class="com.yanggb.TransferServiceImpl"/>
</beans>
8:将一个类声明为Spring的bean的注解有哪些?
- @Repository,这个注解和@Component、@Controller和我们最常见的@Service注解是一个作用,都可以将一个类声明为一个Spring的Bean。它们的区别到不在于具体的语义上,更多的是在于注解的定位上。之前说过,企业级应用注重分层开发的概念,因此,对这四个相似的注解应当有以下的理解:
- @Repository注解,对应的是持久层即Dao层,其作用是直接和数据库交互,通常来说一个方法对应一条具体的Sql语句
- @Service注解,对应的是服务层即Service层,其作用是对单条/多条Sql语句进行组合处理,当然如果简单的话就直接调用Dao层的某个方法了
- @Controller注解,对应的是控制层即MVC设计模式中的控制层,其作用是接收用户请求,根据请求调用不同的Service取数据,并根据需求对数据进行组合、包装返回给前端
- @Component注解,这个更多对应的是一个组件的概念,如果一个Bean不知道属于拿个层,可以使用@Component注解标注
9:Spring事务管理的方式有几种?
9.1:编程式事务:在代码中硬编码(不推荐使用)。
9.2:声明式事务:在配置文件中配置(推荐使用),分为基于XML的声明式事务和基于注解的声明式事务。
9.3:spring事务总结
Spring事务和数据库事务都是为了保证数据的一致性和完整性。它们之间的关系可以从以下几个方面来理解:
- 封装:Spring事务是对数据库事务的封装。Spring提供了一套抽象的事务管理接口,使得我们可以在不考虑底层数据库具体实现的情况下,进行事务管理。
- 管理:Spring事务管理器可以管理多个数据库事务,实现分布式事务管理。这是数据库事务本身无法做到的。
- 传播行为:Spring事务提供了丰富的事务传播行为,如REQUIRED、REQUIRES_NEW、NESTED等,这些都是数据库事务所不具备的。
- 隔离级别:Spring事务可以设置不同的隔离级别,如READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE等,这些都是基于数据库事务的隔离级别。
- 异常处理:Spring事务对于运行时异常和检查型异常都会进行回滚,而数据库事务只对运行时异常进行回滚。
总的来说,Spring事务是对数据库事务的一种高级封装和扩展,使得我们在进行事务管理时,更加方便
10:高并发下数据影响
10.1:脏读:
当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据。
10.2:丢失修改:
指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。
10.3:不可重复读:
一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。
10.4:幻读:
一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,
11:4种事务特性
11.1:原子性 (atomicity):
强调事务的不可分割
11.2:一致性 (consistency):
事务的执行的前后数据的完整性保持一致.
11.3:隔离性 (isolation):
一个事务执行的过程中,不应该受到其他事务的干扰
11.4:持久性(durability) :
事务一旦结束,数据就持久到数据库
12:设置事务隔离级别(5种)
- DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
12.1:读未提交(read uncommited) :
脏读,不可重复读,虚读都有可能发生
12.2:读已提交 (read commited):
避免脏读。但是不可重复读和虚读有可能发生
12.3:可重复读 (repeatable read) :
避免脏读和不可重复读.但是虚读有可能发生.
12.4:串行化的 (serializable) :
避免以上所有读问题.
Mysql 默认:可重复读
Oracle 默认:读已提交
13:事务的传播行为
Spring事务的传播机制是指在一个方法调用另一个方法时,如何处理事务的传播。以下是Spring事务传播机制的一些要点:
13.1: REQUIRED(默认):
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
13.2:SUPPORTS:
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式执行。
13.3:MANDATORY:
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
13.4.:REQUIRES_NEW:
创建一个新的事务,如果当前存在事务,则挂起当前事务。
13.5.:NOT_SUPPORTED:
以非事务方式执行操作,如果当前存在事务,则挂起当前事务。
13.6: NEVER:
以非事务方式执行操作,如果当前存在事务,则抛出异常。
13.7.:NESTED:
如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。
- 这些传播行为可以通过@Transactional注解的propagation属性来指定。
14:解释自动装配的各种模式?
自动装配提供五种不同的模式供Spring容器用来自动装配beans之间的依赖注入:
14.1:no
默认模式,不进行自动装配,需要手动指定依赖注入
14.2:byName
根据属性名自动装配,Spring容器会自动装配和Bean的属性名相同的Bean。
14.3:byType
根据属性类型自动装配,Spring容器会自动装配和Bean属性类型相同的Bean。
14.4:constructor
根据构造函数自动装配,Spring容器会自动装配构造函数参数类型相同的Bean。
14.5:autodetect
如果有默认的构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。
15:有哪些不同类型的IOC(依赖注入)?
15.1:构造器依赖注入
构造器依赖注入在容器触发构造器的时候完成,该构造器有一系列的参数,每个参数代表注入的对象。
15.2:Setter方法依赖注入
首先容器会触发一个无参构造函数或无参静态工厂方法实例化对象,之后容器调用bean中的setter方法完成Setter方法依赖注入。
16:循环依赖
- 循环依赖是什么?
- 这个就是两个Bean之间循环依赖导致的栈溢出,原因是IOC创建 A的时候,发现需要new B ,于是又去创建B,创建B的时候,发现又需要A…
16.1:Spring采用了三级缓存
- 1,第一级缓存:用于存放完全初始化好的单例Bean实例。
- 2,第二级缓存:用于存放早期暴露的单例Bean实例,即在实例化过程中,尚未完成属性填充的Bean。
- 3,第三级缓存:用于存放Bean工厂,即用于创建Bean实例的工厂。
16.2:什么是早期暴露的对象
- 所谓的早提提前暴露的对象就是说,你是一个不完整的对象,你的属性还没有值,你的对象也没有被初始化。这就是早期暴露的对象,只是提前拿出来给你认识认识。但他非常重要。这是多级缓存解决循环依赖问题的一个巧妙的地方。
16.3:三个步骤:
- 1:实例化2:填充属性3:初始化
1:创建A,第一步就在三级缓存中实例化,分配内存空间,产生内存地址引用,但是这个时候还是null。
2:帮A进行填充属性,发现有需要B,于是去容器中找B的引用。
3:当然找不到B,所以要创建B。
4:创建B跟创建A步骤一样,先实例化,分配内存,保存引用。
5:发现填充B需要A,于是回去容器找A。
6:一级缓存没有,二级缓存没有,三级缓存找到了A的引用,于是返回(此时的A还是null,但是总比没有好),同时把A移到二级缓存中,此时B的引用已经及找到并且可以给到A,所以这个没问题。
7:B拿着A的引用,首先成功初始化。然后返回。
8:A获得初始化成功的B,也成功初始化。
9:至此全流程结束,循环引用问题完美解决~
17:@Autowired 与@Resource的区别
@Autowired 和 @Resource 的区别如下:
17.1:来源
- @Autowired 是 Spring 框架提供的注解。
- @Resource 是 Java EE 提供的注解,也被 Spring 框架支持。
17.2. 装配方式
- @Autowired 根据类型进行自动装配。
- @Resource 根据名称进行自动装配。
17.3. 可选性
- @Autowired 是非必需的,如果找不到匹配的 Bean,属性可以为 null。
- @Resource 是必需的,如果找不到匹配的 Bean,会抛出异常。
17.4. 适用范围
- @Autowired 适用于构造函数、setter 方法、字段和配置方法。
- @Resource 主要用于字段和配置方法。
总的来说,@Autowired 更灵活,而 @Resource 更严格。
18:AOP常用注解
使用AOP注解的步骤如下:
-
创建一个切面类,使用@Aspect注解标记。
-
在切面类中定义切点,使用@Pointcut注解标记。
-
定义通知方法,使用@Before、@After、@Around等注解标记。
-
在Spring配置文件中启用AOP,通常使用aop:aspectj-autoproxy标签。
-
在需要应用切面的Bean上添加相应的切点注解,如@Before、@After等。
这样就可以使用AOP注解来实现面向切面编程。
18.1:AOP(面向切面编程)在Spring中常用的注解包括
-
@Aspect:用于定义切面,结合其他注解定义切点和通知。
-
@Pointcut:定义切点,指定在哪些方法上应用通知。
-
@Before:在目标方法执行前执行通知。
-
@After:在目标方法执行后执行通知。
-
@Around:在目标方法执行前后执行通知,可以控制目标方法的执行。
-
@AfterReturning:在目标方法正常返回后执行通知。
-
@AfterThrowing:在目标方法抛出异常后执行通知。