03-Spring Boot

一、IOC、DI

IOC与DI是spring框架最重要的概念,IOC指把对象的创建交给spring容器,DI指如何使用Spring创建的对象。

1.IOC

在类顶部添加spring内置的注解即可,常见注解包括@Service,@RestController,@Component

kotlin 复制代码
@RestController
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {  // Spring 自动传入
        this.userService = userService;
    }
}

2.DI

DI指如何将Spring管理的类注入到当前类中。如下案例演示UserController类如何使用UserService类

kotlin 复制代码
@Service // ① UserService 注册为 Bean
public class UserService {
    private final UserMapper userMapper;
    public UserService(UserMapper userMapper) {  // ② 构造器注入
        this.userMapper = userMapper;
    }
}

@RestController  // ① UserController 注册为 Bean
public class UserController {
    private final UserService userService;
    public UserController(UserService userService) {  // ② 构造器注入
        this.userService = userService;
    }
}

3.第三方工具注入

spring中service、controller、mapper层的类通过注解实现spring的bean管理,但是第三方工具类被bean管理需要特殊操作

  1. 在xxxCondig配置类顶部添加注解@Configuration
  2. 任意定义方法,方法的返回值就是工具类,并且添加注解@Bean。此时这个类就被Bean管理了
typescript 复制代码
@Configuration
public class AppConfig {

    @Bean
    public Clock clock() {
        return new Clock()
    }
}
  1. 其它类使用工具类通过构造器注入
kotlin 复制代码
@Service
public class TimeService {

    private final Clock clock;

    public TimeService(Clock clock) {
        this.clock = clock;
    }

    public String now() {
        return clock.instant().toString();
    }
}

二、web处理

spring的web处理包含,路由、参数都在control层。spring内置大量注解来直接解析http请求

@RequestMapping:路由

@GetMapping:get请求

@PostMapping:post请求

@RequestBody:请求体

@PathVariable:路径变量

@RequestParam:查询参数

Java 复制代码
@RestController
@RequestMapping("/api/users")
public class UserApiController {

    private final Map<Long, User> store = new HashMap<>();
    private final AtomicLong idGenerator = new AtomicLong(1);

    // 构造时放两条测试数据
    public UserApiController() {
        store.put(1L, new User(1L, "张三", "zhangsan@example.com"));
        store.put(2L, new User(2L, "李四", "lisi@example.com"));
        idGenerator.set(3);
    }

    /** GET /api/users */
    @GetMapping
    public List<User> list() {
        return new ArrayList<>(store.values());
    }

    /** GET /api/users/1 */
    @GetMapping("/{id}")
    public User getById(@PathVariable Long id) {
        User user = store.get(id);
        if (user == null) {
            throw new RuntimeException("用户不存在: " + id);
        }
        return user;
    }

    /** POST /api/users  Body: {"username":"王五","email":"..."} */
    @PostMapping
    public User create(@RequestBody User user) {
        long id = idGenerator.getAndIncrement();
        user.setId(id);
        store.put(id, user);
        return user;
    }

    /** PUT /api/users/1  Body: {"username":"...","email":"..."} */
    @PutMapping("/{id}")
    public User update(@PathVariable Long id, @RequestBody User user) {
        if (!store.containsKey(id)) {
            throw new RuntimeException("用户不存在: " + id);
        }
        user.setId(id);
        store.put(id, user);
        return user;
    }

    /** DELETE /api/users/1 */
    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        store.remove(id);
    }
    
    
    // 查询参数  /users/search?keyword=张&page=1
    @GetMapping("/users/search")
    public List<User> search(
            @RequestParam String keyword,
            @RequestParam(defaultValue = "1") int page) { }
}

三、环境配置

Spring Boot 使用 application.propertiesapplication.yml 统一管理配置。

Java 复制代码
server.port=8080
spring.application.name=spring_demo_maven

spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_demo
spring.datasource.username=root
spring.datasource.password=你的密码

3.1 读取配置

@value:在属性上添加@value即可读取配置项,缺点是需要逐个读取。

kotlin 复制代码
@RestController
public class ConfigDemoController {

    @Value("${spring.application.name}")
    private String appName;

