Spring面试题总结

  1. 1. Spring
    1. 1.1 Spring框架是什么?有什么优势?
    2. 1.2 什么是Spring IoC容器?
    3. 1.3 什么是依赖注入?
    4. 1.4 依赖注入有几种方式?
    5. 1.5 Spring中的Bean是什么?
    6. 1.6 BeanFactory和ApplicationContext有什么区别?
    7. 1.7 Spring提供了哪些配置方式?
    8. 1.8 Bean的作用域有哪些?
    9. 1.9 如何理解IoC和DI?
    10. 1.10 Bean的生命周期有几个阶段?
    11. 1.11 什么是AOP?
    12. 1.12 AOP的实现方式有哪些?
    13. 1.13 Spring框架中用到了哪些设计模式?
  2. 2. Spring MVC
    1. 2.1 什么是Spring MVC?工作原理是什么?
    2. 2.2 介绍一下Spring MVC的核心组件
  3. 3. Spring注解
    1. 3.1 Spring中常用的注解有哪些?
    2. 3.2 @Controller和@RestController有什么区别?
    3. 3.3 @GetMapping、@PostMapping和@RequestMapping有什么区别?
    4. 3.4 @RequestParam和@PathVariable有什么区别?
    5. 3.5 详细讲一下@Autowired有什么用?
  4. 4. Spring Boot
    1. 4.1 为什么要用Spring Boot?
    2. 4.2 Spring Boot中如何实现对不同环境的属性配置文件的支持?
    3. 4.3 如何理解Spring Boot中的Starters?
    4. 4.4 Spring Boot Starters的工作原理是什么?
    5. 4.5 Spring Boot的核心注解是什么?主要由哪几个注解组成?
    6. 4.6 Spring Boot中如何使用Bean
    7. 4.7 RESTful是什么?
  5. 5. Spring Cloud
    1. 5.1 什么是Spring Cloud?
    2. 5.2 详细介绍一下分布式项目与微服务架构
    3. 5.3 使用Spring Cloud有什么优势?
    4. 5.4 服务注册和发现是什么意思?Spring Cloud如何实现?
    5. 5.5 负载均衡有什么作用?
    6. 5.6 Spring Cloud和Dubbo有什么区别?
    7. 5.7 Sping Cloud微服务之间如何通讯?

Spring 面试题总结,涉及 Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis 等内容,文章将不断更新。

1. Spring

1.1 Spring框架是什么?有什么优势?

Spring 框架是一个轻量级的 Java 开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的 JavaSE/JavaEE full-stack(一站式)轻量级开源框架,为开发 Java 应用程序提供全面的基础架构支持。Spring 的最根本的使命是解决企业级应用开发的复杂性,即简化 Java 开发。Spring 框架的优势主要包括:

  • 方便解耦,简化开发:Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给 Spring 管理。
  • AOP 编程的支持:Spring 提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
  • 声明式事务的支持:只需要通过配置就可以完成对事务的管理,而无需手动编程。
  • 方便程序的测试:Spring 对 Junit4 支持,可以通过注解方便地测试 Spring 程序。
  • 方便集成各种优秀框架:Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis 等)的直接支持。
  • 降低 JavaEE API 的使用难度:Spring 对 JavaEE 开发中非常难用的一些 API(如 JDBC、JavaMail、远程调用等),都提供了封装,使这些 API 应用难度大大降低。

1.2 什么是Spring IoC容器?

Spring IoC(Spring Inversion of Control)容器,是 Spring 框架的核心部分。IoC 即控制反转,是一种设计思想,在 Java 开发中,IoC 意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

Spring IoC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。具体来说,Spring IoC 容器负责创建对象、管理对象(通过依赖注入)、装配对象、配置对象,并且管理这些对象的整个生命周期。

使用 IoC 的目的,主要是为了降低类之间的耦合。通过控制反转,对象的创建和对象之间的依赖关系处理,交给 Spring 容器来管理,不用程序员自己创建和维护。这样,应用程序无需直接在代码中创建相关的对象,应用程序由 IoC 容器进行组装。这种方式不仅降低了类之间的耦合,也使得代码更加简洁,更易于测试和维护。

1.3 什么是依赖注入?

依赖注入(Dependency Injection,DI)是一种设计模式,也是 Spring 框架的核心概念之一。它的主要作用是去除 Java 类之间的依赖关系,实现松耦合,以便于开发测试。

在传统的程序设计过程中,当某个角色(可能是一个 Java 实例,调用者)需要另一个角色(另一个 Java 实例,被调用者)的协助时,通常由调用者来创建被调用者的实例。这种方式会导致调用者与被调用者之间产生紧密的耦合关系,使得代码难以修改和测试。

依赖注入的思想是,不在类内部直接创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给需要的类来使用。例如,A 类要依赖 B 类,A 类不再直接创建 B 类,而是把这种依赖关系配置在外部 XML(或 Java Config)文件中,然后由 Spring 容器根据配置信息创建、管理 bean 类。

这样,调用者不需要关心被调用者的创建和销毁,只需要关心如何使用被调用者,从而实现了调用者和被调用者之间的解耦。这种方式不仅降低了类之间的耦合,也使得代码更加简洁,更易于测试和维护。

举一个例子,加入我们有一个接口和两个实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface MessageService {
String getMessage();
}

public class TextMessageService implements MessageService {
public String getMessage() {
return "Text Message";
}
}

