MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Kotlin Spring Boot Web开发

2021-02-077.8k 阅读

Kotlin 与 Spring Boot 简介

Kotlin 是一种现代编程语言,由 JetBrains 开发,与 Java 兼容并运行在 Java 虚拟机(JVM)上。它简洁、安全且具备函数式编程特性,如 lambda 表达式、扩展函数等,极大提升了开发效率。

Spring Boot 是 Spring 框架的一个子集,旨在简化基于 Spring 的应用程序的初始搭建以及开发过程。它遵循“约定优于配置”的原则,通过自动配置机制,减少了大量样板代码,使得开发者能够快速构建生产级别的应用程序。

搭建 Kotlin Spring Boot Web 开发环境

  1. 安装 JDK:确保系统安装了 Java 开发工具包(JDK),建议使用 JDK 8 或更高版本。
  2. 安装 Kotlin 插件:如果使用 IntelliJ IDEA,它原生支持 Kotlin。对于 Eclipse,需要安装 Kotlin 插件。
  3. 创建 Spring Boot 项目
    • 使用 Spring Initializr:访问 https://start.spring.io/,选择项目元数据,如项目类型为 Maven 或 Gradle,语言选择 Kotlin,Spring Boot 版本等。在依赖中添加 Spring Web 依赖,这将用于构建 Web 应用。然后下载生成的项目压缩包并解压。
    • 使用 IDE:在 IntelliJ IDEA 中,选择 File -> New -> Project,然后在左侧选择 Spring Initializr,按照向导配置项目,同样选择 Kotlin 作为语言并添加 Spring Web 依赖。

基本的 Kotlin Spring Boot Web 应用示例

  1. 项目结构

    • 典型的 Kotlin Spring Boot 项目结构如下:
    ├── src
    │   ├── main
    │   │   ├── kotlin
    │   │   │   └── com
    │   │   │       └── example
    │   │   │           └── demo
    │   │   │               ├── DemoApplication.kt
    │   │   │               └── controller
    │   │   │                   └── HelloController.kt
    │   │   └── resources
    │   │       ├── application.properties
    │   └── test
    │       └── kotlin
    │           └── com
    │               └── example
    │                   └── demo
    │                       └── DemoApplicationTests.kt
    ├── pom.xml (for Maven) or build.gradle.kts (for Gradle Kotlin DSL)
    
  2. 创建控制器(Controller)

    • Kotlin 中的控制器用于处理 HTTP 请求并返回响应。创建一个 HelloController.kt 文件:
    package com.example.demo.controller
    
    import org.springframework.web.bind.annotation.GetMapping
    import org.springframework.web.bind.annotation.RestController
    
    @RestController
    class HelloController {
        @GetMapping("/hello")
        fun hello(): String {
            return "Hello, Kotlin Spring Boot!"
        }
    }
    
    • 在上述代码中:
      • @RestController 注解表明该类是一个 RESTful 风格的控制器,会自动将返回值序列化为 JSON 格式(在这种情况下字符串也会直接返回)。
      • @GetMapping("/hello") 注解映射一个 HTTP GET 请求到 hello 方法,当访问 /hello 路径时,会执行该方法。
  3. 启动应用

    • Maven:在项目根目录下运行 mvn spring-boot:run 命令。
    • Gradle:如果使用 Gradle Kotlin DSL,运行 ./gradlew bootRun 命令。
    • 应用启动后,访问 http://localhost:8080/hello,将会看到 Hello, Kotlin Spring Boot! 的响应。

处理请求参数

  1. 路径参数

    • 可以在控制器方法中接收路径参数。修改 HelloController.kt
    package com.example.demo.controller
    
    import org.springframework.web.bind.annotation.GetMapping
    import org.springframework.web.bind.annotation.PathVariable
    import org.springframework.web.bind.annotation.RestController
    
    @RestController
    class HelloController {
        @GetMapping("/hello/{name}")
        fun helloWithName(@PathVariable name: String): String {
            return "Hello, $name!"
        }
    }
    
    • 这里 @PathVariable 注解用于将路径中的 {name} 部分绑定到方法参数 name 上。访问 http://localhost:8080/hello/John,会返回 Hello, John!
  2. 查询参数

    • 接收查询参数也很简单。继续修改 HelloController.kt
    package com.example.demo.controller
    
    import org.springframework.web.bind.annotation.GetMapping
    import org.springframework.web.bind.annotation.RequestParam
    import org.springframework.web.bind.annotation.RestController
    
    @RestController
    class HelloController {
        @GetMapping("/greet")
        fun greet(@RequestParam(name = "name", defaultValue = "World") name: String): String {
            return "Greetings, $name!"
        }
    }
    
    • @RequestParam 注解用于绑定查询参数。name 参数指定了查询参数的名称,defaultValue 设置了默认值。访问 http://localhost:8080/greet 会返回 Greetings, World!,而访问 http://localhost:8080/greet?name=Jane 会返回 Greetings, Jane!