    @Value("${server.port}")
    private int port;

    @GetMapping("/config/demo")
    public Map<String, Object> demo() {
        return Map.of("appName", appName, "port", port);
    }
}

@ConfigurationProperties

通过在类上添加注解@ConfigurationProperties,告诉spring这个类上的属性映射配置项内容。prefix前缀可以替代配置项的公共前缀。

ini 复制代码
//配置项内容
app.name=dzp
app.age=22
Java 复制代码
@ConfigurationProperties(prefix = "app")
public class AppProperties {
    private String name = "默认标题";
    private int age = 1
    public void setName(String name) {
        this.name = name
    }
    public void setAge(int age) {
        this.age = age
    }
}

四、三层架构

spring标准的三层架构写法依次是

makefile 复制代码
Mapper:数据库访问

Service:业务逻辑

Controller:接收请求,响应。

如下是一份案例,三层逐层调用。

Java 复制代码
/*Mapper层*/
@Repository
public class UserStore {
  private final Map<Long, User> store = new HashMap<>();
  private final AtomicLong idGenerator = new AtomicLong(1);
  public UserStore() {
    store.put(1L, new User(1L, "张三", "zhangsan@example.com"));
    idGenerator.set(3);
  }
  
  public User findById(Long id) {
    return store.get(id);
  }
  public User save(User user) {
    long id = idGenerator.getAndIncrement();
    user.setId(id);
    store.put(id, user);
    return user;
  }
  public boolean deleteById(Long id) {
    return store.remove(id) != null ? true : false;
  }
}

/*Service层*/
@Service
public class UserService{
    private final UserStore userStore;
    public UserService(UserStore userStore) {
        this.UserStore = userStore;
    }
    public User findById(Long id) {
      //业务校验id
      //调用mapper层数据库
      return userStore.findById(id);
    }
    
}

/*Controller层*/
@RestController
@RequestMapping("/api/users")
public class UserController {
  private final UserService userService;
  public UserController(UserService userService) {
    this.userService = userService;
  }
  
  @GetMapping("/{id}")
  public User getById(@PathVariable Long id) {
    return userService.findById(id)
  } 

五、异常

spring支持配置全局异常拦截,任何地方的异常都可以被全局异常拦截。

  1. 添加全局异常类,头部添加@RestControllerAdvice
  2. 添加多个异常类方法,头部添加注解@ExceptionHandler(自定义异常类.class)
  3. service、controller中抛出异常时,全局异常拦截器根据异常类型匹配方法返回包装的结果
Java 复制代码
@RestControllerAdvice
public class GlobalException {

  @ExceptionHandler(BusinessException.class)
  @ResponseStatus(HttpStatus.OK)
  public Result<Void> handleBusinessException(BusinessException e) {
    return Result.fail(e.getCode(), e.getMessage());
  }

