📃个人主页:个人主页
🔥系列专栏:Java面试专题
目录
1.你知道 Spring 框架中有哪些重要的模块吗?
2. 谈谈你对 IOC 的认识。
3. 谈谈你对 AOP 的认识。
4.在实际写代码时,有没有用到过 AOP?用过的话是在什么地方或者什么场景下?
5.Spring中的事务是如何实现的
6.Transaction 在哪些情况下可能会失效?
7.说说你对 Spring 中的 Bean 的理解。
8. 有哪些方法往 Spring 容器中添加 Bean。
9.Spring框架中的单例bean是线程安全的吗?
10.Spring的bean的生命周期
1.你知道 Spring 框架中有哪些重要的模块吗?
(1)核心层
Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块。
(2)AOP层
AOP:面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强
Aspects:AOP是思想,Aspects是对AOP思想的具体实现
(3)数据层
Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现
(4)Web层
提供创建 Web 应用程序的能力
(5)Test层
Spring主要整合了Junit来完成单元测试和集成测试
2. 谈谈你对 IOC 的认识。
IoC(Inversion of Control)是控制反转的意思,这是一种面向对象编程的设计思想。在不采用这种思想的情况下,我们需要自己维护对象与对象之间的依赖关系,很容易造成对象之间的耦合度过高,在一个大型的项目中这十分的不利于代码的维护。IoC则可以解决这种问题,它可以帮我们维护对象与对象之间的依赖关系,降低对象之间的耦合度。
说到IoC就不得不说DI(Dependency Injection),DI是依赖注入的意思,它是IoC实现的实现方式,就是说IoC是通过DI来实现的。由于IoC这个词汇比较抽象而DI却更直观,所以很多时候我们就用DI来代替它,在很多时候我们简单地将IoC和DI划等号,这是一种习惯。而实现依赖注入的关键是IoC容器,它的本质就是一个工厂。
在具体的实现中,主要由三种注入方式:
-
构造方法注入
就是被注入对象可以在它的构造方法中声明依赖对象的参数列表,让外部知道它需要哪些依赖对象。然后,IoC Service Provider会检查被注入的对象的构造方法,取得它所需要的依赖对象列表,进而为其注入相应的对象。构造方法注入方式比较直观,对象被构造完成后,即进入就绪状态,可以马上使用。
-
setter方法注入
通过setter方法,可以更改相应的对象属性。所以,当前对象只要为其依赖对象所对应的属性添加setter方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中。setter方法注入虽不像构造方法注入那样,让对象构造完成后即可使用,但相对来说更宽松一些, 可以在对象构造完成后再注入。
-
接口注入
相对于前两种注入方式来说,接口注入没有那么简单明了。被注入对象如果想要IoC Service Provider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。IoC Service Provider最终通过这些接口来了解应该为被注入对象注入什么依赖对象。相对于前两种依赖注入方式,接口注入比较死板和烦琐。
总体来说,构造方法注入和setter方法注入因为其侵入性较弱,且易于理解和使用,所以是现在使用最多的注入方式。而接口注入因为侵入性较强,近年来已经不流行了。
🍉扩展阅读🍉
如何使用 IOC
在 Spring5 之后,注解代替配置文件成了编程的主流趋势,下面展示如何使用注解实现 IOC。
首先创建一个配置文件类 MainConfig,这个类使用 @Configuration 修饰,声明它是个配置类:
import org.springframework.context.annotation.Configuration;@Configuration
public class MainConfig {
}
创建具体的 Student 类:
public class Student {private String name;private int age;public int getAge() {return age;}public String getName() {return name;}public void setAge(int age) {this.age = age;}public void setName(String name) {this.name = name;}
}
在未使用 IOC 之前,创建对象实例的方式是通过 new()。比如创建 Student 类时通过 Student student = new Student()
这行代码。而使用 IOC 的话需要把 Student 这个类交给 Spring 容器。在 MainConfig 类中增加以下代码:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MainConfig {@Beanpublic Student student(){return new Student();}
}
被 Spring 容器管理的 Bean 可以通过 @Autowired 注解和 annotationConfigApplicationContext.getBean("student",Student.class);
的方式获取到 Bean 对象。
3. 谈谈你对 AOP 的认识。
AOP(Aspect-Oriented Programming:面向切面编程)翻译为面向切面编程,能够将那些与业务无关的逻辑封装起来,从而减少系统的重复代码,降低模块之间的耦合度,同时也具备着更强的可扩展能力。
AOP的术语:
-
连接点(join point):对应的是具体被拦截的对象,因为Spring只能支持方法,所以被拦截的对象往往就是指特定的方法,AOP将通过动态代理技术把它织入对应的流程中。
-
切点(point cut):有时候,我们的切面不单单应用于单个方法,也可能是多个类的不同方法,这时,可以通过正则式和指示器的规则去定义,从而适配连接点。切点就是提供这样一个功能的概念。
-
通知(advice):就是按照约定的流程下的方法,分为前置通知、后置通知、环绕通知、事后返回通知和异常通知,它会根据约定织入流程中。
-
目标对象(target):即被代理对象。
-
引入(introduction):是指引入新的类和其方法,增强现有Bean的功能。
-
织入(weaving):它是一个通过动态代理技术,为原有服务对象生成代理对象,然后将与切点定义匹配的连接点拦截,并按约定将各类通知织入约定流程的过程。
-
切面(aspect):是一个可以定义切点、各类通知和引入的内容,SpringAOP将通过它的信息来增强Bean的功能或者将对应的方法织入流程。
Spring AOP:
AOP可以有多种实现方式,而Spring AOP支持如下两种实现方式。
-
JDK动态代理:这是Java提供的动态代理技术,可以在运行时创建接口的代理实例。Spring AOP默认采用这种方式,在接口的代理实例中织入代码。
-
CGLib动态代理:采用底层的字节码技术,在运行时创建子类代理的实例。当目标对象不存在接口时,Spring AOP就会采用这种方式,在子类实例中织入代码。
4.在实际写代码时,有没有用到过 AOP?用过的话是在什么地方或者什么场景下?
记录操作日志,缓存,spring实现的事务
我曾经写过一个自定义日志的功能是通过 AOP 实现,核心是:使用aop中的环绕通知+切点表达式(找到要记录日志的方法)通过环绕通知的参数获取请求方法的参数(类、方法、注解、请求方式等),获取到这些参数以后,保存到数据库。
5.Spring中的事务是如何实现的
Spring提供了两种事务管理方式:编程式事务管理和声明式事务管理。
编程式事务管理需要在代码中显式地声明事务的开始和结束,并手动处理事务的提交或回滚。这种方式比较灵活,但也比较繁琐。
声明式事务管理则是通过配置来实现事务管理,不需要在代码中显式地声明事务的开始和结束。声明式事务管理可以使用XML或注解方式进行配置。XML方式需要在Spring的配置文件中定义事务管理器和事务切面,而注解方式则使用@Transactional注解来标记需要进行事务管理的方法。
6.Transaction 在哪些情况下可能会失效?
①当用 Transactional 修饰非 public 方法时,Transactional 注解是不会生效的。
因为底层cglib是基于父子类来实现的,子类是不能重载父类的private方法的,所以无法很好的利用代理
②如果在一个类的内部调用了事务方法,Transactional 也不会生效。
开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
这是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
③默认情况下只会在捕获了 RuntimeException 后 Transactional 注解才会生效,如果在代码中捕获了异常后未抛出,则 Transactional 失效。
7.说说你对 Spring 中的 Bean 的理解。
Spring 中的 Bean 简单来讲就是一个个被 Spring 容器管理的 Java 对象,我们写了一个类之后,这个类只是一个单纯的 Java 类,可以通过 new() 的方式去创建它。当我们把这个类添加到 Spring 的容器里之后,这个类就变成了 Bean,由 Spring 容器管理,可以通过自动注入的方式去使用。
8. 有哪些方法往 Spring 容器中添加 Bean。
常用的添加 Bean 的方式主要有以下几种方法:
- @Bean: 写一个普通的类时最常用的添加 Bean 的方式
- @ComponentScan + @Controller @Service @Component @Repository:@ComponentScan 用来扫描后面的四个注解,被后面四个注解修饰的类如果被 @ComponentScan 扫描到,就会注册到 Spring 容器中。
- @Import:通过导入的方式注入 Bean。
- @ImportBeanDefinitionRegister:和 Import 类似,可以指定 Bean 的名称。
9.Spring框架中的单例bean是线程安全的吗?
不是线程安全的
Spring框架中有一个@Scope注解,默认的值就是singleton,单例的。因为一般在spring的bean的中都是注入无状态的对象,没有线程安全问题,如果在bean中定义了可修改的成员变量,是要考虑线程安全问题的,可以使用多例或者加锁来解决。
10.Spring的bean的生命周期
①通过BeanDefinition获取bean的定义信息
②调用构造函数实例化bean
③bean的依赖注入
④处理Aware接口(BeanNameAware、 BeanFactoryAware、ApplicationContextAware)
⑤Bean的后置处理器BeanPostProcessor-前置
⑥初始化方法(InitializingBean、init-method)
⑦Bean的后置处理器BeanPostProcessor-后置
⑧销毁bean