Spring面试题

Spring面试题

序号内容链接地址
1Java面试题https://blog.csdn.net/golove666/article/details/137360180
2JVM面试题 https://blog.csdn.net/golove666/article/details/137245795
3Servlet面试题 https://blog.csdn.net/golove666/article/details/137395779
4Maven面试题 https://blog.csdn.net/golove666/article/details/137365977
5Git面试题https://blog.csdn.net/golove666/article/details/137368870
6Gradle面试题https://blog.csdn.net/golove666/article/details/137368172
7Jenkins 面试题 https://blog.csdn.net/golove666/article/details/137365214
8Tomcat面试题 https://blog.csdn.net/golove666/article/details/137364935
9Docker面试题 https://blog.csdn.net/golove666/article/details/137364760
10多线程面试题 https://blog.csdn.net/golove666/article/details/137357477
11Mybatis面试题 https://blog.csdn.net/golove666/article/details/137351745
12Nginx面试题 https://blog.csdn.net/golove666/article/details/137349465
13Spring面试题 https://blog.csdn.net/golove666/article/details/137334729
14Netty面试题https://blog.csdn.net/golove666/article/details/137263541
15SpringBoot面试题https://blog.csdn.net/golove666/article/details/137192312
16SpringBoot面试题1 https://blog.csdn.net/golove666/article/details/137383473
17Mysql面试题 https://blog.csdn.net/golove666/article/details/137261529
18Redis面试题 https://blog.csdn.net/golove666/article/details/137267922
19PostgreSQL面试题 https://blog.csdn.net/golove666/article/details/137385174
20Memcached面试题 https://blog.csdn.net/golove666/article/details/137384317
21Linux面试题https://blog.csdn.net/golove666/article/details/137384729
22HTML面试题 https://blog.csdn.net/golove666/article/details/137386352
23JavaScript面试题 https://blog.csdn.net/golove666/article/details/137385994
24Vue面试题https://blog.csdn.net/golove666/article/details/137341572
25Ajax面试题https://blog.csdn.net/golove666/article/details/137421929
26Python面试题 https://blog.csdn.net/golove666/article/details/137385635
27Spring Cloud Alibaba面试题 https://blog.csdn.net/golove666/article/details/137372112
28SpringCloud面试题 https://blog.csdn.net/golove666/article/details/137345465
29RabbitMQ面试题 https://blog.csdn.net/golove666/article/details/137344188
30Dubbo面试题 https://blog.csdn.net/golove666/article/details/137346834
31Elasticsearch面试题https://blog.csdn.net/golove666/article/details/137348184
32Oracle面试题https://blog.csdn.net/golove666/article/details/137350452
33Android面试题https://blog.csdn.net/golove666/article/details/137358253
34Kafka面试题 https://blog.csdn.net/golove666/article/details/137358607
35ZooKeeper面试题 https://blog.csdn.net/golove666/article/details/137359255
36Kubernetes面试题 https://blog.csdn.net/golove666/article/details/137365540
37Flink面试题 https://blog.csdn.net/golove666/article/details/137369555
38Hadoop面试题https://blog.csdn.net/golove666/article/details/137370194
39Hive面试题https://blog.csdn.net/golove666/article/details/137371835
40Hbase面试题 https://blog.csdn.net/golove666/article/details/137381853
41Spark面试题https://blog.csdn.net/golove666/article/details/137382815
42Golang面试题 https://blog.csdn.net/golove666/article/details/137395486
43Solr面试题 https://blog.csdn.net/golove666/article/details/137420799

1 Spring Core / IOC容器

1.1 描述什么是Spring框架及其核心特性。

Spring框架是一个开源的Java平台,用于创建高性能的应用程序和服务。它于2003年首次发布,由Rod Johnson创立,并受到了他所著《Expert One-on-One J2EE Design and Development》这本书的启发。Spring框架以其“控制反转(Inversion of Control, IoC)”和“依赖注入(Dependency Injection, DI)”的核心概念而闻名,现在已经发展为包含多个项目的广泛框架和生态系统。以下是Spring框架的核心特性:

1. 轻量级容器:
Spring框架的核心是一个轻量级的IoC容器,负责实例化、配置和组装对象。

2. 依赖注入(DI):
Spring通过DI支持松耦合,在运行时将依赖关系注入到组件中。

3. 面向切面编程(AOP):
AOP允许定义方法拦截器和切点,以实现如事务管理、安全性、缓存等关注点的分离。

4. 事务管理:
提供了一致的事务管理接口,可以支持声明式事务管理。

5. 模型-视图-控制器(MVC):
Spring MVC提供了一个可配置的MVC实现用于构建Web应用程序。

6. 数据访问与持久化:
提供了对JDBC、JPA、Hibernate等数据访问技术的抽象,简化了数据库交互。

7. 消息传递:
支持使用JMS进行同步或异步消息传递。

8. 验证和格式化:
内置类型转换和验证,支持使用注解或编程式方式。

9. 强大的JavaBean风格配置:
可以使用基于XML的配置文件或JDK注解进行配置。

10. 包含丰富的API:
提供了方便集成其他框架的API,如Quartz、JDK Timer、JMS等。

11. 批处理以及任务调度:
Spring Batch和Spring Task/Quartz等工具提供了创建批处理任务的能力。

12. 测试支持:
提供了强大的测试功能,支持JUNIT和Mockito集成测试。

13. 国际化和本地化:
提供了国际化的文本消息支持。

14. 注解支持:
在Spring 2.5中引入了对注解的支持,大大简化了配置。

随着Spring生态系统的发展,Spring也扩展到了安全、Web Flow、云、Social等领域,形成了Spring Security、Spring Social、Spring Cloud等项目。

使用Spring框架能够提高开发效率,简化企业级应用的开发,并促进良好的编程实践如编程到接口和基于责任的类设计。

1.2 什么是依赖注入(DI)?

依赖注入(Dependency Injection,简称 DI)是一种软件设计模式,其目的是减少类之间的耦合,增加程序的灵活性和可测试性。在这种模式中,一个类的依赖项不是由类本身创建,而是在类被创建时由外部环境(通常是框架或容器)注入。

依赖注入的组成部分

依赖注入通常涉及以下几个主要概念:

  • 依赖:对象需要的其他对象,通常是作为参数传递给构造函数或方法。
  • 服务:被依赖的对象的实例,它提供某些功能或数据。
  • 客户端:需要服务的对象。
  • 注入器:负责创建服务并将其注入到客户端的系统。

依赖注入的方式

  1. 构造方法注入:依赖通过类的构造方法传递给对象。
  2. 属性注入:依赖直接赋值给类的属性。这通常通过公共属性(或称为 setter 方法)完成。
  3. 方法注入:依赖通过方法参数传递到对象,通常是初始化方法。

依赖注入的好处

  • 降低耦合度:类不需要知道依赖的具体实现,只需要关注于其接口。
  • 提高可测试性:由于依赖是外部提供的,所以在单元测试时可以很容易地将真实的实现替换为模拟对象(Mock objects)。
  • 提高代码维护性:更改依赖的实现不需要修改使用它的类的代码。
  • 提升灵活性和可重用性:通过接口注入依赖,可以在不同环境下重用类,因为依赖可以灵活改变。

依赖注入的例子

在 Java 中,Spring 框架提供了依赖注入的实现。以下是一个简单的例子,展示了构造方法注入:

public class UserService {
    private final UserRepository userRepository;

    // 使用构造方法注入依赖
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    // 类的方法使用 userRepository 对象
}

在这个例子中,UserService 需要 UserRepository。使用依赖注入,我们不需要在 UserService 内部实例化 UserRepository,而是在创建 UserService 实例的时候提供它:

UserRepository userRepository = new UserRepository();
UserService userService = new UserService(userRepository);

依赖注入的挑战

  • 过度设计:错误的或者无节制的使用依赖注入可能导致过于复杂的设计和难以理解的代码。
  • 性能影响:尽管影响不大,但大量的依赖注入操作可能会对程序启动时间有所影响。
  • 学习曲线:理解依赖注入的概念,以及如何在各种框架内使用它可能需要时间。

尽管存在挑战,但依赖注入作为一种重要的设计模式,在创建易于维护和扩展的应用程序方面扮演着关键角色。

1.3 解释Spring框架中的Bean生命周期。

Spring框架中的Bean生命周期描述了从创建到销毁过程中,Bean经历的各种阶段和回调方法。以下是Bean在Spring中的典型生命周期步骤:

  1. 实例化

    • Spring容器首先实例化Bean的对象。
  2. 填充属性

    • Spring根据配置文件或注解,注入Bean的依赖属性。
  3. Bean名称的赋值

    • 如果Bean实现了BeanNameAware接口,Spring会调用setBeanName()方法,传入Bean的ID。
  4. Bean工厂的赋值

    • 如果Bean实现了BeanFactoryAware接口,Spring会调用setBeanFactory()方法,传入BeanFactory
  5. ApplicationContext的赋值

    • 如果Bean实现了ApplicationContextAware接口,Spring调用setApplicationContext()方法,传入ApplicationContext
  6. 前置处理

    • 通过Bean的PostProcessors接口,Spring调用postProcessBeforeInitialization()方法。
  7. 初始化

    • 如果Bean实现了InitializingBean接口,调用afterPropertiesSet()方法。
    • 如果Bean的配置定义了init-method,该方法也会被调用。
  8. 后置处理

    • BeanPostProcessorpostProcessAfterInitialization()方法会被调用。
  9. 使用Bean

    • 现在,Bean已准备好使用,并且可以注入到其他Bean中或通过getBean()方法检索。
  10. 销毁前

    • 通过DestructionAwareBeanPostProcessors接口,可以实现自定义销毁前的逻辑。
  11. 销毁

    • 如果Bean实现了DisposableBean接口,调用destroy()方法。
    • 如果Bean的配置定义了destroy-method,该方法也会被调用。

以下是生命周期步骤的示例实现:

public class ExampleBean implements InitializingBean, DisposableBean {

    // 构造函数
    public ExampleBean() {
        //Bean实例化的逻辑
    }

    // 由BeanNameAware接口提供
    public void setBeanName(String name) {
        // 设置Bean的名称
    }

    // 由BeanFactoryAware接口提供
    public void setBeanFactory(BeanFactory beanFactory) {
        // 设置BeanFactory
    }

    // 由ApplicationContextAware接口提供
    public void setApplicationContext(ApplicationContext context) {
        // 设置ApplicationContext实例
    }

    // 由InitializingBean接口提供
    public void afterPropertiesSet() {
        // Bean属性设置后的初始化工作
    }

    // 自定义初始化方法
    public void init() {
        // 自定义的初始化方法
    }

    // 由DisposableBean接口提供
    public void destroy() {
        // 当Bean销毁时,释放资源
    }

    // 自定义销毁方法
    public void customDestroy() {
        // 自定义的销毁方法
    }
}

在配置文件中指定init-method和destroy-method:

<bean id="exampleBean" class="com.example.ExampleBean"
      init-method="init" destroy-method="customDestroy">
    <!-- bean属性配置 -->
</bean>

Spring Bean生命周期的理解非常重要,因为它影响了Bean的创建、配置设置、内存管理和应用程序的整体行为。通过实现特定的回调接口或指定方法,我们可以在Bean生命周期的各个阶段插入自定义逻辑,以增强应用组件的功能。

1.4 解释Spring的Bean作用域。

