【Spring面试全家桶】Spring之6.0核心新特性介绍


Spring 6.0是目前最新的Spring版本,其中包含了许多新特性和改进。以下是Spring 6.0核心新特性的介绍:

1. WebFlux

Spring 6.0引入了WebFlux,这是一种全新的非阻塞Web框架,用于构建高性能的Web应用程序。WebFlux结合了Reactor框架和Spring MVC框架,使得应用程序能够处理大量并发请求,同时保持响应时间短。

WebFlux是Spring框架的一个模块,它是Spring 5.0引入的响应式编程模型的一部分。它提供了一种更加高效和可伸缩的方法来处理Web请求和响应。WebFlux是一种反应式Web框架,它使用了非阻塞的I/O和事件驱动的模型,能够处理更高的并发量和更短的响应时间。

WebFlux的核心是Reactor框架,该框架为响应式编程提供了强大的支持。Reactor采用了基于流的编程模型,它提供了Flux和Mono两个核心类来处理流和单个元素。Flux表示一组元素的集合,而Mono表示一个元素。这两个类的使用和操作方式类似于Java8的Stream。

除了Reactor框架,WebFlux还使用了Netty服务器,它是一个高性能的非阻塞网络编程框架。Netty的核心是事件驱动模型,它使用少量的线程池来处理大量的连接。

WebFlux的工作原理是,当一个请求到达服务器时,它会被Netty服务器接受并传递给WebFlux框架。WebFlux会根据请求的内容和路由信息来选择相应的处理器,处理器会使用Reactor框架处理请求并生成响应。最后,响应会被传回到Netty服务器并发送给客户端。

WebFlux的优点是,它能够处理更高的并发量和更短的响应时间,同时还支持异步编程和流式处理。它还提供了一种类似于Spring MVC的编程模型,使得迁移和学习成本较低。

需要注意的是,虽然WebFlux可以提高应用程序的性能和可伸缩性,但并不是所有的应用程序都需要使用它。应该根据具体的应用场景和需求来选择合适的框架和技术。

Java代码示例:

下面是一个简单的WebFlux应用程序示例,该应用程序使用WebFlux框架和Reactor框架来处理HTTP请求和响应。

引入依赖:

在pom.xml文件中添加以下依赖:

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

编写控制器:

创建一个控制器类,用于处理HTTP请求和响应。使用@RestController注解,该注解将类标记为控制器,并自动将响应转换为JSON格式。

@RestController
public class UserController {

    @GetMapping("/users")
    public Flux<User> getAllUsers() {
        // 获取所有用户的逻辑
    }

    @GetMapping("/users/{id}")
    public Mono<User> getUserById(@PathVariable String id) {
        // 根据ID获取用户的逻辑
    }

    @PostMapping("/users")
    public Mono<User> createUser(@RequestBody User user) {
        // 创建用户的逻辑
    }

    @PutMapping("/users/{id}")
    public Mono<User> updateUser(@PathVariable String id, @RequestBody User user) {
        // 更新用户的逻辑
    }

    @DeleteMapping("/users/{id}")
    public Mono<Void> deleteUser(@PathVariable String id) {
        // 删除用户的逻辑
    }
}

编写服务层:

创建一个服务类,用于实现业务逻辑。使用@Service注解标注该类为服务类。

@Service
public class UserService {

    public Flux<User> getAllUsers() {
        // 获取所有用户的逻辑
    }

    public Mono<User> getUserById(String id) {
        // 根据ID获取用户的逻辑
    }

    public Mono<User> createUser(User user) {
        // 创建用户的逻辑
    }

    public Mono<User> updateUser(String id, User user) {
        // 更新用户的逻辑
    }

    public Mono<Void> deleteUser(String id) {
        // 删除用户的逻辑
    }
}

编写应用程序类:

创建一个应用程序类,用于启动应用程序。使用@SpringBootApplication注解标注该类为Spring Boot应用程序。