public class EmailMessageService implements MessageService {
public String getMessage() {
return "Email Message";
}
}

然后,我们有一个使用 MessageServiceMessagePrinter 类:

1
2
3
4
5
6
7
8
9
10
11
12
public class MessagePrinter {
private MessageService service;

// 通过构造器注入依赖
public MessagePrinter(MessageService service) {
this.service = service;
}

public void printMessage() {
System.out.println(this.service.getMessage());
}
}

在 Spring 的配置文件中,我们可以定义 MessageServiceMessagePrinterbean,并通过构造器注入依赖:

1
2
3
4
5
6
7
<beans>
<bean id="messageService" class="com.example.TextMessageService"/>

<bean id="messagePrinter" class="com.example.MessagePrinter">
<constructor-arg ref="messageService"/>
</bean>
</beans>

在这个例子中,MessagePrinter 依赖于 MessageService。通过 Spring IoC 容器和依赖注入,我们可以在外部配置文件中定义这种依赖关系,而不需要在 MessagePrinter 类中硬编码依赖的实现类。这样,我们可以轻松地更改 MessageService 的实现,而无需修改 MessagePrinter 类的代码,这就是依赖注入的优势。

如果我们运行 MessagePrinterprintMessage() 方法,它将打印出 Text Message。这是因为我们在 Spring 的配置文件中将 TextMessageService 定义为 MessageService 的实现类,然后通过构造器注入的方式将其注入到了 MessagePrinter 中。所以,当我们调用 printMessage() 方法时,它会调用 TextMessageServicegetMessage() 方法。

总的来说,依赖注入是一种消除类之间依赖关系的设计模式,它使得对象之间的依赖关系更加清晰,代码更加灵活,更易于测试和维护。

1.4 依赖注入有几种方式?

在 Spring 中,有四种常见的依赖注入方式:

(1)属性注入(Field Injection):直接在需要注入的字段上使用 @Autowired@Resource 等注解:

1
2
3
4
5
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
}

(2)Setter 注入(Setter Injection):在 setter 方法上使用 @Autowired@Resource 等注解。在 SpringBoot 中,由于 WebSocket 的特殊性,它是由容器管理的,而不是由 Spring 管理的 Bean,每次 WebSocket 连接都会创建一个新的 WebSocket 实例(非单例模式),因此不能直接使用属性注入的方式来注入 WebSocket。假如我们要在 WebSocket 中使用 Mapper,那么我们就可以用 Setter 注入的方式:

1
2
3
4
5
6
7
8
9
10
@Component
@ServerEndpoint(value = "/websocket")
public class WebSocketServer {
private static UserMapper userMapper;

@Autowired
public void setUserMapper(UserMapper userMapper) {
WebSocketServer.userMapper = userMapper;
}
}

(3)构造器注入(Constructor Injection):在构造器上使用 @Autowired@Resource 等注解:

1
2
3
4
5
6
7
8
9
@Service
public class UserService {
private UserMapper userMapper;

@Autowired
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
}

(4)静态工厂的方法注入:通过静态工厂方法创建 Bean,并在 Spring 配置文件中声明:

1
2
3
4
5
public class BeanFactory {
public static UserService createUserService() {
return new UserService();
}
}

在 Spring 配置文件中声明:

1
<bean id="userService" class="com.example.BeanFactory" factory-method="createUserService" />

1.5 Spring中的Bean是什么?

在 Spring 中,构成应用程序主干并由 Spring IoC 容器管理的对象称为 Bean。Bean 是一个由 Spring IoC 容器实例化、组装和管理的对象。简而言之,Spring Bean 是 Spring 框架在运行时管理的对象。Spring Bean 是任何 Spring 应用程序的基本构建块。你编写的大多数应用程序逻辑代码都将放在 Spring Bean 中。Spring Bean 的管理包括:创建一个对象,提供依赖项(例如其他 Bean,配置属性),拦截对象方法调用以提供额外的框架功能,销毁一个对象。

1.6 BeanFactory和ApplicationContext有什么区别?

BeanFactoryApplicationContext 都是 Spring 框架中用于创建和管理 Bean 的容器。

BeanFactory 是 Spring 框架的最基本的容器,它是 Spring 的核心部分。BeanFactory 通过一个配置文件来管理和创建 Bean。BeanFactory 中的 Bean 是懒加载的,也就是说只有在调用 getBean() 方法去请求某个 Bean 时才会创建实例,这样可以提高程序的性能和启动速度,帮助我们节省资源。

ApplicationContextBeanFactory 的子接口,它是一个更加强大的容器。ApplicationContext 可以像 BeanFactory 一样创建和管理 Bean,但是它还可以提供其他的功能,比如支持国际化、事件传播、资源加载等。ApplicationContext 是在程序启动时就将所有的 Bean 全部实例化,因此在程序运行时可以直接获取已经创建好的 Bean,从而提高了程序的响应速度。

1.7 Spring提供了哪些配置方式?