在 Spring 框架中,Bean 的作用域决定了 Spring 容器如何创建 Bean 的实例,管理它们的生命周期,以及它们在不同的上下文中如何交互。Spring 提供了几种内置的 Bean 作用域。以下是最常用的作用域类型:

  1. Singleton(默认):
    在 Spring IoC 容器中对于每个 Spring 容器只有一个 Bean 实例。它是默认的作用域。无论你多少次请求 singleton 作用域的 Bean,总是返回同一个 Bean 实例。

  2. Prototype
    对每个请求(或引用),都会创建一个新的 Bean 实例,不会缓存。适用于所有的 stateful beans。

  3. Request(仅 Web 应用程序):
    每次 HTTP 请求都会创建一个新的 Bean,它们仅在单个 HTTP request 生命周期内有效。

  4. Session(仅 Web 应用程序):
    在一个 HTTP Session 内,一个 Bean 定义对应一个实例。不同的 HTTP Session 会有不同的 Bean 实例。

  5. Application(仅 Web 应用程序):
    在一个 ServletContext 生命周期内,一个 Bean 定义对应一个实例。通常用于全局的共享 Bean,比如全局的配置对象。

  6. WebSocket(仅 Web 应用程序):
    在一个 WebSocket 生命周期内,一个 Bean 定义对应一个实例。

除了这些标准的作用域,Spring 还允许开发者自定义作用域。

作用域的选择通常基于 Bean 是否需要保存状态(stateful)还是无状态(stateless),以及 Bean 是否需要跨不同的请求或会话共享。

例如,如果你的 Bean 没有字段或状态,那么使用 singleton 作用域是合适的;但如果你的 Bean 维护特定用户的会话信息,那么将其定义为 session 作用域更合适。

Bean 的作用域可以在 Bean 定义时通过 scope 属性来指定。在基于 XML 的配置中,你可以通过 scope 属性来设置作用域;在基于注解的配置中,可以使用 @Scope 注解。

例如:

<!-- XML-based configuration -->
<bean id="myBean" class="com.example.MyBean" scope="prototype"/>

// Annotation-based configuration
@Component
@Scope("prototype")
public class MyBean {
    // ...
}

正确选择和管理 Bean 的作用域是 Spring Framework 中管理依赖和 Bean 生命周期的关键部分。

1.5 如何在Spring中实现Bean自动装配?

在Spring框架中,Bean的自动装配(autowiring)是通过Spring IoC(控制反转)容器自动实现依赖注入的一个特性。Spring提供了几种不同的自动装配策略,可以通过以下几种方式来实现:

1. 基于注解的自动装配

Spring 2.5引入了基于注解的配置,你可以在你的Bean定义中使用@Autowired注解来实现自动装配。

@Component
public class UserService {
    
    @Autowired
    private UserRepository userRepository; // Spring将自动注入这个依赖

    // ...
}

@Autowired注解可以应用在构造器、属性和方法(如setter方法)上。当@Autowired应用于属性时,你不需要提供setter方法。

为了启用注解的自动装配,需要在Spring配置中添加<context:annotation-config/>或使用@Configuration@ComponentScan注解。

2. 基于Java配置的自动装配

在Java配置类中,可以使用@Bean注解定义Bean,并通过方法参数实现自动装配。

@Configuration
public class AppConfig {

    @Bean
    public UserService userService(UserRepository userRepository) {
        return new UserService(userRepository);
    }

    // 省略其他@Bean定义
}

在上面的例子中,Spring将自动装配传递给userService方法的UserRepository参数。

3. 自动扫描组件

你可以使用@ComponentScan注解,并结合@Component@Service@Repository@Controller等注解,让Spring自动发现和注册Bean到IoC容器中。

@Configuration
@ComponentScan("com.example")
public class AppConfig {
    // 不需要定义具体的@Bean方法
}

在该配置中,Spring会扫描com.example包(以及子包)下所有使用了上述注解的类,并相应注册为Spring Bean。

4. XML配置的自动装配

在Spring的XML配置文件中,你可以使用autowire属性来声明自动装配的策略。

<bean id="userService" class="com.example.UserService" autowire="byType"/>

autowire属性有几个值:

  • no(默认):不自动装配,Bean引用必须在配置文件中明确定义。
  • byName:按Bean的名字(id)自动装配。
  • byType:按Bean的类型自动装配。
  • constructor:使用构造函数自动装配。
  • autodetect:自动检测构造函数或者按类型自动装配(不建议使用,已在Spring 5中废弃)。

注意事项

  • @Autowired在找不到合适的Bean进行装配时,默认行为是抛出异常。如果你想允许null值,可以设置required属性为false
  • 注解@Primary@Qualifier可以用来进一步细化自动装配过程中的选择逻辑。
  • 使用@Resource@Inject注解也可以实现类似@Autowired的自动装配功能,它们分别来自JSR-250和JSR-330。

根据应用的不同情况和个人偏好选择合适的自动装配方式。注解方式由于其简便性和可读性,通常是首选的自动装配方式。

1.6 什么是Spring容器?

Spring 容器是 Spring 框架的核心部分,负责创建、配置和管理应用程序中的对象实例。这些对象称为 Beans,它们通过控制反转(Inversion of Control,IoC)来组装和管理,也就是说对象之间的依赖关系由容器来处理,而不是对象本身。

Spring 容器通过读取配置元数据来管理 Beans 的生命周期和依赖关系。这些配置元数据可以是基于 XML 的配置文件、Java 注解或 Java 配置类。

Spring 容器的主要职责包括:

  • Bean 的创建和管理:容器负责创建 Bean 实例,配置它们,并管理它们的生命周期。
  • 依赖注入:容器通过依赖注入把对象需要的其他对象(依赖)注入到它们之中。
  • Bean 生命周期管理:容器管理 Bean 的完整生命周期,包括初始化和销毁等。
  • 提供不同的 Bean 作用域:例如单例(Singleton)、原型(Prototype)、会话(Session)和请求(Request)等。
  • AOP 集成:容器支持面向切面编程(Aspect-Oriented Programming),方便实现如事务管理、安全性、缓存等功能。

Spring 中的容器类型:

Spring 框架提供了几种类型的容器:

  • BeanFactory:是最简单的容器,提供基本的依赖注入支持。通常只在资源较少的设备或轻量级应用中使用。
  • ApplicationContext:是 BeanFactory 的子接口,添加了更全面的功能,如事件传播、资源加载、国际化等,适合企业级应用。在大多数 Spring 应用中,都是使用 ApplicationContext。

主要的 ApplicationContext 实现有:

  • ClassPathXmlApplicationContext:从类路径下的 XML 文件加载配置元数据。
  • FileSystemXmlApplicationContext:从文件系统中的 XML 文件加载配置元数据。
  • AnnotationConfigApplicationContext:从注解类中读取配置元数据。
  • WebApplicationContext:为 Web 应用程序提供上下文信息,是 ApplicationContext 接口的一个扩展。

Spring 容器是通过 IoC 来促进松耦合和代码分离的,通过外部配置和单责任原则,Spring 容器这样的 IoC 容器大幅度提高了企业级 Java 应用的开发效率和可维护性。

1.7 解释Spring中的PropertySource。

在Spring框架中,PropertySource 是对键值对属性源的抽象,它用于提供对配置数据的访问。这可以包括各种来源的配置数据,比如属性文件(properties files)、系统环境变量、JVM系统属性、YAML文件、JSON文件或者其他任意需要的来源。

PropertySource 类在Spring的org.springframework.core.env包下,常与Environment接口配合使用,它是Spring环境抽象的一部分,主要负责配置属性的解析和处理。

功能和用法

PropertySource 通过一个名称和包含的属性源泛型对象进行构造。Spring环境中的每个PropertySource 都有一个唯一的名字,在实际使用时,你可以按名称查找属性源,并获取其中的属性值。

例如,假设你有一个名为application.properties的文件,它包含了你的应用配置。你可以将这个文件注册到Spring环境中的属性源,然后通过环境对象来获取提供的属性值:

Environment env = applicationContext.getEnvironment();
String somePropertyValue = env.getProperty("some.property");

在Spring中注册自定义PropertySource

注册自定义PropertySource 可以通过实现EnvironmentPostProcessor接口,或者在Spring Context初始化过程中手动注册:

public class CustomEnvPostProcessor implements EnvironmentPostProcessor {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        PropertySource<?> customPropertySource = ... // 初始化你的PropertySource
        environment.getPropertySources().addLast(customPropertySource); // 添加到环境中
    }
}

@ConfigurationProperties的结合使用

在Spring Boot中,PropertySource 经常与@ConfigurationProperties相结合使用,这可以将外部化配置自动绑定到Java Beans上:

@ConfigurationProperties(prefix = "some")
public class SomeProperties {
    private String property;

    // standard getters and setters
}

当Spring Boot应用启动时,前缀为some的属性(例如some.property)会被绑定到SomeProperties实例的相应字段。

与属性文件结合使用

Spring @PropertySource 注解可以添加属性源到Spring的环境中:

@Configuration
@PropertySource("classpath:/com/app/configs/properties/default.properties")
public class AppConfig {
    // ...
}

层次性

Spring Environment中的多个PropertySource对象组成一个层次结构,这允许你为应用中的不同组件、不同环境(如开发环境、生产环境)定义不同的配置,并根据需要覆盖属性值。

总之,PropertySource 是一个强大的机制,允许开发者以灵活和统一的方式访问配置属性。通过利用Spring的属性源抽象,可以更好地管理应用程序配置,并轻松适应各种部署环境。

1.8 如何在Spring中实现国际化?

Spring 框架提供了一组内置功能,以支持国际化(i18n),使应用程序能够根据不同的地区显示不同的消息。以下是在 Spring 应用程序中实现国际化的基本步骤:

1. 定义消息资源

首先,你需要为你的应用定义一系列消息文件(通常是 properties 文件),每个文件包含特定的语言和地区的翻译。这些文件应遵循命名约定 messages_xx.properties,其中 xx 是语言代码,YY 是国家代码(可选)。例如:

  • messages.properties(默认消息文件)
  • messages_en.properties(英语消息)
  • messages_de.properties(德语消息)
  • messages_fr_FR.properties(法国法语消息)

消息文件的内容类似于键值对的列表:

welcome.message=Welcome to our application!
logout.message=You have been logged out successfully.

2. 配置国际化资源

在 Spring 的配置文件中配置一个 MessageSource,这通常在您的 @Configuration 类中完成,如下所示:

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasename("messages");
    messageSource.setDefaultEncoding("UTF-8");
    return messageSource;
}

setBasename 定义了资源文件的基础名称,Spring 会根据请求的地区自动选择适合的资源文件。

3. 配置地区解析器

定义一个 LocaleResolver 来解析请求的地区。这可以是基于会话、cookie、请求头等的解析器。例如,使用 AcceptHeaderLocaleResolver

@Bean
public LocaleResolver localeResolver() {
    AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
    localeResolver.setDefaultLocale(Locale.US); // 默认地区
    return localeResolver;
}

4. 使用消息

在代码中使用 MessageSource 获取国际化消息:

@Autowired
private MessageSource messageSource;

public String getWelcomeMessage(Locale locale) {
    return messageSource.getMessage("welcome.message", null, locale);
}

在这里,getMessage 方法根据提供的地区 locale 和消息键 "welcome.message" 来检索合适的翻译。

5. Controller 中的国际化

在 Spring MVC 中,您可以在 Controller 中通过 @RequestParamLocaleContextHolder 获得当前地区,并根据用户的地区返回国际化的响应:

@GetMapping("/")
public String homePage(@RequestParam(name = "lang", required = false) Locale locale, Model model) {
    model.addAttribute("greeting", messageSource.getMessage("welcome.message", null, locale));
    return "home";
}

应用中的国际化

  • 对于视图层(例如 Thymeleaf 或 JSP),您可以使用特定的标签来显示国际化的消息。
  • 对于 RESTful 应用,你可以在解析请求并返回响应时考虑地区信息。

Spring 的国际化支持让应用程序轻松适应不同的语言和文化,从而为全球用户提供更加丰富和本地化的用户体验。

2 Spring AOP

2.1 什么是面向方面编程(AOP)?

面向方面编程(Aspect-Oriented Programming,AOP)是一种编程范式,它旨在将横切关注点(cross-cutting concerns)与业务逻辑分离,从而提高模块化。横切关注点是指那些影响多个模块的问题,例如日志记录、安全性、事务管理等。

在传统的面向对象编程(OOP)中,这些关注点通常与业务逻辑交织在一起,这可能会导致代码重复、耦合度过高,且难以维护。AOP提供了一种机制,允许开发者将这些横切关注点独立于业务逻辑之外,在运行时动态织入到程序中的特定点,通常被称为“切入点”(Join points)。