@SpringBootApplication
public class WebFluxApplication {

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

运行应用程序:

运行WebFlux应用程序,使用Postman等工具发送HTTP请求,验证应用程序是否正常工作。

2. Reactive Spring Data

Reactive Spring Data是Spring 6.0中的另一个重要新特性。它提供了一种响应式的编程模型,用于访问和操作数据。与传统的Spring Data不同,Reactive Spring Data支持非阻塞I/O操作,可以更好地适应高并发的应用场景。

Reactive Spring Data是Spring框架中的一个重要新特性,它提供了一种响应式编程模型,以适应高并发的应用场景。相比于传统的Spring Data,它支持非阻塞I/O操作,从而可以更好地处理数据访问和操作。这里我们将深入探讨其工作原理和运行原理。

Reactive Spring Data的工作原理是基于响应式编程模型的。在这个模型中,数据流被定义为一组事件序列,这些事件可以是值的变化、消息的传递或者错误的发生。同时,响应式编程模型也提供了一系列的操作符,可以用于处理这些事件,例如转换、过滤、合并等。

在Reactive Spring Data中,数据访问和操作的过程被看作是一组数据流。这些数据流可以是基于数据源的,例如数据库、消息队列等,也可以是基于应用程序内部的,例如内存、线程等。在这些数据流中,每一个操作都会产生一个数据流,例如查询、更新、删除等。这些数据流之间可以相互连接和组合,形成复杂的操作序列。

为了支持这种响应式的编程模型,Reactive Spring Data引入了一系列的新组件。其中最重要的是Reactive Streams API,它定义了一系列的接口和规范,用于描述和操作基于流的异步访问模型。此外,Reactive Spring Data还引入了一系列的Reactive Repositories,用于支持基于响应式编程模型的数据访问和操作。

在运行时,Reactive Spring Data与底层数据源的交互是基于非阻塞I/O操作的。这些操作使用了Java NIO中的一些关键技术,例如异步通道和选择器等。通过这些技术,Reactive Spring Data可以在高并发的场景下,更好地处理数据访问和操作,提高应用程序的吞吐量和响应速度。

总之,Reactive Spring Data是一种基于响应式编程模型的数据访问和操作框架。它支持非阻塞I/O操作,可以更好地适应高并发的应用场景。在运行时,它与底层数据源的交互是基于Java NIO中的一些关键技术。

示例代码如下:

  1. 使用Reactive Repository来定义异步数据访问方法
public interface ReactivePersonRepository extends ReactiveCrudRepository<Person, String> {
    Flux<Person> findByAgeGreaterThan(int age);
}
  1. 使用Flux来处理返回的数据流
@RestController
public class PersonController {
    private final ReactivePersonRepository personRepository;

    public PersonController(ReactivePersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    @GetMapping("/persons")
    public Flux<Person> getAllPersons() {
        return personRepository.findAll();
    }

    @GetMapping("/persons/{id}")
    public Mono<Person> getPersonById(@PathVariable(value = "id") String personId) {
        return personRepository.findById(personId);
    }

    @GetMapping(value = "/persons", params = "age")
    public Flux<Person> getPersonByAge(@RequestParam(value = "age") int age) {
        return personRepository.findByAgeGreaterThan(age);
    }
}
  1. 使用WebFlux进行异步处理并返回Flux
@RestController
public class PersonController {
    private final ReactivePersonRepository personRepository;