Spring 提供了以下三种主要的配置方式:

  • 基于 XML 的配置:在 Spring1.x 时代,都是基于 XML 来进行配置,用 XML 文件来管理 Bean 之间的关系。例如,你可以在 XML 文件中定义一个 Bean,然后在需要的地方引用这个 Bean。
  • 基于注解的配置:Spring2.5 以后开始支持用注解的方式来配置依赖注入。可以用注解的方式来替代 XML 方式的 Bean 描述,可以将 Bean 描述转移到组件类的内部,只需要在相关类上、方法上或者字段声明上使用注解即可。例如,你可以使用 @Component 或其子类(@Repository@Service@Controller)来定义 Bean。
  • 基于 Java API 的配置:Spring3.0 以后,提供了 Java 配置的能力,Spring4.x和SpringBoot都推荐使用Java配置2。例如,你可以使用@Configuration和@Bean注解来定义和配置bean12。

注意 Spring 框架默认并不启用注解配置方式,你需要在配置文件中添加相应的配置才能启用注解配置方式:

1
2
3
4
5
6
<beans>
<!-- 启用注解配置 -->
<context:annotation-config/>

<!-- 其他bean的定义 -->
</beans>

<context:annotation-config/> 是 Spring 框架中的一个 XML 配置元素,用于启用注解驱动的 Spring 容器。它会自动扫描 Spring 容器中的所有组件,包括 @Service@Repository@Controller@Component 等注解标注的类,并将它们注册到 Spring 容器中。

也可以使用 <context:component-scan/>,如果使用了 <context:component-scan/>,那么 <context:annotation-config/> 就不再需要,因为 <context:component-scan/> 除了具有 <context:annotation-config/> 的功能之外,还可以在指定的 Package 下扫描以及注册 Java Bean。

1.8 Bean的作用域有哪些?

  • singleton:单例模式,在整个 Spring IoC 容器中,使用 singleton 定义的 Bean 将只有一个实例。这是 Spring 的默认作用域。
  • prototype:原型模式,每次通过容器的 getBean() 方法获取 prototype 定义的 Bean 时,都将产生一个新的 Bean 实例。
  • request:对于每次 HTTP 请求,使用 request 定义的 Bean 都将产生一个新实例。只有在 Web 应用中使用 Spring 时,该作用域才有效。
  • session:对于每次 HTTP Session,使用 session 定义的 Bean 都将产生一个新实例。同样只有在 Web 应用中使用 Spring 时,该作用域才有效。
  • application:限定一个 Bean 的作用域为 ServletContext 的生命周期。该作用域仅适用于 Web 的 Spring WebApplicationContext 环境。
  • globalSession:全局 Session 作用域,仅在基于 portlet 的 Web 应用中才有意义,Spring5 已经没有了。

1.9 如何理解IoC和DI?

IoC(Inversion of Control,控制反转)和 DI(Dependency Injection,依赖注入)是 Spring 框架的核心概念,它们是面向对象编程的重要设计原则。IoC 和 DI 是同一个概念的不同角度描述,IoC 是一种设计思想,DI 是这种思想的一种实现方式。

IoC 是一种设计思想,不是一种技术。在 Java 开发中,IoC 意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。这意味着,所有的类都会在 Spring 容器中登记,告诉 Spring 你是什么,你需要什么,然后 Spring 会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 Spring 来控制,也就是说控制对象生存周期的不再是引用它的对象,而是 Spring。

DI 是 IoC 的一种实现方式。DI 是一种将调用者与被调用者分离的思想,组件之间的依赖关系由容器在运行时决定,形象的说,是由容器动态地将某个依赖关系注入到组件之中,这样你就可以使用 @Autowired@Resource 等注解来实现自动注入。

1.10 Bean的生命周期有几个阶段?

  • 实例化(Instantiation):Spring 根据配置文件或注解等方式创建 Bean 的实例。
  • 属性赋值(Populate):Spring 将实例化后的 Bean 的属性值设置到对应的属性中。
  • 初始化(Initialization):如果 Bean 实现了 InitializingBean 接口或在配置文件中通过 init-method 指定了初始化方法,则在 Bean 初始化完成后调用该方法。
  • 销毁(Destruction):如果 Bean 实现了 DisposableBean 接口或在配置文件中通过 destroy-method 指定了销毁方法,则在容器关闭时会调用该方法。

1.11 什么是AOP?

AOP(Aspect Oriented Programming),即面向切面编程,是一种通过预编译方式和运行期动态代理实现程序功能的统一维护的技术。它是 OOP(面向对象编程)的延续,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。

AOP 的主要目标是将业务处理逻辑与系统服务分离开来,然后通过声明性的方式将系统服务应用到业务处理逻辑中。简单来说,AOP 就是把我们程序重复的代码抽取出来,在需要执行的时候使用动态代理技术在不修改源码的基础上,对我们的已有方法进行增强。

AOP 的主要术语包括:

  • 切面(Aspect):切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如说事务处理和日志处理可以理解为两个切面。
  • 连接点(JoinPoint):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
  • 切入点(PointCut):切入点是对连接点进行拦截的条件定义。
  • 通知(Advice):通知是指拦截到连接点之后要执行的代码,包括了 aroundbeforeafter 等不同类型的通知。
  • 目标对象(Target):目标对象指将要被增强的对象,即包含主业务逻辑的类对象。
  • 织入(Weaving):织入是将切面和业务逻辑对象连接起来,并创建通知代理的过程。