AOP的主要概念:

  1. 切面(Aspect):切面是模块化横切关注点的单位,可以包含多个通知和切入点。
  2. 通知(Advice):通知定义了在切入点执行的横切逻辑。它可以是:
    • 前置通知(Before):在方法执行之前运行的代码。
    • 后置通知(After):在方法执行之后运行的代码,不管方法执行结果如何。
    • 环绕通知(Around):可以自定义在方法执行前后的行为的代码。
    • 后置返回通知(After Returning):在方法成功执行后运行的代码。
    • 异常通知(After Throwing):在方法抛出异常退出时执行的代码。
  3. 切入点(Join Point):是程序执行过程中的一些明确的点,例如方法的调用或特定异常的抛出。
  4. 切入点表达式(Pointcut Expression):定义了在哪些切入点应用通知的表达式,比如所有的set方法。
  5. 织入(Weaving):是将切面连接到其他应用程序类型或对象上的过程,可以在编译时、类加载时或运行时进行。

在Spring框架中,AOP是通过Spring AOP模块和AspectJ提供支持的。Spring AOP可以直接与Spring IoC容器集成,在运行时通过代理模式实现AOP的织入。AspectJ则是一个功能更为强大的AOP框架,它扩展了Java语言,提供了编译时和类加载时织入的能力。

AOP的使用案例包括但不限于:

  • 日志记录(Logging):记录方法的执行信息。
  • 事务管理(Transaction Management):声明性地管理事务。
  • 安全性(Security):执行访问控制。
  • 错误处理(Error Handling):统一的异常管理。
  • 性能监控(Performance Monitoring):记录方法的执行时间。

面向方面编程通过提供一种分离横切关注点并将它们独立地实现的方式,有助于使代码更易于维护和重用。

2.2 解释AOP中的术语:切点、连接点、通知、切面和织入。

在面向切面编程(AOP)中,一些核心术语定义了如何将横切关注点(cross-cutting concerns)集成到应用程序的模块化结构中。以下是这些术语的解释:

  1. 切面(Aspect)
    切面是跨越多个类或组件的关注点(例如日志记录、安全等)。在 AOP 语境下,切面可以被看作是封装了特定横切逻辑(如通知和切点)的模块或类。

  2. 连接点(Join Point)
    连接点是应用程序执行过程中的一个点,比如方法执行、异常处理或字段修改等地方。在连接点上,切面可以插入其定义的行为。

  3. 通知(Advice)
    通知定义切面在特定连接点执行的行为。有多种类型的通知:

    • 前置通知(Before advice):在连接点方法之前执行。
    • 后置通知(After returning advice):在连接点方法正常完成后执行。
    • 异常通知(After throwing advice):如果连接点方法抛出异常后执行。
    • 最终通知(After (finally) advice):无论连接点方法如何结束,都将执行。
    • 环绕通知(Around advice):在连接点方法运行前后执行,可以替代原有方法执行。
  4. 切点(Pointcut)
    切点是一组连接点,它指定了切面应该在哪些连接点上应用其通知。切点可以通过表达式来匹配特定的方法签名或类类型。

  5. 织入(Weaving)
    织入是把切面应用到目标对象来创建新的代理对象的过程。这可以在编译时(使用特定的编译器)、类加载时或运行时执行。Spring AOP 默认在运行时通过代理进行织入。

举例来说,如果你有一个关于日志记录的切面,可能会包括如下定义:

  • 切点:一个表达式,例如 “Service” 的所有方法,即这些服务类中的所有方法都是切点。
  • 通知:真正的日志逻辑,例如在每个服务方法开始前打印日志。
  • 织入:通知逻辑“织入”到应用程序中的哪个执行点上。

通过这些术语的组合和相互作用,AOP 为实现关注点分离和代码模块化提供了一个强大的框架。在 Spring AOP 中,这些术语被应用于利用代理模式在运行时为对象动态添加横切行为。

2.3 Spring支持哪些类型的AOP?

在 Spring 框架中,AOP(面向切面编程)提供了非侵入式的方式来增加额外的行为到现有的代码中,主要用于日志记录、性能统计、安全控制、事务处理等各种领域。Spring AOP 支持以下几种类型的切面:

1. 基于代理的 AOP

Spring AOP 默认使用基于代理的方法来实现 AOP,这涉及到两种代理方式:

  • JDK 动态代理:主要用于代理接口。如果一个被代理的对象实现了至少一个接口,Spring AOP 会使用 JDK 动态代理将调用转发给指定的方法。

  • CGLIB 代理:如果被代理的对象没实现任何接口,Spring AOP 会使用 CGLIB 库生成被代理对象的子类,并且拦截方法调用。CGLIB 可以代理类而不仅仅是接口。

在这种方式中,你可以通过编写切面(Aspect)、通知(Advice)和切点(Pointcut)定义非业务的代码,并把它们声明为在特定的连接点(Join Points)执行。

2. AspectJ 切面

Spring AOP 也支持 AspectJ 切面定义格式,虽然它仅使用 AspectJ 切点表达式语言,并不提供完全的 AspectJ 功能。AspectJ 是一个强大的并且成熟的 AOP 解决方案,它通过特殊的编译器或加载时织入来处理切面的创建。

3. 注解驱动的 AOP

Spring框架中很方便的使用注解来定义切面:

  • @Aspect:Spring AOP 允许使用 @Aspect 注解来定义一个切面。

  • @Before, @After, @Around, @AfterReturning, @AfterThrowing:使用这些注解来声明不同类型的通知,分别对应于方法执行之前、之后、环绕执行、返回后和抛出异常后的情景。

4. 通知类型(Advice Types)

无论是基于代理还是 AspectJ 切面,Spring AOP 支持以下类型的通知:

  • Before:方法执行之前执行的通知。

  • After Returning:方法成功执行后的通知。

  • After Throwing:方法抛出异常退出时的通知。

  • After (finally):不论成功还是异常退出的通知。

  • Around:包围一个方法的执行,可以决定是否执行方法,并自定义它的执行前和执行后的行为。

5. 切点表达式(Pointcut Expressions)

为了指定通知应该应用的方法或类,Spring AOP 使用了切点表达式语言,该语言是 AspectJ 切点表达式语言的一个子集,可以非常精确地找到你想要增强的代码区域。

示例:基于注解的切面定义

@Aspect
@Component // 允许 Spring 扫描到这个切面
public class MyAspect {

    @Before("execution(* com.example.service.MyService.*(..))") // 切点表达式
    public void doSomethingBefore(JoinPoint joinPoint) {
        // advice 执行的动作...
    }
    
    // 其他通知...
}

使用 Spring AOP 时,你通常不需要直接与底层代理机制打交道,而是通过声明通知和切点来组织横切关注点,从而提供了一个高层次、更容易使用的面向切面的编程模型。

3 Spring MVC / Web

3.1 解释Spring MVC的工作原理。

Spring MVC(Model-View-Controller)是 Spring 框架的一部分,用于创建 Web 应用程序。它遵循 MVC 设计模式,通过将应用程序分成模型(Model)、视图(View)和控制器(Controller)来实现分层架构,这有助于管理应用程序的复杂性。

Spring MVC 的工作原理:

  1. 请求收发

    • 用户请求通过网络到达 Spring Web 应用程序。
    • web.xml 文件中,配置的前端控制器 DispatcherServlet 捕获所有的请求。
  2. 请求处理

    • DispatcherServlet 查询 HandlerMapping 以确定请求由哪个 Controller 处理。
    • 找到相应的 Controller 后,DispatcherServlet 将请求发送到该 Controller
  3. 控制器逻辑

    • Controller 处理传入的请求,执行应用程序的业务逻辑,并返回一个 ModelAndView 对象,该对象包含模型数据和视图的名称。
  4. 模型填充

    • Model 包含业务数据,通常是由 Controller 操作业务逻辑得到,并将其传递给视图用于渲染。
  5. 视图解析

    • DispatcherServlet 会将 ModelAndView 对象传递给 ViewResolver 来解析视图。
    • ViewResolver 根据视图名称识别并选择需要渲染的特定视图。
  6. 视图渲染

    • 一旦 View 被选中,DispatcherServlet 将模型数据传递给它,View 使用这些数据渲染页面。
    • 渲染完成的页面(HTML/CSS/JavaScript 等)作为 HTTP 响应返回给用户的浏览器。
  7. 返回响应

    • 用户接收到渲染完成的页面,代表着原始请求流程的完成。

核心组件:

  • DispatcherServlet:作为前端控制器,处理所有的请求并分发给相应的 Controller
  • HandlerMapping:用于查找每个请求的处理程序或 Controller
  • Controller:处理 HTTP 请求,执行业务逻辑并返回 ModelAndView
  • ModelAndView:封装了响应的模型数据和视图信息。
  • ViewResolver:解析视图名到实际视图的组件。
  • View:表示 MVC 中的 V,用于渲染返回给用户的页面。

Spring MVC 提供了丰富的注解去简化开发工作,包括 @Controller@RequestMapping@ResponseBody@RequestParam 等,这些都使得将请求映射到对应的处理方法更加容易。

Spring MVC 架构清晰并且可扩展,它利用 Spring 的依赖注入特性来组织 Web 应用程序的组件,使得 Web 应用程序易于测试和维护。

3.2 什么是DispatcherServlet?

DispatcherServlet 是Spring MVC框架核心的前端控制器(Front Controller)。它负责接收HTTP请求、组织请求处理流程、调度请求到对应的处理器(controller),以及解析视图和返回响应。作为Spring Web MVC框架的中心组件,DispatcherServlet 提供了Spring的集成特性,如依赖注入和声明式事务管理。

在一个基于Spring MVC的Web应用程序中,通常会在web.xml配置文件中或通过Java配置类配置一个或多个DispatcherServlet 实例。每个DispatcherServlet 实例对应着一个Spring应用上下文(ApplicationContext),其中定义了Beans、控制器(Controllers)、视图解析器(View Resolvers)、处理器映射(Handler Mappings)等组件。

工作流程

当一个请求到达DispatcherServlet时,会按以下流程处理:

  1. 请求映射(Request Mapping)
    DispatcherServlet 使用一组HandlerMapping策略来确定请求的目标处理器。

  2. 调用处理器(Invoke Handler)
    一旦找到合适的处理器,DispatcherServlet 将委派请求给它,并传递HttpServletRequestHttpServletResponse等。

  3. 处理器适配器(Handler Adapter)
    通常请求会通过一个实现了HandlerAdapter接口的适配器,以保证任何类型的处理器都能与DispatcherServlet 无缝协作。

  4. 模型填充(Model Population)
    处理器执行业务逻辑后,会返回一个ModelAndView实例,其中包含了模型数据和视图名。

  5. 视图解析(View Resolution)
    通过配置的ViewResolver,将逻辑视图名解析为具体的View实例。

  6. 结果渲染(Render Results)
    最后,View 负责将模型数据渲染到客户端,生成最终的HTML、JSON或XML等格式的响应。

配置

在基于web.xml的传统Spring MVC项目中,DispatcherServlet的配置可能如下所示:

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

在Spring Boot项目中,没有必要手动配置DispatcherServlet,因为Spring Boot会自动注册并配置DispatcherServlet

DispatcherServlet为Spring MVC的运行提供了清晰、定义明确的流程,同时利用了Spring框架的诸多特性,提供灵活、可扩展的Web应用架构。

3.3 描述一个典型的Web请求的生命周期。

一个典型的 Web 请求生命周期涉及客户端(通常是网络浏览器或应用程序)和服务器之间的一系列交互步骤。以下是详细介绍从请求生成到接收响应整个过程的各个阶段:

1. 请求发起

  • 用户操作:用户在浏览器中输入网址、在应用中点击按钮或通过 API 发起请求。
  • 客户端处理:浏览器或应用构建 HTTP 请求,包括正确的 HTTP 方法(GET、POST、PUT、DELETE 等)、URI、HTTP 头信息以及可能的请求体。