    public PersonController(ReactivePersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    @GetMapping("/persons")
    public Flux<Person> getAllPersons() {
        return personRepository.findAll()
            .delayElements(Duration.ofSeconds(1));
    }

    @GetMapping("/persons/{id}")
    public Mono<Person> getPersonById(@PathVariable(value = "id") String personId) {
        return personRepository.findById(personId);
    }

    @GetMapping(value = "/persons", params = "age")
    public Flux<Person> getPersonByAge(@RequestParam(value = "age") int age) {
        return personRepository.findByAgeGreaterThan(age)
            .delayElements(Duration.ofSeconds(1));
    }
}

其中,WebFlux可以通过异步处理来提高应用程序的响应速度,在上述代码中使用了delayElements方法来模拟异步处理的延迟。

3. Spring Native

Spring Native是一个新的项目,它允许开发人员将Spring应用程序转换为本机映像,以提高性能和启动时间。这个项目利用了GraalVM的优势,可以将Spring应用程序编译成本地二进制文件。

Spring Native是一个旨在提高Spring应用程序性能和启动时间的新项目。它利用GraalVM的优势,可以将Spring应用程序编译为本地二进制文件。应用程序编译为本地二进制文件可以使启动时间减少到数毫秒,同时还可以提高应用程序的性能。

在编译为本地二进制文件时,Spring Native将应用程序依赖项预先编译进本地映像中,从而避免了运行时解析和加载依赖项的开销。这可以极大地提高应用程序的性能,并且也有助于减少应用程序的内存占用。

与传统的Java应用程序相比,由于本地映像已经包含了应用程序的依赖项,因此启动时间也得以显着缩短。在一些测试中,使用Spring Native编译的应用程序的启动时间甚至可以缩短到数毫秒。这对于需要快速启动的云原生应用程序来说非常重要。

总之,Spring Native是一个非常有用的项目,可以帮助开发人员将Spring应用程序转换为本地映像,从而提高性能和启动时间。它是一个值得关注的新技术,Java开发人员可以深入了解并应用到实际的项目中。

Java代码示例:

下面是一个简单的Spring Boot应用程序示例,展示如何使用Spring Native编译器将应用程序编译为本地二进制文件。

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
    
    @RestController
    public class MyController {
        
        @GetMapping("/hello")
        public String sayHello() {
            return "Hello, world!";
        }
    }
}

然后,在pom.xml文件中,需要添加spring-native-starter依赖项:

<dependency>
    <groupId>org.springframework.experimental</groupId>
    <artifactId>spring-native</artifactId>
    <version>0.9.2</version>
</dependency>

最后,使用以下命令将应用程序编译为本地二进制文件:

./mvnw package -Pnative

当编译完成后,可以在target目录中找到编译后的本地二进制文件。

总之,Spring Native是一个非常有用的项目,可以帮助Java开发人员将应用程序编译为本地二进制文件,以提高性能和启动时间。需要注意的是,Spring Native目前仍处于开发阶段,可能存在一些稳定性和兼容性问题,需要谨慎使用。

4. Spring WebSocket支持

Spring 6.0还提供了对WebSocket协议的全面支持,使得开发人员可以更轻松地构建实时通信应用程序。

WebSocket是一种在单个TCP连接上进行全双工通信的协议,它可以在浏览器和服务器之间实现实时通信。在传统的HTTP协议中,每次请求都需要建立新的连接,而WebSocket协议可以在一条连接上实现长期的通信,从而减少了连接建立的开销,同时也提高了通信的实时性和性能。

在Spring 6.0中,对WebSocket协议的支持是通过Spring WebSock模块实现的。该模块提供了一些核心的类和接口,包括:

  • WebSocketSession:表示一个WebSocket会话,封装了底层的Socket连接和相关的属性信息。
  • WebSocketHandler:用于处理WebSocket请求和响应消息的抽象接口,开发人员需要实现该接口来处理具体的业务逻辑。
  • TextMessage:表示一个文本消息,用于在WebSocket会话中发送和接收消息。

除了上述核心类和接口外,Spring WebSock模块还提供了一些相关的注解和配置类,用于简化WebSocket应用程序的开发和配置。

具体地说,开发人员可以通过在Spring MVC的配置类中添加@EnableWebSocket注解来启用WebSocket支持,并通过实现WebSocketConfigurer接口来配置WebSocket相关参数和处理器。在处理器中,可以通过注解来标识不同的消息类型,如@MessageMapping用于处理来自客户端的消息,@SendTo用于将处理结果发送回客户端。

总的来说,Spring 6.0中对WebSocket协议的支持,可以让开发人员更加方便地构建实时通信应用程序,同时也提高了应用程序的性能和实时性。在底层实现上,Spring WebSock模块通过封装和优化底层的WebSocket协议实现,使得开发人员可以专注于业务逻辑的实现,而无需过多关注底层的细节。

Java代码示例:

启用WebSocket支持:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