1.12 AOP的实现方式有哪些?

  • 通过 Spring API 实现:这种方式的核心是通过编写增强类来继承 Spring API 提供的接口。例如,你可以编写业务接口和实现类,然后编写增强类,并实现 Spring API 相关接口的方法。然后在 resource 目录下新建 applicationContext 文件,实现 Java 类的创建和 AOP 的织入,最后编写测试类。
  • 通过自定义类来实现:这种方式比较推荐。你可以自定义切入类,然后在 Spring 中配置,最后编写测试类。
  • 使用注解实现:你可以自定义增强类(注解实现),然后在 Spring 配置文件中,注册 Bean,并增加支持注解的配置,最后编写测试类。
  • 使用 JDK 提供的代理方式:这种方式不依赖于 Spring。你可以使用 JDK 提供的代理方式来实现 AOP,包括静态和动态两种方式。
  • 使用 Spring 纯配置实现:你可以通过 Spring 的配置文件来实现 AOP。
  • 使用 Spring 注解:你可以通过 Spring 的注解来实现 AOP。
  • 动态代理和字节码增强:Spring AOP 的实现主要基于动态代理和字节码增强两种技术。动态代理是一种在运行时生成代理对象的技术,在代理对象中可以添加额外的逻辑,比如切面逻辑。Spring AOP 通过 JDK 动态代理和 CGLIB 动态代理两种方式实现代理对象的生成。

1.13 Spring框架中用到了哪些设计模式?

(1)单例模式(Singleton):Spring 中的 Bean 默认都是单例的,这就是单例模式的应用。例如:

1
2
3
4
5
6
7
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}

在这个例子中,myService Bean 在整个应用中只有一个实例。在单例模式中,Spring容器会确保每个由 @Bean 注解定义的 Bean 在整个应用中只有一个实例。

(2)工厂模式(Factory):Spring 使用工厂模式来创建 Bean。同单例模式的示例代码所示,在这个例子中,AppConfig 类就像一个工厂,myService() 方法就是工厂方法,用来创建 MyService 的实例。Spring 的 @Bean 注解同时实现了这两种模式。

(3)模板方法模式(Template Method):Spring 的 JdbcTemplateHibernateTemplate 等都是模板方法模式的应用。例如:

1
2
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.execute("CREATE TABLE CUSTOMERS (ID INTEGER, NAME VARCHAR(100))");

(4)代理模式(Proxy):Spring AOP 就是通过代理模式实现的。例如:

1
2
3
4
5
6
7
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.myapp.MyService.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature());
}
}

在这个例子中,LoggingAspect 创建了一个代理,它在 MyService 的所有方法执行前打印日志。

(5)观察者模式(Observer):Spring 事件处理就是观察者模式的一个例子。当一个事件被发布时,所有注册的监听器都会收到通知。例如:

1
2
3
4
5
public class MyEventListener implements ApplicationListener<MyEvent> {
public void onApplicationEvent(MyEvent event) {
System.out.println("Received: " + event.getData());
}
}

2. Spring MVC

2.1 什么是Spring MVC?工作原理是什么?

Spring MVC 是 Spring 框架的一部分,它是一个基于 Java 的全功能 MVC Web 应用程序框架。MVC(Model-View-Controller)代表模型-视图-控制器,这是一种设计模式,用于将应用程序的数据访问、用户界面和业务逻辑分离开来。

Spring MVC 的工作原理如下:

  • 用户发送请求:用户通过浏览器发送一个 HTTP 请求,直接请求到 DispatcherServlet
  • DispatcherServlet(前端控制器):请求被 Spring MVC 的 DispatcherServlet 捕获。DispatcherServlet 的作用类似于一个中央处理器,它会负责调用其他组件来处理请求。
  • HandlerMapping(控制器映射器):DispatcherServlet 会调用 HandlerMapping 解析请求对应的 Handler,即找出处理这个请求的 Controller
  • Controller(控制器):找到合适的 Controller 后,DispatcherServlet 会将请求交给它。Controller 是真正处理请求的地方,它会处理用户的请求,并返回一个 ModelAndView 对象。ModelAndView 包含了模型(Model)数据和视图(View)名称。
  • ViewResolver(视图解析器):DispatcherServlet 会把 ModelAndView 对象传给 ViewResolverViewResolver 会根据视图名称解析出真正的视图。
  • View(视图):最后,DispatcherServlet 会渲染视图,并把模型数据填充进去。这个视图就是最终呈现给用户的页面。

2.2 介绍一下Spring MVC的核心组件

Spring MVC 的核心组件主要包括以下几个:

  • DispatcherServlet(前端控制器):这是 Spring MVC 框架的核心,负责将请求路由到其他组件。它处理所有的 HTTP 请求和响应。
  • HandlerMapping(控制器映射器):它的任务是根据请求的 URL 找到正确的 Controller
  • Controller(控制器):这是应用程序的实际控制器,负责处理用户请求并返回一个模型和视图。
  • HandlerAdapter(控制器适配器):它负责调用 Controller 中的方法。
  • ViewResolver(视图解析器):它负责解析视图名并返回一个具体的视图对象。
  • View(视图):这是最终呈现给用户的页面。

除此之外,Spring MVC 还有一些其他的组件:

  • HandlerExceptionResolver(处理器异常解析器):它负责处理在 Controller 执行过程中抛出的异常。
  • LocaleResolver(区域解析器):它用于确定用户的区域,这对于国际化和本地化非常重要。
  • MultipartResolver(多部分解析器):它用于处理 multipart 请求,例如文件上传。
  • ThemeResolver(主题解析器):它用于确定应用程序的主题,这对于个性化布局非常有用。
  • RequestToViewNameTranslator(请求到视图名转换器):它用于在 Controller 没有明确返回视图名时,提供一个默认的视图名。
  • FlashMapManager(Flash 映射管理器):它用于存储和检索 FlashMap 模型,FlashMap 模型用于在重定向场景中存储属性。

