1、Spring 是什么?特性?有哪些模块?
Spring 是一个轻量级、非入侵式的控制反转 (IoC) 和面向切面 (AOP) 的框架。
1.1 Spring 有哪些特性呢?
- IoC 和 DI 的支持:Spring 的核心就是一个大的工厂容器,可以维护所有对象的创建和依赖关系,Spring 工厂用于生成 Bean,并且管理 Bean 的生命周期,实现高内聚低耦合的设计理念。
- AOP 编程的支持:Spring 提供了面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等切面功能。
- 声明式事务的支持:支持通过配置就来完成对事务的管理,而不需要通过硬编码的方式,以前重复的一些事务提交、回滚的 JDBC 代码,都可以不用自己写了。
- 快捷测试的支持:Spring 对 Junit 提供支持,可以通过注解快捷地测试 Spring 程序。
- 快速集成功能:方便集成各种优秀框架,Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz 等)的直接支持。
- 复杂 API 模板封装:Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了模板化的封装,这些封装 API 的提供使得应用难度大大降低。
1.2 简单说一下什么是AOP 和 IoC?
AOP:面向切面编程,是一种编程范式,它的主要作用是将那些与核心业务逻辑无关,但是对多个对象产生影响的公共行为封装起来,如日志记录、性能统计、事务等。
IoC:控制反转,是一种设计思想,它的主要作用是将对象的创建和对象之间的调用过程交给 Spring 容器来管理。
2、Spring 有哪些模块呢?
Spring 框架是分模块存在,除了最核心的 Spring Core Container 是必要模块之外,其他模块都是可选,大约有 20 多个模块。最主要的七大模块:
- Spring Core:Spring 核心,它是框架最基础的部分,提供 IoC 和依赖注入 DI 特性。
- Spring Context:Spring 上下文容器,它是 BeanFactory 功能加强的一个子接口。
- Spring Web:它提供 Web 应用开发的支持。
- Spring MVC:它针对 Web 应用中 MVC 思想的实现。
- Spring DAO:提供对 JDBC 抽象层,简化了 JDBC 编码,同时,编码更具有健壮性。
- Spring ORM:它支持用于流行的 ORM 框架的整合,比如:Spring + Hibernate、Spring + iBatis、Spring + JDO 的整合等。
- Spring AOP:即面向切面编程,它提供了与 AOP 兼容的编程实现。
3、Spring 有哪些常用注解呢?
Spring 提供了大量注解简化 Java 应用的开发和配置,主要用于 Web 开发、往容器注入 Bean、AOP、事务控制等。
3.1 Web 开发方面有哪些注解呢?
- @Controller:用于标注控制层组件。
- @RestController:是 @Controller 和 @ResponseBody 的结合体,返回 JSON 数据时使用。
- @RequestMapping:用于映射请求 URL 到具体的方法上,还可以细分为:
@PostMapping:只能用于处理 POST 请求
@DeleteMapping:只能用于处理 DELETE 请求
@PutMapping:只能用于处理 PUT 请求
@GetMapping:只能用于处理 GET 请求 - @ResponseBody:直接将返回的数据放入 HTTP 响应正文中,一般用于返回 JSON 数据。
- @RequestBody:表示一个方法参数应该绑定到 Web 请求体。
- @PathVariable:用于接收路径参数,比如 @RequestMapping(“/hello/{name}”),name 就是路径参数。
- @RequestParam:用于接收请求参数。比如 @RequestParam(name = “key”) String key,key 就是请求参数。
3.2 容器类注解有哪些呢?
- @Component:标识一个类为 Spring 组件,使其能够被 Spring 容器自动扫描和管理。
- @Controller:标识一个控制器组件(控制层)。
- @Service:标识一个业务逻辑组件(服务层)。
- @Repository:标识一个数据访问组件(持久层)。
- @Autowired:按类型自动注入依赖。
- @Resource:按名称自动注入依赖。
- @Configuration:用于定义配置类,可替换 XML 配置文件。
- @Value:用于将 Spring Boot 中 application.properties 配置的属性值赋值给变量。
3.3 AOP 方面有哪些注解呢?
@Aspect 用于声明一个切面,可以配合其他注解一起使用,比如:
@After:在方法执行之后执行。
@Before:在方法执行之前执行。
@Around:方法前后均执行。
@PointCut:定义切点,指定需要拦截的方法。
3.4 事务注解有哪些?
主要就是 @Transactional,用于声明一个方法需要事务支持。
4、Spring 中应用了哪些设计模式呢?
- 工厂模式:BeanFactory 和 ApplicationContext,实现 Bean 的创建和管理。
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean myBean = context.getBean(MyBean.class);
- 单例模式:这样可以保证 Bean 的唯一性,减少系统开销。
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService1 = context.getBean(MyService.class);
MyService myService2 = context.getBean(MyService.class);
// This will print "true" because both references point to the same instance
System.out.println(myService1 == myService2);
- 代理模式:来实现 AOP 横切关注点(如事务管理、日志记录、权限控制等)
@Transactional
public void myTransactionalMethod() {
// 方法实现
}
4.1 Spring如何实现单例模式?
Spring 通过 IOC 容器实现单例模式,具体步骤是:单例 Bean 在容器初始化时创建并使用 DefaultSingletonBeanRegistry 提供的 singletonObjects 进行缓存。在请求 Bean 时,Spring 会先从缓存中获取。
// 单例缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
public Object getSingleton(String beanName) {
return this.singletonObjects.get(beanName);
}
protected void addSingleton(String beanName, Object singletonObject) {
this.singletonObjects.put(beanName, singletonObject);
}
5、Spring 容器、Web 容器之间的区别?
Spring 容器是 Spring 框架的核心部分,负责管理应用程序中的对象生命周期和依赖注入。
Web 容器(也称 Servlet 容器),是用于运行 Java Web 应用程序的服务器环境,支持 Servlet、JSP 等 Web 组件。常见的 Web 容器包括 Apache Tomcat、Jetty等。
Spring MVC 是 Spring 框架的一部分,专门用于处理 Web 请求,基于 MVC(Model-View-Controller)设计模式。
6、说一说什么是 IoC、DI?
所谓的 IoC,就是由容器来控制对象的生命周期和对象之间的关系。控制对象生命周期的不再是引用它的对象,而是容器,这就叫控制反转(Inversion of Control)。于是,对于某个对象来说,以前是它控制它依赖的对象,现在是所有对象都被 Spring 控制。IOC 是一种思想,DI 是实现 IOC 的具体方式,比如说利用注入机制(如构造器注入、Setter 注入)将依赖传递给目标对象。
6.1 为什么要使用 IoC 呢?
在平时的 Java 开发中,如果我们要实现某一个功能,可能至少需要两个以上的对象来协助完成,在没有 Spring 之前,每个对象在需要它的合作对象时,需要自己 new 一个,比如说 A 要使用 B,A 就对 B 产生了依赖,也就是 A 和 B 之间存在了一种耦合关系。有了 Spring 之后,就不一样了,创建 B 的工作交给了 Spring 来完成,Spring 创建好了 B 对象后就放到容器中,A 告诉 Spring 我需要 B,Spring 就从容器中取出 B 交给 A 来使用。这就是 IoC 的好处,它降低了对象之间的耦合度,使得程序更加灵活,更加易于维护。
7、说说 BeanFactory 和 ApplicantContext?
BeanFactory 位于整个 Spring IoC 容器的顶端,ApplicationContext 算是 BeanFactory 的子接口。最主要的方法就是 getBean(),这个方法负责从容器中返回特定名称或者类型的 Bean 实例。ApplicationContext 继承了 HierachicalBeanFactory 和 ListableBeanFactory 接口,是 BeanFactory 的自动挡版本,是 Spring 应用的默认方式。
8、你知道 Spring 容器启动阶段会干什么吗?
Spring 的 IoC 容器工作的过程可以划分为两个阶段:容器启动阶段和 Bean 实例化阶段。其中容器启动阶段主要做的工作是加载和解析配置文件,保存到对应的 Bean 定义中。
8.1 说说 Spring 的 Bean 实例化方式
- 构造方法的方式:在类上使用 @Component(或@Service、@Repository 等特定于场景的注解)标注类,然后通过构造方法注入依赖。
@Component
public class ExampleBean {
private DependencyBean dependency;
@Autowired
public ExampleBean(DependencyBean dependency) {
this.dependency = dependency;
}
}
- 静态工厂的方式:在这种方式中,Bean 是由一个静态方法创建的,而不是直接通过构造方法。
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
- 实例工厂方法的方式:与静态工厂方法相比,实例工厂方法依赖于某个类的实例来创建 Bean。这通常用在需要通过工厂对象的非静态方法来创建 Bean 的场景。
public class ServiceLocator {
public ClientService createClientServiceInstance() {
return new ClientService();
}
}
- FactoryBean 接口的方式:FactoryBean 是一个特殊的 Bean 类型,可以在 Spring 容器中返回其他对象的实例。通过实现 FactoryBean 接口,可以自定义实例化逻辑,这对于构建复杂的初始化逻辑非常有用。
public class ToolFactoryBean implements FactoryBean<Tool> {
private int factoryId;
private int toolId;
@Override
public Tool getObject() throws Exception {
return new Tool(toolId);
}
@Override
public Class<?> getObjectType() {
return Tool.class;
}
@Override
public boolean isSingleton() {
return true;
}
// setter and getter methods for factoryId and toolId
}
9、你是怎么理解 Bean 的?
Bean 是指由 Spring 容器管理的对象,它的生命周期由容器控制,包括创建、初始化、使用和销毁。通过三种方式声明:注解方式、XML 配置、Java 配置。
- 使用 @Component、@Service、@Repository、@Controller 等注解定义,主流。
- 基于 XML 配置,Spring Boot 项目已经不怎么用了。
- 使用 Java 配置类创建 Bean:
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
}
9.1 @Component 和 @Bean 的区别
@Component 是 Spring 提供的一个类级别注解,由 Spring 自动扫描并注册到 Spring 容器中。@Bean 是一个方法级别的注解,用于显式地声明一个 Bean,当我们需要第三方库或者无法使用 @Component 注解类时,可以使用 @Bean 来将其实例注册到容器中。
10、能说一下 Bean 的生命周期吗?
Bean 的生命周期大致分为五个阶段:
- 实例化:Spring 容器根据 Bean 的定义创建 Bean 的实例,相当于执行构造方法,也就是 new 一个对象。
- 属性赋值:相当于执行 setter 方法为字段赋值。
- 初始化:初始化阶段允许执行自定义的逻辑,比如设置某些必要的属性值、开启资源、执行预加载操作等,以确保 Bean 在使用之前是完全配置好的。
- 销毁:相当于执行 = null,释放资源。
11、为什么 IDEA 不推荐使用 @Autowired 注解注入 Bean?
这是因为字段注入的方式不能像构造方法那样使用 final 注入不可变对象。
11.1 @Autowired 和 @Resource 注解的区别?
@Autowired 是 Spring 提供的注解,按类型(byType)注入。
@Resource 是 Java EE 提供的注解,按名称(byName)注入。
12、Spring 有哪些自动装配的方式?
- byName:根据名称进行自动匹配。
- byType:根据类型进行自动匹配。
- constructor:与 byType 类似, 针对构造函数注入。
- autodetect:如果 Bean 提供无参构造函数,则采用 byType,否则采用 constructor。
13、Bean 的作用域有哪些?
在 Spring 中,Bean 默认是单例的,即在整个 Spring 容器中,每个 Bean 只有一个实例。可以通过在配置中指定 scope 属性,将 Bean 改为多例(Prototype)模式,这样每次获取的都是新的实例。
@Bean
@Scope("prototype") // 每次获取都是新的实例
public MyBean myBean() {
return new MyBean();
}
除了单例和多例,Spring 还支持其他作用域,如请求作用域(Request)、会话作用域(Session)等,适合 Web 应用中特定的使用场景。
- request:每一次 HTTP 请求都会产生一个新的 Bean,该 Bean 仅在当前 HTTP Request 内有效。
- session:同一个 Session 共享一个 Bean,不同的 Session 使用不同的 Bean。
- globalSession:同一个全局 Session 共享一个 Bean,Spring 5 中已经移除。
14、Spring 中的单例 Bean 会存在线程安全问题吗?
Spring Bean 的默认作用域是单例(Singleton),这意味着 Spring 容器中只会存在一个 Bean 实例,并且该实例会被多个线程共享。如果单例 Bean 是无状态的,也就是没有成员变量,那么这个单例 Bean 是线程安全的。比如 Spring MVC 中的 Controller、Service、Dao 等,基本上都是无状态的。但如果 Bean 的内部状态是可变的,且没有进行适当的同步处理,就可能出现线程安全问题。
14.1 单例 Bean 线程安全问题怎么解决呢?
- 使用局部变量,局部变量是线程安全的,因为每个线程都有自己的局部变量副本。尽量使用局部变量而不是共享的成员变量。
- 尽量使用无状态的 Bean,即不在 Bean 中保存任何可变的状态信息。、
- 同步访问。如果 Bean 中确实需要保存可变状态,可以通过 synchronized 关键字或者 Lock 接口来保证线程安全。
public class MyService {
private int sharedVar;
public synchronized void increment() {
sharedVar++;
}
}
或者将 Bean 中的成员变量保存到 ThreadLocal 中,ThreadLocal 可以保证多线程环境下变量的隔离。
public class MyService {
private ThreadLocal<Integer> localVar = ThreadLocal.withInitial(() -> 0);
public void process() {
localVar.set(localVar.get() + 1);
}
}
再或者使用线程安全的工具类,比如说 AtomicInteger、ConcurrentHashMap、CopyOnWriteArrayList 等。
public class MyService {
private ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
public void putValue(String key, String value) {
map.put(key, value);
}
}
- 将 Bean 定义为原型作用域(Prototype)。原型作用域的 Bean 每次请求都会创建一个新的实例,因此不存在线程安全问题。
15、说说循环依赖?
A 依赖 B,B 依赖 A,或者 C 依赖 C,就成了循环依赖。
15.1 Spring 可以解决哪些情况的循环依赖?
第四种可以,第五种不可以是因为 Spring 在创建 Bean 时默认会根据自然排序进行创建,所以 A 会先于 B 进行创建。
16、Spring 怎么解决循环依赖呢?
Spring 通过三级缓存机制来解决循环依赖:
- 一级缓存:存放完全初始化好的单例 Bean。
- 二级缓存:存放正在创建但未完全初始化的 Bean 实例。
- 三级缓存:存放 Bean 工厂对象,用于提前暴露 Bean。
16.1 三级缓存解决循环依赖的过程是什么样的?
- 实例化 Bean 时,将其早期引用放入三级缓存。
- 其他依赖该 Bean 的对象,可以从缓存中获取其引用。
- 初始化完成后,将 Bean 移入一级缓存。
假如 A、B 两个类发生循环依赖:
A 实例的初始化过程:
-
创建 A 实例,实例化的时候把 A 的对象⼯⼚放⼊三级缓存,表示 A 开始实例化了,虽然这个对象还不完整,但是先曝光出来让大家知道。
-
A 注⼊属性时,发现依赖 B,此时 B 还没有被创建出来,所以去实例化 B。
-
同样,B 注⼊属性时发现依赖 A,它就从缓存里找 A 对象。依次从⼀级到三级缓存查询 A。发现可以从三级缓存中通过对象⼯⼚拿到 A,虽然 A 不太完善,但是存在,就把 A 放⼊⼆级缓存,同时删除三级缓存中的 A,此时,B 已经实例化并且初始化完成了,把 B 放入⼀级缓存。
-
接着 A 继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的 B 对象,A 对象创建也完成,删除⼆级缓存中的 A,同时把 A 放⼊⼀级缓存。
-
最后,⼀级缓存中保存着实例化、初始化都完成的 A、B 对象。
17、为什么要三级缓存?二级不⾏吗?
不行,主要是为了生成代理对象。因为三级缓存中放的是生成具体对象的匿名内部类,获取 Object 的时候,可以生成代理对象,也可以返回普通对象。使⽤三级缓存主要是为了保证不管什么时候使用的都是⼀个对象。
假设只有二级缓存:往二级缓存中放的显示⼀个普通的 Bean 对象,Bean 初始化过程中,通过 BeanPostProcessor 去生成代理对象之后,覆盖掉二级缓存中的普通 Bean 对象,那么可能就导致取到的 Bean 对象不一致了。
17.1 如果缺少第二级缓存会有什么问题?
如果没有二级缓存,Spring 无法在未完成初始化的情况下暴露 Bean。会导致代理 Bean 的循环依赖问题,因为某些代理逻辑无法在三级缓存中提前暴露。最终可能抛出 BeanCurrentlyInCreationException
18、@Autowired 的实现原理?
实在 Bean 的初始化阶段,会通过 Bean 后置处理器来进行一些前置和后置的处理。实现 @Autowired 的功能,也是通过后置处理器来完成的。这个后置处理器就是 AutowiredAnnotationBeanPostProcessor。
- Spring 在创建 bean 的过程中,最终会调用到 doCreateBean()方法,在 doCreateBean()方法中会调用 populateBean ()方法,来为 bean 进行属性填充,完成自动装配等工作。
- 在 populateBean()方法中一共调用了两次后置处理器,第一次是为了判断是否需要属性填充,如果不需要进行属性填充,那么就会直接进行 return,如果需要进行属性填充,那么方法就会继续向下执行,后面会进行第二次后置处理器的调用,这个时候,就会调用到 AutowiredAnnotationBeanPostProcessor 的 postProcessPropertyValues()方法,在该方法中就会进行@Autowired 注解的解析,然后实现自动装配。
19、说说什么是 AOP?
AOP 指的是面向切面编程,把业务逻辑中相同的代码抽取到一个独立的模块中,实现了业务逻辑和通用逻辑的分离。
19.1 AOP 有哪些核心概念?
- 切面(Aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象。
- 连接点(Join Point):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中,连接点指的是被拦截到的方法,实际上连接点还可以是字段或者构造方法。
- 切点(Pointcut):对连接点进行拦截的定位
- 通知(Advice):指拦截到连接点之后要执行的代码,也可以称作增强。
- 目标对象 (Target):代理的目标对象。
- 引介(introduction):一种特殊的增强,可以动态地为类添加一些属性和方法。
- 织入(Weabing):织入是将增强添加到目标类的具体连接点上的过程。
19.2 织入有哪几种方式?
- 编译期织入:切面在目标类编译时被织入。
- 类加载期织入:切面在目标类加载到 JVM 时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。
- 运行期织入:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。
Spring AOP 采用运行期织入,而 AspectJ 可以在编译期织入和类加载时织入。
19.3 AspectJ 是什么?
AspectJ 是一个 AOP 框架,它可以做很多 Spring AOP 干不了的事情,比如说支持编译时、编译后和类加载时织入切面。并且提供更复杂的切点表达式和通知类型。
// 定义一个切面
@Aspect
public class LoggingAspect {
// 定义一个切点,匹配 com.example 包下的所有方法
@Pointcut("execution(* com.example..*(..))")
private void selectAll() {}
// 定义一个前置通知,在匹配的方法执行之前执行
@Before("selectAll()")
public void beforeAdvice() {
System.out.println("A method is about to be executed.");
}
}
19.4 AOP 有哪些环绕方式?
AOP 一般有 5 种环绕方式:
- 前置通知 (@Before):在目标方法执行之前执行。
- 返回通知 (@AfterReturning):在目标方法成功执行并返回结果后执行。
- 异常通知 (@AfterThrowing):在目标方法抛出异常后执行。
- 后置通知 (@After):无论目标方法是否成功执行(无论是否抛出异常),都会执行的后置处理。
- 环绕通知 (@Around):最强大也是最复杂的通知类型,可以在目标方法执行前后都执行自定义逻辑,甚至可以修改方法的行为或决定是否执行目标方法。
多个切面的情况下,可以通过 @Order 指定先后顺序,数字越小,优先级越高。
19.5 Spring AOP 发生在什么时候?
Spring AOP 基于运行时代理机制,这意味着 Spring AOP 是在运行时通过动态代理生成的,而不是在编译时或类加载时生成的。在 Spring 容器初始化 Bean 的过程中,Spring AOP 会检查 Bean 是否需要应用切面。如果需要,Spring 会为该 Bean 创建一个代理对象,并在代理对象中织入切面逻辑。这一过程发生在 Spring 容器的后处理器(BeanPostProcessor)阶段。
19.6 简单总结一下 AOP
AOP,也就是面向切面编程,是一种编程范式,旨在提高代码的模块化。比如说可以将日志记录、事务管理等分离出来,来提高代码的可重用性。
19.7 AOP和 OOP 的关系?
AOP 和 OOP 是互补的编程思想:
- AOP 提供了解决横切关注点(如日志、权限、事务等)的机制,将这些逻辑集中管理。
- OOP 通过类和对象封装数据和行为,专注于核心业务逻辑。
20、AOP的使用场景有哪些?
AOP 的使用场景有很多,比如说日志记录、事务管理、权限控制、性能监控等。我的项目中主要利用 AOP 来放重复提交。
- 第一步,自定义注解作为切点。
- 第二步,配置 AOP 切面。
- 第三步,在使用的地方加上自定义注解。
- 第四步,当接口被调用时,就可以看到防重复提交效果。