   @Override
   public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
      registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("*");
   }

   @Bean
   public WebSocketHandler myHandler() {
      return new MyHandler();
   }
}

实现WebSocketHandler接口:

public class MyHandler implements WebSocketHandler {

   @Override
   public void afterConnectionEstablished(WebSocketSession session) throws Exception {
      // 连接建立时调用
   }

   @Override
   public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
      // 接收到消息时调用
      String msg = message.getPayload().toString();
      // 处理消息逻辑
      session.sendMessage(new TextMessage("处理结果"));
   }

   @Override
   public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
      // 传输错误时调用
   }

   @Override
   public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
      // 连接关闭时调用
   }

   @Override
   public boolean supportsPartialMessages() {
      return false;
   }
}

处理来自客户端的消息:

@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
   Thread.sleep(1000); // 模拟处理时间
   return new Greeting("Hello, " + message.getName() + "!");
}

其中,HelloMessage和Greeting都是自定义的消息类型,用于传递数据。@MessageMapping注解用于标识处理来自客户端的消息,@SendTo注解用于将处理结果发送给指定的订阅者(在此例中为/topic/greetings)。

5. Kotlin扩展支持

Kotlin是一种流行的静态类型编程语言,Spring 6.0提供了对Kotlin扩展的全面支持。这意味着开发人员可以使用Kotlin编写更简洁、更安全、更高效的代码,并获得更好的开发体验。

Kotlin是一种静态类型编程语言,由JetBrains开发,旨在成为Java的替代品。Kotlin具有简洁、直观和易于学习的语法,并提供了各种现代编程语言的功能,包括函数式编程、惰性求值和扩展函数。

Spring 6.0是Spring Framework的最新版本,提供了对Kotlin的全面支持。这意味着开发人员可以使用Kotlin编写Spring应用程序,从而获得更简洁、更安全、更高效的代码,并提高开发体验。

Kotlin在Spring框架中的扩展主要包括以下方面:

  1. Kotlin支持Spring Boot开发。Spring Boot是一个快速开发框架,使用Kotlin可以更轻松地创建Spring Boot应用程序。

  2. Kotlin提供了一种更安全的方式来编写Spring中的依赖注入代码。Kotlin支持类型推断和null安全性,这意味着开发人员可以更轻松地避免潜在的空指针异常和类型转换错误。

  3. Kotlin扩展函数可以增强Spring框架的功能。开发人员可以使用Kotlin扩展函数来扩展Spring的核心类,增强其功能。

  4. Kotlin支持协程,在Spring中使用Kotlin协程可以提高异步编程的效率和可读性。

总之,Kotlin对于Spring框架的支持使得开发人员可以更加方便地编写Spring应用程序,并且可以获得更高效的代码和更好的开发体验。

Java代码示例:

以下是一个使用Kotlin编写的基本的Spring Boot应用程序:

@SpringBootApplication
class MyApp

fun main(args: Array<String>) {
    runApplication<MyApp>(*args)
}

使用Kotlin编写的依赖注入代码:

@Component
class MyComponent(private val myService: MyService) {

    fun doSomething() {
        myService.doSomething()
    }
}

@Service
class MyService(private val myRepository: MyRepository) {

    fun doSomething() {
        myRepository.getData()
    }
}