3. Spring注解

3.1 Spring中常用的注解有哪些?

  • @Component:标注一个普通的 Spring Bean 类。
  • @Controller:标注一个控制器组件类。
  • @Service:标注一个业务逻辑层组件类。
  • @Repository:标注一个数据访问层组件类。
  • @Autowired:由 Spring 提供的注解,用于自动装配 Bean。
  • @RequestMapping:用于映射 Web 请求,包括访问路径和参数。
  • @ResponseBody:支持将返回值放在 response 内,而不是一个页面,通常用户返回 JSON 数据。
  • @RestController:该注解为一个组合注解,相当于 @Controller@ResponseBody 的组合,注解在类上,意味着,该 Controller 的所有方法都默认加上了 @ResponseBody
  • @ExceptionHandler:用于全局处理控制器里的异常。
  • @PathVariable:用于接收路径参数,比如 @RequestMapping("/hello/{name}") 申明的路径,将注解放在参数中前,即可获取该路径的 name 值,通常作为 RESTful 的接口实现方法。
  • @EnableAsync:在配置类中,通过此注解开启对异步任务的支持。
  • @Async:在实际执行的 Bean 方法使用该注解来申明其是一个异步任务。
  • @EnableScheduling:在配置类上使用,开启计划任务的支持。
  • @Scheduled:来申明这是一个任务,包括 cronfixDelayfixRate 等类型。

3.2 @Controller和@RestController有什么区别?

(1)@Controller@Controller 注解表示该类是一个 Web 控制器,通常与 @RequestMapping 注解一起使用,用于处理 HTTP 请求。在 @Controller 中,我们可以返回一个视图(View),这在 Spring Web MVC 中非常常见。例如:

1
2
3
4
5
6
7
8
9
10
11
12
@Controller
@RequestMapping("/books")
public class BookController {
@GetMapping("/{id}")
public @ResponseBody Book getBook(@PathVariable int id) {
return findBookById(id);
}

private Book findBookById(int id) {
// ...
}
}

在这个例子中,BookController 类被标记为一个控制器,/books 是它的请求映射路径。getBook() 方法用于处理对 /books/{id} 路径的 GET 请求,其中 {id} 是路径变量。

(2)@RestController@RestController@Controller 的特化,它包含了 @Controller@ResponseBody 两个注解。这意味着,当一个类被 @RestController 注解标记后,该类的所有方法都会默认添加 @ResponseBody 注解。因此通常用于创建 RESTful Web 服务。例如:

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RequestMapping("/books")
public class BookRestController {
@GetMapping("/{id}")
public Book getBook(@PathVariable int id) {
return findBookById(id);
}

private Book findBookById(int id) {
// ...
}
}

在这个例子中,BookRestController 类被 @RestController 注解标记,因此不需要再每个请求处理方法上都添加 @ResponseBody 注解。

总的来说二者的主要区别在于,@Controller 通常用于处理返回视图的请求,而 @RestController 通常用于处理返回 JSON 或 XML 响应的请求。

3.3 @GetMapping、@PostMapping和@RequestMapping有什么区别?

(1)@RequestMapping:这是一个通用的注解,可以处理所有类型的 HTTP 请求。你可以通过 method 属性来指定处理的 HTTP 方法类型(如 GET、POST 等)。例如:

1
2
3
4
5
6
7
8
9
@RequestMapping(value = "/users", method = RequestMethod.GET)
public Users getUsers() {
// ...
}

@RequestMapping(value = "/users", method = RequestMethod.POST)
public User createUser(User user) {
// ...
}

(2)@GetMapping:这是 @RequestMapping 的一个特化版本,用于处理 GET 请求,等价于 @RequestMapping(method = RequestMethod.GET)。例如:

1
2
3
4
@GetMapping("/users")
public Users getUsers() {
// ...
}

(3)@PostMapping:同样是 @RequestMapping 的一个特化版本,用于处理 POST 请求,等价于 @RequestMapping(method = RequestMethod.POST)。例如:

1
2
3
4
@PostMapping("/users")
public User createUser(User user) {
// ...
}

3.4 @RequestParam和@PathVariable有什么区别?

(1)@RequestParam:用于从请求参数中提取值。例如,对于 URL:http://localhost:8080/books?id=1,你可以使用 @RequestParam 来获取 id 参数的值:

1
2
3
4
@GetMapping("/books")
public String getBook(@RequestParam("id") String id) {
// ...
}

(2)@PathVariable:用于从 URI 路径中提取值。例如,对于 URL:http://localhost:8080/books/1,你可以使用 @PathVariable 来获取 id

1
2
3
4
@GetMapping("/books/{id}")
public String getBook(@PathVariable("id") String id) {
// ...
}

3.5 详细讲一下@Autowired有什么用?

在 Spring 框架中,@Autowired 注解用于实现自动依赖注入。这意味着你不需要在代码中明确指定依赖关系,Spring 会自动为你完成这个工作,从而简化了代码并提高了可维护性。

@Autowired 注解可以应用于字段、构造器和方法:

(1)字段上的 @Autowired:当 @Autowired 注解应用于字段时,Spring 会在创建 Bean 时自动注入相应的依赖。在下面这个例子中,myService 字段会被自动注入一个 MyService 类型的 Bean:

1
2
@Autowired
private MyService myService;

(2)构造器上的 @Autowired:当 @Autowired 注解应用于构造器时,Spring 会在创建 Bean 时自动注入构造器的参数。在下面这个例子中,MyClass 的构造器会被自动注入一个 MyService 类型的 Bean:

1
2
3
4
5
6
7
8
class MyClass {
private MyService myService;

@Autowired
public MyClass(MyService myService) {
this.myService = myService;
}
}

(3)方法上的 @Autowired:当 @Autowired 注解应用于方法时,Spring 会在创建 Bean 时自动注入方法的参数。这通常用于 Setter 方法,但也可以用于其他任何方法。例如:

1
2
3
4
@Autowired
public void setMyService(MyService myService) {
this.myService = myService;
}

4. Spring Boot

4.1 为什么要用Spring Boot?

Spring Boot 是 Spring 框架的一个扩展,它的目标是简化 Spring 应用程序的配置和部署,Spring Boot 具有以下优势:

  • 快速开发:Spring Boot 提供了许多默认配置,这使得开发人员可以快速开始开发。例如,使用 Spring MVC 需要大量的 XML Bean 定义和自定义 servlet 类,但使用 Spring Boot 只需要添加一个 starter 依赖即可,无需任何代码生成或 XML 配置。
  • 有用的 Starters:Spring Boot Starters 是包含库和一些自动配置的 Maven 描述符。这些 Starters 可以为 Spring Boot 应用程序提供功能。例如,你想设置数据库连接,或者你想与消息队列进行通信或发送电子邮件,Spring Boot 都可以覆盖。
  • 嵌入式 Web 服务器:Spring Boot 提供了对嵌入式 Tomcat、Jetty 和 Undertow 服务器的开箱即用支持。这样,开发人员不必担心在传统的应用服务器中部署 Web 应用程序。
  • 丰富的 IDE 支持:所有主要的 IDE 都为 Spring Boot 提供了代码辅助支持。
  • 生产就绪的功能:Spring Boot 提供了开箱即用的监控、度量和日志记录功能。这些功能使开发人员可以避免额外的配置。

4.2 Spring Boot中如何实现对不同环境的属性配置文件的支持?

在 Spring Boot 中,你可以使用 Spring 的 Profile 功能来支持不同环境的属性配置文件。你可以为每个环境创建一个单独的配置文件,然后在运行应用程序时指定要使用的配置文件。

例如,假设你有开发环境(dev)、质量保证环境(qa)和生产环境(prod)。你可以在与 application.properties 文件相同的位置创建三个文件:

  • application-dev.properties:用于开发环境。
  • application-qa.properties:用于质量保证环境。
  • application-prod.properties:用于生产环境。

然后,你只需要在 application.properties 文件中设置 spring.profiles.active 属性,来指定当前的环境。例如,如果你想使用质量保证环境,你可以设置 spring.profiles.active=qa

此外,你还可以通过 JVM 参数来指定活动的配置文件。例如,你可以在启动应用程序时设置 -Dspring.profiles.active=dev

4.3 如何理解Spring Boot中的Starters?

在 Spring Boot 中,Starters 可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包。你可以一站式集成 Spring 及其他技术,快速地添加和管理项目的依赖,而不需要到处找示例代码和依赖包。

例如,如果你想使用 Spring JPA 访问数据库,只要加入 spring-boot-starter-data-jpa 启动器依赖就能使用了。同样,如果你想创建一个 RESTful 的 Web 应用,你可以添加 spring-boot-starter-web

4.4 Spring Boot Starters的工作原理是什么?

Spring Boot Starters 的工作原理主要包括以下几个步骤:

  • 引入模块所需的相关 Jar 包:Spring Boot Starter 会将具备某种功能的 Jar 包打包到一起,可以简化依赖导入的过程。例如,我们导入 spring-boot-starter-web 这个 Starter,则和 Web 开发相关的 Jar 包都一起导入到项目中了。
  • 自动配置各个模块所需的属性:Spring Boot 在启动时会去依赖的 Starter 包中寻找 resources/META-INF/spring.factories 文件,然后根据文件中配置的 Jar 包去扫描项目所依赖的 Jar 包。接着根据 spring.factories 配置加载 AutoConfigure 类。根据 @Conditional 注解的条件,进行自动配置并将 Bean 注入 Spring Context。
  • Bean 的发现和加载:Spring Boot 默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包括依赖包中的类,那么依赖包中的 Bean 是如何被发现和加载的?Spring Boot 在启动类上我们一般会加入 @SpringBootApplication 注解,此注解的源码中的 @EnableAutoConfiguration 注解引入了 @Import 这个注解,该注解导入自动配置功能类 AutoConfigurationImportSelector,主要方法 getCandidateConfigurations() 使用了 SpringFactoriesLoader.loadFactoryNames() 方法加载 META-INF/spring.factories 的文件(spring.factories 声明具体自动配置)。

4.5 Spring Boot的核心注解是什么?主要由哪几个注解组成?

Spring Boot 的核心注解是 @SpringBootApplication。这个注解实际上是以下三个注解的组合:

  • @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
  • @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项。如关闭数据源的自动配置功能:@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
  • @ComponentScan:Spring 组件扫描功能,让 Spring Boot 扫描到 Configuration 类并把它加入到程序上下文。

