Spring Boot面试问答

1. Spring Boot 基础知识

问题 1:什么是Spring Boot?它与Spring框架有何不同?

回答: Spring Boot是基于Spring框架的一个开源框架,旨在简化新Spring应用的初始化和开发过程。与传统的Spring框架相比,Spring Boot提供了以下优势:

  • 自动配置:根据项目依赖自动配置Spring应用,减少繁琐的XML或Java配置。
  • 起步依赖(Starters):通过一组预定义的依赖管理,简化项目的依赖配置。
  • 内嵌服务器:内置了Tomcat、Jetty等服务器,简化部署过程。
  • 生产就绪特性:集成了监控、健康检查等生产环境所需的功能(如Spring Boot Actuator)。
  • 命令行界面(CLI):支持通过命令行快速创建和测试Spring应用。

问题 2:Spring Boot的自动配置是如何工作的?

回答: Spring Boot的自动配置通过@EnableAutoConfiguration注解启用,通常与@SpringBootApplication注解一起使用。自动配置基于项目的类路径依赖和已定义的Bean,使用条件注解(如@ConditionalOnClass@ConditionalOnMissingBean等)来判断是否应用特定的配置。

自动配置模块通常定义在META-INF/spring.factories文件中,Spring Boot在启动时会扫描这些配置,根据项目的实际情况应用相应的配置。例如,如果项目中存在H2数据库的依赖,Spring Boot会自动配置内嵌的H2数据库。

问题 3:什么是Spring Boot Starter?举几个常用的Starter例子。

回答: Spring Boot Starter是一组方便的依赖描述符,旨在简化项目的依赖管理。通过引入Starter,开发者可以一次性引入一组相关的库,而无需逐一添加。

常用的Spring Boot Starter包括:

  • spring-boot-starter-web:用于构建Web应用,包括Spring MVC、Tomcat等。
  • spring-boot-starter-data-jpa:集成Spring Data JPA,方便使用Hibernate进行ORM。
  • spring-boot-starter-security:集成Spring Security,提供认证和授权功能。
  • spring-boot-starter-test:包含测试相关的依赖,如JUnit、Mockito、Spring Test等。
  • spring-boot-starter-actuator:提供生产就绪的功能,如监控、健康检查等。

2. 配置与属性

问题 1:Spring Boot中如何管理配置属性?

回答: Spring Boot通过application.propertiesapplication.yml文件管理配置属性。这些文件位于src/main/resources目录下,用于定义应用的各种配置,如数据库连接、服务器端口、日志级别等。

此外,Spring Boot支持外部化配置,允许通过命令行参数、环境变量、配置服务器等方式覆盖默认配置。

问题 2:@Value注解与@ConfigurationProperties注解的区别是什么?

回答:

  • @Value注解:用于注入单个配置属性到Bean的字段、方法参数或构造函数参数中。适用于简单的属性注入。

    @Value("${app.name}")
    private String appName;
    
  • @ConfigurationProperties注解:用于将一组相关的配置属性绑定到一个Bean上。适用于复杂的属性集成,并支持类型安全和数据校验。

    @ConfigurationProperties(prefix = "app")
    public class AppProperties {
        private String name;
        private String version;
        // getters and setters
    }
    

区别总结:

  • 范围@Value适用于单个属性,@ConfigurationProperties适用于一组相关属性。
  • 类型安全@ConfigurationProperties提供类型安全和数据校验支持。
  • 可维护性:对于多个相关配置,@ConfigurationProperties更易于维护和管理。

问题 3:Spring Boot中的Profiles是什么?如何使用它们?

回答: Spring Boot的Profiles(配置文件)用于根据不同的环境(如开发、测试、生产)加载不同的配置。通过激活不同的Profile,可以灵活地管理应用在不同环境下的配置差异。

使用方法:

  1. 定义配置文件:

    • application-dev.properties:开发环境配置
    • application-prod.properties:生产环境配置
  2. 激活Profile:

    • 通过命令行参数:

      java -jar myapp.jar --spring.profiles.active=prod
      
    • 通过环境变量:

      export SPRING_PROFILES_ACTIVE=dev
      
    • application.properties中设置默认Profile:

      spring.profiles.active=dev
      
  3. 使用Profile注解:

    在配置类上使用@Profile注解,使其仅在特定Profile激活时生效。

    @Configuration
    @Profile("prod")
    public class ProductionConfig {
        // 生产环境特定Bean
    }
    

3. Spring Boot Actuator

问题 1:什么是Spring Boot Actuator?它提供了哪些功能?

回答: Spring Boot Actuator是一个用于在生产环境中监控和管理Spring Boot应用的模块。它通过一组预定义的端点,提供应用的健康状况、指标、环境信息、线程信息等。

主要功能包括:

  • 健康检查(/actuator/health):提供应用的健康状态,如数据库连接、磁盘空间等。
  • 应用信息(/actuator/info):展示应用的自定义信息。
  • 指标(/actuator/metrics):收集和展示应用的性能指标,如内存使用、HTTP请求数等。
  • 日志级别管理(/actuator/loggers):实时调整应用的日志级别。
  • 线程转储(/actuator/threaddump):获取当前线程的信息。
  • 环境信息(/actuator/env):查看应用的环境变量和配置属性。
  • 审计事件(/actuator/auditevents):记录和查看应用的审计事件。

问题 2:如何配置和使用Spring Boot Actuator?

回答: 步骤:

  1. 添加依赖:

    pom.xml中添加Spring Boot Actuator依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  2. 配置端点:

    application.propertiesapplication.yml中配置需要暴露的端点:

    management:
      endpoints:
        web:
          exposure:
            include: health,info,metrics,loggers
      endpoint:
        health:
          show-details: always
    

    说明:

    • include:指定要暴露的端点。
    • show-details:配置健康端点是否显示详细信息。
  3. 访问端点:

    启动应用后,可以通过以下URL访问Actuator端点:

    http://localhost:8080/actuator/health
    http://localhost:8080/actuator/info
    http://localhost:8080/actuator/metrics
    
  4. 自定义端点:

    可以创建自定义的Actuator端点,扩展监控功能。

    @Component
    @Endpoint(id = "custom")
    public class CustomEndpoint {
    
        @ReadOperation
        public String customEndpoint() {
            return "Custom Endpoint Response";
        }
    }
    

    访问URL:

    http://localhost:8080/actuator/custom
    

4. 数据访问

问题 1:Spring Boot如何集成Spring Data JPA?

回答: Spring Boot通过spring-boot-starter-data-jpa起步依赖集成了Spring Data JPA。集成步骤如下:

  1. 添加依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    
  2. 配置数据源:

    application.propertiesapplication.yml中配置数据库连接信息:

    spring.datasource.url=jdbc:h2:mem:testdb
    spring.datasource.driverClassName=org.h2.Driver
    spring.datasource.username=sa
    spring.datasource.password=
    spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
    spring.jpa.hibernate.ddl-auto=update
    
  3. 定义实体类:

    @Entity
    @Table(name = "users")
    public class User {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String username;
        private String email;
    
        // Getters and Setters
    }
    
  4. 创建Repository接口:

    @Repository
    public interface UserRepository extends JpaRepository<User, Long> {
        User findByUsername(String username);
    }
    
  5. 使用Repository:

    在服务层或控制器中注入并使用UserRepository进行数据操作。

问题 2:什么是Repository接口?它如何工作?

回答: Repository接口是Spring Data提供的一个抽象层,用于简化数据访问层的开发。通过定义接口并继承Spring Data的特定接口(如JpaRepository),Spring Data会自动为接口生成实现类,提供常用的数据访问方法。

常用的Repository接口:

  • CrudRepository:提供基本的CRUD操作。
  • JpaRepository:继承自CrudRepository,提供更多JPA相关的操作,如分页和排序。
  • PagingAndSortingRepository:提供分页和排序功能。

示例:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

工作原理: Spring Data通过动态代理机制,在运行时为Repository接口生成实现类,解析方法名称中的查询关键字(如findByUsername),并生成相应的查询语句。