2. DNS 解析

  • 域名解析:浏览器向 DNS 服务器查询输入网址的 IP 地址。如果 DNS 信息被缓存了,则此步骤可被快速完成。

3. 建立连接

  • TCP 握手:一旦获取到服务器的IP地址,客户端与服务器之间建立 TCP 连接,这通常涉及一个三次握手过程。

4. 发送 HTTP 请求

  • HTTP 请求传输:TCP 连接建立后,客户端通过该连接发送 HTTP 请求到服务器。

5. 服务器处理请求

  • Web 服务器接收:Web 服务器(如 Apache、Nginx 或其他应用服务器)接收到请求后,根据请求类型决定如何处理。
  • 应用服务器处理:请求可能转发给后端应用服务器(如 Node.js、Django、Spring等),它将处理应用逻辑。
  • 数据库交互:在处理过程中,应用服务器可能会查询数据库或与其他服务交互以获取或存储数据。

6. 生成响应

  • 构建响应:应用服务器执行完必要的处理后,构建一个 HTTP 响应,包括状态码(如 200 OK)、响应头信息和响应体(如 HTML、JSON 等)。

7. 发送 HTTP 响应

  • 传输响应数据:响应数据通过 TCP 连接回传给客户端。

8. 客户端处理响应

  • 浏览器渲染:对于网页请求,浏览器将解析 HTML/CSS/JavaScript,并渲染页面给用户。
  • 关闭连接:HTTP/1.1 协议中可以保持持久连接,而在 HTTP/2 中默认是多路复用。而关闭 TCP 连接遵循四次挥手的过程,但可以由任一端启动。

9. 后续处理

  • 保存缓存:浏览器可能会根据响应头信息决定缓存网页数据。
  • 跟踪会话:服务器可能每次访问时使用 cookie 或 session 跟踪用户的状态。

这个生命周期中的许多步骤都涉及了各种 Web 技术、协议和最佳实践,比如安全传输(HTTPS)、跨源资源共享(CORS)、内容分发网络(CDN)、负载均衡和 Web 缓存等。了解这个生命周期可以帮助开发人员设计并构建更高效、安全并可扩展的网络应用。

3.4 如何使用@RequestMapping注解?

在Spring框架中,@RequestMapping注解是用来映射Web请求(例如HTTP GET或HTTP POST)到Spring的处理器方法的。从Spring 4.3版本开始,为了进一步简化常见的HTTP方法映射,新增了一些更具体的注解,例如@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping,它们都是@RequestMapping的特定变种。

使用@RequestMapping注解的基本方式包括:

@Controller
public class MyController {

    // 处理HTTP GET请求
    @RequestMapping(value = "/myGetPath", method = RequestMethod.GET)
    public String handleGetRequest() {
        // 处理请求的代码
        return "getViewName";
    }

    // 处理HTTP POST请求
    @RequestMapping(value = "/myPostPath", method = RequestMethod.POST)
    public String handlePostRequest() {
        // 处理请求的代码
        return "postViewName";
    }

    // 通过@RequestParam绑定请求参数
    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public String handleGetRequestWithParam(@RequestParam("id") String userId) {
        // 使用 userId 获取或处理用户的代码
        return "userView";
    }

    // 处理多种HTTP请求方法
    @RequestMapping(value = "/multiMethodsPath", method = {RequestMethod.GET, RequestMethod.POST})
    public String handleMultipleMethodsRequest() {
        // 处理GET或POST请求的代码
        return "multiMethodsView";
    }
}

方法参数和返回值:

  • 方法参数可以自动绑定来自HTTP请求的参数,如表单数据或查询字符串。
  • 返回值通常是一个视图名,但也可以根据需要返回其他响应类型(如ResponseEntityModelAndView、直接写入响应的void返回类型)。返回值也可以被映射到响应正文(使用@ResponseBody注解)。

使用其它HTTP方法专用映射注解的示例:

@Controller
public class MyController {

    @GetMapping("/myGetPath")
    public String handleGetRequest() {
        // ...
    }

    @PostMapping("/myPostPath")
    public String handlePostRequest() {
        // ...
    }
    
    // 其他方法类似,使用 @PutMapping, @DeleteMapping, @PatchMapping
}

特性:

  • valuepath属性用于指定请求映射的URL。
  • method属性用于指定HTTP方法。
  • 可以指定多个URL到同一个方法。
  • 可以处理请求参数、请求头等,使用注解如@RequestParam@RequestHeader@RequestBody等进行绑定和映射。

使用@RequestMapping或特定的HTTP映射注解是在Spring MVC中定义控制器方法和请求映射关系的标准做法。这使得控制器对外包括RESTful服务的终端点定义清晰和简单。

4 Spring Boot

4.1 Spring Boot有哪些优点?

Spring Boot 是 Spring 框架的一个扩展,它极大地简化了基于 Spring 的应用开发。其核心思想是约定优于配置,Spring Boot 为开发者提供了一种快速搭建 Spring 应用程序的方法。以下是 Spring Boot 的一些主要优点:

  1. 快速启动和开发
    Spring Boot 内置了许多默认配置,减少了项目搭建和配置的工作量,使得开发人员可以迅速开始编写业务代码。

  2. 独立运行
    Spring Boot 应用是自包含的,可以打包成单个可执行 JAR 或 WAR 文件,这些包含了嵌入式的 Web 服务器,比如 Tomcat、Jetty 或 Undertow,使得你不再需要部署到外部容器中。

  3. 自动配置
    Spring Boot 自动配置特性基于项目在类路径中添加的依赖项,它会尝试根据添加的库自动配置你的 Spring 应用。

  4. 生产就绪
    它提供了大量的生产级特性,如健康检查、度量信息收集、外部化配置等,通过 Actuator 模块方便地管理和监控应用程序。

  5. 无代码生成和 XML 配置需求
    Spring Boot 不需要任何类型的代码生成和不需要 XML 配置文件。所有的配置都可以通过注解和属性文件完成。

  6. 微服务支持
    Spring Boot 适合构建可独立发布的微服务应用,与 Spring Cloud 结合可以方便地提供云端服务。

  7. 广泛的社区和文档支持
    作为一个流行的开源框架,Spring Boot 有着广泛的社区支持,提供大量教程、指南和最佳实践。

  8. 灵活的依赖管理
    通过 “starters”,它提供了许多现成的依赖管理集,这些都是预先定义好了能协同工作的依赖项。

  9. 容易测试
    Spring Boot 提供多种测试工具,支持单元测试和集成测试,帮助构建质量更高、稳定性更强的软件。

  10. 开箱即用
    来自广泛的 Spring 技术栈和第三方库的自动集成,让你专注于应用程序的构建而不是花费时间进行项目的配置。

总之,Spring Boot 能够让开发者以最小的麻烦快速地启动和运行新的 Spring 项目,同时提供了丰富的工具集来支持应用程序的开发到生产环境部署的整个生命周期。

4.2 解释自动配置(auto-configuration)。

自动配置是 Spring Boot 的一个核心特性,它允许框架根据添加的依赖关系、各种属性设置和其他因素来猜测并配置应用程序所需的组件。这种机制可以简化配置的过程,让开发者快速启动和运行 Spring 应用程序。

工作原理

当 Spring Boot 应用程序启动时,它会执行一系列的自动配置步骤,其中涉及到:

  1. 启动监听器和初始化器:在 Spring 应用程序上下文初始化时,会检查和执行启动监听器(ApplicationContextInitializers)和初始化器(ApplicationListeners)。

  2. 应用@EnableAutoConfiguration注解:通过 @SpringBootApplication 或特定配置类上的 @EnableAutoConfiguration 注解,告知 Spring Boot 开始自动配置过程。

  3. 读取 spring.factories 文件:Spring Boot 会读取类路径下所有 JAR 文件中 META-INF/spring.factories 文件,这里面指定了哪些类需要被自动配置。

  4. 条件化配置:自动配置类通常采用带有 @Conditional 注解的条件化配置方式,这意味着只有在某些条件成立时,相应的自动配置才会生效。条件可以是类路径上的类存在、Bean 不存在、某个属性值的存在等。

  5. 配置默认和自定义属性:自动配置类可以配置默认值,但如果在应用程序的配置文件(如 application.propertiesapplication.yml)中定义了自定属性,这些属性会覆盖掉默认值。

  6. 创建和注册 Bean:满足条件的自动配置类会创建 Bean 并注册到应用程序上下文中。

示例

下面是一个 @EnableAutoConfiguration 的典型用例,它可能是隐式通过 @SpringBootApplication 注解包含的:

@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}

Spring Boot 的自动配置会尝试为你配置如下组件:

  • Spring MVC 的 DispatcherServlet;
  • Jackson、Gson、Jsonb 等 JSON 工具(如果这些库在类路径上);
  • JdbcTemplate、JPA 和 Hibernate(如果数据库相关依赖存在);
  • Embedded Tomcat、Jetty 或 Undertow(如果web相关依赖存在);
  • 及许多其他常见的库和框架。

自定义和控制自动配置

尽管自动配置是一个强大的特性,但有时开发者可能需要自定义或细致地控制自动配置的行为。以下是几种方式:

  • 排除自动配置类:可以通过在 @SpringBootApplication 上使用 exclude 属性或者设置 spring.autoconfigure.exclude 属性来排除特定的自动配置类。

    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    public class MyApp { /* ... */ }
    
  • 使用配置属性:可以在 application.propertiesapplication.yml 中指定属性来覆盖自动配置的默认行为。

  • 条件注解:自定义的配置类也可以使用 @Conditional 相关的注解来实现类似的条件化逻辑。

自动配置最大限度地减少了显式声明配置的需要,让开发者可以专注于应用程序的特定部分,同时在需要时仍然具有足够的灵活性进行定制。

4.3 如何在Spring Boot中自定义starter?

在 Spring Boot 中,starter 是一种由依赖项组成的便捷方式,它提供了快速启动并运行 Spring 应用程序所需的所有必要依赖项。创建自定义 starter 可以被视为创建一个库,使得在多个应用程序之间共享特定的配置或服务变得简单。

以下是创建自定义 Spring Boot starter 的基本步骤:

1. 创建 Starter 项目

首先,你需要创建一个新的 Maven 或 Gradle 项目。这个项目将包含你的自定义 starter 代码。

例如,在 pom.xml 中,你的项目可能会这样定义:

<groupId>com.example</groupId>
<artifactId>my-custom-starter</artifactId>
<version>1.0-SNAPSHOT</version>

2. 添加 Starter 依赖

自定义 starter 项目通常依赖 Spring Boot 的 starter 项目。添加所需的依赖项到你的 starter 的 pom.xml 文件中。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- 其他你需要的依赖 -->
</dependencies>

3. 自动配置类

创建一个 Java 类来定义自动配置和条件。这个类应该使用 @Configuration 注解,并通过 @Conditional 注解指定在什么条件下应该自动应用这个配置。

@Configuration
@ConditionalOnClass({ YourService.class })
@ConditionalOnProperty(prefix = "your.starter", name = "enabled", havingValue = "true", matchIfMissing = true)
public class YourAutoConfiguration  {

    @Bean
    @ConditionalOnMissingBean
    public YourService yourService(){
        return new YourServiceImpl();
    }
}

4. 资源文件 spring.factories

在项目的 resources/META-INF 目录下创建一个文件名为 spring.factories,然后在其中指定你的自动配置类。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.YourAutoConfiguration

5. Starter 的属性配置

如果你的自定义 starter 有任何可配置的属性,可以通过在 resources 目录下创建 application.properties 文件来设置默认值。

6. 测试 Starter

创建一个测试应用程序来验证你的自定义 starter 的功能。包括你刚刚创建的 starter 作为依赖,并尝试运行应用程序以确认自动配置按预期工作。

7. 打包并发布

一旦你的 starter 准备好并且经过测试,你可以编译并打包它,然后发布到 Maven 中央仓库或你选择的任何其他仓库。这样其他项目就可以作为依赖来使用它了。