4.6 Spring Boot中如何使用Bean

在 Spring Boot 中,你可以通过使用 @Bean 注解来声明一个 Bean。@Bean 注解告诉 Spring 一个方法会返回一个对象,这个对象应该被注册为 Spring 应用上下文中的 Bean。默认情况下,Bean 的名称是由方法名决定的,但你也可以在 @Bean 注解中通过 name 属性来设置 Bean 的名称。

例如,以下是一个简单的 @Bean 注解的使用示例:

1
2
3
4
5
6
7
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}

在这个例子中,myBean() 方法被注解为 @Bean,所以它会返回一个新的 MyBean 实例,这个实例将被注册为 Spring 应用上下文中的 Bean。

总的来说,你不需要在 Spring Boot 中手动配置 Bean,你只需要使用 @Bean 注解,Spring Boot 就会自动为你创建和管理 Bean。

4.7 RESTful是什么?

RESTful 是一种软件架构风格,它主要用于客户端和服务器交互类的软件。在 RESTful 风格中,用户发起请求的发送方式有 GET、POST、DELETE、PUT 等方式对请求的处理方法进行区分。这样可以在前后端分离式的开发中使得前端开发人员不会对请求的资源地址产生混淆和大量的检查方法名的麻烦,形成一个统一的接口,使得 Web 服务变得更加简洁、有层次,易于实现缓存等机制。

在 Spring Boot 中,开发 RESTful 接口非常简单,通过不同的注解来支持前端的请求,除了经常使用的 @RestController 注解外,Spring Boot 还提供了一些组合注解。这些注解来帮助简化常用的 HTTP 方法的映射,并更好地表达被注解方法的语义。

例如,Spring Boot 提供了与 REST 操作方式(GET、POST、PUT、DELETE)对应的注解:

  • @GetMapping:处理 GET 请求。
  • @PostMapping:处理 POST 请求。
  • @PutMapping:用于更新资源。
  • @DeleteMapping:处理删除请求。
  • @PatchMapping:用于更新部分资源。

这些注解就是我们使用的 @RequestMapping 的简写版本:@GetMapping 其实就等于 @RequestMapping(value = "/xxx", method = RequestMethod.GET)

5. Spring Cloud

5.1 什么是Spring Cloud?

Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发。Spring Cloud 为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理、服务发现、断路器、智能路由、微代理、控制总线)。分布式系统的协调导致了样板模式,使用 Spring Cloud 开发人员可以快速地支持实现这些模式的服务和应用程序。

Spring Cloud 并不重复造轮子,而是将市面上开发得比较好的模块集成进去,进行封装,从而减少了各模块的开发成本。

总的来说,Spring Cloud 是微服务系统架构的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶。

5.2 详细介绍一下分布式项目与微服务架构

分布式项目是指将一个大型的项目切割成多个小项目,每个小项目都是一套独立的系统。这些小项目被打成 Jar 包,然后通过互相引用(以 Jar 包的形式)来组装成原来的完整项目。每个子业务都是一套独立的系统,子业务之间相互协作,最终完成整体的大业务。这种方式可以提高系统的可扩展性高可用性,解决高并发的问题,并且可以利用分布式存储将数据分片到多个节点上,不仅可以提高性能,同时也可以使用多个节点对同一份数据进行备份。

微服务架构是一种软件开发框架,它将一个大型的应用程序划分为许多小的、独立的服务。每个服务都有自己的技术栈,包括数据库和数据管理模型。这些服务通常通过 REST API、事件流和消息代理进行通信,并按照业务能力进行组织。

微服务架构的主要优点包括:

  • 代码更容易更新:可以直接添加新特性或功能,而不必更新整个应用。
  • 团队可以对不同的组件使用不同的技术栈和不同的编程语言。
  • 组件可以相互独立地扩展,从而减少与必须扩展整个应用相关的浪费和成本。

然而,微服务架构也带来了一些挑战,例如管理复杂性的增加、日志记录数据的增加、新版本可能导致的向后兼容性问题、应用涉及更多网络连接可能导致的延迟和连接问题等。尽管如此,微服务架构仍然被广泛采用,因为它可以提高开发效率,使组织能够更快地响应业务需求。

微服务架构和分布式系统是两个不同的概念,它们的主要区别在于设计目标和实现方式:

  • 分布式系统:分布式系统的核心就是拆分,只要是将一个项目拆分成了多个模块,并将这些模块分开部署,那就算是分布式。分布式解决的是系统性能问题,即解决系统部署上单点的问题,尽量让组成系统的子系统分散在不同的机器上进而提高系统的吞吐能力。分布式是部署层面的东西,即强调物理层面的组成,系统的各子系统部署在不同计算机上。
  • 微服务架构:微服务架构通过更细粒度的服务切分,使得整个系统的迭代速度和并行程度更高,但是运维的复杂度和性能会随着服务的粒度更细而增加。微服务重在解耦合,使每个模块都独立。微服务是设计层面的东西,一般考虑如何将系统从逻辑上进行拆分,也就是垂直拆分。微服务可以是分布式的,即可以将不同服务部署在不同计算机上,当然如果量小也可以部署在单机上。

总的来说,分布式系统和微服务架构都是为了提高系统的可扩展性和可维护性,但它们的关注点和实现方式有所不同。