处理表单数据

  1. 创建表单模型

    • 首先创建一个 Kotlin 数据类来表示表单数据。在 com.example.demo.model 包下创建 User.kt
    package com.example.demo.model
    
    data class User(val username: String, val password: String)
    
    • data class 是 Kotlin 中用于快速创建简单数据类的语法,它自动生成了 equalshashCodetoString 等方法。
  2. 处理表单提交

    • HelloController.kt 中添加处理表单提交的方法:
    package com.example.demo.controller
    
    import com.example.demo.model.User
    import org.springframework.web.bind.annotation.PostMapping
    import org.springframework.web.bind.annotation.RequestBody
    import org.springframework.web.bind.annotation.RestController
    
    @RestController
    class HelloController {
        @PostMapping("/register")
        fun register(@RequestBody user: User): String {
            return "User ${user.username} registered with password ${user.password}"
        }
    }
    
    • @PostMapping 注解映射 HTTP POST 请求。@RequestBody 注解将请求体中的 JSON 数据反序列化为 User 对象。可以使用工具如 Postman 发送 POST 请求到 /register,请求体为 {"username":"testuser","password":"testpass"},将会得到相应的注册信息反馈。

数据库操作

  1. 添加数据库依赖

    • 以 MySQL 为例,在 pom.xml(Maven)中添加以下依赖:
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql - connector - java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring - boot - starter - jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring - boot - starter - data - jpa</artifactId>
    </dependency>
    
    • 对于 Gradle Kotlin DSL,在 build.gradle.kts 中添加:
    runtimeOnly("mysql:mysql - connector - java")
    implementation("org.springframework.boot:spring - boot - starter - jdbc")
    implementation("org.springframework.boot:spring - boot - starter - data - jpa")
    
  2. 配置数据库连接

    • application.properties 文件中添加数据库连接配置:
    spring.datasource.url=jdbc:mysql://localhost:3306/mydb
    spring.datasource.username=root
    spring.datasource.password=password
    spring.jpa.database - platform=org.hibernate.dialect.MySQL5Dialect
    
  3. 创建实体类

    • com.example.demo.entity 包下创建 Product.kt
    package com.example.demo.entity
    
    import javax.persistence.Entity
    import javax.persistence.GeneratedValue
    import javax.persistence.GenerationType
    import javax.persistence.Id
    
    @Entity
    data class Product(
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        val id: Long? = null,
        val name: String,
        val price: Double
    )
    
    • @Entity 注解表明该类是一个 JPA 实体,@Id 注解指定主键,@GeneratedValue 定义了主键生成策略。
  4. 创建数据访问层(Repository)

    • com.example.demo.repository 包下创建 ProductRepository.kt
    package com.example.demo.repository
    
    import com.example.demo.entity.Product
    import org.springframework.data.jpa.repository.JpaRepository
    
    interface ProductRepository : JpaRepository<Product, Long>
    
    • JpaRepository 提供了基本的 CRUD 操作方法,这里 Product 是实体类,Long 是主键类型。
  5. 在控制器中使用 Repository

    • 修改 HelloController.kt 来展示数据库操作:
    package com.example.demo.controller
    
    import com.example.demo.entity.Product
    import com.example.demo.repository.ProductRepository
    import org.springframework.web.bind.annotation.*
    
    @RestController
    class HelloController(private val productRepository: ProductRepository) {
        @PostMapping("/products")
        fun addProduct(@RequestBody product: Product): Product {
            return productRepository.save(product)
        }
    
        @GetMapping("/products")
        fun getProducts(): List<Product> {
            return productRepository.findAll()
        }
    }
    
    • 构造函数注入了 ProductRepositoryaddProduct 方法保存一个新的 Product 到数据库,getProducts 方法获取所有产品。

视图渲染

  1. 添加模板引擎依赖

    • 以 Thymeleaf 为例,在 pom.xml 中添加:
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring - boot - starter - thymeleaf</artifactId>
    </dependency>
    
    • 对于 Gradle Kotlin DSL:
    implementation("org.springframework.boot:spring - boot - starter - thymeleaf")
    
  2. 创建视图模板

    • src/main/resources/templates 目录下创建 index.html
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Product List</title>
    </head>
    <body>
        <h1>Products</h1>
        <ul>
            <li th:each="product : ${products}">
                [[${product.name}]] - [[${product.price}]]
            </li>
        </ul>
    </body>
    </html>
    
    • Thymeleaf 使用特殊的语法(如 th:each)来动态渲染数据。
  3. 在控制器中返回视图

    • 修改 HelloController.kt
    package com.example.demo.controller
    
    import com.example.demo.entity.Product
    import com.example.demo.repository.ProductRepository
    import org.springframework.stereotype.Controller
    import org.springframework.ui.Model
    import org.springframework.web.bind.annotation.GetMapping
    
    @Controller
    class HelloController(private val productRepository: ProductRepository) {
        @GetMapping("/")
        fun index(model: Model): String {
            val products = productRepository.findAll()
            model.addAttribute("products", products)
            return "index"
        }
    }
    
    • 这里使用 @Controller 注解,方法返回视图名称 index,并将产品数据添加到模型中,Thymeleaf 会根据模板和模型数据渲染出最终的 HTML 页面。