@Repository
class MyRepository {

    fun getData() {
        // retrieve data from database
    }
}

使用Kotlin扩展函数扩展Spring框架:

fun <T : Any> T?.toOptional(): Optional<T> {
    return Optional.ofNullable(this)
}

@Repository
class MyRepository {

    fun getDataById(id: Long): Optional<Data> {
        // retrieve data from database
        return data.toOptional()
    }
}

使用Kotlin协程进行异步编程:

@Service
class MyService(private val myRepository: MyRepository) {

    suspend fun getData() {
        withContext(Dispatchers.IO) {
            // retrieve data from database
        }
    }
}

6. JUnit5支持

JUnit5是一个全新的JUnit测试框架,它已经成为Java生态系统中最受欢迎的测试框架之一。Spring 6.0提供了对JUnit5的全面支持,使得开发人员可以更轻松地编写和运行测试用例。

JUnit5是一个全新的JUnit测试框架,它与之前的JUnit版本相比带来了许多改进和新功能。其中最引人注目的改进是JUnit5的模块化结构和扩展性,可以让开发人员更加灵活地编写和管理测试用例。

另一个重要的改进是JUnit Jupiter API,它提供了一些新的注解和断言方法来编写测试用例。例如,@Test注解用于标记测试方法,@BeforeEach注解用于在每个测试方法执行之前执行一些准备工作,@AfterEach注解用于在每个测试方法执行之后执行一些清理工作,@DisplayName注解用于为测试方法提供自定义名称,assertAll()方法可以同时验证多个条件等等。

Spring 6.0提供了对JUnit5的全面支持,Spring Test框架中的许多功能都已经针对JUnit5进行了更新和升级。在Spring 6.0中,开发人员可以使用@SpringBootTest注解来标记测试类,并使用@ExtendWith注解将SpringExtension类与JUnit Jupiter集成。这使得开发人员可以在测试中使用Spring ApplicationContext,Bean注入和其他Spring功能。

总的来说,JUnit5和Spring 6.0的结合为Java开发人员提供了一个更加现代化,灵活和强大的测试工具链。这些框架在底层都是通过反射机制和动态代理来实现测试用例的执行和管理,同时也利用了Java中的一些高级语言特性和设计模式来提高代码的可读性和可维护性。

JUnit5是JUnit测试框架的新版本,与之前的版本相比它提供了更多的改进和新特性,其中最重要的改进是JUnit5的模块化结构和扩展性,这使开发人员更加灵活地编写和管理测试用例。JUnit Jupiter API是JUnit5提供的一个新的API,它提供了更多新的注解和断言方法来编写测试用例。Spring 6.0完全支持JUnit5,并对Spring Test框架进行了更新和升级,开发人员可以使用@SpringBootTest注解标记测试类,并使用@ExtendWith注解将SpringExtension类与JUnit Jupiter集成。JUnit5和Spring 6.0的结合为Java开发人员提供了一个现代化,灵活和强大的测试工具链。

以下是一个简单的JUnit5测试用例的Java代码示例:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
 
public class CalculatorTest {
 
    @Test
    @DisplayName("Simple addition test")
    public void testAddition() {
        Calculator calculator = new Calculator();
        assertEquals(2, calculator.add(1,1));
    }
}

在上面的代码中,我们使用了JUnit5的@Test注解来标记测试方法,@DisplayName注解来为测试方法提供自定义名称,assertEquals方法来进行断言。

总的来说,Spring 6.0为开发人员提供了一系列新的工具和技术,使得构建高性能、可靠、易于维护的应用程序更加容易。

7.实战中Spring之6.0核心新特性的问题与解决方案

问题:

  1. 集成WebFlux时,如何正确处理异常?

解决方案:使用WebExceptionHandler处理全局异常,或使用@ControllerAdvice和@ExceptionHandler处理控制器方法异常。

@ControllerAdvice
public class ExceptionHandler {