5.3 使用Spring Cloud有什么优势?

  • 约定优于配置:Spring Cloud 提供了一套默认的配置,使得开发人员可以更专注于业务逻辑的开发。
  • 适用于各种环境:无论是开发环境、部署 PC Server,还是各种云环境(例如阿里云、AWS 等),Spring Cloud 都可以适用。
  • 隐藏了组件的复杂性:Spring Cloud 提供了声明式、无 XML 的配置方式,隐藏了组件的复杂性。
  • 开箱即用、快速启动:Spring Cloud 提供了一套完整的微服务解决方案,使得开发人员可以快速启动项目。
  • 轻量级的组件:Spring Cloud 整合的组件大多比较轻量。
  • 组件丰富、选型中立、功能齐全:Spring Cloud 为微服务架构提供了非常完整的支持,有丰富的组件选择,开发人员可以根据需求选择合适的组件。
  • 灵活:Spring Cloud 的组成部分是解耦合的,开发人员可以按需灵活挑选技术选型。
  • 服务拆分粒度更细:有利于资源重复利用,提高开发效率。
  • 采用去中心化思想:服务之间采用轻量级通讯,适合互联网时代,产品迭代周期更短。

5.4 服务注册和发现是什么意思?Spring Cloud如何实现?

服务注册是指将服务的元数据(例如服务名、IP 地址、端口号等)注册到注册中心中,以便其他服务可以发现它。例如,一个微服务启动后,会将自己的信息(通常是这个服务的 IP 和端口)注册到一个公共的组件上去(比如 ZooKeeper、Consul)。

服务发现是指客户端从注册中心中查找和选择可用的服务实例,并通过负载均衡策略来分配请求。也就是说,新注册的这个服务模块能够及时的被其他调用者发现。不管是服务新增和服务删减都能实现自动发现。

Spring Cloud 实现服务注册和发现的方式主要有以下几种:

  • Eureka:Eureka 是 Netflix 开源的一款提供服务注册和发现的产品。Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理。在微服务应用启动后,Eureka Client 会向 Eureka Server 发送心跳。如果 Eureka Server 在多个心跳周期内没有接收到某个节点的心跳,则会将该节点移除。
  • Consul:Consul 是一种服务网格解决方案,提供了包括服务发现、配置和分段功能。这些功能中的每一个都可以根据需要独立使用,也可以一起使用以构建全堆栈服务网格。Consul 是适用于底层服务发现和配置的工具。
  • ZooKeeper:ZooKeeper 是一个开源的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 Hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

5.5 负载均衡有什么作用?

负载均衡是一种重要的网络技术,它可以有效地提高网络服务的性能、可用性和安全性。负载均衡的主要作用包括:

  • 解决并发压力:通过将客户端的请求分发到多个服务器,负载均衡可以有效地解决并发压力,提高应用处理性能,增加吞吐量,加强网络处理能力。
  • 提供故障转移:负载均衡可以检测后端服务的运行状况,自动检测异常实例,并快速实施故障转移;当实例恢复正常时,它将自动恢复负载。这样,即使某个服务器出现故障,负载均衡也可以保证服务的高可用性。
  • 提供网站伸缩性(扩展性):当业务压力增加时,可以通过将主机添加到后端服务器池来提高性能。当压力降低时,可以减少宿主。这样,负载均衡可以根据业务需求动态地添加或减少服务器数量,提供网站的伸缩性。
  • 安全防护:负载均衡设备上可以做一些过滤,黑白名单等处理,提供安全防护。

5.6 Spring Cloud和Dubbo有什么区别?

Spring Cloud 和 Dubbo 都是现在主流的微服务框架,但它们之间存在一些主要的区别:

  • 初始定位不同:Spring Cloud 定位为微服务架构下的一站式解决方案,而 Dubbo 是 SOA(Service-Oriented Architecture,面向服务的架构)时代的产物,它的关注点主要在于服务的调用和治理。
  • 生态环境不同:Spring Cloud 依托于 Spring 平台,具备更加完善的生态体系;而 Dubbo 一开始只是做 RPC(Remote Procedure Call,远程过程调用)通信协议的远程调用,生态相对匮乏,现在才逐渐丰富起来。
  • 调用方式不同:Spring Cloud 是采用 HTTP 协议做远程调用,接口一般是 REST 风格,比较灵活;Dubbo 是采用 Dubbo 协议,接口一般是 Java 的 Service 接口,格式固定。
  • 服务网关:Dubbo 没有服务网关,而 Spring Cloud 使用的是 Spring Cloud Netflix Zuul。
  • 分布式配置:Dubbo 没有分布式配置,而 Spring Cloud 使用的是 Spring Cloud Config。
  • 服务跟踪:Dubbo 没有服务跟踪,而 Spring Cloud 使用的是 Spring Cloud Sleuth。
  • 消息总线:Dubbo 没有消息总线,而 Spring Cloud 使用的是 Spring Cloud Bus。
  • 数据流:Dubbo 没有数据流,而 Spring Cloud 使用的是 Spring Cloud Stream。

5.7 Sping Cloud微服务之间如何通讯?

在 Spring Cloud 中,微服务之间的通信主要有两种方式:

  • 同步通信:Dubbo 通过 RPC 远程过程调用,而 Spring Cloud 通过 REST 接口 JSON 调用等(HTTP 通信)。
  • 异步通信:消息队列,如:RabbitMq、ActiveMq、Kafka 等。