问题 3:Spring Boot中如何进行事务管理?

回答: Spring Boot通过Spring框架的事务管理机制提供了强大的事务管理支持。事务管理主要通过@Transactional注解实现,适用于方法级别或类级别的事务控制。

使用方法:

  1. 开启事务管理:

    在主类或配置类上添加@EnableTransactionManagement注解(Spring Boot默认已启用)。

    @SpringBootApplication
    @EnableTransactionManagement
    public class MyAppApplication {
        public static void main(String[] args) {
            SpringApplication.run(MyAppApplication.class, args);
        }
    }
    
  2. 使用@Transational注解:

    @Service
    public class UserService {
        
        @Autowired
        private UserRepository userRepository;
        
        @Transactional
        public void createUser(User user) {
            userRepository.save(user);
            // 其他数据库操作
        }
    }
    

事务属性:

  • Propagation:事务的传播行为,如REQUIREDREQUIRES_NEW等。
  • Isolation:事务的隔离级别,如READ_COMMITTEDSERIALIZABLE等。
  • readOnly:是否为只读事务。
  • rollbackFor:指定哪些异常会导致事务回滚。

示例:

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
public void updateUser(User user) {
    userRepository.save(user);
    // 其他操作
}

5. RESTful API

问题 1:如何在Spring Boot中创建RESTful API?

回答: 在Spring Boot中创建RESTful API主要通过@RestController注解实现。@RestController结合@RequestMapping等注解,定义API的端点和处理方法。

步骤:

  1. 定义实体类:

    @Entity
    @Table(name = "users")
    public class User {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String username;
        private String email;
        // Getters and Setters
    }
    
  2. 创建Repository接口:

    @Repository
    public interface UserRepository extends JpaRepository<User, Long> {
        User findByUsername(String username);
    }
    
  3. 创建Controller类:

    @RestController
    @RequestMapping("/api/users")
    public class UserController {
        
        @Autowired
        private UserService userService;
        
        @GetMapping
        public List<User> getAllUsers() {
            return userService.getAllUsers();
        }
        
        @GetMapping("/{id}")
        public ResponseEntity<User> getUserById(@PathVariable Long id) {
            return userService.getUserById(id)
                    .map(ResponseEntity::ok)
                    .orElse(ResponseEntity.notFound().build());
        }
        
        @PostMapping
        public ResponseEntity<User> createUser(@RequestBody User user) {
            User savedUser = userService.saveUser(user);
            return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
        }
        
        @PutMapping("/{id}")
        public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
            return userService.updateUser(id, user)
                    .map(ResponseEntity::ok)
                    .orElse(ResponseEntity.notFound().build());
        }
        
        @DeleteMapping("/{id}")
        public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
            if (userService.deleteUser(id)) {
                return ResponseEntity.noContent().build();
            } else {
                return ResponseEntity.notFound().build();
            }
        }
    }
    

说明:

  • @RestController:标识该类为RESTful控制器,所有方法默认返回JSON响应。
  • @RequestMapping:定义API的基础路径。
  • HTTP方法注解@GetMapping@PostMapping@PutMapping@DeleteMapping对应不同的HTTP请求方法。
  • @PathVariable和**@RequestBody**:用于绑定URL路径变量和请求体数据。

问题 2:@RestController与@Controller的区别?

回答:

  • @Controller:用于定义Spring MVC的控制器,通常与视图模板(如Thymeleaf)一起使用,返回视图名称。

    @Controller
    public class HomeController {
        @GetMapping("/home")
        public String home(Model model) {
            model.addAttribute("message", "Welcome!");
            return "home"; // 返回home.html模板
        }
    }
    
  • @RestController:是@Controller@ResponseBody的组合,适用于构建RESTful API,所有方法的返回值都会自动序列化为JSON或其他格式的响应体。

    @RestController
    @RequestMapping("/api")
    public class ApiController {
        @GetMapping("/users")
        public List<User> getUsers() {
            return userService.getAllUsers();
        }
    }
    

总结:

  • @Controller:适用于传统的Web应用,返回视图。
  • @RestController:适用于RESTful API,返回数据。

问题 3:如何处理异常并返回自定义的错误响应?

回答: 在Spring Boot中,可以通过@ControllerAdvice@ExceptionHandler注解实现全局异常处理,确保API返回一致且有意义的错误响应。

步骤:

  1. 创建自定义异常类:

    public class ResourceNotFoundException extends RuntimeException {
        public ResourceNotFoundException(String message) {
            super(message);
        }
    }
    
  2. 创建全局异常处理器:

    @ControllerAdvice
    public class GlobalExceptionHandler {
        
        @ExceptionHandler(ResourceNotFoundException.class)
        public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
            ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());
            return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
        }
        
        @ExceptionHandler(Exception.class)
        public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
            ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An unexpected error occurred.");
            return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
        }
        
        // 错误响应类
        public static class ErrorResponse {
            private int status;
            private String message;
            
            public ErrorResponse(int status, String message) {
                this.status = status;
                this.message = message;
            }
            
            // Getters and Setters
        }
    }
    
  3. 在Controller中抛出自定义异常:

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id " + id));
        return ResponseEntity.ok(user);
    }
    

说明:

  • @ControllerAdvice:定义全局异常处理器,适用于所有控制器。
  • @ExceptionHandler:指定处理特定异常的方法。
  • ErrorResponse:定义统一的错误响应格式。

问题 4:如何进行数据验证?

回答: Spring Boot通过JSR-303规范(如Hibernate Validator)提供了强大的数据验证支持。通过在实体类字段上添加验证注解,并在Controller中使用@Valid注解触发验证。

步骤:

  1. 在实体类中添加验证注解:

    @Entity
    @Table(name = "users")
    public class User {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @NotBlank(message = "用户名不能为空")
        @Size(min = 3, max = 50, message = "用户名长度必须在3到50之间")
        private String username;
    
        @NotBlank(message = "邮箱不能为空")
        @Email(message = "邮箱格式不正确")
        private String email;
    
        @Min(value = 18, message = "年龄必须至少18岁")
        @Max(value = 100, message = "年龄不能超过100岁")
        private int age;
    
        // Getters and Setters
    }
    
  2. 在Controller中使用@Valid注解:

    @PostMapping
    public ResponseEntity<User> createUser(@Valid @RequestBody User user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            // 处理验证错误,返回错误信息
            return ResponseEntity.badRequest().build();
        }
        User savedUser = userService.saveUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
    }
    

说明:

  • @Valid:触发Bean的验证。
  • BindingResult:保存验证结果,用于判断是否有错误。
  • 验证注解:如@NotBlank@Size@Email等,用于定义字段的验证规则。

6. 安全性

问题 1:Spring Boot如何集成Spring Security?

回答: Spring Boot通过spring-boot-starter-security起步依赖集成了Spring Security。集成步骤如下:

  1. 添加依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
  2. 配置安全规则:

    创建一个配置类,继承WebSecurityConfigurerAdapter(Spring Boot 2.x及之前版本)或定义SecurityFilterChain Bean(Spring Boot 3.x及以上版本)。

    示例(Spring Boot 3.x):

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http
                .csrf().disable()
                .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers("/public/**").permitAll()
                    .anyRequest().authenticated()
                )
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults());
            return http.build();
        }
    }
    

