环境准备

  • 系统:MacOS
  • 开发:IntelliJ IDEA
  • 语言:Java8
  • 其它:Mysql、Redis

脚手架代码

Spring提供了一个创建项目脚手架的官网,在这里可以直接定制项目的框架代码。例如:

注意:在Dependencies中,添加了Web依赖。

点击【Generate Project】,即可下载项目框架代码。

创建工程

将框架代码包解压后放到工作目录。打开IDEA,点击【File] -> 【Open】,打开对应目录。

启动工程

找到com.spring.demo.demo下的DemoApplication,右键点击运行后,console中即可显示Spring启动的信息。

Controller

在传统MVC架构中,Controller负责接收Http请求并返回相应的应答,这个应答可以是一个页面,也可以是一个JSON对象。方便起见,本教程使用RestfulAPI为例。

添加RestController

创建一个UserController,负责响应User相关的业务请求。对于纯数据的API接口,使用@RestControll进行标注,这样每一个API接口的返回值就会被转化为JSON结构的数据。

package com.spring.demo.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @RequestMapping("/user")
    public String find() {
        return "UserA";
    }
}

重启应用,在浏览器中请求http://localhost:8080/user,即可看到请求的返回内容:UserA。

路径参数

对于带路径参数的请求,可以通过@PathVariable 标注来获取参数值。如:

package com.spring.demo.demo.controller;

import org.springframework.web.bind.annotation.PathVariable;
...

@RestController
public class UserController {

    @RequestMapping("/user/{id}")
    public String find(@PathVariable int id) {
        return "用户ID[" + id + "]";
    }
}

POST请求

对于POST请求,可以使用@PostMapping 来标注方法,接收的JSON数据需要使用@RequestBody来标注。

...
    @PostMapping("/user")
    public void create(@RequestBody UserCreateRequest user) {
        System.out.println(user.getName());
    }
...

这里的UserCreateRequest,用来接收并转换JSON数据。

package com.spring.demo.demo.dto;

public class UserCreateRequest {

    private String name;
    private boolean gender;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isGender() {
        return gender;
    }

    public void setGender(boolean gender) {
        this.gender = gender;
    }
}

在IDEA的Terminal中,使用curl来进行测试。

curl -XPOST 'http://127.0.0.1:8080/user' -H 'Content-Type: application/json' -d'{"name":"用户A","gender":0}'

参数校验

有时我们需要对传入参数进行非空或有效值的校验,这个处理应该在正式进入controller前进行。

1. 添加@Validated标注

在SpringMVC中,对输入参数进行校验通常使用@Validated标注。

...
    @PostMapping("/user")
    public void create(@RequestBody @Validated UserCreateRequest user) {
        System.out.println(user.getName());
    }
...

2. 添加校验规则标注@

在对应的字段上,加上对应的校验规则标注。

package com.spring.demo.demo.dto;

import javax.validation.constraints.NotNull;

public class UserCreateRequest {

    @NotNull
    private String name;

    private boolean gender;

    // Getters & Setters
    ...
}

添加后重启服务,发送不含name字段的POST请求,结果如下:

Service

MVC框架中,Controller负责接收请求和相应,Service则负责具体的业务处理,即Model层。

Service在定义是需要使用@Service标注,SpringBoot在启动中将会注册该Service并在Controller通过DI来实例化并使用该Service。

一般来说,我们会创建一个Service的接口类和一个对应的实现类。

IUserService

package com.spring.demo.demo.service;

public interface IUserService {
    public String findUser(int id);
}

UserService

package com.spring.demo.demo.service;

import org.springframework.stereotype.Service;

@Service
public class UserService implements  IUserService {

    public String findUser(int id) {
        return "用户" + id;
    }
}

在调用时,Controller会直接应用接口类,并添加@Autowired标签。这里的调用原理,即是Sping最著名的DI(依赖注入)和IoC(控制反转)。

package com.spring.demo.demo.controller;

import com.spring.demo.demo.dto.UserCreateRequest;
...

@RestController
public class UserController {

    @Autowired
    IUserService userService;

    @RequestMapping("/user/{id}")
    public String find(@PathVariable int id) {
        return userService.findUser(id);
    }

    @PostMapping("/user")
    public void create(@RequestBody @Validated UserCreateRequest user) {
        System.out.println(user.getName());
    }
}

此时重启Spring,并使用curl进行请求,可以得到结果:

$ curl -XGET 'http://127.0.0.1:8080/user/100' 
$ 用户100

Repository

Spring本身集成了Spring Data JPA,用来作为访问数据库的ORM工具,它采用了Hibernate实现。在本教程,我们将实现User的增和查的工作。

添加依赖

修改pom.xml,添加对mysql和jpa的支持。

  ...
  <dependencies>
    ...
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
  </dependencies>

服务配置

修改/resources/application.properties,添加相关的设置。

...
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
...
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.show-sql=true

添加Repository和Entity