mvn clean install
# 或者发布到远程仓库
mvn deploy

记住,在创建和维护自定义 starter 时,保持它的轻量级,注意不要在其中包含过多的依赖项,以防止潜在的依赖冲突。

创建自定义 Spring Boot starter 使得特定的自动配置和公共依赖可在各个项目间复用,这样做能够大量减少重复代码和配置,从而提升效率。如果你在创建自定义 starter 的过程中遇到任何问题,或者需要更详细的指导,请随时提问。

4.4 解释Spring Boot中的Actuator。

Spring Boot Actuator是Spring Boot的一个子项目,它提供了一系列生产准备(production-ready)特性,帮助你监控和管理Spring Boot应用程序。Actuator 主要通过HTTP端点或JMX beans来暴露应用程序的内部状况。

Actuator 包含了很多内置端点,每个端点都有特定的用途,以下是一些重要的内置端点:

  1. /actuator/health:显示应用的健康信息,可以显示数据库、磁盘空间、自定义检查等状态。
  2. /actuator/info:显示应用程序的基本信息,例如版本、描述或自定义属性。
  3. /actuator/metrics:显示应用的各种度量指标信息,例如内存使用情况、GC活动、Web请求统计等。
  4. /actuator/httptrace:显示HTTP跟踪信息(需要显式启用)。
  5. /actuator/env:显示应用的环境属性,包括配置文件、环境变量、系统属性等。
  6. /actuator/logfile:直接返回日志文件的内容(如果日志文件路径被设置)。
  7. /actuator/threaddump:执行并返回线程的dump信息。

此外,Actuator 还允许你添加自定义端点来暴露应用程序特有的操作或信息,以及调整端点的安全性和暴露的细节。

Spring Boot Actuator 还集成了Micrometer,这是一个度量库,可以将度量指标信息发送到各种支持的监控系统,比如Prometheus、InfluxDB、DataDog、Elastic等。

在Spring Boot中使用Actuator,通常需要在你的pom.xmlbuild.gradle中添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

一旦引入Actuator依赖,就可以在应用的配置文件中(例如application.propertiesapplication.yml)激活和配置Actuator的端点。

Actuator的端点可能包含敏感信息,因此在生产环境中,你需要考虑安全性,配置端点的访问权限,比如通过Spring Security限制访问。

总而言之,Spring Boot Actuator 是Spring Boot应用程序生命周期管理的一个重要工具,它为运行中的应用程序提供了丰富的实时信息和操作界面,使得维护和问题诊断变得简便且高效。

5 Spring Data

5.1 什么是Spring Data JPA?

Spring Data JPA (Java Persistence API) 是 Spring Data 项目的一部分,它旨在简化数据访问层的实现。Spring Data JPA 通过提供基于 JPA 的库来提高数据访问层(通常称为 Repository 或 DAO 层)的开发效率,为开发者与关系型数据库交互提供了一个更高层次的抽象。

以下是 Spring Data JPA 的一些核心特性:

1. Repository 抽象

Spring Data JPA 提供了 Repository 接口的各种实现,这使得进行 Create、Read、Update 和 Delete (CRUD) 操作变得非常简单。

你可以通过扩展 RepositoryCrudRepositoryJpaRepository 接口来创建自定义的 Repository 接口,并为其实体类声明方法。Spring Data JPA 在运行时会自动提供实现。

2. 查询方法解析

Spring Data JPA 支持从方法名自动生成查询。例如,在 Repository 接口中声明的方法 findByName(String name) 将自动生成一个查询,从而让你能够按照 name 属性查询实体。

3. 注解查询

除了从方法名创建查询,Spring Data JPA 还允许通过使用 @Query 注解直接在方法上定义查询。你可以使用 JPQL(Java Persistence Query Language)或原生 SQL 语句来指定查询。

public interface UserRepository extends JpaRepository<User, Long> {
    @Query("SELECT u FROM User u WHERE u.email = :email")
    User findByEmailAddress(@Param("email") String email);
}

4. 透明事务管理

Spring Data repositories 默认已经添加了 @Transactional 注解,处理了事务的起始和结束,因此通常情况下,开发者不必担心事务管理的细节。

5. 分页和排序支持

Spring Data JPA 提供了类似 PageableSort 的抽象来处理分页和排序,不需编写任何特定数据库的分页代码。

Page<User> findAll(Pageable pageable);

6. 投影(Projections)

支持返回基于实体的子集,这意味着可以定义接口来仅获取需要的数据。

7. 规范(Specifications)

Spring Data JPA 可以与 JPA Criteria API 联用,使用规范来构建动态查询。

使用 Spring Data JPA 的优点

  • 减少样板代码:不再需要编写大量标准的数据访问代码。
  • 短时间内提升生产力:简单的 CRUD 操作和查询方法对开发者来说更加直观。
  • 易于集成和测试:Spring Data JPA 可以很好地融合到 Spring 应用中,并且可以使用 H2 之类的内存数据库非常方便地进行集成测试。
  • 可移植性:基于 JPA 标准,所以不依赖于特定的数据库。

Spring Data JPA 是 Spring 生态系统中的强大工具,它极大地简化了基于 JPA 的数据访问代码的开发工作,对于追求高效开发且结构清晰的 Java 应用程序非常有帮助。

5.2 如何在Spring中使用repositories?

在Spring框架中,特别是在使用Spring Data的上下文里,Repositories 提供了一种简单的数据访问抽象和数据持久化的领域层。

Repositories通常用在以下场景中:

  1. 数据访问层(Data Access Layer)
    作为连接数据库和应用程序的领域模型之间的桥梁,Repository 负责直接与数据源通信,进行 CRUD(创建、读取、更新、删除)操作。

  2. 领域驱动设计(Domain-Driven Design, DDD)
    在DDD中,Repository 是集合的一种概念,它提供了查找和持久化领域对象的机制。

在Spring中使用Repositories的基本步骤:

  1. 定义领域模型
    首先对业务实体进行建模,以 Java 类(比如 @Entity JPA 实体)的形式定义域模型。

    @Entity
    public class User {
        @Id
        private Long id;
        private String name;
        // Getters and setters...
    }
    
  2. 创建Repository接口
    接着,创建一个扩展了Repository或其子接口(如 CrudRepositoryJpaRepository)的接口。Spring Data 在运行时根据接口定义动态创建实现。

    public interface UserRepository extends CrudRepository<User, Long> {
        List<User> findByName(String name);
    }
    
  3. 使用Repository
    在服务或控制器中,自动注入(Autowire)Repository接口,并使用定义好的方法进行数据操作。

    @Service
    public class UserService {
    
        @Autowired
        private UserRepository userRepository;
    
        public User getUser(Long id) {
            return userRepository.findById(id).orElse(null);
        }
    
        public List<User> findUsersByName(String name) {
            return userRepository.findByName(name);
        }
    
        // 更多业务逻辑...
    }
    

Repository接口的实现方式:

  • 简单方法
    通过扩展CrudRepositoryPagingAndSortingRepository,你可以直接获得包括增删改查在内的许多常见操作的实现。

  • 自定义查询
    也可以声明自定义的查询方法,Spring Data 会自动根据方法的名字来实现这些方法。你还可以使用 @Query 注解自定义实现的SQL或JPQL查询。

  • 复杂查询
    对于更复杂的业务逻辑,可以在自己编写的Repositories接口中添加自定义方法,并提供一个实现类,这个实现类需要遵循特定的命名约定(通常是在实现类名字上加后缀Impl)。

Spring Data Repositories提供了一种比直接使用ORM工具更加简洁和不容易出错的方法。通过定义接口并遵循一系列规则和命名约定,你可以快速实现数据访问层,从而让你能专注于业务逻辑。

5.3 解释懒加载和急加载。

懒加载(Lazy Loading)和急加载(Eager Loading)是与数据加载相关的两种策略,它们通常用于应用程序中的 ORM(对象关系映射)以及处理一对多和多对一关系:

懒加载(Lazy Loading):

懒加载是一种延迟数据加载的策略,即仅在实际需要数据时才从数据库中检索相关数据。这可以提高应用程序启动的速度,因为最初只加载必要的数据,而不是一开始就加载全部关联数据。

  • 优点

    • 减少初始加载时间,因为一开始只加载必需的对象,关联的复杂属性暂不加载。
    • 节省资源,特别是当关联数据很少或几乎不被访问时。
  • 缺点

    • 可能导致“N+1 选择问题”(即每次访问关联对象时都需要执行单独的查询,从而执行多次数据库查询)。
    • 对象的完整加载需要应用程序处于数据库会话中(或者需要保持连接打开),稍后访问这些对象时可能会遇到未初始化的懒加载属性。

急加载(Eager Loading):

相反,急加载策略一开始就加载所有需要的数据,包括所有相关联的数据。当从数据库中检索一个对象时,所有相关联的对象也会立即被加载,并可供即时使用。

  • 优点

    • 无需额外的数据库查询,对象和其关联对象一起加载,可以方便地访问关联数据。
    • 避免懒加载可能出现的延迟和懒加载异常。
  • 缺点

    • 可能加载了太多不需要的数据,造成初始加载时间较长和内存消耗增加。
    • 当关联数据庞大时,即使这部分数据不被频繁访问,仍会占用大量资源。

在某些 ORM 框架中,如 Hibernate 或 JPA,可以在类或关联映射上通过注解来配置懒加载或急加载的策略。例如,在 JPA 中,你可以使用 @OneToMany@ManyToOne 等注解中的 fetch 属性来指定加载策略:

@Entity
public class Post {
    @OneToMany(fetch = FetchType.LAZY) // 懒加载
    private List<Comment> comments;
}

@Entity
public class Comment {
    @ManyToOne(fetch = FetchType.EAGER) // 急加载
    private Post post;
}

选择哪种加载策略取决于具体应用程序的需求和优化目标。通常规则是如果你通常需要立即访问关联数据,则急加载是合适的;如果你通常不需要关联数据或关联数据是按需加载的,则懒加载可能是更好的选择。正确使用懒加载和急加载可以优化应用程序性能和用户体验。

6 Spring Security

6.1 描述Spring Security框架。

Spring Security 是针对 Java 应用程序的一个强大且可高度自定义的身份验证和访问控制框架。它是 Spring 生态系统的关键组件之一,致力于为企业应用程序提供综合的安全解决方案。Spring Security 框架主要围绕下面几个核心领域提供功能:

身份验证(Authentication)

Spring Security 支持多种身份验证机制,如表单基础认证、OAuth2、LDAP、基于JWT的认证、HTTP基本认证等。身份验证过程负责确保用户是谁,通常会涉及用户名和密码的校验,或者Token的验证。身份验证成功后,用户的身份信息会被存储在安全的上下文中。

授权(Authorization)

一旦身份验证结束,授权就是确定用户是否拥有执行特定操作的权限。Spring Security 提供方法级别的安全性,通过注解(如@PreAuthorize@Secured 等)控制方法的访问。此外,还可以对URL级别的请求进行权限控制,定义哪些角色可以访问哪些资源。

Web安全

默认情况下,Spring Security 会配置一系列过滤器来拦截网络请求和响应。它可以保护web应用程序免受常见的网络安全攻击,例如CSRF(跨站请求伪造)、XSS(跨站脚本)等。Spring Security 也提供了防止会话固定、点击劫持和其他网络攻击的功能。

密码加密

Spring Security 支持多种密码存储格式和密码编码策略。它提倡BCrypt加密算法,确保即使数据被泄露,密码也不容易被暴力破解。

方法安全

它也支持在服务层添加安全控制,通过注解可以控制方法的执行权限。

会话管理

Spring Security 提供了灵活的会话管理,支持会话固化保护、并发会话控制等功能。

默认安全配置

通过自动配置功能@EnableWebSecurity,它提供了合理的默认安全配置,这使得开发者可以快速引入安全性到他们的应用程序中,而无需从头开始编写大量的安全配置代码。

LDAP身份验证

Spring Security 还支持使用轻量级目录访问协议(LDAP)进行身份验证。