    @ExceptionHandler(value = { Exception.class })
    public ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
        // 处理异常
        return new ResponseEntity<>("Error occurred: " + ex.getMessage(), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
  1. 如何在Spring Boot 2中实现自定义Spring Security验证?

解决方案:创建自定义UserDetailsService和AuthenticationProvider实现身份验证,或扩展WebSecurityConfigurerAdapter和实现configure(HttpSecurity http)方法。

自定义UserDetailsService示例:

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库或其他数据源获取用户信息
        User user = userRepository.findByUsername(username);

        if (user == null) {
            throw new UsernameNotFoundException("User not found with username: " + username);
        }

        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
                new ArrayList<>());
    }
}

自定义AuthenticationProvider示例:

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        UserDetails user = userDetailsService.loadUserByUsername(username);

        if (user.getPassword().equals(password)) {
            return new UsernamePasswordAuthenticationToken(username, password, user.getAuthorities());
        } else {
            throw new BadCredentialsException("Authentication failed for " + username);
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}
  1. 如何在Spring Boot 2中使用WebClient进行异步非阻塞调用?

解决方案:创建WebClient实例并使用retrieve()方法调用API,使用Mono和Flux处理返回的响应。

WebClient示例:

WebClient webClient = WebClient.create("https://example.com/api");

Mono<String> responseMono = webClient.get()
        .uri("/resource")
        .retrieve()
        .bodyToMono(String.class);

responseMono.subscribe(response -> {
    // 处理响应
}, error -> {
    // 处理错误
});
  1. 如何在Spring Boot 2中配置全局CORS?

解决方案:使用CorsRegistry配置跨域策略,或使用@CrossOrigin注释处理单个控制器方法。

配置跨域策略示例:

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("*")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

@CrossOrigin注释示例:

@RestController
public class ExampleController {

    @GetMapping("/example")
    @CrossOrigin(origins = "*", allowedHeaders = "*")
    public String example() {
        // 处理请求
        return "example response";
    }
}
  1. 如何在Spring Boot 2中配置缓存?

解决方案:使用@EnableCaching注释启用缓存,使用@Cacheable和@CachePut注释缓存控制器方法或服务方法。

启用缓存示例:

@EnableCaching
@SpringBootApplication
public class MyApp {

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

使用@Cacheable和@CachePut示例:

@RestController
public class ExampleController {

    @Autowired
    private ExampleService exampleService;

    @GetMapping("/example")
    @Cacheable(value = "exampleCache", key = "#id")
    public ExampleResponse example(@RequestParam("id") int id) {
        // 调用服务方法
        return exampleService.getById(id);
    }

    @PostMapping("/example")
    @CachePut(value = "exampleCache", key = "#example.id")
    public ExampleResponse createExample(@RequestBody ExampleRequest example) {
        // 调用服务方法
        return exampleService.create(example);
    }
}
  1. 如何在Spring Boot 2中使用响应式数据流?

解决方案:使用Flux和Mono处理响应式数据流,或使用ReactiveMongoRepository访问MongoDB。

Flux和Mono示例:

Flux<String> stringFlux = Flux.just("hello", "world");

Mono<Integer> integerMono = Mono.just(42);

// 处理Flux数据流
stringFlux.subscribe(str -> System.out.println("Received: " + str));

// 处理Mono数据流
integerMono.flatMap(num -> {
    return Mono.just("The answer is: " + num);
}).subscribe(str -> System.out.println("Received: " + str));

ReactiveMongoRepository示例:

public interface UserRepository extends ReactiveMongoRepository<User, String> {

    Flux<User> findByAgeGreaterThan(int age);
}

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public Flux<User> getByAgeGreaterThan(int age) {
        return userRepository.findByAgeGreaterThan(age);
    }
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Java程序员廖志伟

赏我包辣条呗

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

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

打赏作者

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

抵扣说明:

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

余额充值