Repository在DDD中是一个非常重要的概念,字面意思来讲它就是一个仓库。它屏蔽了SQL和数据库操作的细节,使得业务代码无需再考虑更细节的数据处理。它和Mybatis可以说是两个完全相反的流派。

Spring Data JPA中,默认实现了Crud操作的Repository,可以直接继承并使用这个框架进行快速的CRUD实现。

package com.spring.demo.demo.repo.repository;

import com.spring.demo.demo.repo.entity.User;
...

@Repository
public interface UserRepository extends CrudRepository<User, Integer> { }

对应数据库中的数据表,会有一个实体Entity来定义它的表结构。

package com.spring.demo.demo.repo.entity;

import javax.persistence.*;

@Entity
@Table(name="t_user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private Integer gender;

    // Getter & Setter
    ...
}

在Service调用中,即可简单的使用save方法来保存新的User数据。

package com.spring.demo.demo.service;

import com.spring.demo.demo.repo.entity.User;
...

@Service
public class UserService implements  IUserService {

    @Autowired
    UserRepository userRepo;

    public String findUser(int id) {
        return "用户" + id;
    }

    public User createUser(User user) {
        return userRepo.save(user);
    }
}

最后在Controller中,修改Service方法并创建User。

package com.spring.demo.demo.controller;

import com.spring.demo.demo.dto.UserCreateRequest;
...

@RestController
public class UserController {

    @Autowired
    IUserService userService;

    @RequestMapping("/user/{id}")
    public String find(@PathVariable int id) {
        return userService.findUser(id);
    }

    @PostMapping("/user")
    public void create(@RequestBody @Validated UserCreateRequest userReq) {

        User user = new User();
        user.setName(userReq.getName());
        user.setGender(userReq.getGender());

        user = userService.createUser(user);
        System.out.println("创建用户 ID=" + user.getId() + " 用户名=" + user.getName());
    }
}

重启Spring并提交创建请求。

$ curl -XPOST 'http://127.0.0.1:8080/user' -H 'Content-Type: application/json' -d'{"name":"用户A","gender":0}'

可以看到,在console中有创建成功的提示信息。

Hibernate: insert into t_user (gender, name) values (?, ?)创建用户 ID=1 用户名=用户A

缓存

对于不常改变的数据,常常需要进行缓存以提高系统性能和增加系统吞吐量。对此,Spring Cache提供了缓存的基本实现。

添加依赖

修改pom.xml,添加对cache的支持。

  ...
  <dependencies>
    ...
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
      <optional>true</optional>
    </dependency>
  </dependencies>

服务配置

修改/resources/application.properties,添加相关的设置。

...
spring.cache.type=simple

打开开关

需要在Application中,打开Cache开关。

package com.spring.demo.demo;

import org.springframework.boot.SpringApplication;
...

@SpringBootApplication
@EnableCaching
public class DemoApplication {

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

}

使用Cacheable标注

对于Cache内容,需要添加一个key名来保存内容。

package com.spring.demo.demo.service;

import com.spring.demo.demo.repo.entity.User;
...

@Service
public class UserService implements  IUserService {

    @Autowired
    UserRepository userRepo;

    @Cacheable(cacheNames = "user")
    public User findUser(Integer id) {
        System.out.println("取得用户操作 ID=" + id);
        return userRepo.findById(id).get();
    }

    public User createUser(User user) {
        return userRepo.save(user);
    }
}

重启Spring,发送GET请求后可以看到,只有第一次执行了SQL操作,说明缓存处理已经完成。

$ curl -XGET 'http://127.0.0.1:8080/user/1' 
$ 用户A
$ 用户A
> 取得用户操作 ID=1=
> Hibernate: select user0_.id as id1_0_0_, user0_.gender as gender2_0_0_, user0_.name as name3_0_0_ from t_user user0_ where user0_.id=?

Redis

Spring的默认Cache处理并不使用Redis,要使用Redis作为缓存应用,需要添加Redis的框架支持。

添加依赖

修改pom.xml,添加对cache的支持。

  ...
  <dependencies>
    ...
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  </dependencies>

服务配置

修改/resources/application.properties,添加相关的设置。

...
spring.redis.host=127.0.0.1
spring.redis.password=123456
spring.redis.port=6379
spring.redis.jedis.pool.max-active=8

序列化对象

由于要保存到redis中,保存的实体对象需要进行序列化。

package com.spring.demo.demo.repo.entity;

import java.io.Serializable;
...

@Entity
@Table(name="t_user")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private Integer gender;

    // Getter & Setter
    ...
}

重启Spring,再试一遍上节的操作,仍然是一样的结果,但缓存已经换为redis。

$ curl -XGET 'http://127.0.0.1:8080/user/1' 
$ 用户A
$ 用户A
> 取得用户操作 ID=1
> Hibernate: select user0_.id as id1_0_0_, user0_.gender as gender2_0_0_, user0_.name as name3_0_0_ from t_user user0_ where user0_.id=?