OAuth2

框架支持使用 OAuth2 协议对 REST API 进行保护,允许您编写支持登录认证、令牌颁发和校验的服务。

Spring Security 通过一系列的安全过滤器链提供它的安全服务,这些过滤器链是Spring MVC的一部分,封装了大多数需要手动实现的安全功能。通过一系列的Java配置或XML配置的方式,开发者可以自定义这些安全策略。

总之,Spring Security 提供了一种声明性的安全访问控制解决方案,易于集成进现有的 Spring 应用中,并且提供了灵活的安全策略来满足各种应用场景的需求。它支持通用的安全规范,易于扩展并且维护活跃,得到了Spring社区和众多企业的推崇和信赖。

6.2 如何在Spring中配置基于HTTP的安全?

在Spring中配置基于HTTP的安全主要依赖于Spring Security库,它提供一个强大的、可定制的认证和访问控制框架。以下是一个基本的配置过程指南,它涉及到在Spring Boot环境中通过继承WebSecurityConfigurerAdapter类设置HTTP安全配置。

  1. 添加Spring Security依赖
    在你的项目中,确保包含了Spring Security的起步依赖。

    pom.xml for Maven:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    

    build.gradle for Gradle:

    implementation 'org.springframework.boot:spring-boot-starter-security'
    
  2. 创建安全配置类
    创建一个新的配置类继承自WebSecurityConfigurerAdapter并覆盖其中的方法以设置安全性细节。

    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                .antMatchers("/", "/home").permitAll() // 允许对根目录和/home的访问
                .anyRequest().authenticated() // 所有其他路径都需要认证
                .and()
                .formLogin()
                .loginPage("/login") // 定义登录页路径
                .permitAll() // 允许所有用户访问登录页
                .and()
                .logout() // 配置登出
                .permitAll(); // 允许所有用户访问登出页
        }
    }
    
  3. 配置用户详细信息服务
    使用configure(AuthenticationManagerBuilder auth) 方法配置用户存储。这可能是内存内的用户、数据库用户或LDAP用户。

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
            .withUser("user").password(passwordEncoder().encode("password")).roles("USER")
            .and()
            .withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN");
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
  4. 配置密码编码器
    最好使用强密码编码器来存储和匹配密码。在Spring 5中,推荐使用BCryptPasswordEncoder

  5. 开启/关闭CSRF保护
    Spring Security默认开启了跨站请求伪造(CSRF)保护。如果需要,你可以根据需求启用或禁用它:

    http.csrf().disable(); // 例如在调用API时通常将其禁用
    
  6. 配置例外规则
    你可以为特定模式的URL设置特殊的安全规则,如允许或拒绝。

  7. 启用HTTPS/SSL
    若要确保安全传输,你应该要求所有交换在加密的HTTP连接上进行:

    http.requiresChannel()
        .anyRequest()
        .requiresSecure();
    

通过上述步骤,你可以设置基于HTTP的安全配置,以保护你的Spring应用免受未授权访问。Spring Security提供了更多高级特性,如OAuth认证、方法级安全性、自定义用户详细信息服务等,这些都可以根据应用程序的需要进行扩展和定制。

6.3 什么是认证和授权?

在安全领域,认证(Authentication)和授权(Authorization)是两个核心概念,它们通常被用于验证用户身份以及确定用户是否有权访问特定的资源或执行特定的操作。

认证(Authentication)

认证是确定用户身份的过程。用户在尝试访问系统资源时,需要提供某种形式的凭据来证明他们是谁。这些凭据通常是用户名和密码,但也可以是更复杂的形式,如多因素认证(MFA),包括一次性密码(OTP)、生物特征认证(指纹或面部识别)或智能卡。

认证过程通常包括以下步骤:

  1. 用户提供身份凭据(如用户名和密码)。
  2. 系统验证提供的凭据与存储的用户数据是否匹配。
  3. 如果凭据有效,系统允许用户进入。否则,系统将拒绝访问并可能记录认证尝试。

授权(Authorization)

在用户通过认证之后,授权是确定用户是否拥有执行特定操作或访问特定资源的权限的过程。授权是基于策略的,通常涉及了对用户角色和权限的检查。

授权过程通常包括以下步骤:

  1. 系统检查已认证用户的角色和权限。
  2. 系统根据预定义的权限规则(策略)决定用户是否有权执行所请求的操作。
  3. 授予或拒绝访问权限。

授权与认证的差异

简而言之,认证是关于“你是谁”,而授权是关于“你能做什么”。认证发生在授权之前,并且只有经过成功认证的用户才会被授权检查他们是否有权执行特定的操作或访问资源。

在Web应用中,认证通常是通过登录过程完成的,而授权可能基于角色的访问控制(Role-Based Access Control, RBAC)或其他更复杂的访问控制模型来实现。技术栈中的许多框架,如Spring Security(对于Java Spring应用程序)和OAuth2、JSON Web Token (JWT) 等协议,提供了认证和授权过程的实现。

6.4 解释OAuth2。

OAuth 2.0 是一个开放标准的授权框架,允许第三方应用在不访问用户的登录信息的情况下访问用户在另一个服务提供商上的特定资源。它的前身是 OAuth 1.0,而 OAuth 2.0 带来了更简洁易用的流程,并增加了多种授权类型来支持不同场景的应用。

OAuth 2.0 的角色

OAuth 2.0 定义了四种角色:

  1. 资源所有者(Resource Owner):能够授权访问受保护资源的实体,通常是用户。
  2. 资源服务器(Resource Server):托管受保护资源(如用户的私人信息)并能响应资源请求的服务器。
  3. 客户端(Client):试图访问资源服务器上受保护资源的第三方应用。
  4. 授权服务器(Authorization Server):验证资源所有者的身份并发出访问令牌的服务器。

OAuth 2.0 的流程

一个典型的 OAuth 2.0 授权流程涉及以下步骤:

  1. 授权请求:客户端请求资源所有者的授权。这个步骤通常涉及将用户重定向到服务提供商的结束点(Endpoint)。
  2. 授权批准:资源所有者批准请求并授予客户端权限。
  3. 令牌请求:客户端使用步骤2中获得的授权向授权服务器申请访问令牌。
  4. 令牌响应:授权服务器对客户端进行身份验证后,同意发放访问令牌。
  5. 访问资源:客户端使用访问令牌向资源服务器发起资源请求。
  6. 资源响应:资源服务器验证令牌的有效性,如验证通过,将提供对资源的访问。

认证流(Grant Types)

OAuth 2.0 支持多种授权类型,适应不同的应用场景:

  • 授权码类型(Authorization Code):适用于有前端和后端分离的 Web 应用,这是最常用的流程。
  • 简化类型(Implicit):适用于纯客户端应用,如单页应用(SPA)。
  • 资源所有者密码凭证(Resource Owner Password Credentials):适用于用户完全信任的应用,如设备软件或操作系统内的应用。
  • 客户端凭证(Client Credentials):适用于客户端访问自己拥有的资源的情况。

安全性

  • OAuth 2.0 提供了令牌而不是用户凭证来访问资源,这增强了安全性。
  • 可以设置令牌的有效期限和访问范围(Scope)。
  • 使用 HTTPS 协议来保护在授权过程中数据的安全传输。

OAuth 2.0 的授权机制被广泛用于现代网络应用,它提供标准化的方式来安全地实现授权需求。大多数流行的网上服务如 Google, Facebook, Microsoft 和 Twitter 都实现了 OAuth 2.0 授权服务,允许用户在各种客户端上安全地访问和分享信息。

7 Spring Cloud

7.1 描述Spring Cloud及其在微服务架构中的用例。

Spring Cloud 是一套构建分布式系统常用操作的工具集,特别是为微服务架构提供了多种模式的实现。它利用Spring Boot的开发便捷性简化了分布式系统基础设施的开发,如服务发现、配置管理、消息路由、负载均衡、断路器、数据监控等,同时它还提供了云端服务的无缝集成。

在微服务架构中,Spring Cloud 提供了以下的关键组件和功能:

服务发现(Service Discovery)

服务发现组件(例如Spring Cloud Netflix Eureka)允许微服务实例注册自己,以便其他服务能发现和进行通信。服务注册中心作为服务发现的数据库,保持所有可用服务实例的记录。

配置管理(Configuration Management)

配置管理组件(如Spring Cloud Config)提供了一个中心化的外部配置服务。服务通过配置服务来获取其配置信息,便于管理所有环境中的配置,并在配置更新时快速生效。

API 网关(API Gateway)

API 网关(如Spring Cloud Gateway)为微服务集群提供了一个统一的入口点。它可以处理跨域、路由、负载均衡、监控和弹性等问题。

客户端负载均衡(Client-side Load Balancing)

Spring Cloud具有客户端负载均衡器(如Spring Cloud Netflix Ribbon),它可以在客户端上从服务注册中心获取服务实例的清单,并实现负载均衡调用。

断路器(Circuit Breaker)

为了处理远程服务调用时的弹性和容错性问题,断路器(如Spring Cloud Netflix Hystrix)允许开发者定义降级逻辑和备用路径,当一项服务调用失败时,不会对系统造成过多负荷。

分布式消息传递(Distributed Messaging)

Spring Cloud Stream 是一个构建消息驱动微服务的框架,它提供了对消息代理的绑定和集成,简化了消息发布者和消费者的开发。

分布式跟踪(Distributed Tracing)

在微服务架构中,服务间的通信链可能很长,分布式跟踪(如Spring Cloud Sleuth和Zipkin)允许开发者跟踪请求在多个微服务间的传播,便于监控和故障排查。

服务网格(Service Mesh)

Spring Cloud集成了Istio等服务网格,提供了一种将服务间通信控制与应用程序代码分离的方法。

云平台集成(Cloud Platform Integration)

Spring Cloud具有与云平台(如Cloud Foundry和Kubernetes)集成的功能,允许应用在多种云平台之间便捷地部署和迁移。


通过这些组件,Spring Cloud简化了很多分布式系统的复杂性。它允许开发小型自治服务集群(微服务),每个服务实现单一业务功能,并能够独立开发、部署、运行和扩展。微服务架构允许组织跨多个团队梳理和演进一套庞大的应用系统。通过Spring Cloud提供的工具和模式,组织可以构建高效、可维护且能在云平台上顺畅运行的系统。

7.2 什么是服务发现和注册?

服务发现和注册是微服务架构中的关键概念,主要用于管理服务实例。在大型分布式系统中,特别是建立在云基础设施上的系统中,服务实例会动态地创建和销毁。服务发现和注册的主要目的是跟踪系统中每个服务的网络位置,并提供这个信息给需要进行通信的其他服务。

服务注册(Service Registration)

服务注册是指服务实例在启动时向服务注册中心注册自己的行为。通常包含服务的位置(如主机名和端口),以及包含服务名、版本、健康状态和元数据等信息的其他信息。服务注册可以是手动的,但在现代的、动态扩展的云环境中,通常是自动的。

注册中心持有所有服务的最新状态和位置信息。常见的服务注册中心例子包括:

  • Eureka
  • Zookeeper
  • Consul
  • etcd

服务发现(Service Discovery)

服务发现是指消费者服务或路由组件查询服务注册中心以访问其他服务实例的行为。服务注册中心返回该服务的实例列表以及它们的网络位置,消费者服务可以根据这些信息发起 RPC (远程过程调用)或 RESTful 请求。

服务发现有两种模式:

  1. 客户端发现模式:客户端直接询问服务注册中心并决定访问哪个服务实例,将负载均衡逻辑委托给客户端。

  2. 服务端发现模式:客户端通过一个中间的路由层(通常称为 API 网关或服务网格)发送请求,这个路由层负责从服务注册中心检索服务实例的网络位置并将请求转发到合适的服务上。

服务发现和注册的优点