错误处理

  1. 全局异常处理

    • 创建一个全局异常处理器类 GlobalExceptionHandler.kt
    package com.example.demo
    
    import org.springframework.http.HttpStatus
    import org.springframework.http.ResponseEntity
    import org.springframework.web.bind.annotation.ControllerAdvice
    import org.springframework.web.bind.annotation.ExceptionHandler
    
    @ControllerAdvice
    class GlobalExceptionHandler {
        @ExceptionHandler(IllegalArgumentException::class)
        fun handleIllegalArgumentException(ex: IllegalArgumentException): ResponseEntity<String> {
            return ResponseEntity(ex.message, HttpStatus.BAD_REQUEST)
        }
    }
    
    • @ControllerAdvice 注解使该类成为全局异常处理器。@ExceptionHandler 注解指定处理的异常类型,这里处理 IllegalArgumentException,返回带有错误信息和 HTTP 400 状态码的响应。
  2. 自定义错误页面

    • src/main/resources/templates/error 目录下创建 404.html 等错误页面,Spring Boot 会自动在发生相应错误时渲染这些页面。例如 404.html
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>404 Not Found</title>
    </head>
    <body>
        <h1>Page Not Found</h1>
    </body>
    </html>
    

安全性

  1. 添加 Spring Security 依赖

    • pom.xml 中添加:
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring - boot - starter - security</artifactId>
    </dependency>
    
    • 对于 Gradle Kotlin DSL:
    implementation("org.springframework.boot:spring - boot - starter - security")
    
  2. 配置 Spring Security

    • 创建 SecurityConfig.kt
    package com.example.demo
    
    import org.springframework.context.annotation.Bean
    import org.springframework.context.annotation.Configuration
    import org.springframework.security.config.annotation.web.builders.HttpSecurity
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
    import org.springframework.security.crypto.password.PasswordEncoder
    
    @Configuration
    @EnableWebSecurity
    class SecurityConfig : WebSecurityConfigurerAdapter() {
        override fun configure(http: HttpSecurity) {
            http
               .authorizeRequests()
                   .antMatchers("/", "/hello").permitAll()
                   .anyRequest().authenticated()
                   .and()
               .formLogin()
                   .loginPage("/login")
                   .permitAll()
                   .and()
               .logout()
                   .permitAll()
        }
    
        @Bean
        fun passwordEncoder(): PasswordEncoder {
            return BCryptPasswordEncoder()
        }
    }
    
    • 上述配置允许 //hello 路径无需认证访问,其他路径需要认证。配置了表单登录和注销功能,并使用 BCryptPasswordEncoder 进行密码加密。
  3. 创建用户服务

    • 创建 UserService.kt
    package com.example.demo
    
    import org.springframework.security.core.userdetails.User
    import org.springframework.security.core.userdetails.UserDetails
    import org.springframework.security.core.userdetails.UserDetailsService
    import org.springframework.security.core.userdetails.UsernameNotFoundException
    import org.springframework.stereotype.Service
    
    @Service
    class UserService : UserDetailsService {
        private val users = mutableListOf(
            User.withUsername("user").password(passwordEncoder().encode("password")).roles("USER").build()
        )
    
        override fun loadUserByUsername(username: String): UserDetails {
            return users.find { it.username == username }
                ?: throw UsernameNotFoundException("User not found with username: $username")
        }
    
        @Bean
        fun passwordEncoder(): PasswordEncoder {
            return BCryptPasswordEncoder()
        }
    }
    
    • 这里简单创建了一个用户,并实现了 UserDetailsService 接口来加载用户信息。

部署 Kotlin Spring Boot 应用

  1. 打包应用

    • Maven:运行 mvn clean package 命令,会在 target 目录下生成一个可执行的 JAR 文件。
    • Gradle:运行 ./gradlew clean build 命令,生成的 JAR 文件在 build/libs 目录下。
  2. 部署到服务器

    • 将生成的 JAR 文件上传到服务器,可以使用工具如 scp
    • 在服务器上,使用 java -jar your - app - name.jar 命令启动应用。如果需要在后台运行,可以使用 nohup java -jar your - app - name.jar & 命令。

通过以上步骤,我们全面地了解了 Kotlin Spring Boot Web 开发的各个方面,从基础搭建到复杂的功能实现,再到部署上线,能够构建出健壮且功能丰富的 Web 应用程序。