  @ExceptionHandler(MethodArgumentNotValidException.class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public Result<Void> handleValidationException(MethodArgumentNotValidException e) {
    FieldError fieldError = e.getBindingResult().getFieldError();
    String message = fieldError != null ? fieldError.getDefaultMessage() : "参数校验失败";
    return Result.fail(400, message);
  }

  @ExceptionHandler(IllegalArgumentException.class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public Result<Void> handleIllegalArgumentException(IllegalArgumentException e) {
    return Result.fail(400, e.getMessage());
  }

  @ExceptionHandler(Exception.class)
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public Result<Void> handleException(Exception e) {
    return Result.fail(500, "服务器内部错误");
  }
}

如下代码在查询用户时,查询失败,抛出BusinessException异常,全局异常类根据类型定位到handleBusinessException这个方法,然后抛出result结果渲染到前端,而不是错误堆栈信息。

Java 复制代码
public User getById(Long id) {
    User user = userMapper.findById(id);
    System.out.println("user"+user);
    if (user == null) {
      throw new BusinessException(404, "用户不存在: " + id);
    }
    return user;
}

六、MyBatis-Plus

七、Redis

redis是非关系型数据库,用于进行数据缓存操作。常搭配mysql进行数据链路的缓存查询

bash 复制代码
1.id查询用户信息
2.优先查询redis,命中直接返回信息,否则查询mysql
3.查询mysql,缓存数据到redis

7.1 数据类型

redis只有五种数据类型string,hash,list,set,zset。

1.String

string保存字符串与数字数据,其api如下

  1. set key value:存储数据
  2. get key:获取数据
  3. exists key:是否存在数据,0不存在,1存在
vbnet 复制代码
SET name "张三"
GET name                  # "张三"
SET counter 0
INCR counter              # 1(计数器 +1)
DEL name                  # 删除 key

2.Hash

hash保存对象数据,其api如下

  1. hset name key1 value1 key2 value2 ...
  2. hget name:获取完整对象数据
  3. hget name key1:获取对象的属性是key1的
  4. hexists key:是否存在数据,0不存在,1存在
perl 复制代码
HSET profile:1 username "张三" email "a@b.com" phone "13800138000"
HGET profile:1 username          # 只取一个 field → "张三"
HGETALL profile:1                # 取全部 field+value(交替返回)
HLEN profile:1                   # field 个数
HEXISTS profile:1 email          # field 是否存在

3.List

list存储有序的数据,保存到数组中

4. Set

set存储无需的,不能重复的数据到容器中

5. ZSet

zset在set基础上,每个数据绑定1个数字值,用于自动排序用。

7.2 spring中使用

企业的spring项目中,使用redis最多的有两种方式StringRedisTemplate、@Cacheable

1.StringRedisTemplate

如下是一段redis配合mysql的数据查询操作,其中redisTemplate负责操作redis,而objectMapper负责做数据转换。 读:redis读取--->json->转换成对象;写:对象-->json字符串-->redis写入

Java 复制代码
public User getById(Long id) {
    String key = KEY_PREFIX + id;
    String json = redisTemplate.opsForValue().get(key);
    if (json != null) {
      try {
        return objectMapper.readValue(json, User.class);
      } catch (JsonProcessingException e) {
        throw new RuntimeException("Failed to parse JSON", e);
      }
    }
    // 缓存未命中,走mysql查询
    User user = userMapper.findById(id);
    if (user == null) {
      throw new RuntimeException("User not found");
    }
    try {
      redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(user), TTL_MINUTES, TimeUnit.MINUTES);
    } catch (JsonProcessingException e) {
      throw new RuntimeException("Failed to serialize user", e);
    }
    return user;
  }
  

2.@Cacheable

@Cacheable通过注解操作redis,其省略了很多代码,但是本质含义和上面方式一样。常用注解有两种,查询和删除。value和key拼接作为redis的实际key,例如参数id是1,则redis实际会执行get user::1去获取缓存结果。

  1. @Cacheable(value = "user", key = "#id")

  2. @CacheEvict(value = "user", key = "#id")

typescript 复制代码
@Cacheable(value = "user", key = "#id")
public User getById(Long id) {
    User user = userMapper.findById(id);
    if (user == null) {
        throw new BusinessException(404, "用户不存在: " + id);
    }
    return user;
}

@CacheEvict(value = "user", key = "#id")
public void delete(Long id) {
    if (userMapper.deleteById(id) == 0) {
        throw new RuntimeException("用户不存在: " + id);
    }
}

八、RocketMQ

九、Spring Cloud

十、测试

相关推荐
前端兰博1 小时前
01-Java开发基础语言
后端
前端兰博1 小时前
02-Servlet、JDBC、Maven、Mybatis
后端
咖啡八杯2 小时前
GoF设计模式——模板方法模式
java·后端·spring·设计模式
码事漫谈3 小时前
Loop 正在排斥人为操作
后端
爱勇宝3 小时前
办公资料反复修改、补传、交接混乱,我做了个桌面工具来解决这件事
前端·后端·程序员
庄周de蝴蝶3 小时前
梅开二度,这次拿下系统分析师
后端·程序员
林小帅5 小时前
NestJS v11 + Prisma v7 ESM 迁移配置解析
前端·javascript·后端
IT_陈寒5 小时前
React的setState竟然不是立刻生效的,害我调试半天
前端·人工智能·后端
逝水无殇6 小时前
C# 字符串(String)详解
开发语言·后端·c#