这些机制允许微服务架构实现以下目标:

  • 动态伸缩与自愈:当一个服务实例发生故障或网络环境变化时,服务注册中心能够及时更新信息,这使得服务发现能够返回健康的、可访问的实例。

  • 负载均衡:服务消费者可以根据得到的服务实例列表实现客户端负载均衡策略。

  • 抽象服务位置:因为服务实例的具体地址被抽象化,所以客户端不需要硬编码服务的位置。

  • 快速反应:注册中心能够快速反映服务实例的上线和下线,支持快速扩容和缩容。

总之,在微服务架构中,服务发现与注册的集成使得服务之间的交互更加灵活和健壮,支持云基础设施的动态性和可扩展性。

7.3 什么是断路器模式?

断路器模式(Circuit Breaker pattern)是一种软件设计模式,常用于微服务架构的错误处理和容错管理。这个模式受电气工程中断路器的启发,其核心作用是检测失败的操作,并在失败次数超越某个阈值时,自动中断(打开)一个操作或一段时间的调用,防止连锁故障和系统崩溃。当检测到系统的健康状况恢复时,断路器会自动恢复(闭合)调用路径。

如何工作

断路器模式有三种主要状态:

  1. 闭合(Closed):在断路器是闭合状态时,请求可以正常执行。如果连续失败次数到达某个阈值,断路器会切换到打开状态。
  2. 打开(Open):当断路器打开后,所有接下来的请求都会被拒绝(通常是返回错误或服务不可用的信息),这样可以防止系统进一步崩溃。在断路器维持打开状态一段时间(称为休眠时间窗口)后,断路器会转换到半开状态。
  3. 半开(Half-Open):断路器会让有限数量的测试请求通过,如果这些请求成功无异常,认为原有问题已解决,断路器回归闭合状态;如果这些尝试仍然失败,断路器则回到打开状态,再次等待休眠时间窗口过去。

断路器模式解决的问题

在分布式系统中,微服务之间会有请求调用。当某个服务出现问题(如性能瓶颈、故障或其他异常)时,如果没有适当的容错和错误处理机制,故障可能会传播到整个系统,导致服务熔断甚至系统整体崩溃——这被称为级联故障。断路器模式提供了一种机制来防止这种情况发生,通过临界检查和状态切换,有效地防止了级联故障的发生。

断路器模式的实践

在实际的应用中,很多服务框架和库都实现了断路器模式的功能:

  • Hystrix:由 Netflix 创建的一个断路器库,广泛用于Spring Cloud体系中,虽然现在宣告停更,但是其设计理念及实现仍值得学习。
  • Resilience4j:一个现代的、轻量级的故障处理库,专门为Java 8 和函数式编程设计,是Hystrix的一个替代者。
  • Spring Retry:Spring提供的简单重试模式库,可以与断路器模式配合使用。
  • Polly:.NET 系统的弹性和瞬态故障处理库,支持断路器、重试等多种策略。

断路器模式是保证分布式系统弹性、稳定性的重要手段,它通过预防错误和故障的传播,保证系统的部分故障不会演变为全网故障,对于提高用户体验和系统稳定性至关重要。

8 Spring Testing

8.1 如何在Spring中执行单元测试和集成测试?

在 Spring 中执行单元测试和集成测试的基础教程和步骤如下:

单元测试

单元测试是用来验证单个组件(如类或方法)的行为是否符合预期。在 Spring 中,可以利用框架如 JUnit 或 TestNG 来进行单元测试,并使用 Mockito 等库来模拟依赖。

添加依赖项

确保你在 pom.xml(Maven)或 build.gradle(Gradle)文件中包含了测试相关的依赖:

<!-- JUnit 5 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

创建单元测试类

使用 @ExtendWith(SpringExtension.class)@SpringJUnitConfig 注解以获得 Spring 测试上下文的支持。

import org.junit.jupiter.api.Test;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.boot.test.mock.mockito.MockBean;

@ExtendWith(SpringExtension.class)
public class SomeServiceTest {

    @MockBean
    private Dependency dependency;

    @Test
    void someTestMethod() {
        // 使用 Mockito 定义模拟对象的行为
        Mockito.when(dependency.someMethod()).thenReturn("value");

        // 调用被测试的方法并断言结果
        Assertions.assertEquals("expected", "actual");
    }
}

集成测试

集成测试涉及到多个组件的互动,比如测试 controller、service 与 repository 的交互。Spring Boot Test 提供了很多工具来支持集成测试。

添加依赖项

确保 spring-boot-starter-test 依赖已经在你的项目中。

创建集成测试类

使用 @SpringBootTest 注解来提供全套的应用程序上下文,并用 @AutoConfigureMockMvc 来配置模拟的 MVC 环境。

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

@SpringBootTest
@AutoConfigureMockMvc
public class SomeControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void someTestMethod() throws Exception {
        // 模拟 HTTP 请求并验证返回结果
        mockMvc.perform(MockMvcRequestBuilders.get("/someEndpoint"))
               .andExpect(MockMvcResultMatchers.status().isOk())
               .andExpect(MockMvcResultMatchers.content().string("expected response"));
    }
}

在集成测试中,通常启动的是一个精简版的应用程序上下文,只包含测试所需的组件。你可以使用不同的配置类或测试属性来实现。

测试数据库交互

Spring 提供了 @DataJpaTest@JdbcTest@MongoDataTest 等,用于测试具体类型的数据访问层代码。

@DataJpaTest
public class SomeRepositoryTest {

    @Autowired
    private SomeRepository repository;

    @Test
    void someTestMethod() {
        // 存储和检索数据,验证结果
        Entity entity = new Entity();
        repository.save(entity);
        Assertions.assertNotNull(entity.getId());
    }
}

Spring Boot 的测试模块提供了许多工具和注解来简化单元测试和集成测试。重点注意层次化结构:低层级的测试(例如单元测试)快且具体;而高层级的测试(如集成测试)则慢且全面。

合理设计单元测试和集成测试,使得测试覆盖足够的代码路径,并且能够检测出潜在的错误和性能问题。

8.2 什么是测试驱动开发(TDD)?

测试驱动开发(Test-Driven Development,TDD)是一种软件开发方法论,其核心理念是在编写实际功能代码之前先编写测试用例。TDD鼓励简单的设计和增量的代码更改,主要流程遵循“红-绿-重构”(Red-Green-Refactor)的周期:

  1. 红色(Red):编写一个单元测试,描述一个特定功能的期望行为。此时该测试会失败,因为相关功能尚未实现。

  2. 绿色(Green):编写最简单的代码来通过这个测试,并得到绿色的通过信号。此时不考虑代码质量,只关注通过测试。

  3. 重构(Refactor):现在测试通过后,可以自由地重构代码,改善代码质量(如提高代码的可读性和性能),而不改变功能。

重复此过程,软件开发者会逐渐构建起代码库,并保证所有新增的功能都有对应的测试保证其正确性。

TDD的好处包含但不限于:

  • 改进设计:由于要先编写测试,开发者通常会更仔细地考虑代码的API设计和数据结构。
  • 更少的缺陷:开发过程中持续的测试有助于尽早发现并修正问题。
  • 文档作用:测试代码本身也起到了文档的作用,清晰地说明了代码的预期用法和行为。
  • 安全的重构:随着项目的发展,有了测试的安全网,重构代码变得更容易和安全。

TDD要求团队成员都维护和理解测试,可能需要一定时间来适应这种开发风格,但许多软件工程师发现,长期来看,TDD可以提高代码质量并减少维护成本。

9 事务管理

9.1 如何在Spring中管理事务?

在 Spring 框架中,事务管理是一个核心特性,它提供了一致的编程式和声明式事务管理机制。Spring 支持编程式事务管理,这意味着你可以在代码中显式管理事务,但更常见的做法是声明式事务管理,通过注解或 XML 配置来管理。

声明式事务管理

Spring 声明式事务管理建立在 AOP(面向切面编程)之上。以下是配置声明式事务的基本步骤:

  1. 配置事务管理器
    对于基于 JDBC 的数据库操作,通常使用 DataSourceTransactionManager。如果是 JPA,则使用 JpaTransactionManager,而对于 Hibernate,则是 HibernateTransactionManager

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
    
  2. 使用 @Transactional 注解
    @Transactional 注解添加到类或方法上。在方法被调用时, Spring 会创建一个代理,以此来确保方法执行在事务的上下文中进行。

    @Service
    public class MyService {
        @Transactional
        public void doSomething() {
            // 数据库操作
        }
    }
    

    当方法用 @Transactional 注解标记时,你可以指定多个事务属性,如传播行为(propagation behavior)、隔离级别(isolation level)、只读状态(read-only)以及回滚规则(rollback rules)。

编程式事务管理

尽管大多数情况下推荐使用声明式事务管理,但在某些情况下,可能需要使用编程式事务管理。这通过 TransactionTemplate 或编程式使用 PlatformTransactionManager 来实现。例如:

@Autowired
private PlatformTransactionManager transactionManager;

public Object someServiceMethod() {
    TransactionTemplate template = new TransactionTemplate(transactionManager);
    return template.execute(status -> {
        // 数据库操作
        return result;
    });
}

在此示例中,TransactionTemplate 是在特定事务内执行操作的回调。

事务传播行为

Spring 提供了多种事务传播选项:

  • REQUIRED:支持当前事务,如果没有则创建新事务(默认行为)。
  • REQUIRES_NEW:创建新事务,并暂停当前事务。
  • SUPPORTS:如果当前存在事务就在其上下文中执行,否则在无事务状态下执行。
  • 其他传播行为如 MANDATORYNEVERNOT_SUPPORTED 等。

事务隔离级别

隔离级别定义了一个事务可能受其他并发事务影响的程度,如:

  • DEFAULT:使用底层数据库默认的隔离级别。
  • READ_COMMITTED:允许从已提交的事务读取数据。
  • READ_UNCOMMITTEDREPEATABLE_READSERIALIZABLE 等。

事务管理的最佳实践

  • 在需要事务支持的业务操作上使用 @Transactional 注解。
  • 尽量在"服务层"(Service Layer)而非"数据访问层"(DAO Layer)应用事务管理。
  • 考虑方法的事务需求,根据需求选择正确的传播行为和隔离级别。
  • 注意异常处理和回滚行为。默认情况下,Spring 只会在遇到运行时异常和错误时回滚事务。

Spring 事务管理是一个灵活而强大的机制,在确保数据一致性和完整性方面扮演着关键角色。通过合理的配置和使用,它可以使事务处理变得简单而有效。

9.2 描述声明式事务和编程式事务的区别。

在软件开发中,事务管理是确保数据完整性和一致性的重要组成部分。在Spring框架中,支持两种类型的事务管理:声明式事务和编程式事务。

声明式事务(Declarative Transaction Management)

声明式事务是一种基于抽象层的事务管理,通常通过元数据(如注解或XML配置)来实现。开发者只需声明方法的事务需求,而无需处理事务的具体开启、提交或回滚。Spring框架会根据声明来自动管理事务。

特点

  1. 易用性:声明式事务的主要优点是简单易用。它允许开发者专注于业务逻辑,而不是事务管理的细节。
  2. AOP支持:它是基于AOP(面向方面编程)的,所以事务管理逻辑被应用到业务逻辑之外。
  3. 隔离性:事务管理代码从业务代码中完全独立出来,增强了代码的结构性和可读性。
  4. 灵活性:可以在不同层级和不同的业务逻辑上应用不同的事务属性(比如隔离级别和传播行为)。
  5. 实现方式:在Spring中常用@Transactional注解,可以应用在类或方法级别。

编程式事务(Programmatic Transaction Management)

编程式事务指的是需要在代码中显式管理事务逻辑的方式。开发者必须通过编程的方式使用事务API来控制事务的整个生命周期。

特点

  1. 控制性:编程式事务为开发者提供了更细粒度的控制能力,适用于那些需要根据运行时判断动态处理事务的情况。
  2. 灵活性:可以根据具体需要,将事务管理代码应用到任何需要的地方。
  3. 复杂性:实现编程式事务需要编写并维护更多的代码,从而增加了复杂性。
  4. 侵入性:它在业务逻辑代码中增加了对特定事务API的依赖,所以相对于声明式事务更加侵入性。
  5. 实现方式:在Spring中,可以使用事务模板类(如TransactionTemplate)或直接使用底层的事务API(如JTA, JDBC)。