说明:

  • csrf().disable():禁用CSRF保护,适用于RESTful API。
  • authorizeHttpRequests:定义授权规则,/public/**允许所有访问,其他请求需认证。
  • formLogin()和httpBasic():启用表单登录和HTTP Basic认证。

问题 2:如何配置基本的身份验证和授权?

回答: 通过Spring Security配置基本的身份验证和授权,可以定义用户详情服务和角色权限。

步骤:

  1. 定义用户详情服务:

    @Service
    public class CustomUserDetailsService implements UserDetailsService {
        
        @Autowired
        private UserRepository userRepository;
        
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userRepository.findByUsername(username);
            if (user == null) {
                throw new UsernameNotFoundException("User not found");
            }
            return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
                    AuthorityUtils.createAuthorityList("ROLE_USER"));
        }
    }
    
  2. 配置认证管理器和安全规则:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
    
        @Autowired
        private CustomUserDetailsService userDetailsService;
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http
                .csrf().disable()
                .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers("/public/**").permitAll()
                    .anyRequest().authenticated()
                )
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults());
            return http.build();
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
            return authConfig.getAuthenticationManager();
        }
    }
    
  3. 创建用户实体类并存储密码:

    @Entity
    @Table(name = "users")
    public class User {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String username;
        private String password;
    
        // Getters and Setters
    }
    
  4. 注册用户时加密密码:

    @Service
    public class UserService {
        
        @Autowired
        private UserRepository userRepository;
        
        @Autowired
        private PasswordEncoder passwordEncoder;
        
        public User saveUser(User user) {
            user.setPassword(passwordEncoder.encode(user.getPassword()));
            return userRepository.save(user);
        }
    }
    

说明:

  • CustomUserDetailsService:实现UserDetailsService,加载用户详情。
  • PasswordEncoder:使用BCryptPasswordEncoder对密码进行加密。
  • SecurityFilterChain:定义安全规则,指定哪些端点需要认证,启用表单登录和HTTP Basic认证。

问题 3:如何自定义Spring Security的登录页面?

回答: 通过配置Spring Security的表单登录,指定自定义的登录页面URL,并创建相应的控制器和模板。

步骤:

  1. 配置自定义登录页面路径:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http
                .csrf().disable()
                .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers("/public/**", "/login", "/register").permitAll()
                    .anyRequest().authenticated()
                )
                .formLogin(form -> form
                    .loginPage("/login")
                    .permitAll()
                )
                .logout(logout -> logout
                    .permitAll()
                );
            return http.build();
        }
    }
    
  2. 创建登录控制器:

    @Controller
    public class AuthController {
    
        @GetMapping("/login")
        public String login() {
            return "login"; // 返回 src/main/resources/templates/login.html
        }
    
        @GetMapping("/register")
        public String register() {
            return "register"; // 返回 src/main/resources/templates/register.html
        }
    }
    
  3. 创建登录页面模板(login.html):

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>登录</title>
    </head>
    <body>
        <h1>登录</h1>
        <form th:action="@{/login}" method="post">
            <div>
                <label>用户名:</label>
                <input type="text" name="username" />
            </div>
            <div>
                <label>密码:</label>
                <input type="password" name="password" />
            </div>
            <button type="submit">登录</button>
        </form>
        <a th:href="@{/register}">注册</a>
    </body>
    </html>
    

说明:

  • loginPage(“/login”):指定自定义的登录页面路径。
  • AuthController:处理登录页面的请求。
  • login.html:定义自定义的登录表单,表单提交到/login

问题 4:JWT认证与OAuth2在Spring Boot中的实现方式。

回答: JWT认证:

JWT(JSON Web Token)是一种无状态的认证机制,通过在客户端存储令牌,实现认证和授权。

实现步骤:

  1. 添加JWT依赖:

    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.5</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
    
  2. 创建JWT工具类:

    @Component
    public class JwtUtil {
    
        private final String jwtSecret = "YourVerySecureSecretKeyThatShouldBeLongEnough";
        private final long jwtExpirationMs = 86400000; // 1 day
    
        private Key getSigningKey() {
            return Keys.hmacShaKeyFor(jwtSecret.getBytes());
        }
    
        public String generateJwtToken(UserDetails userDetails) {
            return Jwts.builder()
                    .setSubject((userDetails.getUsername()))
                    .setIssuedAt(new Date())
                    .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
                    .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                    .compact();
        }
    
        public String getUsernameFromJwtToken(String token) {
            return Jwts.parserBuilder()
                    .setSigningKey(getSigningKey())
                    .build()
                    .parseClaimsJws(token)
                    .getBody()
                    .getSubject();
        }
    
        public boolean validateJwtToken(String authToken) {
            try {
                Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(authToken);
                return true;
            } catch (JwtException e) {
                // Log exception
            }
            return false;
        }
    }
    
  3. 创建认证过滤器:

    @Component
    public class JwtAuthTokenFilter extends OncePerRequestFilter {
    
        @Autowired
        private JwtUtil jwtUtil;
        
        @Autowired
        private CustomUserDetailsService userDetailsService;
        
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
                    
            String jwt = parseJwt(request);
            if (jwt != null && jwtUtil.validateJwtToken(jwt)) {
                String username = jwtUtil.getUsernameFromJwtToken(jwt);
                
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authentication = 
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
            
            filterChain.doFilter(request, response);
        }
        
        private String parseJwt(HttpServletRequest request) {
            String headerAuth = request.getHeader("Authorization");
            
            if (headerAuth != null && headerAuth.startsWith("Bearer ")) {
                return headerAuth.substring(7);
            }
            
            return null;
        }
    }
    
  4. 配置Spring Security集成JWT过滤器:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
    
        @Autowired
        private JwtAuthTokenFilter jwtAuthTokenFilter;
        
        @Autowired
        private CustomUserDetailsService userDetailsService;
        
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http
                .csrf().disable()
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(auth -> auth
                    .requestMatchers("/auth/**").permitAll()
                    .anyRequest().authenticated()
                )
                .addFilterBefore(jwtAuthTokenFilter, UsernamePasswordAuthenticationFilter.class);
            return http.build();
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
            return authConfig.getAuthenticationManager();
        }
    }
    
  5. 创建认证控制器:

    @RestController
    @RequestMapping("/auth")
    public class AuthController {
    
        @Autowired
        private AuthenticationManager authenticationManager;
        
        @Autowired
        private JwtUtil jwtUtil;
        
        @Autowired
        private CustomUserDetailsService userDetailsService;
        
        @PostMapping("/login")
        public ResponseEntity<?> authenticateUser(@RequestParam String username, @RequestParam String password) {
            Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(username, password)
            );
            
            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
            String jwt = jwtUtil.generateJwtToken(userDetails);
            
            return ResponseEntity.ok(new JwtResponse(jwt));
        }
    
        public static class JwtResponse {
            private String token;
            
            public JwtResponse(String token) {
                this.token = token;
            }
            
            public String getToken() {
                return token;
            }
            
            public void setToken(String token) {
                this.token = token;
            }
        }
    }
    

说明:

  • JWT认证:通过生成JWT令牌实现无状态认证,客户端在后续请求中携带令牌进行认证。
  • OAuth2:是一种授权框架,允许第三方应用在资源所有者授权下,访问受保护的资源。实现方式涉及更多步骤,如客户端注册、授权服务器配置等。

7. 缓存

问题 1:Spring Boot中如何配置和使用缓存?

回答: Spring Boot通过Spring Cache提供了统一的缓存抽象,支持多种缓存实现(如Ehcache、Redis、Caffeine等)。使用步骤如下:

  1. 添加缓存依赖:

    以Ehcache为例:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>org.ehcache</groupId>
        <artifactId>ehcache</artifactId>
    </dependency>
    
  2. 启用缓存:

    在主类或配置类上添加@EnableCaching注解:

    @SpringBootApplication
    @EnableCaching
    public class MyAppApplication {
        public static void main(String[] args) {
            SpringApplication.run(MyAppApplication.class, args);
        }
    }
    
  3. 配置缓存提供者:

    application.yml中配置Ehcache:

    spring:
      cache:
        type: ehcache
    

    创建ehcache.xml配置文件(位于src/main/resources):

    <?xml version="1.0" encoding="UTF-8"?>
    <config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
            xmlns='http://www.ehcache.org/v3'
            xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd">
    
        <cache alias="users">
            <heap unit="entries">1000</heap>
            <expiry>
                <ttl unit="minutes">10</ttl>
            </expiry>
        </cache>
    </config>
    
  4. 使用缓存注解:

    @Service
    public class UserService {
        
        @Autowired
        private UserRepository userRepository;
    
        @Cacheable(value = "users", key = "#id")
        public User getUserById(Long id) {
            return userRepository.findById(id).orElse(null);
        }
    
        @CacheEvict(value = "users", key = "#id")
        public void deleteUser(Long id) {
            userRepository.deleteById(id);
        }
    
        @CachePut(value = "users", key = "#user.id")
        public User updateUser(User user) {
            return userRepository.save(user);
        }
    }
    

说明:

  • @Cacheable:标注方法的返回值需要缓存。
  • @CacheEvict:标注方法需要清除缓存。
  • @CachePut:标注方法更新缓存。

问题 2:@Cacheable、@CacheEvict等注解的作用是什么?

回答: Spring Cache提供了一组注解,用于简化缓存操作:

  • @Cacheable:在方法执行前检查缓存,如果存在则直接返回缓存结果;否则执行方法并将结果缓存。

    @Cacheable(value = "users", key = "#id")
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
  • @CacheEvict:在方法执行后清除指定的缓存。

    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
    
  • @CachePut:在方法执行后更新缓存,不会影响方法的执行逻辑。

    @CachePut(value = "users", key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }
    
  • @Caching:组合多个缓存操作。

    @Caching(
        put = { @CachePut(value = "users", key = "#user.id") },
        evict = { @CacheEvict(value = "users", allEntries = true) }
    )
    public User saveUser(User user) {
        return userRepository.save(user);
    }
    
  • @CacheConfig:定义类级别的缓存配置,减少重复代码。

    @Service
    @CacheConfig(cacheNames = {"users"})
    public class UserService {
        
        @Cacheable(key = "#id")
        public User getUserById(Long id) { ... }
        
        @CacheEvict(key = "#id")
        public void deleteUser(Long id) { ... }
        
        @CachePut(key = "#user.id")
        public User updateUser(User user) { ... }
    }
    

总结:

  • @Cacheable:用于读取缓存。
  • @CacheEvict:用于清除缓存。
  • @CachePut:用于更新缓存。
  • @Caching:用于组合多个缓存操作。
  • @CacheConfig:用于类级别的缓存配置。

问题 3:如何选择合适的缓存提供者?

回答: 选择缓存提供者需要根据应用的需求、性能要求和使用场景来决定。常见的缓存提供者包括:

  • Ehcache
    • 优点:易于配置,支持本地缓存,适用于单节点应用。
    • 适用场景:需要快速的本地缓存存储,简单易用。
  • Redis
    • 优点:支持分布式缓存,持久化存储,高可用性,丰富的数据结构。
    • 适用场景:需要分布式缓存、跨多个应用实例共享缓存数据、高并发场景。
  • Caffeine
    • 优点:高性能、本地缓存,低延迟,支持异步缓存。
    • 适用场景:需要极低延迟的本地缓存,高吞吐量场景。
  • Hazelcast
    • 优点:分布式缓存,内存计算,支持数据分区和高可用性。
    • 适用场景:需要分布式数据存储、集群支持的应用。

选择建议:

  • 单节点应用:可以选择Ehcache或Caffeine,因其配置简单且性能优越。
  • 分布式应用:推荐使用Redis或Hazelcast,以支持跨多个节点的缓存共享和高可用性。
  • 高性能需求:Caffeine因其高吞吐量和低延迟,适用于对性能要求极高的场景。

8. 微服务

问题 1:Spring Boot在微服务架构中扮演什么角色?

回答: Spring Boot在微服务架构中扮演着关键角色,提供了快速构建独立、可部署、松耦合的微服务的能力。其主要优势包括:

  • 快速开发:通过自动配置和起步依赖,快速构建微服务。
  • 轻量级:生成独立的可执行JAR,简化部署。
  • 集成Spring Cloud:与Spring Cloud集成,支持服务注册与发现、负载均衡、断路器、配置管理等微服务所需的功能。
  • 监控与管理:通过Spring Boot Actuator和其他工具,监控和管理微服务的健康状况和性能。

问题 2:如何使用Spring Cloud与Spring Boot构建微服务?

回答: Spring Cloud提供了一系列工具和库,扩展Spring Boot以支持微服务架构的需求。使用Spring Cloud与Spring Boot构建微服务的步骤如下:

  1. 添加Spring Cloud依赖:

    使用Spring Initializr或手动添加依赖。在pom.xml中添加Spring Cloud Starter依赖。

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2023.0.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
        </dependency>
        <!-- 其他依赖 -->
    </dependencies>
    
  2. 配置服务注册与发现(Eureka):

    Eureka Server:

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

    Eureka Server配置(application.yml):

    server:
      port: 8761
    
    eureka:
      client:
        register-with-eureka: false
        fetch-registry: false
      server:
        enable-self-preservation: false
    

    Eureka Client:

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

    Eureka Client配置(application.yml):

    spring:
      application:
        name: user-service
    
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
    
  3. 配置API网关(Spring Cloud Gateway):

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

    API网关配置(application.yml):

    server:
      port: 8080
    
    spring:
      cloud:
        gateway:
          routes:
            - id: user-service
              uri: lb://user-service
              predicates:
                - Path=/users/**
    
  4. 集成断路器(Resilience4j):

    示例:在服务调用中使用断路器

    @Service
    public class OrderService {
        
        @Autowired
        private RestTemplate restTemplate;
    
        @CircuitBreaker(name = "orderService", fallbackMethod = "fallbackOrder")
        public Order getOrder(Long orderId) {
            return restTemplate.getForObject("http://order-service/orders/" + orderId, Order.class);
        }
    
        public Order fallbackOrder(Long orderId, Throwable throwable) {
            return new Order(orderId, "Default Order");
        }
    }
    

说明:

  • Eureka:实现服务注册与发现,微服务实例动态注册到Eureka Server。
  • API网关:统一入口,路由请求到不同的微服务。
  • 断路器:通过Resilience4j实现服务的容错处理,防止服务雪崩效应。

问题 3:什么是服务注册与发现?如何实现?

回答: 服务注册与发现是微服务架构中的核心组件,允许服务实例动态注册到服务注册中心,并通过服务发现机制进行通信,提升系统的灵活性和可扩展性。

实现步骤:

  1. 选择服务注册中心:常用的服务注册中心包括Eureka、Consul、Zookeeper等。

  2. 配置Eureka Server:

    • 创建Eureka Server应用:

      @SpringBootApplication
      @EnableEurekaServer
      public class EurekaServerApplication {
          public static void main(String[] args) {
              SpringApplication.run(EurekaServerApplication.class, args);
          }
      }
      
    • 配置文件(application.yml):

      server:
        port: 8761
      
      eureka:
        client:
          register-with-eureka: false
          fetch-registry: false
        server:
          enable-self-preservation: false
      
  3. 配置Eureka Client:

    • 添加依赖:

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      
    • 主类注解:

      @SpringBootApplication
      @EnableEurekaClient
      public class UserServiceApplication {
          public static void main(String[] args) {
              SpringApplication.run(UserServiceApplication.class, args);
          }
      }
      
    • 配置文件(application.yml):

      spring:
        application:
          name: user-service
      
      eureka:
        client:
          service-url:
            defaultZone: http://localhost:8761/eureka/
      
  4. 验证注册与发现:

    启动Eureka Server和多个Eureka Client,访问http://localhost:8761,在Eureka Dashboard中查看注册的服务实例。

说明:

  • Eureka Server:作为服务注册中心,管理所有微服务的注册信息。
  • Eureka Client:微服务实例,通过Eureka Client依赖注册到Eureka Server,并进行服务发现。

问题 4:断路器模式在微服务中的应用。

回答: 断路器模式用于防止故障级联,提升系统的容错能力。当某个服务出现故障或响应时间过长时,断路器会打开,阻止进一步的请求,允许系统快速恢复或提供备用响应。

实现方式: 在Spring Boot中,可以使用Resilience4j或Hystrix实现断路器模式。由于Hystrix已停止维护,推荐使用Resilience4j。

使用Resilience4j实现断路器:

  1. 添加依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
    </dependency>
    
  2. 配置断路器(application.yml):

    resilience4j:
      circuitbreaker:
        instances:
          orderService:
            registerHealthIndicator: true
            slidingWindowSize: 10
            failureRateThreshold: 50
            waitDurationInOpenState: 60000
    
  3. 使用@CircuitBreaker注解:

    @Service
    public class OrderService {
        
        @Autowired
        private RestTemplate restTemplate;
    
        @CircuitBreaker(name = "orderService", fallbackMethod = "fallbackOrder")
        public Order getOrder(Long orderId) {
            return restTemplate.getForObject("http://order-service/orders/" + orderId, Order.class);
        }
    
        public Order fallbackOrder(Long orderId, Throwable throwable) {
            return new Order(orderId, "Default Order");
        }
    }
    

说明:

  • @CircuitBreaker:定义断路器名称和回退方法。
  • fallbackMethod:当断路器打开或服务调用失败时,调用的备用方法。

9. 测试

问题 1:Spring Boot中如何进行单元测试和集成测试?

回答: Spring Boot提供了强大的测试支持,通过spring-boot-starter-test起步依赖集成了多种测试框架和工具,如JUnit、Mockito、Spring Test等。

添加依赖:

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

单元测试示例:

使用JUnit和Mockito进行单元测试,模拟依赖并测试业务逻辑。

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;
    
    @Test
    public void testGetUserById() {
        User user = new User();
        user.setId(1L);
        user.setUsername("john");
        Mockito.when(userRepository.findById(1L)).thenReturn(Optional.of(user));
        
        Optional<User> result = userService.getUserById(1L);
        Assertions.assertTrue(result.isPresent());
        Assertions.assertEquals("john", result.get().getUsername());
    }
}

集成测试示例:

使用@SpringBootTest注解加载Spring应用上下文,进行端到端的测试。

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class UserControllerIntegrationTest {
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Test
    public void testGetAllUsers() {
        ResponseEntity<User[]> response = restTemplate.getForEntity("/api/users", User[].class);
        Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());
        Assertions.assertNotNull(response.getBody());
    }
}

说明:

  • @SpringBootTest:加载完整的Spring应用上下文,适用于集成测试。
  • @ExtendWith(MockitoExtension.class):启用Mockito扩展,适用于单元测试。
  • TestRestTemplate:用于集成测试中发送HTTP请求。

问题 2:@SpringBootTest注解的作用是什么?

回答: @SpringBootTest注解用于在测试时加载完整的Spring应用上下文,适用于集成测试。它可以模拟真实的运行环境,测试不同组件之间的协作。

使用方法:

@SpringBootTest
public class MyIntegrationTest {
    
    @Autowired
    private UserService userService;
    
    @Test
    public void testServiceMethod() {
        // 测试逻辑
    }
}

常用属性:

  • webEnvironment:定义测试时的Web环境,如MOCKRANDOM_PORTDEFINED_PORTNONE

    • RANDOM_PORT:启动一个随机端口的服务器,用于发送真实的HTTP请求。
    @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
    
  • classes:指定启动的配置类。

说明:

  • @SpringBootTest适用于需要完整Spring上下文的集成测试,确保所有组件按预期协作。

问题 3:如何使用MockMVC测试控制器?

回答: MockMVC是Spring Test模块提供的一个工具,用于模拟HTTP请求并测试Spring MVC控制器,而无需启动真实的Web服务器。

使用步骤:

  1. 添加测试依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
  2. 编写控制器测试类:

    @WebMvcTest(UserController.class)
    public class UserControllerTest {
        
        @Autowired
        private MockMvc mockMvc;
        
        @MockBean
        private UserService userService;
        
        @Test
        public void testGetAllUsers() throws Exception {
            List<User> users = Arrays.asList(new User(1L, "john", "john@example.com"));
            Mockito.when(userService.getAllUsers()).thenReturn(users);
            
            mockMvc.perform(MockMvcRequestBuilders.get("/api/users"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$[0].username").value("john"));
        }
    }
    

说明:

  • @WebMvcTest:只加载与Web相关的Bean,适用于控制器层测试。
  • MockMvc:用于模拟HTTP请求和断言响应。
  • @MockBean:创建并注入模拟的服务层Bean,避免真实依赖。

10. 部署与监控

问题 1:Spring Boot应用如何打包和部署?

回答: Spring Boot应用可以打包为可执行的JAR或WAR文件,内嵌服务器,实现独立部署。

打包方式:

  • 可执行JAR:

    使用Maven或Gradle进行打包,内嵌Tomcat等服务器。

    mvn clean package
    

    运行应用:

    java -jar target/myapp.jar
    
  • 可执行WAR:

    适用于部署到外部的Servlet容器,如Tomcat。

    pom.xml中配置打包类型:

    <packaging>war</packaging>
    

    修改主类继承SpringBootServletInitializer

    @SpringBootApplication
    public class MyAppApplication extends SpringBootServletInitializer {
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
            return application.sources(MyAppApplication.class);
        }
        public static void main(String[] args) {
            SpringApplication.run(MyAppApplication.class, args);
        }
    }
    

部署方式:

  • 云平台:如AWS、Azure、Google Cloud,使用容器化技术(Docker)或无服务器架构。
  • 虚拟机/物理服务器:直接在服务器上运行JAR文件或部署WAR文件到Servlet容器。
  • 容器化部署:使用Docker将应用封装为容器,结合Kubernetes进行编排和管理。

问题 2:什么是可执行JAR?它的优点是什么?

回答: 可执行JAR是一个包含了应用所有依赖和内嵌服务器的JAR文件,能够独立运行,不依赖外部的Servlet容器。

优点:

  • 简化部署:只需一个JAR文件,减少部署复杂性。
  • 内嵌服务器:无需安装和配置外部的Servlet容器,如Tomcat、Jetty等。
  • 一致性:确保开发、测试和生产环境的一致性。
  • 便携性:可以轻松地在不同环境间迁移和运行。

示例:

打包应用为可执行JAR:

mvn clean package

运行应用:

java -jar target/myapp.jar

问题 3:如何使用Spring Boot Actuator监控应用的健康状况?

回答: 通过Spring Boot Actuator的健康端点,可以监控应用的健康状况,包括数据库连接、磁盘空间、内存使用等。

步骤:

  1. 添加Actuator依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  2. 配置健康端点:

    application.yml中配置健康端点的信息展示:

    management:
      endpoints:
        web:
          exposure:
            include: health,info
      endpoint:
        health:
          show-details: always
    
  3. 访问健康端点:

    启动应用后,访问以下URL查看健康状况:

    http://localhost:8080/actuator/health
    

    示例响应:

    {
        "status": "UP",
        "components": {
            "db": {
                "status": "UP",
                "details": {
                    "database": "H2",
                    "hello": 1
                }
            },
            "diskSpace": {
                "status": "UP",
                "details": {
                    "total": 499963174912,
                    "free": 123456789012,
                    "threshold": 10485760
                }
            }
        }
    }
    

说明:

  • health端点提供应用的健康状态。
  • show-details配置决定是否展示详细的健康信息。

问题 4:如何集成Prometheus与Grafana进行监控?

回答: 通过Spring Boot Actuator与Micrometer集成,可以将应用的指标导出到Prometheus,并使用Grafana进行可视化展示。

步骤:

  1. 添加Prometheus依赖:

    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
    
  2. 配置Prometheus端点:

    application.yml中启用Prometheus端点:

    management:
      endpoints:
        web:
          exposure:
            include: health,info,metrics,prometheus
    
  3. 启动Prometheus和Grafana:

    • Prometheus

      • 下载并解压Prometheus。

      • 配置prometheus.yml文件,添加Spring Boot应用的监控端点。

        scrape_configs:
          - job_name: 'spring-boot-app'
            metrics_path: '/actuator/prometheus'
            static_configs:
              - targets: ['localhost:8080']
        
      • 启动Prometheus:

        ./prometheus --config.file=prometheus.yml
        
    • Grafana

      • 下载并启动Grafana。
      • 在Grafana中添加Prometheus为数据源。
      • 创建或导入仪表盘,展示应用的性能指标。

说明:

  • Prometheus:负责采集和存储应用的指标数据。
  • Grafana:负责从Prometheus获取数据并进行可视化展示。

11. 高级特性

问题 1:Spring Boot如何实现事件驱动编程?

回答: Spring Boot通过Spring的事件发布和监听机制,实现了事件驱动编程。可以在应用中定义和发布事件,并通过监听器进行处理。

步骤:

  1. 定义自定义事件类:

    public class MyCustomEvent extends ApplicationEvent {
        private String message;
        
        public MyCustomEvent(Object source, String message) {
            super(source);
            this.message = message;
        }
        
        public String getMessage() {
            return message;
        }
    }
    
  2. 发布事件:

    @Service
    public class MyService {
        
        @Autowired
        private ApplicationEventPublisher eventPublisher;
        
        public void performAction() {
            // 业务逻辑
            eventPublisher.publishEvent(new MyCustomEvent(this, "Action performed"));
        }
    }
    
  3. 监听事件:

    @Component
    public class MyEventListener {
        
        @EventListener
        public void handleMyCustomEvent(MyCustomEvent event) {
            System.out.println("Received event: " + event.getMessage());
            // 处理事件
        }
    }
    

说明:

  • ApplicationEventPublisher:用于发布事件。
  • @EventListener:标识方法为事件监听器,处理特定类型的事件。
  • 事件类:继承自ApplicationEvent,定义事件的属性和行为。

问题 2:如何自定义Spring Boot的自动配置?

回答: 通过创建自定义的自动配置类,并使用条件注解控制其生效条件,可以扩展或覆盖Spring Boot的默认自动配置。

步骤:

  1. 创建自动配置类:

    @Configuration
    @ConditionalOnClass(MyService.class)
    @EnableConfigurationProperties(MyProperties.class)
    public class MyAutoConfiguration {
        
        @Bean
        @ConditionalOnMissingBean
        public MyService myService(MyProperties properties) {
            return new MyService(properties.getConfig());
        }
    }
    
  2. 定义配置属性类:

    @ConfigurationProperties(prefix = "my")
    public class MyProperties {
        private String config;
        // Getters and Setters
    }
    
  3. 注册自动配置类:

    META-INF/spring.factories文件中注册自动配置类:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.example.myapp.config.MyAutoConfiguration
    
  4. 使用自定义自动配置:

    在应用的application.yml中配置自定义属性:

    my:
      config: "custom-config-value"
    

说明:

  • @ConditionalOnClass:仅在指定类存在时生效。
  • @ConditionalOnMissingBean:仅在没有定义指定Bean时生效。
  • @EnableConfigurationProperties:启用配置属性类绑定。
  • spring.factories:注册自动配置类,使其被Spring Boot扫描并应用。

问题 3:Spring Boot中的异步处理是如何实现的?

回答: Spring Boot通过@Async注解和异步任务执行器,实现方法的异步执行。可以将耗时操作放在异步线程中执行,提高应用的响应性能。

步骤:

  1. 启用异步支持:

    在主类或配置类上添加@EnableAsync注解:

    @SpringBootApplication
    @EnableAsync
    public class MyAppApplication {
        public static void main(String[] args) {
            SpringApplication.run(MyAppApplication.class, args);
        }
    }
    
  2. 定义异步方法:

    @Service
    public class EmailService {
        
        @Async
        public CompletableFuture<Void> sendEmail(String to, String subject, String body) {
            // 发送邮件的逻辑
            return CompletableFuture.completedFuture(null);
        }
    }
    
  3. 调用异步方法:

    @Service
    public class UserService {
        
        @Autowired
        private EmailService emailService;
        
        public void registerUser(User user) {
            // 注册用户的逻辑
            emailService.sendEmail(user.getEmail(), "Welcome", "Thank you for registering!");
        }
    }
    

说明:

  • @EnableAsync:启用Spring的异步方法执行能力。
  • @Async:标识方法为异步执行,返回类型可以是voidFutureCompletableFuture等。
  • 异步执行器:默认使用SimpleAsyncTaskExecutor,可以自定义TaskExecutor Bean。

12. 性能优化

问题 1:如何优化Spring Boot应用的性能?

回答: 优化Spring Boot应用的性能可以从以下几个方面入手:

  1. 数据库优化:
    • 使用合适的索引,提高查询效率。
    • 避免N+1查询问题,使用fetch joinEntityGraph
    • 实现查询缓存,减少数据库访问次数。
  2. 缓存机制:
    • 使用Spring Cache对频繁访问的数据进行缓存。
    • 选择合适的缓存提供者,如Redis、Caffeine等。
  3. 连接池优化:
    • 使用高性能的连接池,如HikariCP,调整连接池参数(最大连接数、最小空闲数等)。
    • 配置连接池的超时时间,防止连接泄漏。
  4. 资源优化:
    • 压缩静态资源,减少网络传输量。
    • 使用CDN加速静态资源的分发。
  5. 并发与异步处理:
    • 使用异步方法处理耗时操作,提高系统吞吐量。
    • 配置线程池,优化并发处理能力。
  6. 内存优化:
    • 监控和调整JVM参数,优化内存使用。
    • 避免内存泄漏,合理管理对象生命周期。
  7. 使用轻量级的依赖:
    • 移除不必要的依赖,减小应用体积,提升启动速度。
  8. 性能监控与调优:
    • 使用Actuator、Prometheus、Grafana等工具监控应用性能,识别瓶颈。
    • 分析GC日志,优化垃圾回收策略。

问题 2:Spring Boot中的HikariCP是什么?如何配置连接池?

回答: HikariCP是Spring Boot默认使用的高性能JDBC连接池,具有低延迟、高吞吐量的特点。配置HikariCP连接池可以通过application.propertiesapplication.yml文件。

配置示例(application.yml):

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: secret
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      idle-timeout: 30000
      max-lifetime: 1800000
      connection-timeout: 20000
      pool-name: HikariCP

常用配置参数:

  • maximum-pool-size:连接池中允许的最大连接数。
  • minimum-idle:连接池中保持的最小空闲连接数。
  • idle-timeout:空闲连接的最大存活时间(毫秒)。
  • max-lifetime:连接的最大存活时间(毫秒)。
  • connection-timeout:获取连接的最大等待时间(毫秒)。
  • pool-name:连接池的名称,便于日志和监控。

说明:

  • 高性能:HikariCP在连接管理上具有高效的实现,适用于高并发和高吞吐量的应用场景。
  • 易于配置:通过简单的配置参数即可优化连接池性能。

问题 3:如何使用缓存提高应用性能?

回答: 通过合理使用缓存,可以减少数据库访问次数,提升应用的响应速度和吞吐量。

使用方法:

  1. 启用缓存:

    在主类或配置类上添加@EnableCaching注解。

    @SpringBootApplication
    @EnableCaching
    public class MyAppApplication {
        public static void main(String[] args) {
            SpringApplication.run(MyAppApplication.class, args);
        }
    }
    
  2. 选择缓存提供者:

    根据需求选择适合的缓存提供者,如Redis、Caffeine、Ehcache等,并配置相关依赖和参数。

  3. 使用缓存注解:

    在服务层的方法上使用@Cacheable@CacheEvict@CachePut等注解进行缓存操作。

    @Service
    public class UserService {
        
        @Autowired
        private UserRepository userRepository;
    
        @Cacheable(value = "users", key = "#id")
        public User getUserById(Long id) {
            return userRepository.findById(id).orElse(null);
        }
    
        @CacheEvict(value = "users", key = "#id")
        public void deleteUser(Long id) {
            userRepository.deleteById(id);
        }
    
        @CachePut(value = "users", key = "#user.id")
        public User updateUser(User user) {
            return userRepository.save(user);
        }
    }
    

最佳实践:

  • 缓存热点数据:将频繁访问且不常变更的数据缓存起来。
  • 合理设置过期时间:根据数据的更新频率设置合适的缓存过期时间,防止缓存不一致。
  • 缓存分区:根据不同的数据类型或业务模块划分缓存,提升缓存命中率和管理效率。
  • 监控缓存:通过监控工具观察缓存的命中率和使用情况,及时调整缓存策略。

13. 国际化(i18n)

问题 1:如何在Spring Boot中实现国际化?

回答: 在Spring Boot中实现国际化(i18n)主要通过配置消息资源文件和使用MessageSource进行消息解析。

步骤:

  1. 创建消息资源文件:

    src/main/resources目录下创建多个messages_*.properties文件:

    • messages.properties(默认语言)
    • messages_zh.properties(中文)
    • messages_en.properties(英文)

    示例:messages.properties

    welcome.message=Welcome to Spring Boot Web Application!
    

    示例:messages_zh.properties

    welcome.message=欢迎使用Spring Boot Web应用!
    
  2. 配置消息源:

    application.yml中配置消息源:

    spring:
      messages:
        basename: messages
        encoding: UTF-8
    
  3. 创建国际化控制器:

    @Controller
    public class InternationalizationController {
        
        @Autowired
        private MessageSource messageSource;
        
        @GetMapping("/i18n")
        public String i18n(@RequestParam(name = "lang", defaultValue = "en") String lang, Model model) {
            Locale locale = new Locale(lang);
            String welcomeMessage = messageSource.getMessage("welcome.message", null, locale);
            model.addAttribute("message", welcomeMessage);
            return "i18n"; // 返回src/main/resources/templates/i18n.html
        }
    }
    
  4. 创建国际化模板(i18n.html):

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>国际化示例</title>
    </head>
    <body>
        <h1 th:text="${message}">欢迎使用Spring Boot Web应用!</h1>
        <a th:href="@{/i18n(lang='en')}">英文</a> | 
        <a th:href="@{/i18n(lang='zh')}">中文</a>
    </body>
    </html>
    

说明:

  • MessageSource:用于解析国际化消息。
  • Locale:表示特定的地理、政治或文化区域。
  • @RequestParam:获取请求中的语言参数,动态切换语言。

问题 2:Spring Boot中如何管理消息资源文件?

回答: Spring Boot通过MessageSource接口管理消息资源文件,自动根据配置文件的位置和名称加载对应的资源。

配置方法:

  1. 配置文件位置和名称:

    默认情况下,Spring Boot会查找src/main/resources/messages.properties及其对应的国际化版本(如messages_zh.properties)。

  2. 配置MessageSource Bean(可选):

    如果需要自定义MessageSource,可以在配置类中定义:

    @Configuration
    public class MessageConfig {
        
        @Bean
        public MessageSource messageSource() {
            ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
            messageSource.setBasename("classpath:messages");
            messageSource.setDefaultEncoding("UTF-8");
            return messageSource;
        }
    }
    
  3. 使用国际化消息:

    在控制器或服务类中通过MessageSource获取国际化消息。

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

说明:

  • ReloadableResourceBundleMessageSource:支持消息资源的热加载,便于开发时实时修改消息内容。
  • basename:定义消息资源文件的基础名称。

14. 其他

问题 1:Spring Boot与Thymeleaf的集成方法。

回答: Spring Boot通过spring-boot-starter-thymeleaf起步依赖集成了Thymeleaf模板引擎,简化了模板的配置和使用。

步骤:

  1. 添加依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
  2. 创建模板文件:

    src/main/resources/templates目录下创建Thymeleaf模板,如home.html

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>首页</title>
    </head>
    <body>
        <h1 th:text="${message}">欢迎使用Spring Boot!</h1>
        <a th:href="@{/logout}">登出</a>
    </body>
    </html>
    
  3. 创建控制器:

    @Controller
    public class HomeController {
        
        @GetMapping("/home")
        public String home(Model model) {
            model.addAttribute("message", "欢迎使用Spring Boot!");
            return "home"; // 返回home.html模板
        }
    }
    

说明:

  • Thymeleaf模板:通过th:*属性与模型数据绑定,实现动态内容渲染。
  • View Resolver:Spring Boot自动配置Thymeleaf的视图解析器,解析返回的视图名称对应的模板文件。

问题 2:Spring Boot中如何处理文件上传和下载?

回答: Spring Boot通过Spring MVC提供了便捷的文件上传和下载支持,使用MultipartFileResource进行文件操作。

文件上传步骤:

  1. 配置文件上传限制:

    application.propertiesapplication.yml中配置文件上传的大小限制。

    spring.servlet.multipart.max-file-size=10MB
    spring.servlet.multipart.max-request-size=10MB
    
  2. 创建上传表单:

    在Thymeleaf模板中创建文件上传表单,如upload.html

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>文件上传</title>
    </head>
    <body>
        <h1>上传文件</h1>
        <form th:action="@{/upload}" method="post" enctype="multipart/form-data">
            <div>
                <label>选择文件:</label>
                <input type="file" name="file" />
            </div>
            <button type="submit">上传</button>
        </form>
        <p th:text="${message}">消息</p>
    </body>
    </html>
    
  3. 创建上传控制器:

    @Controller
    public class FileController {
        
        private final String uploadDir = "uploads/";
        
        @GetMapping("/upload")
        public String showUploadForm() {
            return "upload";
        }
        
        @PostMapping("/upload")
        public String handleFileUpload(@RequestParam("file") MultipartFile file, Model model) {
            if (file.isEmpty()) {
                model.addAttribute("message", "请选择一个文件进行上传");
                return "upload";
            }
            
            try {
                // 创建上传目录(如果不存在)
                Path path = Paths.get(uploadDir);
                if (!Files.exists(path)) {
                    Files.createDirectories(path);
                }
                
                // 保存文件
                Path filePath = path.resolve(file.getOriginalFilename());
                Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
                
                model.addAttribute("message", "文件上传成功: " + file.getOriginalFilename());
            } catch (IOException e) {
                model.addAttribute("message", "文件上传失败: " + e.getMessage());
            }
            
            return "upload";
        }
    }
    

文件下载步骤:

  1. 创建下载控制器:

    @Controller
    public class FileDownloadController {
        
        private final String uploadDir = "uploads/";
        
        @GetMapping("/download/{filename}")
        public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
            try {
                Path filePath = Paths.get(uploadDir).resolve(filename).normalize();
                Resource resource = new UrlResource(filePath.toUri());
                
                if (!resource.exists()) {
                    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
                }
                
                String contentType = Files.probeContentType(filePath);
                if (contentType == null) {
                    contentType = "application/octet-stream";
                }
                
                return ResponseEntity.ok()
                        .contentType(MediaType.parseMediaType(contentType))
                        .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
                        .body(resource);
            } catch (MalformedURLException | IOException e) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
            }
        }
    }
    
  2. 创建下载链接:

    在模板中添加下载链接,如home.html

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>首页</title>
    </head>
    <body>
        <h1 th:text="${message}">欢迎使用Spring Boot!</h1>
        <a href="/upload">上传文件</a>
        <h2>下载文件</h2>
        <ul>
            <li><a th:href="@{'/download/' + ${filename}}">下载文件名</a></li>
            <!-- 动态生成文件列表 -->
        </ul>
        <a href="/logout">登出</a>
    </body>
    </html>
    

说明:

  • 文件上传:通过MultipartFile接收上传的文件,使用Files.copy保存到服务器指定目录。
  • 文件下载:通过Resource加载文件,设置响应头Content-Disposition实现下载。

15. 常见Spring Boot面试问题汇总

以下是一些常见的Spring Boot面试问题及其简要回答,帮助你系统地复习和准备。

基础知识

  1. 什么是Spring Boot?它与Spring框架有何不同?

    **回答:**Spring Boot是基于Spring框架的一个开源框架,旨在简化Spring应用的初始化和开发过程。相比传统Spring,Spring Boot提供自动配置、起步依赖、内嵌服务器和生产就绪特性,减少了大量的配置工作。

  2. Spring Boot的主要特性有哪些?

    **回答:**主要特性包括自动配置、起步依赖、内嵌服务器、命令行界面(CLI)、生产就绪特性(如Actuator)、简化的部署方式等。

  3. 如何创建一个Spring Boot项目?

    **回答:**可以通过Spring Initializr网站(https://start.spring.io/)生成项目骨架,选择所需的依赖,下载并导入到IDE中。也可以使用IDE的Spring Boot项目向导或Maven/Gradle命令行工具创建项目。

配置与属性

  1. 如何在Spring Boot中进行外部化配置?

    **回答:**通过application.propertiesapplication.yml文件管理配置属性,支持通过命令行参数、环境变量、配置服务器等方式覆盖默认配置,实现外部化配置。

  2. @Value注解与@ConfigurationProperties注解的区别是什么?

    回答:@Value用于注入单个配置属性,适用于简单场景;@ConfigurationProperties用于绑定一组相关配置属性,提供类型安全和数据校验,适用于复杂配置。

Actuator

  1. 什么是Spring Boot Actuator?它提供了哪些功能?

    **回答:**Actuator是用于监控和管理Spring Boot应用的模块,提供健康检查、指标收集、环境信息、日志管理、审计事件等功能,通过一组预定义的端点实现。

  2. 如何创建自定义的Actuator端点?

    **回答:**创建一个类,使用@Component@Endpoint注解,定义方法并使用@ReadOperation@WriteOperation等注解标识操作。

    @Component
    @Endpoint(id = "custom")
    public class CustomEndpoint {
        
        @ReadOperation
        public String customEndpoint() {
            return "Custom Endpoint Response";
        }
    }
    

数据访问

  1. Spring Boot如何集成Spring Data JPA?

    **回答:**通过添加spring-boot-starter-data-jpa依赖,配置数据源属性,定义实体类和Repository接口,Spring Boot会自动配置Spring Data JPA,实现数据访问。

  2. 什么是Repository接口?它如何工作?

    **回答:**Repository接口是Spring Data提供的抽象层,用于简化数据访问。通过继承JpaRepository等接口,Spring Data自动生成实现类,提供CRUD操作和自定义查询方法。

  3. Spring Boot中如何进行事务管理?

    **回答:**通过@Transactional注解标注方法或类,Spring Boot结合Spring事务管理器实现事务的自动管理,支持事务的传播行为和隔离级别配置。

RESTful API

  1. 如何在Spring Boot中创建RESTful API?

    **回答:**使用@RestController@RequestMapping等注解定义控制器类和端点方法,结合Spring Data进行数据操作,实现标准的RESTful接口(GET、POST、PUT、DELETE)。

  2. @RestController与@Controller的区别?

    回答:@RestController@Controller@ResponseBody的组合,适用于构建RESTful API,返回数据而非视图;@Controller适用于传统Web应用,返回视图名称。

安全性

  1. Spring Boot如何集成Spring Security?

    **回答:**通过添加spring-boot-starter-security依赖,配置安全规则类,定义认证和授权策略,如HTTP Basic认证、表单登录、角色权限等。

  2. 如何配置基本的身份验证和授权?

    **回答:**实现UserDetailsService加载用户信息,使用@EnableWebSecuritySecurityFilterChain定义安全规则,使用@PreAuthorize等注解进行方法级权限控制。

  3. JWT认证与OAuth2在Spring Boot中的实现方式。

    **回答:**JWT认证通过生成和验证JSON Web Token实现无状态认证;OAuth2通过授权码模式、客户端凭证模式等实现第三方授权和认证,集成Spring Security OAuth2模块实现。

缓存

  1. Spring Boot中如何配置和使用缓存?

    **回答:**通过添加spring-boot-starter-cache依赖,启用缓存注解(@EnableCaching),选择并配置缓存提供者(如Redis、Ehcache),在服务层使用@Cacheable@CacheEvict等注解管理缓存。

  2. @Cacheable、@CacheEvict等注解的作用是什么?

    回答:@Cacheable用于缓存方法返回值,@CacheEvict用于清除缓存,@CachePut用于更新缓存,@Caching用于组合多个缓存操作,@CacheConfig用于类级别的缓存配置。

微服务

  1. Spring Boot在微服务架构中扮演什么角色?

    **回答:**Spring Boot作为微服务的基础框架,提供快速开发、轻量级部署、自动配置等功能,通过与Spring Cloud集成支持服务注册与发现、负载均衡、断路器、API网关等微服务必需的组件。

  2. 如何使用Spring Cloud与Spring Boot构建微服务?

    **回答:**通过添加Spring Cloud相关依赖(如Eureka、Spring Cloud Gateway),配置服务注册与发现、API网关路由、断路器等组件,构建独立、可扩展的微服务应用。

  3. 什么是服务注册与发现?如何实现?

    **回答:**服务注册与发现用于动态管理微服务实例,通过服务注册中心(如Eureka)实现服务的注册、注销和发现,确保服务之间的通信和负载均衡。

测试

  1. Spring Boot中如何进行单元测试和集成测试?

    **回答:**通过spring-boot-starter-test依赖,使用JUnit、Mockito进行单元测试,使用@SpringBootTest进行集成测试,结合MockMVC、TestRestTemplate等工具模拟和验证应用行为。

  2. @SpringBootTest注解的作用是什么?

    回答:@SpringBootTest用于加载完整的Spring应用上下文,适用于集成测试,确保所有组件按预期协作。可以配置不同的Web环境,如RANDOM_PORT进行真实HTTP请求测试。

部署与监控

  1. Spring Boot应用如何打包和部署?

    **回答:**Spring Boot应用可以打包为可执行的JAR文件(内嵌服务器)或WAR文件(部署到外部Servlet容器)。使用Maven或Gradle进行打包,通过命令行或CI/CD工具部署到服务器、云平台或容器化环境中。

  2. 什么是可执行JAR?它的优点是什么?

    **回答:**可执行JAR是包含所有依赖和内嵌服务器的JAR文件,能够独立运行。优点包括简化部署、内嵌服务器、便携性高、启动速度快。

  3. 如何使用Spring Boot Actuator监控应用的健康状况?

    **回答:**通过启用Actuator并配置健康端点,访问/actuator/health查看应用的健康状态,包含数据库连接、磁盘空间、内存使用等信息。

  4. 如何集成Prometheus与Grafana进行监控?

    **回答:**通过添加Micrometer Prometheus依赖,启用Prometheus端点,配置Prometheus抓取Spring Boot应用的/actuator/prometheus指标,使用Grafana连接Prometheus数据源,创建仪表盘展示应用性能指标。

高级特性

  1. Spring Boot如何实现事件驱动编程?

    **回答:**通过定义自定义事件类,使用ApplicationEventPublisher发布事件,使用@EventListener注解的监听器处理事件,实现事件驱动的业务逻辑。

  2. 如何自定义Spring Boot的自动配置?

    **回答:**创建自定义的自动配置类,使用条件注解控制其生效条件,定义需要自动配置的Bean,注册自动配置类到META-INF/spring.factories文件中,使其被Spring Boot扫描和应用。

  3. Spring Boot中的异步处理是如何实现的?

    **回答:**通过启用异步支持(@EnableAsync),在方法上使用@Async注解,将方法异步执行,返回FutureCompletableFuture等类型,提升应用的响应性能和并发处理能力。

  4. 什么是Spring Boot的CommandLineRunner和ApplicationRunner接口?

    回答:CommandLineRunnerApplicationRunner接口用于在Spring Boot应用启动后执行特定代码。它们的区别在于ApplicationRunner提供了对应用参数的访问。

    示例:

    @Component
    public class MyAppRunner implements CommandLineRunner {
        @Override
        public void run(String... args) throws Exception {
            System.out.println("Application started with CommandLineRunner");
        }
    }
    
    @Component
    public class MyAppApplicationRunner implements ApplicationRunner {
        @Override
        public void run(ApplicationArguments args) throws Exception {
            System.out.println("Application started with ApplicationRunner");
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

愤怒的代码

如果您有受益,欢迎打赏博主😊

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

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

打赏作者

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

抵扣说明:

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

余额充值