一、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管理需要特殊操作
- 在xxxCondig配置类顶部添加注解@Configuration
- 任意定义方法,方法的返回值就是工具类,并且添加注解@Bean。此时这个类就被Bean管理了
typescript
@Configuration
public class AppConfig {
@Bean
public Clock clock() {
return new Clock()
}
}
- 其它类使用工具类通过构造器注入
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.properties 或 application.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支持配置全局异常拦截,任何地方的异常都可以被全局异常拦截。
- 添加全局异常类,头部添加@RestControllerAdvice
- 添加多个异常类方法,头部添加注解@ExceptionHandler(自定义异常类.class)
- 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如下
- set key value:存储数据
- get key:获取数据
- exists key:是否存在数据,0不存在,1存在
vbnet
SET name "张三"
GET name # "张三"
SET counter 0
INCR counter # 1(计数器 +1)
DEL name # 删除 key
2.Hash
hash保存对象数据,其api如下
- hset name key1 value1 key2 value2 ...
- hget name:获取完整对象数据
- hget name key1:获取对象的属性是key1的
- 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去获取缓存结果。
-
@Cacheable(value = "user", key = "#id")
-
@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);
}
}