示例对比

声明式事务使用@Transactional注解:

@Transactional
public void doSomeBusinessLogic() {
    // 方法体内的业务逻辑
}

编程式事务使用TransactionTemplate

public void doSomeBusinessLogic() {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            try {
                // 方法体内的业务逻辑
            } catch (SomeBusinessException e) {
                status.setRollbackOnly();
            }
        }
    });
}

在实践中,声明式事务是更为推荐的方式,因为它的维护成本较低,易于理解和实施,且充分利用了Spring框架的AOP功能。相比之下,编程式事务通常只在需要对事务实现精细控制的场景中使用。

10 处理器和操作

10.1 如何在Spring中管理异常?

在 Spring 中有多种方法来管理和处理异常。以下是在使用 Spring 进行应用开发时常用的几种异常处理策略:

全局异常处理(Global Exception Handling)

使用 @ControllerAdvice@ExceptionHandler 注解可以在全局范围内捕获和处理异常。你可以定义一个类使用 @ControllerAdvice,然后在其中使用 @ExceptionHandler 标注的方法来处理特定的异常:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(CustomException.class)
    public ResponseEntity<String> handleCustomException(CustomException ex) {
        // 处理异常
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGeneralException(Exception ex) {
        // 处理异常
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

使用 ResponseEntityExceptionHandler 进行异常处理

ResponseEntityExceptionHandler 是一个便利的基类,专门用于 @ControllerAdvice 类,它提供了一些用于处理常见异常的方法。你可以扩展这个类并覆盖它的方法来提供自己的异常处理逻辑:

@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
                                                                  HttpHeaders headers,
                                                                  HttpStatus status,
                                                                  WebRequest request) {
        // 向客户端返回自定义错误消息
        ErrorDetails errorDetails = new ErrorDetails("Invalid request body");
        return buildResponseEntity(errorDetails, HttpStatus.BAD_REQUEST);
    }
    
    // 用于构建 ResponseEntity 的辅助方法
    private ResponseEntity<Object> buildResponseEntity(ErrorDetails errorDetails, HttpStatus status) {
        return new ResponseEntity<>(errorDetails, status);
    }
}

注解驱动的异常处理

在 Controller 层方法中,你可以使用 @ExceptionHandler 处理相关联的 Controller 层或特定方法抛出的异常。这种方式不是全局的,只对当前 Controller 有效:

@Controller
public class MyController {

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ModelAndView handleIllegalArgumentException(IllegalArgumentException ex) {
        // 处理异常,返回视图
        return new ModelAndView("errorPage", "error", ex.getMessage());
    }
}

手动异常处理

在一些情况下,你可能希望在代码中手动捕获并处理异常,比如,在你的 service 方法中使用 try-catch 块来处理特定逻辑:

@Service
public class MyService {
    
    public void performSomeOperation() {
        try {
            // 业务逻辑
        } catch (SomeBusinessException ex) {
            // 处理逻辑异常
        }
    }
}

整合 SimpleMappingExceptionResolver

在 Spring MVC 中,还可以定义 SimpleMappingExceptionResolver 来映射特定异常到特定的视图名称:

@Bean
public SimpleMappingExceptionResolver exceptionResolver() {
    SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
    Properties mappings = new Properties();
    mappings.setProperty(CustomException.class.getName(), "customErrorView");
    resolver.setExceptionMappings(mappings);
    return resolver;
}

整合以上各种异常处理方式可以帮助你更好地控制异常并提供给用户合适的反馈。在 Spring Boot 中,很多默认配置都已经帮你处理了基本的异常情况,但仍然推荐使用 @ControllerAdvice@RestControllerAdvice 以全局方式来捕获和处理异常。

10.2 如何在Spring中配置和使用属性占位符?

在Spring框架中,属性占位符用于从外部属性文件中加载配置信息,并在Spring的bean配置文件中使用这些属性。这种方式使配置外部化,便于管理和修改,而不需要更改实际的代码文件。

使用 PropertyPlaceholderConfigurerPropertySourcesPlaceholderConfigurer

在较早的Spring版本中,可以通过XML配置中的 PropertyPlaceholderConfigurer bean来加载属性文件,并替换配置文件中的占位符。

从Spring 3.1开始,推荐使用PropertySourcesPlaceholderConfigurer,它支持基于注解的配置以及新的环境抽象。

XML配置示例:

<!-- 在Spring XML配置文件中定义属性占位符配置器 -->
<context:property-placeholder location="classpath:app.properties" />

<!-- Now you can use properties in your beans -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

上述配置使得Spring会在类路径下查找 app.properties 文件,并将 ${} 符号内的占位符替换为属性文件中定义的真实值。

Java配置示例:

如果你更倾向于使用Java进行配置,可以通过下面这种方式配置 PropertySourcesPlaceholderConfigurer

@Configuration
@PropertySource("classpath:app.properties") // 引入外部属性文件
public class AppConfig {
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyConfig() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    // 其他Bean定义
}

上述的Java配置类相当于XML配置中的 <context:property-placeholder> 标签。

使用 @Value 注解

你可以使用@Value注解直接在组件的属性中注入属性值。

@Component
public class SomeService {
    @Value("${some.property}")
    private String propertyValue;

    // Getters and setters
}

确保已经配置好属性文件的加载,并且 @PropertySource 注解指向了正确的属性文件。

使用 Spring Boot 的 application.propertiesapplication.yml

在Spring Boot应用中,你无需手动定义 PropertySourcesPlaceholderConfigurer。Spring Boot自动载入 application.propertiesapplication.yml 文件中的属性,并将它们添加到应用的环境属性中。

# application.properties 示例
example.property=value
// 在Spring组件中注入属性值
@Component
public class ExampleComponent {
    @Value("${example.property}")
    private String exampleProperty;

    // Getters and setters
}

Spring Boot 也提供@ConfigurationProperties注解,可以更方便地将配置文件中的属性绑定到一个POJO类。

属性占位符在管理外部配置时非常有用,特别是在需要对配置进行频繁更改的环境中。使用属性占位符可以有效地将配置的细节从代码中抽离出来,甚至可以实现在不重启Spring容器的情况下更改配置。

11 性能和最佳实践

11.1 在Spring应用程序中提高性能的方法是什么?

在 Spring 应用程序中提高性能可以从多个方面来进行考虑,以下是一些常见的性能优化方法:

1. 数据库优化

  • 使用连接池:配置合适的数据库连接池(如 HikariCP),以减少连接开销。
  • 索引策略:确保数据库查询利用到了适当的索引。
  • 避免 N+1 查询问题:使用诸如 JOIN FETCH 或批量抓取来减少数据库查询次数。
  • 批处理:对数据库进行读写以减少网络往返次数。

2. 缓存

  • 使用缓存机制:采用缓存解决方案(如 EhCache、Redis、Caffeine)来缓存热点数据。
  • 缓存静态资源:使用服务器或 CDN 缓存静态资源,如 CSS、JavaScript 和图片文件。
  • 合理的缓存策略:确认缓存有效期和大小与应用需求相匹配。

3. 应用设计和编码

  • 懒加载 vs 提前加载:在 ORM 映射(如 JPA/Hibernate)中选择合适的加载策略。
  • 异步处理:使用异步执行来释放用户线程,如使用 @Async 注解。
  • 理解和消除性能瓶颈:分析并优化慢执行的方法或业务逻辑。
  • 代码优化:减少不必要的计算,合理使用数据结构和算法。

4. 使用合适的数据结构和算法

选择正确的数据结构和算法对于避免性能问题至关重要。

5. 内存和资源管理

  • 优化 JVM 配置:调整 JVM 内存设置和垃圾收集器选项,比如 -Xmx-Xms 和 GC 策略。
  • 资源清理:确保资源如 I/O 流、数据库连接能及时关闭。

6. 并发和线程管理

  • 线程池配置:合理配置线程池来处理任务,如 @EnableAsyncExecutor 配置。
  • 合适的同步机制:避免不必要的同步,尽可能使用无锁或细粒度锁策略。

7. 应用服务器和 JVM 性能

  • 选择正确的服务器:根据需要选择合适的 Web 服务器和 Servlet 容器。
  • 监控 JVM 性能:使用 JMX、VisualVM 或其他工具监控 JVM 性能。

8. API 优化

  • 减少响应大小:如使用数据压缩和小型 JSON/XML 格式响应。
  • 优化 API 调用:减少前端到后端的请求次数,合理设计 API 接口和数据传输对象(DTO)。

9. 自动化性能测试

  • 定期进行性能测试:使用工具(如 JMeter、Gatling)进行定期的负载测试。

10. 使用 Spring Boot 属性和特性

  • 采用 Spring Boot 的生产就绪特性:如 actuator 来监控和管理应用程序。

11. 面向切面编程(AOP)

  • AOP:避免编写冗余代码,使用 AOP 通过拦截器或切面来处理日志记录、事务管理等。

提高性能不仅仅是修复性能问题,而是利用整体架构与细节的改进来提升用户体验和应用的可扩展性。在进行任何重大性能优化之前,首先要明确问题的本质和业务目标。

11.2 解释Spring中的设计模式实践。

Spring框架广泛地使用了设计模式,设计模式是解决软件设计问题的最佳实践。熟悉这些模式可以帮助开发者更好地理解和使用Spring框架。以下是Spring框架中实践的几种关键设计模式的说明:

  1. 单例模式(Singleton Pattern)
    Spring中的Bean默认是单例的,这意味着每个Spring容器中只有一个Bean实例。单例模式可以节约资源和内存,因为对象实例在整个应用中共享。

  2. 工厂模式(Factory Pattern)
    Spring使用工厂模式通过BeanFactory和ApplicationContext来创建对象。这使得管理和配置对象生命周期和依赖更加灵活。

  3. 代理模式(Proxy Pattern)
    Spring AOP功能内部使用代理模式来创建横切关注点,例如事务管理、安全检查等。这些代理可以是JDK动态代理或CGLIB代理。

  4. 模板方法模式(Template Method Pattern)
    Spring提供了多个模板类,例如JdbcTemplate、HibernateTemplate等,用于执行常见类型的操作,而不必担心资源管理。

  5. 前端控制器模式(Front Controller Pattern)
    Spring MVC的DispatcherServlet充当前端控制器,处理所有的HTTP请求并将其委托给应用程序中的适当处理器。

  6. 策略模式(Strategy Pattern)
    Spring使用策略模式来允许选择不同的算法或策略来执行特定任务。例如,Resource接口允许使用多种策略来加载资源。

  7. 观察者模式(Observer Pattern)
    Spring事件驱动模型基于观察者模式,其中ApplicationEventPublisher用于发布事件,而ApplicationListener用于监听这些事件。

  8. 建造者模式(Builder Pattern)
    用于构建复杂对象,如在Spring Security中配置安全规则时,通过HttpSecurity来构建安全配置的链式调用。

  9. 装饰者模式(Decorator Pattern)
    在Spring中,可以使用装饰者模式添加额外的责任或行为到对象上,比如在装饰者模式中使用多个InputStream和OutputStream装饰类。

  10. 原型模式(Prototype Pattern)
    如果Bean的作用域设置为prototype,每次请求都会创建一个新的Bean实例,这与原型模式相对应。

  11. 适配器模式(Adapter Pattern)
    Spring中广泛使用适配器模式来将不同的框架和库集成到Spring上下文中,以提供统一的接口。

  12. 状态模式(State Pattern)
    有时用于Spring Web Flow框架中管理不同状态的流程控制。

  13. 责任链模式(Chain of Responsibility Pattern)
    例如Spring Security使用责任链来组织多个过滤器。

Spring框架通过这些设计模式提供了一个结构良好、易于扩展和维护的系统。程序员可以通过学习和识别这些设计模式来更深入地理解Spring的内部工作机制,从而写出更高效、更优雅的代码。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

御风行云天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值