Java面试八股文-框架篇

1. Spring框架

1.1 什么是Spring框架?它的核心功能是什么?

Spring框架是一个轻量级的Java企业级应用开发框架,它为Java应用提供了全面的基础设施和编程模型,使开发者能够更加专注于业务逻辑的实现。

核心功能

功能 描述 作用
控制反转(IoC) 将对象的创建和依赖关系的管理交给Spring容器,而不是由对象自己创建和管理依赖 实现对象之间的解耦,提高代码的可维护性和可测试性
依赖注入(DI) 容器将依赖的对象注入到需要的对象中 简化对象的创建和管理,减少代码耦合
面向切面编程(AOP) 将横切关注点(如日志、事务、安全等)与业务逻辑分离 提高代码的可维护性和可重用性
事务管理 提供声明式事务管理,简化事务处理代码 确保数据的一致性和完整性
数据访问 整合各种数据访问技术(如JDBC、ORM框架等) 简化数据访问代码,提高开发效率
Web MVC 提供Web应用开发框架,支持RESTful风格的Web服务 简化Web应用的开发
测试支持 提供强大的测试框架,支持单元测试和集成测试 提高代码的质量和可靠性
企业级集成 与各种企业级技术(如JMS、JCA等)集成 增强应用的功能和扩展性

Spring的优势

  • 轻量级:核心容器很小,不需要依赖其他外部库
  • 非侵入性:应用代码不需要实现Spring特定的接口
  • 模块化:可以根据需要选择使用Spring的不同模块
  • 可测试性:依赖注入使得测试更加容易
  • 灵活:可以与其他框架无缝集成
  • 生态丰富:拥有庞大的生态系统和社区支持

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// 1. 定义用户实体类
public class User {
private int id;
private String username;
private String password;
// getter和setter方法
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}

// 2. 定义一个服务接口
public interface UserService {
void addUser(String username, String password);
User getUserById(int id);
}

// 3. 实现服务接口
@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserDao userDao;

@Override
@Transactional
public void addUser(String username, String password) {
userDao.addUser(username, password);
}

@Override
public User getUserById(int id) {
return userDao.getUserById(id);
}
}

// 4. 定义数据访问接口
public interface UserDao {
void addUser(String username, String password);
User getUserById(int id);
}

// 5. 实现数据访问接口
@Repository
public class UserDaoImpl implements UserDao {

@Autowired
private JdbcTemplate jdbcTemplate;

@Override
public void addUser(String username, String password) {
String sql = "INSERT INTO users (username, password) VALUES (?, ?)";
jdbcTemplate.update(sql, username, password);
}

@Override
public User getUserById(int id) {
String sql = "SELECT * FROM users WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new Object[]{id}, (rs, rowNum) -> {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
});
}
}

// 6. 配置类
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.example")
public class AppConfig {

@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("password");
return dataSource;
}

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}

// 7. 主类
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);

// 使用服务
userService.addUser("admin", "123456");
User user = userService.getUserById(1);
System.out.println("User: " + user.getUsername());

context.close();
}
}

1.2 什么是IoC和DI?它们的区别是什么?

IoC(Inversion of Control,控制反转)

  • 是一种设计思想,将对象的创建和依赖关系的管理交给容器,而不是由对象自己创建和管理依赖
  • 传统的程序设计中,对象的创建和依赖关系的管理由对象自己控制,而IoC将这种控制权转移给了容器
  • 目的是实现对象之间的解耦,提高代码的可维护性和可测试性

DI(Dependency Injection,依赖注入)

  • 是IoC的具体实现方式,容器将依赖的对象注入到需要的对象中
  • 依赖注入的方式包括:构造函数注入、setter方法注入、字段注入

区别

特性 IoC DI
性质 设计思想 实现方式
关注点 控制权的转移 依赖的注入方式
目的 解耦 实现IoC
应用 架构设计 具体实现

依赖注入的方式

注入方式 描述 优点 缺点
构造函数注入 通过构造函数参数注入依赖 强制性依赖,确保对象创建时依赖已注入 构造函数参数可能过多
Setter方法注入 通过setter方法注入依赖 灵活性高,可选择性注入依赖 依赖可能在使用时未注入
字段注入 通过字段直接注入依赖 代码简洁,使用方便 不利于测试,耦合度高

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 传统方式:对象自己创建和管理依赖
public class UserService {
private UserDao userDao;

public UserService() {
// 自己创建依赖
this.userDao = new UserDaoImpl();
}

public void addUser(String username, String password) {
userDao.addUser(username, password);
}
}

// IoC方式:容器创建和管理依赖
public class UserService {
private UserDao userDao;

// 1. 构造函数注入
public UserService(UserDao userDao) {
this.userDao = userDao;
}

// 2. setter方法注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

public void addUser(String username, String password) {
userDao.addUser(username, password);
}
}

// 3. 字段注入(使用@Autowired注解)
@Service
public class UserService {
@Autowired
private UserDao userDao;

public void addUser(String username, String password) {
userDao.addUser(username, password);
}
}

// Spring配置
@Configuration
public class AppConfig {
@Bean
public UserDao userDao() {
return new UserDaoImpl();
}

@Bean
public UserService userService(UserDao userDao) {
return new UserService(userDao);
}
}

// 使用
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.addUser("admin", "123456");
context.close();
}
}

1.3 Spring中的Bean作用域有哪些?

Spring中的Bean作用域定义了Bean实例的生命周期和可见范围,主要包括以下几种:

作用域 描述 生命周期 适用场景
singleton 单例,默认作用域,整个应用中只有一个实例 容器启动时创建,容器关闭时销毁 无状态的Bean,如服务层、DAO层
prototype 原型,每次获取Bean时都会创建一个新的实例 获取时创建,不再管理其生命周期 有状态的Bean,如命令对象、表单对象
request 请求,每个HTTP请求创建一个实例 请求开始时创建,请求结束时销毁 Web应用中与请求相关的Bean
session 会话,每个HTTP会话创建一个实例 会话开始时创建,会话结束时销毁 Web应用中与会话相关的Bean,如用户信息
application 应用,每个ServletContext创建一个实例 应用启动时创建,应用关闭时销毁 Web应用中全局共享的Bean
websocket WebSocket,每个WebSocket会话创建一个实例 WebSocket会话开始时创建,会话结束时销毁 WebSocket应用中与会话相关的Bean

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 1. 单例作用域(默认)
@Service
// @Scope("singleton") // 默认值,可省略
public class UserService {
// 服务实现
private int count = 0;

public void increment() {
count++;
System.out.println("Count: " + count);
}
}

// 2. 原型作用域
@Component
@Scope("prototype")
public class UserForm {
private String username;
private String password;
// getter和setter
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}

// 3. 请求作用域
@Component
@Scope("request")
public class RequestContext {
private HttpServletRequest request;
// 构造函数注入
public RequestContext(HttpServletRequest request) {
this.request = request;
}

public String getRequestPath() {
return request.getRequestURI();
}
}

// 4. 会话作用域
@Component
@Scope("session")
public class UserSession {
private User user;
// getter和setter
public User getUser() { return user; }
public void setUser(User user) { this.user = user; }
}

// 5. 应用作用域
@Component
@Scope("application")
public class ApplicationContext {
private Map<String, Object> attributes = new HashMap<>();

public void setAttribute(String key, Object value) {
attributes.put(key, value);
}

public Object getAttribute(String key) {
return attributes.get(key);
}
}

Bean作用域的配置方式

  1. 使用@Scope注解:适用于注解配置方式

    1
    2
    3
    4
    5
    @Component
    @Scope("prototype")
    public class UserForm {
    // 实现
    }
  2. 使用XML配置:适用于XML配置方式

    1
    <bean id="userForm" class="com.example.UserForm" scope="prototype"/>

注意事项

  • singleton作用域的Bean在容器启动时创建,prototype作用域的Bean在每次获取时创建
  • request、session、application作用域只在Web应用中有效
  • 使用这些作用域时,需要在Web应用中配置RequestContextListenerRequestContextFilter
  • 对于singleton Bean依赖prototype Bean的情况,需要使用@Lookup注解或ObjectFactory来确保每次获取到新的prototype Bean

1.4 什么是AOP?AOP的核心概念有哪些?

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它将横切关注点(如日志、事务、安全等)与业务逻辑分离,提高代码的可维护性和可重用性。

AOP的核心概念

概念 描述 作用
切面(Aspect) 横切关注点的模块化,包含通知和切点 定义需要增强的横切逻辑
连接点(Join Point) 程序执行过程中的点,如方法调用、异常抛出等 表示可以被增强的位置
通知(Advice) 在连接点执行的代码 定义增强的具体逻辑
切点(Pointcut) 匹配连接点的表达式 确定哪些连接点会被增强
目标对象(Target) 被切面增强的对象 原始业务逻辑对象
代理(Proxy) AOP框架创建的代理对象 执行增强后的代码
织入(Weaving) 将切面应用到目标对象并创建代理对象的过程 实现AOP的核心过程

通知类型

通知类型 描述 执行时机 特点
前置通知(Before) 在连接点执行前执行 方法调用前 不能阻止方法执行
后置通知(After Returning) 在连接点正常执行后执行 方法返回后 可以获取返回值
异常通知(After Throwing) 在连接点抛出异常后执行 方法抛出异常后 可以获取异常信息
最终通知(After) 在连接点执行后执行,无论是否异常 方法执行后 类似于finally块
环绕通知(Around) 包围连接点,在连接点前后执行 方法调用前后 可以控制方法执行

AOP的实现方式

  1. 基于代理

    • JDK动态代理:基于接口的代理
    • CGLIB代理:基于类的代理
  2. 基于字节码增强

    • AspectJ:编译时织入

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// 1. 定义用户实体类
public class User {
private int id;
private String username;
private String password;

public User(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}

// getter和setter
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }

@Override
public String toString() {
return "User{id=" + id + ", username='" + username + "'}";
}
}

// 2. 定义一个服务接口
public interface UserService {
void addUser(String username, String password);
User getUserById(int id);
}

// 3. 实现服务接口
@Service
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username, String password) {
System.out.println("Adding user: " + username);
// 业务逻辑
}

@Override
public User getUserById(int id) {
System.out.println("Getting user by id: " + id);
// 业务逻辑
return new User(id, "admin", "123456");
}
}

// 4. 定义切面
@Aspect
@Component
public class LoggingAspect {
// 定义切点
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void userServicePointcut() {}

// 前置通知
@Before("userServicePointcut()")
public void beforeAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[Before] Method: " + methodName + ", Args: " + Arrays.toString(args));
}

// 后置通知
@AfterReturning(pointcut = "userServicePointcut()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[After Returning] Method: " + methodName + ", Result: " + result);
}

// 异常通知
@AfterThrowing(pointcut = "userServicePointcut()", throwing = "exception")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[After Throwing] Method: " + methodName + ", Exception: " + exception.getMessage());
}

// 最终通知
@After("userServicePointcut()")
public void afterAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[After] Method: " + methodName);
}

// 环绕通知
@Around("userServicePointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[Around Before] Method: " + methodName + ", Args: " + Arrays.toString(args));

Object result = null;
try {
// 执行目标方法
result = joinPoint.proceed();
System.out.println("[Around After Returning] Result: " + result);
} catch (Exception e) {
System.out.println("[Around After Throwing] Exception: " + e.getMessage());
throw e;
} finally {
System.out.println("[Around Finally]");
}

return result;
}
}

// 5. 配置类
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 不需要显式声明Bean,ComponentScan会自动扫描
}

// 6. 主类
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);

// 调用方法
userService.addUser("admin", "123456");
System.out.println("---------------------------------");
User user = userService.getUserById(1);
System.out.println("User: " + user.getUsername());

context.close();
}
}

AOP的应用场景

  • 日志记录:记录方法的调用、参数和返回值
  • 事务管理:控制事务的开始、提交和回滚
  • 安全检查:检查用户是否有权限执行操作
  • 性能监控:统计方法的执行时间
  • 异常处理:统一处理方法抛出的异常
  • 缓存:缓存方法的返回结果
  • 权限控制:控制方法的访问权限

AOP的优缺点

优点

  • 代码解耦:将横切关注点与业务逻辑分离
  • 代码复用:横切关注点可以在多个地方重用
  • 代码维护性:横切关注点的变更只需要修改一处
  • 业务逻辑清晰:业务代码只关注核心业务逻辑

缺点

  • 增加系统复杂度:AOP的引入增加了系统的复杂性
  • 调试难度:增强后的代码可能难以调试
  • 性能开销:代理和织入过程会带来一定的性能开销

1.5 Spring中的事务传播行为有哪些?

事务传播行为是指当一个方法调用另一个方法时,事务如何在这些方法之间传播。Spring中定义了7种事务传播行为:

传播行为 描述 适用场景 特点
REQUIRED 如果当前没有事务,创建一个新事务;如果当前有事务,加入当前事务 大多数场景,如业务逻辑方法 默认行为
SUPPORTS 如果当前有事务,加入当前事务;如果当前没有事务,以非事务方式执行 可选事务的场景,如查询操作 灵活选择
MANDATORY 如果当前有事务,加入当前事务;如果当前没有事务,抛出异常 必须在事务中执行的操作 强制事务
REQUIRES_NEW 创建一个新事务,暂停当前事务 需要独立事务的场景,如日志记录 独立事务
NOT_SUPPORTED 以非事务方式执行,暂停当前事务 不需要事务的场景,如批量操作 非事务执行
NEVER 以非事务方式执行,如果当前有事务,抛出异常 绝对不能在事务中执行的操作 禁止事务
NESTED 如果当前有事务,创建一个嵌套事务;如果当前没有事务,创建一个新事务 需要部分回滚的场景 嵌套事务

详细解释

1. REQUIRED

  • 这是默认的事务传播行为
  • 如果当前存在事务,就加入到当前事务中
  • 如果当前不存在事务,就创建一个新的事务
  • 适用场景:大多数业务逻辑方法,如用户注册、订单创建等

2. SUPPORTS

  • 如果当前存在事务,就加入到当前事务中
  • 如果当前不存在事务,就以非事务方式执行
  • 适用场景:可选事务的操作,如查询操作,这些操作可以在事务中执行,也可以不在事务中执行

3. MANDATORY

  • 如果当前存在事务,就加入到当前事务中
  • 如果当前不存在事务,就抛出异常
  • 适用场景:必须在事务中执行的操作,如某些关键的业务操作

4. REQUIRES_NEW

  • 无论当前是否存在事务,都创建一个新的事务
  • 如果当前存在事务,就暂停当前事务,直到新事务完成
  • 适用场景:需要独立事务的操作,如日志记录、审计操作等,这些操作应该与主业务逻辑事务隔离

5. NOT_SUPPORTED

  • 以非事务方式执行
  • 如果当前存在事务,就暂停当前事务,直到非事务操作完成
  • 适用场景:不需要事务的操作,如批量数据导入、报表生成等

6. NEVER

  • 以非事务方式执行
  • 如果当前存在事务,就抛出异常
  • 适用场景:绝对不能在事务中执行的操作,如某些特殊的系统操作

7. NESTED

  • 如果当前存在事务,就创建一个嵌套事务,嵌套事务是外部事务的一部分
  • 如果当前不存在事务,就创建一个新的事务
  • 嵌套事务可以独立回滚,而不会影响外部事务
  • 适用场景:需要部分回滚的操作,如批量操作中部分失败的情况

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 1. 定义UserService
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private LogService logService;

// 默认使用REQUIRED传播行为
@Transactional
public void addUser(String username, String password) {
System.out.println("UserService.addUser - 开始");
try {
userDao.addUser(username, password);
// 调用logService的方法,使用REQUIRES_NEW传播行为
logService.logOperation("addUser", username);
// 模拟异常
if (username.equals("error")) {
throw new RuntimeException("模拟异常");
}
} catch (Exception e) {
System.out.println("UserService.addUser - 异常: " + e.getMessage());
throw e;
} finally {
System.out.println("UserService.addUser - 结束");
}
}
}

// 2. 定义LogService
@Service
public class LogService {
@Autowired
private LogDao logDao;

// 使用REQUIRES_NEW传播行为,创建新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOperation(String operation, String username) {
System.out.println("LogService.logOperation - 开始");
try {
logDao.addLog(operation, username);
} finally {
System.out.println("LogService.logOperation - 结束");
}
}
}

// 3. 测试类
public class TransactionTest {
@Autowired
private UserService userService;

@Test
public void testTransactionPropagation() {
// 测试正常情况
userService.addUser("admin", "123456");
System.out.println("=================================");
// 测试异常情况
try {
userService.addUser("error", "123456");
} catch (Exception e) {
System.out.println("测试异常情况 - 捕获异常: " + e.getMessage());
}
}
}

执行结果分析

  1. 当调用userService.addUser("admin", "123456")时:

    • UserService.addUser在事务中执行
    • LogService.logOperation创建新事务执行
    • 两个事务都成功提交
  2. 当调用userService.addUser("error", "123456")时:

    • UserService.addUser在事务中执行
    • LogService.logOperation创建新事务执行并成功提交
    • UserService.addUser抛出异常,事务回滚
    • 结果:用户数据回滚,但日志数据已提交

事务传播行为的使用场景

传播行为 典型使用场景
REQUIRED 核心业务逻辑,如用户注册、订单创建
SUPPORTS 查询操作,如获取用户信息
MANDATORY 必须在事务中执行的操作,如资金转账
REQUIRES_NEW 日志记录、审计操作
NOT_SUPPORTED 批量数据导入、报表生成
NEVER 特殊系统操作,如系统初始化
NESTED 批量操作,如批量导入数据,部分失败不影响整体

注意事项

  • 事务传播行为只在方法调用时生效,同一类内部的方法调用不会触发事务传播行为(需要使用AOP代理)
  • 不同的事务传播行为适用于不同的场景,需要根据具体情况选择合适的传播行为
  • 嵌套事务(NESTED)需要数据库支持保存点(Savepoint)功能
  • 使用REQUIRES_NEW时,新事务与原事务完全独立,原事务的回滚不会影响新事务
  • 使用NESTED时,嵌套事务是原事务的一部分,原事务的回滚会影响嵌套事务

2. Spring Boot

2.1 什么是Spring Boot?它的核心功能是什么?

Spring Boot是一个基于Spring框架的快速开发框架,它简化了Spring应用的初始化和开发过程,使开发者能够快速构建生产级别的应用。Spring Boot的设计理念是”约定优于配置”,通过提供合理的默认值和自动配置,减少了开发者的配置工作。

核心功能

功能 描述 优势
自动配置(Auto-configuration) 根据项目的依赖自动配置应用 减少XML配置,提高开发效率
起步依赖(Starter Dependencies) 提供预配置的依赖集合 简化依赖管理,避免版本冲突
内嵌容器(Embedded Containers) 内置Tomcat、Jetty、Undertow等Web容器 无需外部容器,直接以JAR包运行
监控(Actuator) 提供应用监控和管理功能 实时查看应用状态,便于运维
无代码生成 基于注解和约定优于配置 减少样板代码,提高可读性
外部化配置 支持多种配置方式 环境隔离,配置灵活
生产就绪 提供健康检查、安全等生产功能 确保应用在生产环境稳定运行
测试支持 提供强大的测试框架 提高代码质量,确保功能正确

详细解释

1. 自动配置(Auto-configuration)

  • 根据项目的依赖自动配置应用
  • 减少了传统Spring应用中大量的XML配置
  • 可以通过application.propertiesapplication.yml文件自定义配置
  • 示例:如果项目依赖了spring-boot-starter-web,Spring Boot会自动配置Tomcat、DispatcherServlet等组件

2. 起步依赖(Starter Dependencies)

  • 提供了一系列预配置的依赖集合,简化了依赖管理
  • 每个起步依赖都针对特定的应用场景,如Web开发、数据访问等
  • 自动处理依赖的版本冲突,确保依赖的兼容性
  • 示例:spring-boot-starter-web包含了Web开发所需的所有依赖

3. 内嵌容器(Embedded Containers)

  • 内置了Tomcat、Jetty、Undertow等Web容器
  • 不需要外部部署容器,应用可以直接以JAR包的形式运行
  • 简化了应用的部署和运行过程
  • 可以通过配置文件自定义容器的参数

4. 监控(Actuator)

  • 提供了应用监控和管理功能
  • 可以查看应用的健康状态、性能指标、环境信息等
  • 支持通过HTTP、JMX等方式访问监控端点
  • 可以自定义监控指标和端点

5. 无代码生成(No Code Generation)

  • 不需要生成代码或XML配置
  • 基于注解和约定优于配置的原则
  • 减少了样板代码,提高了开发效率

6. 外部化配置(Externalized Configuration)

  • 支持多种配置方式,如属性文件、环境变量、命令行参数等
  • 可以根据不同的环境(开发、测试、生产)使用不同的配置
  • 支持配置的热更新

7. 生产就绪(Production Ready)

  • 提供了健康检查、安全、监控等生产环境所需的功能
  • 支持优雅停机、应用指标收集等
  • 可以与云平台无缝集成

8. 测试支持(Testing Support)

  • 提供了强大的测试框架,支持单元测试和集成测试
  • 内置了多种测试注解和工具
  • 支持测试环境的自动配置

Spring Boot的优势

  • 快速开发:简化了应用的初始化和配置过程
  • 开箱即用:提供了大量的预配置组件
  • 减少样板代码:基于注解和约定优于配置的原则
  • 微服务友好:适合构建微服务架构
  • 社区活跃:拥有庞大的社区支持和丰富的文档
  • 易于部署:可以打包为可执行JAR包,简化部署流程
  • 监控完善:内置Actuator,提供全面的监控功能

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 1. Spring Boot应用的主类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

// 2. 控制器
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/users")
public List<User> getUsers() {
// 业务逻辑
return Arrays.asList(new User(1, "admin"), new User(2, "user"));
}
}

// 3. 用户实体类
public class User {
private int id;
private String name;

public User(int id, String name) {
this.id = id;
this.name = name;
}

// getter和setter
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}

// 4. 配置文件(application.yml)
server:
port: 8080
servlet:
context-path: /app

spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver

jpa:
hibernate:
ddl-auto: update
show-sql: true

// 5. 测试类
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;

@Test
public void testGetUsers() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/users"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(2));
}
}

Spring Boot的应用场景

  • Web应用:构建RESTful API、传统Web应用等
  • 微服务:作为微服务架构的基础框架
  • 批处理:处理大量数据的批处理任务
  • 云原生应用:与云平台无缝集成
  • IoT应用:构建物联网应用
  • 命令行应用:构建命令行工具
  • 企业应用:快速开发企业级应用
  • 移动后端:为移动应用提供后端服务

2.2 Spring Boot的启动流程?

Spring Boot的启动流程是一个复杂的过程,涉及多个步骤和组件的初始化。以下是详细的启动流程:

1. 执行main方法

  • 调用SpringApplication.run()方法,这是Spring Boot应用的入口点
  • 创建SpringApplication实例,初始化必要的组件

2. 初始化SpringApplication

  • 配置资源加载器(ResourceLoader
  • 确定应用类型(Web应用或非Web应用)
  • 加载初始化器(ApplicationContextInitializer
  • 加载监听器(ApplicationListener
  • 确定主类(通过堆栈跟踪找到main方法所在的类)

3. 调用run方法

  • 启动计时器,记录启动时间
  • 创建并配置Environment(环境)
  • 打印Banner(可以通过spring.banner.location自定义)
  • 创建ApplicationContext(应用上下文)
  • 刷新上下文
  • 执行CommandLineRunner和ApplicationRunner
  • 启动完成,打印启动时间

4. 准备环境(Environment)

  • 加载系统属性(System Properties)
  • 加载环境变量(Environment Variables)
  • 加载配置文件(application.properties/yml,支持多环境配置)
  • 加载命令行参数
  • 解析Profiles(如dev、test、prod)

5. 创建ApplicationContext

  • 根据应用类型创建相应的ApplicationContext
    • Web应用(Servlet):AnnotationConfigServletWebServerApplicationContext
    • Web应用(Reactive):AnnotationConfigReactiveWebServerApplicationContext
    • 非Web应用:AnnotationConfigApplicationContext
  • 应用初始化器(ApplicationContextInitializer
  • 注册监听器(ApplicationListener

6. 刷新上下文(refresh)

  • 准备刷新上下文
  • 加载Bean定义(通过@ComponentScan扫描)
  • 处理BeanFactoryPostProcessor(如@Configuration类的处理)
  • 注册BeanPostProcessor(用于Bean的增强)
  • 初始化事件发布器
  • 注册监听器
  • 实例化单例Bean(非延迟加载的)
  • 完成刷新

7. 启动内嵌容器

  • 如果是Web应用,创建并启动内嵌容器(Tomcat、Jetty、Undertow等)
  • 注册Servlet、Filter、Listener等
  • 启动容器

8. 执行CommandLineRunner和ApplicationRunner

  • 按照顺序执行所有实现了CommandLineRunnerApplicationRunner接口的Bean
  • 这些Runner可以在应用启动后执行一些初始化任务
  • CommandLineRunner接收原始字符串参数,ApplicationRunner接收ApplicationArguments对象

9. 应用启动完成

  • 打印启动成功信息
  • 应用开始接收请求

启动流程的关键组件

组件 职责 重要性
SpringApplication 协调应用启动过程 核心
Environment 管理配置信息 重要
ApplicationContext 管理Bean生命周期 核心
EmbeddedWebServer 处理HTTP请求 Web应用必需
CommandLineRunner/ApplicationRunner 执行初始化任务 扩展点

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 1. Spring Boot应用主类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// 启动Spring Boot应用
SpringApplication.run(Application.class, args);
}
}

// 2. 实现CommandLineRunner接口
@Component
@Order(1) // 执行顺序
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner executed with args: " + Arrays.toString(args));
// 执行初始化任务,如加载缓存、初始化数据等
}
}

// 3. 实现ApplicationRunner接口
@Component
@Order(2) // 执行顺序
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner executed with args: " + args.getOptionNames());
System.out.println("Non-option args: " + args.getNonOptionArgs());
// 执行初始化任务
}
}

// 4. 实现ApplicationContextInitializer
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("ApplicationContextInitializer executed");
// 初始化上下文,如添加属性源
}
}

// 5. 实现ApplicationListener
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
System.out.println("ApplicationListener executed: ApplicationStartedEvent");
// 监听应用启动事件
}
}

启动流程的扩展点

扩展点 执行时机 用途
ApplicationContextInitializer ApplicationContext创建前 初始化上下文
ApplicationListener 监听应用事件 响应应用事件
CommandLineRunner 应用启动后 执行初始化任务
ApplicationRunner 应用启动后 执行初始化任务(更丰富的参数)
@PostConstruct Bean初始化后 初始化Bean
SmartInitializingSingleton 所有单例Bean初始化完成后 执行全局初始化
BeanPostProcessor Bean创建前后 增强Bean
BeanFactoryPostProcessor Bean定义加载后 修改Bean定义

启动流程的事件

  • ApplicationStartingEvent:应用启动开始时触发
  • ApplicationEnvironmentPreparedEvent:环境准备完成时触发
  • ApplicationContextInitializedEvent:ApplicationContext初始化完成时触发
  • ApplicationPreparedEvent:ApplicationContext准备完成时触发
  • ApplicationStartedEvent:应用启动完成时触发
  • ApplicationReadyEvent:应用就绪时触发
  • ApplicationFailedEvent:应用启动失败时触发

注意事项

  • 启动流程中的任何步骤失败都会导致应用启动失败
  • 可以通过spring.main.*配置项自定义启动行为
  • 可以通过@SpringBootApplication的属性自定义组件扫描和自动配置行为
  • 可以通过SpringApplicationBuilder自定义SpringApplication的配置
  • 启动时间过长可能是因为Bean初始化耗时或自动配置过多,可以通过分析启动日志和使用Spring Boot Actuator进行优化

2.3 Spring Boot的自动配置原理?

Spring Boot的自动配置是其核心特性之一,它根据项目的依赖和配置,自动配置应用所需的组件,减少了开发者的配置工作。Spring Boot的自动配置基于”约定优于配置”的原则,通过提供合理的默认值和条件配置,使得开发者可以专注于业务逻辑的实现。

自动配置的原理

1. 基于条件注解(@Conditional)

  • Spring Boot使用条件注解来决定是否应用某个配置
  • 条件注解包括:@ConditionalOnClass@ConditionalOnMissingClass@ConditionalOnBean@ConditionalOnMissingBean@ConditionalOnProperty
  • 这些注解可以根据类的存在、Bean的存在、属性的设置等条件来决定是否应用配置

2. 扫描META-INF/spring.factories文件

  • Spring Boot在启动时会扫描classpath下所有的META-INF/spring.factories文件
  • 这些文件中定义了自动配置类的全限定名
  • Spring Boot会加载这些自动配置类,并根据条件注解决定是否应用

3. 自动配置的加载过程

  • 启动Spring Boot应用时,@SpringBootApplication注解会开启自动配置(通过@EnableAutoConfiguration
  • @EnableAutoConfiguration会导入AutoConfigurationImportSelector
  • AutoConfigurationImportSelector会扫描META-INF/spring.factories文件,加载自动配置类
  • 对每个自动配置类,检查其条件注解是否满足
  • 如果条件满足,应用该自动配置类

4. 自动配置的优先级

  • 自动配置类的优先级低于用户自定义的配置
  • 用户可以通过@Primary注解或配置文件覆盖自动配置
  • 可以通过@EnableAutoConfiguration(exclude = {...})排除某些自动配置
  • 可以通过spring.autoconfigure.exclude属性在配置文件中排除自动配置

5. 条件注解的使用

条件注解 作用 示例 应用场景
@ConditionalOnClass 当类存在时生效 @ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet") 当依赖了某个库时应用配置
@ConditionalOnMissingClass 当类不存在时生效 @ConditionalOnMissingClass(name = "org.springframework.web.servlet.DispatcherServlet") 当未依赖某个库时应用配置
@ConditionalOnBean 当Bean存在时生效 @ConditionalOnBean(name = "dataSource") 当某个Bean已存在时应用配置
@ConditionalOnMissingBean 当Bean不存在时生效 @ConditionalOnMissingBean(name = "dataSource") 当某个Bean不存在时应用配置
@ConditionalOnProperty 当属性满足条件时生效 @ConditionalOnProperty(name = "spring.datasource.enabled", havingValue = "true") 根据配置属性决定是否应用配置
@ConditionalOnWebApplication 当是Web应用时生效 @ConditionalOnWebApplication 只在Web应用中应用配置
@ConditionalOnNotWebApplication 当不是Web应用时生效 @ConditionalOnNotWebApplication 只在非Web应用中应用配置
@ConditionalOnExpression 当SpEL表达式为true时生效 @ConditionalOnExpression("${spring.datasource.enabled:false}") 根据复杂的表达式决定是否应用配置
@ConditionalOnResource 当资源存在时生效 @ConditionalOnResource(resources = "classpath:application.properties") 当某个资源存在时应用配置
@ConditionalOnJndi 当JNDI存在时生效 @ConditionalOnJndi("java:comp/env/jdbc/dataSource") 当JNDI资源存在时应用配置

6. 自动配置的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 1. 自动配置类示例
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnMissingBean(RestTemplate.class)
@EnableConfigurationProperties(RestTemplateProperties.class)
public class RestTemplateAutoConfiguration {

private final RestTemplateProperties properties;

public RestTemplateAutoConfiguration(RestTemplateProperties properties) {
this.properties = properties;
}

@Bean
@ConditionalOnMissingBean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
if (properties.getConnectTimeout() != null) {
builder.setConnectTimeout(properties.getConnectTimeout());
}
if (properties.getReadTimeout() != null) {
builder.setReadTimeout(properties.getReadTimeout());
}
return builder.build();
}

@Bean
@ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder();
}
}

// 2. 配置属性类
@ConfigurationProperties(prefix = "spring.rest-template")
public class RestTemplateProperties {
private Duration connectTimeout;
private Duration readTimeout;

// getter和setter
public Duration getConnectTimeout() { return connectTimeout; }
public void setConnectTimeout(Duration connectTimeout) { this.connectTimeout = connectTimeout; }
public Duration getReadTimeout() { return readTimeout; }
public void setReadTimeout(Duration readTimeout) { this.readTimeout = readTimeout; }
}

// 3. 在META-INF/spring.factories文件中注册
// org.springframework.boot.autoconfigure.EnableAutoConfiguration=
// com.example.autoconfigure.RestTemplateAutoConfiguration

7. 自定义自动配置

步骤

  1. 创建一个配置类,使用@Configuration注解
  2. 使用条件注解来控制配置的应用条件
  3. 创建配置属性类,使用@ConfigurationProperties注解
  4. META-INF/spring.factories文件中注册自动配置类
  5. 打包成jar包,供其他项目依赖

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 1. 自定义服务接口
public interface MyService {
String sayHello();
}

// 2. 自定义服务实现
public class MyServiceImpl implements MyService {
private final String message;

public MyServiceImpl(String message) {
this.message = message;
}

@Override
public String sayHello() {
return message;
}
}

// 3. 配置属性类
@ConfigurationProperties(prefix = "my.service")
public class MyServiceProperties {
private String message = "Hello, World!";

// getter和setter
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}

// 4. 自定义自动配置类
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyServiceAutoConfiguration {

private final MyServiceProperties properties;

public MyServiceAutoConfiguration(MyServiceProperties properties) {
this.properties = properties;
}

@Bean
@ConditionalOnMissingBean
public MyService myService() {
return new MyServiceImpl(properties.getMessage());
}
}

// 5. META-INF/spring.factories文件
// org.springframework.boot.autoconfigure.EnableAutoConfiguration=
// com.example.autoconfigure.MyServiceAutoConfiguration

8. 自动配置的最佳实践

  • 只在必要时使用自动配置:避免过度使用自动配置,只在确实能简化配置的场景下使用
  • 为自动配置提供合理的默认值:确保默认配置能够满足大多数场景的需求
  • 允许用户通过配置文件覆盖默认配置:通过@ConfigurationProperties提供配置选项
  • 使用条件注解来确保自动配置只在合适的条件下应用:避免在不适合的场景下应用配置
  • 提供清晰的文档:说明自动配置的行为和配置选项
  • 遵循Spring Boot的命名约定:自动配置类通常以AutoConfiguration结尾
  • 使用@Order注解控制自动配置的顺序:确保自动配置按正确的顺序应用

9. 查看自动配置的状态

  • 启动日志:通过启动时的日志查看自动配置的应用情况
  • Actuator端点:使用spring-boot-actuator/actuator/conditions端点查看自动配置的条件评估结果
  • Debug模式:使用--debug参数启动应用,查看详细的自动配置日志
  • spring-boot-starter-test:使用@SpringBootTestAutoConfigurationImportedEvent测试自动配置

10. 自动配置的原理流程图

  1. 启动应用 → 2. @SpringBootApplication → 3. @EnableAutoConfiguration → 4. AutoConfigurationImportSelector → 5. 扫描META-INF/spring.factories → 6. 加载自动配置类 → 7. 检查条件注解 → 8. 应用满足条件的配置 → 9. 完成自动配置

注意事项

  • 自动配置是基于约定优于配置的原则
  • 自动配置的优先级低于用户自定义的配置
  • 可以通过@EnableAutoConfiguration(exclude = {...})排除不需要的自动配置
  • 可以通过spring.autoconfigure.exclude属性在配置文件中排除自动配置
  • 自动配置类通常位于org.springframework.boot.autoconfigure包下
  • 自动配置的顺序很重要,有些自动配置依赖于其他自动配置的结果
  • 可以通过@AutoConfigureBefore@AutoConfigureAfter@AutoConfigureOrder注解控制自动配置的顺序

2.4 Spring Boot的配置方式有哪些?

Spring Boot提供了多种配置方式,以满足不同场景的需求。以下是Spring Boot的主要配置方式:

1. 配置文件

(1) application.properties文件

  • 基于键值对的配置文件格式
  • 位于src/main/resources目录下
  • 支持@占位符引用其他配置
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 服务器配置
    server.port=8080
    server.servlet.context-path=/app

    # 数据源配置
    spring.datasource.url=jdbc:mysql://localhost:3306/test
    spring.datasource.username=root
    spring.datasource.password=password

    # 引用其他配置
    my.app.name=My Application
    my.app.version=1.0.0
    my.app.full-name=${my.app.name} v${my.app.version}

(2) application.yml文件

  • 基于YAML格式的配置文件,更加简洁易读
  • 位于src/main/resources目录下
  • 支持缩进和嵌套结构
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    server:
    port: 8080
    servlet:
    context-path: /app

    spring:
    datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: password
    jpa:
    hibernate:
    ddl-auto: update
    show-sql: true

    my:
    app:
    name: My Application
    version: 1.0.0
    full-name: ${my.app.name} v${my.app.version}

(3) 多环境配置文件

  • 可以为不同环境创建不同的配置文件
  • 命名格式:application-{profile}.properties/yml
  • 示例:
    • application-dev.yml:开发环境配置
    • application-test.yml:测试环境配置
    • application-prod.yml:生产环境配置
  • 通过spring.profiles.active属性激活对应环境的配置
  • 可以同时激活多个环境,如spring.profiles.active=dev,local

(4) 外部配置文件

  • 可以将配置文件放在应用外部,如config目录或指定路径
  • 优先级:外部配置文件 > 应用内配置文件
  • 可以通过spring.config.location指定配置文件位置

2. 命令行参数

  • 在启动应用时通过命令行参数指定配置
  • 格式:--key=value
  • 示例:
    1
    java -jar myapp.jar --server.port=8080 --spring.profiles.active=prod
  • 命令行参数的优先级最高,可以覆盖其他配置

3. 环境变量

  • 通过操作系统的环境变量进行配置
  • Spring Boot会自动将环境变量转换为配置属性
  • 格式:环境变量名使用大写,下划线替换点,前缀为SPRING_
  • 示例:
    • 环境变量SERVER_PORT对应配置属性server.port
    • 环境变量SPRING_DATASOURCE_URL对应配置属性spring.datasource.url
  • 在Docker容器中特别有用

4. 系统属性

  • 通过JVM系统属性进行配置
  • 格式:-Dkey=value
  • 示例:
    1
    java -Dserver.port=8080 -Dspring.profiles.active=prod -jar myapp.jar
  • 系统属性的优先级高于环境变量

5. 注解配置

(1) @Value注解

  • 用于注入单个配置属性
  • 支持SpEL表达式
  • 支持默认值
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Component
    public class MyComponent {
    @Value("${server.port}")
    private int port;

    @Value("${spring.datasource.url}")
    private String datasourceUrl;

    @Value("${my.app.name:Default App}")
    private String appName;

    @Value("#{T(java.lang.Math).random() * 100}")
    private double randomNumber;
    }

(2) @ConfigurationProperties注解

  • 用于将一组相关的配置属性绑定到一个Java类
  • 支持属性验证、嵌套属性和松散绑定
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @ConfigurationProperties(prefix = "spring.datasource")
    public class DataSourceProperties {
    private String url;
    private String username;
    private String password;
    private String driverClassName;

    // getter和setter
    public String getUrl() { return url; }
    public void setUrl(String url) { this.url = url; }
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
    public String getDriverClassName() { return driverClassName; }
    public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; }
    }

6. 配置中心

  • 使用外部配置中心,如Spring Cloud Config、Apollo、Nacos等
  • 集中管理配置,支持动态更新
  • 适用于微服务架构
  • 支持配置的版本管理和环境隔离

7. 编程方式配置

  • 通过Java代码进行配置
  • 实现EnvironmentPostProcessor接口
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    Map<String, Object> properties = new HashMap<>();
    properties.put("my.custom.property", "value");
    properties.put("server.port", 8081);
    environment.getPropertySources().addLast(new MapPropertySource("myProperties", properties));
    }
    }

    // 需要在META-INF/spring.factories中注册
    // org.springframework.boot.env.EnvironmentPostProcessor=
    // com.example.MyEnvironmentPostProcessor

8. 配置优先级

Spring Boot的配置优先级从高到低依次为:

  1. 命令行参数
  2. 系统属性 (-D参数)
  3. 环境变量
  4. 应用内的application-{profile}.properties/yml文件
  5. 应用内的application.properties/yml文件
  6. 外部的application-{profile}.properties/yml文件
  7. 外部的application.properties/yml文件
  8. 默认配置(Spring Boot内置的默认值)

9. 配置的使用

(1) 注入配置

  • 使用@Value注解注入单个配置
  • 使用@ConfigurationProperties注解绑定配置类
  • 使用Environment接口获取配置

(2) 配置的热更新

  • 开发环境下,使用spring-boot-devtools可以实现配置的热更新
  • 生产环境下,可以使用Spring Cloud Config或其他配置中心实现配置的动态更新
  • 对于@ConfigurationProperties绑定的配置类,可以添加@RefreshScope注解实现热更新

(3) 配置的验证

  • 使用@Validated和JSR-303注解进行配置验证
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @ConfigurationProperties(prefix = "my.app")
    @Validated
    public class MyAppProperties {
    @NotNull
    private String name;

    @Min(1)
    @Max(100)
    private int maxUsers;

    @Pattern(regexp = "^[a-zA-Z0-9]+$")
    private String apiKey;

    // getter和setter
    }

10. 配置的最佳实践

  • 使用YAML格式:YAML格式更加简洁易读,支持嵌套结构
  • 使用多环境配置:为不同环境创建不同的配置文件
  • 使用配置属性类:通过@ConfigurationProperties将配置绑定到Java类,提高类型安全性
  • 使用配置验证:添加验证规则,确保配置的正确性
  • 避免硬编码:将所有配置项外部化,便于维护
  • 使用配置中心:在微服务架构中使用配置中心集中管理配置
  • 配置加密:对于敏感配置(如密码、API密钥),使用加密工具进行加密
  • 配置文档:为配置项添加注释,说明其用途和默认值

11. 配置示例

完整的application.yml配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 服务器配置
server:
port: 8080
servlet:
context-path: /app
tomcat:
max-threads: 200
min-spare-threads: 10

# 数据源配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
# 缓存配置
cache:
type: redis
# 消息队列配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest

# 自定义配置
my:
app:
name: My Application
version: 1.0.0
description: This is a Spring Boot application
features:
- feature1
- feature2
- feature3
config:
timeout: 30
retry: 3

# 日志配置
logging:
level:
root: info
com.example: debug
file:
name: myapp.log

使用@ConfigurationProperties的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 1. 定义配置属性类
@ConfigurationProperties(prefix = "my.app")
@Validated
public class MyAppProperties {
private String name;
private String version;
private String description;
private List<String> features;
private Config config;

// 内部配置类
public static class Config {
private int timeout;
private int retry;

// getter和setter
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
public int getRetry() { return retry; }
public void setRetry(int retry) { this.retry = retry; }
}

// getter和setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public List<String> getFeatures() { return features; }
public void setFeatures(List<String> features) { this.features = features; }
public Config getConfig() { return config; }
public void setConfig(Config config) { this.config = config; }
}

// 2. 启用配置属性
@SpringBootApplication
@EnableConfigurationProperties(MyAppProperties.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

// 3. 使用配置
@Service
public class MyService {
private final MyAppProperties properties;

public MyService(MyAppProperties properties) {
this.properties = properties;
}

public void printConfig() {
System.out.println("App Name: " + properties.getName());
System.out.println("App Version: " + properties.getVersion());
System.out.println("Features: " + properties.getFeatures());
System.out.println("Timeout: " + properties.getConfig().getTimeout());
}
}

2.5 Spring Boot的核心注解有哪些?

Spring Boot提供了一系列核心注解,用于简化应用的开发和配置。以下是Spring Boot的主要核心注解:

1. @SpringBootApplication

  • 作用:Spring Boot应用的核心注解,是一个组合注解
  • 包含的注解
    • @Configuration:标记类为配置类
    • @EnableAutoConfiguration:开启自动配置
    • @ComponentScan:扫描组件
  • 属性
    • exclude:排除指定的自动配置类
    • excludeName:排除指定名称的自动配置类
    • scanBasePackages:指定组件扫描的包路径
    • scanBasePackageClasses:指定组件扫描的类所在的包
  • 使用场景:应用的主类上,标识这是一个Spring Boot应用
  • 示例
    1
    2
    3
    4
    5
    6
    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    public class Application {
    public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
    }
    }

2. @Configuration

  • 作用:标记类为配置类,替代XML配置文件
  • 使用场景:定义Bean配置的类上
  • 示例
    1
    2
    3
    4
    5
    6
    7
    @Configuration
    public class AppConfig {
    @Bean
    public UserService userService() {
    return new UserServiceImpl();
    }
    }

3. @EnableAutoConfiguration

  • 作用:开启Spring Boot的自动配置功能
  • 使用场景:通常与@Configuration一起使用,或通过@SpringBootApplication间接使用
  • 属性
    • exclude:排除指定的自动配置类
    • excludeName:排除指定名称的自动配置类
  • 示例
    1
    2
    3
    4
    5
    @Configuration
    @EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
    public class AppConfig {
    // 配置内容
    }

4. @ComponentScan

  • 作用:扫描指定包下的组件,包括@Component@Service@Repository@Controller
  • 属性
    • basePackages:指定扫描的包路径
    • basePackageClasses:指定扫描的类所在的包
    • excludeFilters:排除指定的组件
    • includeFilters:包含指定的组件
  • 使用场景:需要自定义组件扫描范围时
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration
    @ComponentScan(
    basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)
    )
    public class AppConfig {
    // 配置内容
    }

5. @RestController

  • 作用:标记类为REST控制器,返回JSON格式的响应
  • 包含的注解@Controller@ResponseBody
  • 使用场景:RESTful API的控制器类上
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @RestController
    @RequestMapping("/api")
    public class UserController {
    @GetMapping("/users")
    public List<User> getUsers() {
    // 业务逻辑
    return Arrays.asList(new User(1, "admin"), new User(2, "user"));
    }
    }

6. @RequestMapping

  • 作用:映射HTTP请求路径到控制器方法
  • 属性
    • value:请求路径
    • method:HTTP方法(GET、POST、PUT、DELETE等)
    • params:请求参数
    • headers:请求头
    • consumes:请求媒体类型
    • produces:响应媒体类型
  • 使用场景:控制器类或方法上,用于定义请求路径
  • 简化注解
    • @GetMapping:处理GET请求
    • @PostMapping:处理POST请求
    • @PutMapping:处理PUT请求
    • @DeleteMapping:处理DELETE请求
    • @PatchMapping:处理PATCH请求
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @RestController
    @RequestMapping("/api")
    public class UserController {
    @GetMapping("/users")
    public List<User> getUsers() {
    // 业务逻辑
    return Arrays.asList(new User(1, "admin"), new User(2, "user"));
    }

    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
    // 业务逻辑
    return user;
    }
    }

7. @Autowired

  • 作用:自动注入依赖
  • 使用场景:字段、构造函数、setter方法上
  • 属性
    • required:是否必须注入,默认为true
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Service
    public class UserService {
    private final UserDao userDao;

    // 构造函数注入(推荐)
    @Autowired
    public UserService(UserDao userDao) {
    this.userDao = userDao;
    }

    // 业务逻辑
    }

8. @Bean

  • 作用:标记方法返回的对象为Spring Bean
  • 属性
    • name:Bean的名称
    • initMethod:初始化方法
    • destroyMethod:销毁方法
    • autowire:自动装配模式
  • 使用场景:配置类中的方法上,用于定义Bean
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Configuration
    public class AppConfig {
    @Bean(name = "dataSource", initMethod = "init", destroyMethod = "close")
    public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/test");
    dataSource.setUsername("root");
    dataSource.setPassword("password");
    return dataSource;
    }
    }

9. @Conditional

  • 作用:根据条件决定是否应用配置
  • 使用场景:配置类或Bean方法上,用于条件配置
  • 派生注解
    • @ConditionalOnClass:当类存在时生效
    • @ConditionalOnMissingClass:当类不存在时生效
    • @ConditionalOnBean:当Bean存在时生效
    • @ConditionalOnMissingBean:当Bean不存在时生效
    • @ConditionalOnProperty:当属性满足条件时生效
    • @ConditionalOnWebApplication:当是Web应用时生效
    • @ConditionalOnNotWebApplication:当不是Web应用时生效
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Configuration
    public class DatabaseConfig {
    @Bean
    @ConditionalOnProperty(name = "spring.datasource.enabled", havingValue = "true")
    public DataSource dataSource() {
    // 配置数据源
    return new DriverManagerDataSource();
    }
    }

10. @ConfigurationProperties

  • 作用:将配置文件中的属性绑定到Java类
  • 属性
    • prefix:配置属性的前缀
    • value:同prefix
    • ignoreInvalidFields:是否忽略无效字段
    • ignoreUnknownFields:是否忽略未知字段
  • 使用场景:配置类上,用于绑定配置属性
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    @ConfigurationProperties(prefix = "spring.datasource")
    public class DataSourceProperties {
    private String url;
    private String username;
    private String password;

    // getter和setter
    }

11. @EnableConfigurationProperties

  • 作用:启用配置属性类
  • 使用场景:配置类上,用于启用@ConfigurationProperties注解的类
  • 示例
    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableConfigurationProperties(DataSourceProperties.class)
    public class Application {
    public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
    }
    }

12. @Value

  • 作用:注入单个配置属性
  • 使用场景:字段、方法参数上,用于注入配置属性
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    @Component
    public class MyComponent {
    @Value("${server.port}")
    private int port;

    @Value("${my.app.name:Default App}")
    private String appName;
    }

13. @Profile

  • 作用:根据环境配置激活不同的Bean
  • 使用场景:类或方法上,用于环境特定的配置
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Configuration
    public class DatabaseConfig {
    @Bean
    @Profile("dev")
    public DataSource devDataSource() {
    // 开发环境数据源
    return new DriverManagerDataSource();
    }

    @Bean
    @Profile("prod")
    public DataSource prodDataSource() {
    // 生产环境数据源
    return new HikariDataSource();
    }
    }

14. @Transactional

  • 作用:标记方法或类为事务性的
  • 属性
    • value:事务管理器的名称
    • propagation:事务传播行为
    • isolation:事务隔离级别
    • timeout:事务超时时间
    • readOnly:是否为只读事务
    • rollbackFor:触发回滚的异常类
    • noRollbackFor:不触发回滚的异常类
  • 使用场景:服务层方法上,用于事务管理
  • 示例
    1
    2
    3
    4
    5
    6
    7
    @Service
    public class UserService {
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void createUser(User user) {
    // 业务逻辑
    }
    }

15. @ControllerAdvice

  • 作用:全局异常处理和全局数据绑定
  • 使用场景:异常处理类上,用于全局异常处理
  • 示例
    1
    2
    3
    4
    5
    6
    7
    @ControllerAdvice
    public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception e) {
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
    }
    }

16. @CrossOrigin

  • 作用:允许跨域请求
  • 属性
    • origins:允许的源
    • methods:允许的HTTP方法
    • allowedHeaders:允许的请求头
    • exposedHeaders:暴露的响应头
    • allowCredentials:是否允许凭证
    • maxAge:预检请求的缓存时间
  • 使用场景:控制器类或方法上,用于跨域请求处理
  • 示例
    1
    2
    3
    4
    5
    6
    @RestController
    @RequestMapping("/api")
    @CrossOrigin(origins = "*", maxAge = 3600)
    public class UserController {
    // 控制器方法
    }

17. @EnableAsync

  • 作用:启用异步方法支持
  • 使用场景:配置类上,用于启用异步方法
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Configuration
    @EnableAsync
    public class AsyncConfig {
    @Bean
    public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10);
    executor.setMaxPoolSize(20);
    executor.setQueueCapacity(30);
    executor.initialize();
    return executor;
    }
    }

18. @Async

  • 作用:标记方法为异步方法
  • 使用场景:方法上,用于异步执行
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    @Service
    public class AsyncService {
    @Async
    public CompletableFuture<String> asyncMethod() {
    // 异步执行的逻辑
    return CompletableFuture.completedFuture("Async result");
    }
    }

19. @EnableScheduling

  • 作用:启用定时任务支持
  • 使用场景:配置类上,用于启用定时任务
  • 示例
    1
    2
    3
    4
    5
    @Configuration
    @EnableScheduling
    public class SchedulingConfig {
    // 配置内容
    }

20. @Scheduled

  • 作用:标记方法为定时任务
  • 属性
    • cron:Cron表达式
    • fixedRate:固定速率执行
    • fixedDelay:固定延迟执行
    • initialDelay:初始延迟
  • 使用场景:方法上,用于定时任务
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    @Service
    public class ScheduledService {
    @Scheduled(fixedRate = 5000)
    public void scheduledTask() {
    // 定时执行的逻辑
    System.out.println("Scheduled task executed at: " + new Date());
    }
    }
    }
    
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    **17. @Scheduled**:
    - **作用**:标记方法为定时任务
    - **属性**:
    - `cron`:Cron表达式
    - `fixedRate`:固定速率执行
    - `fixedDelay`:固定延迟执行
    - **使用场景**:定时任务方法上
    - **示例**:
    ```java
    @Component
    public class ScheduledTasks {
    @Scheduled(fixedRate = 10000) // 每10秒执行一次
    public void reportCurrentTime() {
    System.out.println("Current time: " + new Date());
    }
    }

18. @EnableAsync

  • 作用:开启Spring的异步方法支持
  • 使用场景:需要使用异步方法时
  • 示例
    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableAsync
    public class Application {
    public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
    }
    }

19. @Async

  • 作用:标记方法为异步方法
  • 使用场景:需要异步执行的方法上
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    @Service
    public class AsyncService {
    @Async
    public CompletableFuture<String> doSomething() {
    // 异步执行的任务
    return CompletableFuture.completedFuture("Done");
    }
    }

20. @CrossOrigin

  • 作用:允许跨域请求
  • 属性
    • origins:允许的源
    • methods:允许的HTTP方法
    • allowedHeaders:允许的请求头
  • 使用场景:需要处理跨域请求的控制器类或方法上
  • 示例
    1
    2
    3
    4
    5
    6
    @RestController
    @CrossOrigin(origins = "*")
    @RequestMapping("/api")
    public class UserController {
    // 控制器方法
    }

注意事项

  • 注解的使用应遵循Spring的最佳实践
  • 有些注解需要配合其他注解使用,如@EnableAsync@Async
  • 注解的属性应根据实际需求进行配置
  • 应理解每个注解的作用和适用场景,避免滥用

3. Spring Cloud

3.1 什么是Spring Cloud?它的核心组件有哪些?

Spring Cloud是Spring官方提供的微服务框架,它基于Spring Boot,提供了一套完整的微服务解决方案,包括服务注册与发现、负载均衡、熔断器、API网关、配置中心等组件。Spring Cloud的设计理念是”开箱即用”,通过提供一系列的starter依赖,简化了微服务的开发和部署。

Spring Cloud的核心组件

组件 作用 功能 适用场景
Eureka 服务注册与发现 服务注册、服务发现、健康检查 微服务架构中的服务管理
Ribbon 客户端负载均衡 负载均衡、服务调用 服务间调用的负载均衡
Hystrix 熔断器 服务降级、服务熔断、服务限流 防止服务雪崩
Feign 声明式REST客户端 简化服务调用、集成Ribbon和Hystrix 服务间调用的简化
Zuul/Gateway API网关 路由转发、过滤器、负载均衡 统一入口、权限控制
Config 配置中心 集中管理配置、动态刷新配置 多环境配置管理
Sleuth 分布式追踪 跟踪请求链路、性能监控 分布式系统的监控
Bus 消息总线 配置刷新、事件传播 配置中心的动态刷新

详细介绍

1. Eureka

  • 作用:服务注册与发现
  • 功能
    • 服务提供者将自己注册到Eureka Server
    • 服务消费者从Eureka Server获取服务列表
    • 支持服务的健康检查和自动剔除
    • 支持集群部署,提高可用性
  • 架构
    • Eureka Server:服务注册中心
    • Eureka Client:服务提供者和消费者
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // Eureka Server配置
    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaServerApplication {
    public static void main(String[] args) {
    SpringApplication.run(EurekaServerApplication.class, args);
    }
    }

    // 服务提供者配置
    @SpringBootApplication
    @EnableEurekaClient
    public class ProviderApplication {
    public static void main(String[] args) {
    SpringApplication.run(ProviderApplication.class, args);
    }
    }

    // 服务消费者配置
    @SpringBootApplication
    @EnableEurekaClient
    public class ConsumerApplication {
    public static void main(String[] args) {
    SpringApplication.run(ConsumerApplication.class, args);
    }
    }
    配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # Eureka Server配置
    server:
    port: 8761

    eureka:
    instance:
    hostname: localhost
    client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
    defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

    # 服务提供者配置
    eureka:
    client:
    service-url:
    defaultZone: http://localhost:8761/eureka/

    spring:
    application:
    name: service-provider

2. Ribbon

  • 作用:客户端负载均衡
  • 功能
    • 基于客户端的负载均衡
    • 支持多种负载均衡策略(轮询、随机、权重等)
    • 与Eureka集成,自动获取服务列表
    • 支持自定义负载均衡策略
  • 负载均衡策略
    • RoundRobinRule:轮询(默认)
    • RandomRule:随机
    • WeightedResponseTimeRule:基于响应时间加权
    • BestAvailableRule:选择并发量最小的
    • AvailabilityFilteringRule:过滤掉故障和并发量高的服务
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @RestController
    public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/hello")
    public String hello() {
    // 使用Ribbon进行负载均衡
    return restTemplate.getForObject("http://SERVICE-PROVIDER/hello", String.class);
    }
    }

    @Configuration
    public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
    return new RestTemplate();
    }

    // 自定义负载均衡策略
    @Bean
    public IRule ribbonRule() {
    return new RandomRule(); // 使用随机策略
    }
    }

3. Hystrix

  • 作用:熔断器,防止服务雪崩
  • 功能
    • 服务降级:当服务不可用时,返回默认值
    • 服务熔断:当服务失败率达到阈值时,自动熔断
    • 服务限流:限制服务的并发请求数
    • 监控面板:Hystrix Dashboard
    • 分布式系统的故障隔离
  • 工作原理
    • 当服务调用失败率超过阈值(默认50%)时,熔断器打开
    • 熔断器打开后,所有请求直接走降级逻辑
    • 经过一段时间(默认5秒)后,熔断器进入半开状态
    • 半开状态下,允许部分请求尝试调用服务
    • 如果调用成功,熔断器关闭;否则,继续保持打开状态
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    @Service
    public class UserService {
    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(
    fallbackMethod = "fallback",
    commandProperties = {
    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000")
    }
    )
    public String getUserInfo(String id) {
    return restTemplate.getForObject("http://USER-SERVICE/user/{id}", String.class, id);
    }

    public String fallback(String id) {
    return "Service unavailable, please try again later.";
    }
    }

    // 启用Hystrix Dashboard
    @SpringBootApplication
    @EnableHystrix
    @EnableHystrixDashboard
    public class HystrixDashboardApplication {
    public static void main(String[] args) {
    SpringApplication.run(HystrixDashboardApplication.class, args);
    }
    }

4. Feign

  • 作用:声明式REST客户端
  • 功能
    • 基于接口的注解方式定义服务调用
    • 自动集成Ribbon和Hystrix
    • 简化服务间的调用
    • 支持请求参数绑定、请求头设置等
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    @FeignClient(name = "USER-SERVICE", fallback = UserServiceFallback.class)
    public interface UserServiceClient {
    @GetMapping("/user/{id}")
    String getUserInfo(@PathVariable("id") String id);

    @PostMapping("/user")
    User createUser(@RequestBody User user);

    @GetMapping("/users")
    List<User> getUsers(@RequestParam("page") int page, @RequestParam("size") int size);
    }

    @Component
    public class UserServiceFallback implements UserServiceClient {
    @Override
    public String getUserInfo(String id) {
    return "Service unavailable, please try again later.";
    }

    @Override
    public User createUser(User user) {
    return null;
    }

    @Override
    public List<User> getUsers(int page, int size) {
    return Collections.emptyList();
    }
    }

    // 启用Feign
    @SpringBootApplication
    @EnableFeignClients
    public class FeignApplication {
    public static void main(String[] args) {
    SpringApplication.run(FeignApplication.class, args);
    }
    }

5. Zuul/Gateway

  • 作用:API网关
  • 功能
    • 路由转发:将请求转发到对应的服务
    • 过滤器:实现认证、授权、限流等功能
    • 负载均衡:集成Ribbon
    • 监控和日志记录
  • Zuul vs Gateway
    • Zuul 1.x:基于Servlet,同步阻塞
    • Gateway:基于WebFlux,异步非阻塞,性能更好
  • 示例(Gateway)
    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableGateway
    public class GatewayApplication {
    public static void main(String[] args) {
    SpringApplication.run(GatewayApplication.class, args);
    }
    }
    配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    spring:
    cloud:
    gateway:
    routes:
    - id: user-service
    uri: lb://USER-SERVICE
    predicates:
    - Path=/user/**
    filters:
    - AddRequestHeader=X-Request-Id, 12345
    - id: order-service
    uri: lb://ORDER-SERVICE
    predicates:
    - Path=/order/**

6. Config

  • 作用:配置中心
  • 功能
    • 集中管理配置
    • 支持多种配置源(Git、SVN、本地文件等)
    • 动态刷新配置
    • 环境隔离
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // Config Server配置
    @SpringBootApplication
    @EnableConfigServer
    public class ConfigServerApplication {
    public static void main(String[] args) {
    SpringApplication.run(ConfigServerApplication.class, args);
    }
    }

    // 客户端配置
    @SpringBootApplication
    public class ConfigClientApplication {
    public static void main(String[] args) {
    SpringApplication.run(ConfigClientApplication.class, args);
    }
    }
    配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # Config Server配置
    server:
    port: 8888

    spring:
    cloud:
    config:
    server:
    git:
    uri: https://github.com/example/config-repo
    search-paths: config

    # 客户端配置
    spring:
    cloud:
    config:
    uri: http://localhost:8888
    name: application
    profile: dev

7. Sleuth

  • 作用:分布式追踪
  • 功能
    • 跟踪请求链路
    • 性能监控
    • 与Zipkin集成,可视化追踪数据
  • 示例
    1
    2
    3
    4
    5
    6
    @SpringBootApplication
    public class SleuthApplication {
    public static void main(String[] args) {
    SpringApplication.run(SleuthApplication.class, args);
    }
    }
    配置文件
    1
    2
    3
    4
    5
    6
    spring:
    zipkin:
    base-url: http://localhost:9411
    sleuth:
    sampler:
    probability: 1.0 # 采样率,1.0表示全部采样

8. Bus

  • 作用:消息总线
  • 功能
    • 配置刷新
    • 事件传播
    • 与RabbitMQ或Kafka集成
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    @SpringBootApplication
    @EnableConfigServer
    @EnableBusRefresh
    public class ConfigServerApplication {
    public static void main(String[] args) {
    SpringApplication.run(ConfigServerApplication.class, args);
    }
    }

Spring Cloud的优势

  • 开箱即用:提供了一系列starter依赖,简化配置
  • 微服务生态:完整的微服务解决方案
  • 与Spring Boot集成:基于Spring Boot,开发体验一致
  • 社区活跃:拥有庞大的社区支持和丰富的文档
  • 可扩展性:支持自定义和扩展

Spring Cloud的应用场景

  • 微服务架构:构建分布式微服务系统
  • 云原生应用:与云平台无缝集成
  • 大型企业应用:复杂的企业级应用
  • 微服务迁移:从单体应用迁移到微服务架构
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    **8. Sleuth**:
    - **作用**:分布式追踪
    - **功能**:
    - 跟踪请求的调用链
    - 收集服务调用的耗时信息
    - 与Zipkin集成,可视化调用链
    - **示例**:
    ```java
    @SpringBootApplication
    public class Application {
    public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
    }
    }

9. Stream

  • 作用:消息驱动
  • 功能
    • 简化消息的发送和接收
    • 支持多种消息中间件(Kafka、RabbitMQ等)
    • 提供统一的消息编程模型
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @EnableBinding(Source.class)
    public class MessageProducer {
    @Autowired
    private Source source;

    public void sendMessage(String message) {
    source.output().send(MessageBuilder.withPayload(message).build());
    }
    }

    @EnableBinding(Sink.class)
    public class MessageConsumer {
    @StreamListener(Sink.INPUT)
    public void receiveMessage(String message) {
    System.out.println("Received message: " + message);
    }
    }

10. Bus

  • 作用:消息总线
  • 功能
    • 用于在微服务之间传递消息
    • 支持配置的动态刷新
  • 示例
    1
    2
    3
    4
    5
    # 配置文件
    spring:
    cloud:
    bus:
    enabled: true

Spring Cloud的版本

  • Spring Cloud使用英文单词作为版本号,按字母顺序排列
  • 常见版本:
    • Hoxton
    • Greenwich
    • Finchley
    • Edgware
    • Dalston

Spring Cloud与Spring Boot的版本对应关系

Spring Cloud版本 Spring Boot版本
Hoxton 2.2.x, 2.3.x
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x

注意事项

  • Spring Cloud的组件在不断演进,一些组件可能会被替换或废弃
  • 在使用Spring Cloud时,应注意版本的兼容性
  • 不同的Spring Cloud版本可能有不同的组件和配置方式

3.2 什么是服务注册与发现?Spring Cloud中如何实现?

服务注册与发现是微服务架构中的重要概念,它解决了服务之间的动态发现和通信问题。

服务注册:服务提供者在启动时,将自己的服务信息(如服务名称、IP地址、端口等)注册到注册中心。

服务发现:服务消费者在需要调用其他服务时,从注册中心获取服务提供者的信息,然后进行调用。

Spring Cloud中通过Eureka实现服务注册与发现

1. Eureka的架构

  • Eureka Server:注册中心,负责管理服务提供者的信息
  • Eureka Client:服务提供者和服务消费者,负责注册服务和发现服务

2. Eureka Server的配置

步骤

  1. 添加依赖
  2. 配置application.yml
  3. 启用Eureka Server

示例

1
2
3
4
5
<!-- 添加依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
# application.yml配置
server:
port: 8761

eureka:
instance:
hostname: localhost
client:
register-with-eureka: false # 不向自己注册
fetch-registry: false # 不从自己获取服务列表
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
1
2
3
4
5
6
7
8
// 启用Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}

3. Eureka Client的配置

步骤

  1. 添加依赖
  2. 配置application.yml
  3. 启用Eureka Client

示例

1
2
3
4
5
<!-- 添加依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
# application.yml配置
spring:
application:
name: service-provider

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true # 优先使用IP地址注册
1
2
3
4
5
6
7
8
// 启用Eureka Client
@SpringBootApplication
@EnableEurekaClient
public class ServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceProviderApplication.class, args);
}
}

4. 服务调用

方式一:使用RestTemplate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;

@GetMapping("/hello")
public String hello() {
// 使用服务名称调用服务
return restTemplate.getForObject("http://SERVICE-PROVIDER/hello", String.class);
}
}

@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 启用负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

方式二:使用Feign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 定义Feign客户端
@FeignClient(name = "SERVICE-PROVIDER")
public interface ProviderClient {
@GetMapping("/hello")
String hello();
}

// 使用Feign客户端
@RestController
public class ConsumerController {
@Autowired
private ProviderClient providerClient;

@GetMapping("/hello")
public String hello() {
return providerClient.hello();
}
}

// 启用Feign
@SpringBootApplication
@EnableFeignClients
public class ServiceConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerApplication.class, args);
}
}

5. Eureka的特性

  • 服务注册:服务提供者将自己的信息注册到Eureka Server
  • 服务续约:服务提供者定期向Eureka Server发送心跳,证明自己还活着
  • 服务下线:服务关闭时,主动向Eureka Server发送下线请求
  • 服务剔除:Eureka Server定期清理没有续约的服务
  • 服务发现:服务消费者从Eureka Server获取服务列表
  • 自我保护机制:当网络分区发生时,Eureka Server会保留所有服务,避免误删

6. Eureka的高可用

  • 集群部署:部署多个Eureka Server,它们之间互相注册
  • 配置示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 第一个Eureka Server
server:
port: 8761

eureka:
instance:
hostname: eureka1
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka2:8762/eureka/

# 第二个Eureka Server
server:
port: 8762

eureka:
instance:
hostname: eureka2
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka1:8761/eureka/

7. 其他服务注册与发现组件

  • Consul:HashiCorp提供的服务注册与发现工具
  • Zookeeper:Apache提供的分布式协调服务,也可用于服务注册与发现
  • Nacos:阿里巴巴提供的服务注册与发现、配置中心

注意事项

  • Eureka Server需要部署多个实例,以保证高可用
  • 服务提供者需要设置合理的续约时间和过期时间
  • 服务消费者需要处理服务不可用的情况
  • 在生产环境中,应使用域名或负载均衡器访问Eureka Server

3.3 什么是负载均衡?Spring Cloud中如何实现?

负载均衡是一种分布式系统设计模式,它将请求分发到多个服务实例上,以提高系统的可用性、可靠性和性能。

负载均衡的作用

  • 提高系统可用性:当某个服务实例故障时,请求可以被分发到其他健康的实例上
  • 提高系统性能:通过并行处理请求,提高系统的处理能力
  • 实现水平扩展:通过增加服务实例的数量,线性提高系统的处理能力

Spring Cloud中通过Ribbon实现负载均衡

1. Ribbon的特点

  • 客户端负载均衡:在客户端进行负载均衡,不需要额外的负载均衡服务器
  • 与Eureka集成:自动从Eureka Server获取服务列表
  • 支持多种负载均衡策略:轮询、随机、权重、响应时间等
  • 可自定义负载均衡策略:可以根据业务需求自定义负载均衡策略

2. Ribbon的配置

步骤

  1. 添加依赖(Spring Cloud Netflix Ribbon)
  2. 配置RestTemplate或Feign
  3. 配置负载均衡策略

示例

1
2
3
4
5
<!-- 添加依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
// 配置RestTemplate并启用负载均衡
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

3. 负载均衡策略

策略名称 描述 适用场景
RoundRobinRule 轮询策略,依次分发请求 所有实例性能相近
RandomRule 随机策略,随机分发请求 所有实例性能相近
WeightedResponseTimeRule 权重响应时间策略,根据响应时间分配权重 实例性能差异较大
BestAvailableRule 最佳可用策略,选择并发请求数最少的实例 实例负载差异较大
AvailabilityFilteringRule 可用性过滤策略,过滤故障实例和并发请求数超过阈值的实例 有实例故障的场景
ZoneAvoidanceRule 区域避免策略,优先选择同一区域的实例 多区域部署

4. 配置负载均衡策略

方式一:全局配置

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class RibbonConfig {
@Bean
public IRule ribbonRule() {
// 使用随机策略
return new RandomRule();
// 使用权重响应时间策略
// return new WeightedResponseTimeRule();
// 使用最佳可用策略
// return new BestAvailableRule();
}
}

方式二:针对特定服务配置

1
2
3
4
# application.yml配置
SERVICE-PROVIDER:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

5. 使用RestTemplate进行负载均衡

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;

@GetMapping("/hello")
public String hello() {
// 使用服务名称调用服务,Ribbon会自动进行负载均衡
return restTemplate.getForObject("http://SERVICE-PROVIDER/hello", String.class);
}
}

6. 使用Feign进行负载均衡

Feign默认集成了Ribbon,所以不需要额外配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义Feign客户端
@FeignClient(name = "SERVICE-PROVIDER")
public interface ProviderClient {
@GetMapping("/hello")
String hello();
}

// 使用Feign客户端
@RestController
public class ConsumerController {
@Autowired
private ProviderClient providerClient;

@GetMapping("/hello")
public String hello() {
// Feign会自动进行负载均衡
return providerClient.hello();
}
}

7. Ribbon的核心组件

  • ILoadBalancer:负载均衡器的核心接口,定义了负载均衡的基本操作
  • IRule:负载均衡策略接口,定义了如何选择服务实例
  • IPing:服务实例健康检查接口,用于检查服务实例是否可用
  • ServerList:服务列表接口,用于获取服务实例列表
  • ServerListFilter:服务列表过滤器,用于过滤服务实例

8. 自定义负载均衡策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class CustomRule extends AbstractLoadBalancerRule {
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
List<Server> servers = lb.getReachableServers();
if (servers == null || servers.isEmpty()) {
return null;
}
// 自定义负载均衡逻辑
// 例如:根据服务实例的元数据进行选择
return servers.get(0);
}

@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// 初始化配置
}
}

// 配置自定义策略
@Configuration
public class RibbonConfig {
@Bean
public IRule ribbonRule() {
return new CustomRule();
}
}

9. 负载均衡的最佳实践

  • 选择合适的负载均衡策略:根据服务实例的特点选择合适的负载均衡策略
  • 配置合理的健康检查:确保只有健康的实例接收请求
  • 监控负载均衡状态:监控服务实例的负载情况,及时调整策略
  • 实现服务降级:当所有服务实例都不可用时,提供降级方案

10. 其他负载均衡方案

  • Nginx:服务器端负载均衡,功能强大,支持多种负载均衡策略
  • HAProxy:服务器端负载均衡,性能优异,支持TCP和HTTP协议
  • Kubernetes Service:基于Kubernetes的负载均衡,适用于容器化环境

注意事项

  • Ribbon是客户端负载均衡,需要在每个客户端配置
  • 负载均衡策略的选择应根据实际业务场景进行调整
  • 应定期检查服务实例的健康状态,确保负载均衡的有效性
  • 在高并发场景下,应合理配置连接池和超时时间

3.4 什么是熔断器?Spring Cloud中如何实现?

熔断器是一种容错机制,用于防止分布式系统中的级联失败。当服务调用失败率达到阈值时,熔断器会熔断服务调用,避免对故障服务的持续调用,从而保护系统的整体稳定性。

熔断器的作用

  • 防止级联失败:当某个服务故障时,避免其他服务对其持续调用,导致整个系统崩溃
  • 提供服务降级:当服务不可用时,返回默认值或备选方案,保证系统的可用性
  • 自我修复:当服务恢复正常时,熔断器会自动关闭,恢复正常的服务调用

Spring Cloud中通过Hystrix实现熔断器

1. Hystrix的特点

  • 服务降级:当服务不可用时,返回默认值
  • 服务熔断:当服务失败率达到阈值时,自动熔断
  • 服务限流:限制服务的并发请求数
  • 监控面板:Hystrix Dashboard,可视化监控服务状态
  • 请求缓存:缓存服务调用的结果
  • 请求合并:合并多个请求,减少网络开销

2. Hystrix的配置

步骤

  1. 添加依赖
  2. 启用Hystrix
  3. 配置熔断器

示例

1
2
3
4
5
<!-- 添加依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
1
2
3
4
5
6
7
8
// 启用Hystrix
@SpringBootApplication
@EnableCircuitBreaker
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

3. 使用Hystrix

方式一:使用@HystrixCommand注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class UserService {
@Autowired
private RestTemplate restTemplate;

@HystrixCommand(
fallbackMethod = "fallback",
commandProperties = {
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000")
}
)
public String getUserInfo(String id) {
return restTemplate.getForObject("http://USER-SERVICE/user/{id}", String.class, id);
}

public String fallback(String id) {
return "Service unavailable, please try again later.";
}
}

方式二:使用Feign集成Hystrix

1
2
3
4
# application.yml配置
feign:
hystrix:
enabled: true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义Feign客户端
@FeignClient(name = "USER-SERVICE", fallback = UserServiceFallback.class)
public interface UserServiceClient {
@GetMapping("/user/{id}")
String getUserInfo(@PathVariable("id") String id);
}

// 定义fallback类
@Component
public class UserServiceFallback implements UserServiceClient {
@Override
public String getUserInfo(String id) {
return "Service unavailable, please try again later.";
}
}

4. Hystrix的工作原理

  • 关闭状态:熔断器关闭,正常处理请求
  • 开启状态:当失败率达到阈值时,熔断器开启,直接执行fallback方法
  • 半开状态:经过一段时间后,熔断器进入半开状态,允许部分请求通过,检查服务是否恢复
  • 恢复关闭:如果半开状态下的请求成功,熔断器关闭;否则,重新进入开启状态

5. Hystrix的核心配置

配置项 描述 默认值
circuitBreaker.requestVolumeThreshold 熔断器触发的最小请求数 20
circuitBreaker.errorThresholdPercentage 熔断器触发的错误率阈值 50%
circuitBreaker.sleepWindowInMilliseconds 熔断器从开启到半开的时间窗口 5000ms
execution.isolation.thread.timeoutInMilliseconds 服务调用的超时时间 1000ms
execution.isolation.strategy 隔离策略(THREAD/SEMAPHORE) THREAD
coreSize 线程池大小 10

6. Hystrix Dashboard

步骤

  1. 添加依赖
  2. 启用Hystrix Dashboard
  3. 配置监控端点

示例

1
2
3
4
5
6
7
8
9
<!-- 添加依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
1
2
3
4
5
6
7
8
// 启用Hystrix Dashboard
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
1
2
3
4
5
6
# application.yml配置
management:
endpoints:
web:
exposure:
include: hystrix.stream

7. Turbine

  • 作用:聚合多个Hystrix流,在一个面板中监控多个服务
  • 配置示例
1
2
3
4
5
<!-- 添加依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
1
2
3
4
5
6
7
8
// 启用Turbine
@SpringBootApplication
@EnableTurbine
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}
1
2
3
4
5
# application.yml配置
turbine:
app-config: service-provider, service-consumer
cluster-name-expression: new String("default")
combine-host-port: true

8. Hystrix的最佳实践

  • 合理设置熔断阈值:根据服务的特性设置合适的请求数和错误率阈值
  • 提供有意义的fallback:fallback方法应返回有意义的默认值,而不是简单的错误信息
  • 监控熔断器状态:通过Hystrix Dashboard监控熔断器的状态,及时发现问题
  • 使用舱壁模式:为不同的服务调用创建不同的线程池,避免服务之间的影响
  • 合理设置超时时间:根据服务的响应时间设置合适的超时时间

9. 替代方案

  • Resilience4j:轻量级的熔断器实现,性能更好,API更简洁
  • Sentinel:阿里巴巴开源的流量控制、熔断降级框架,功能丰富

10. 服务降级的策略

  • 静态返回值:返回固定的默认值
  • 缓存返回值:返回缓存的旧值
  • 备选服务:调用备选服务
  • 模拟数据:生成模拟数据返回

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class UserService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private UserCache userCache;

@HystrixCommand(fallbackMethod = "fallback")
public String getUserInfo(String id) {
return restTemplate.getForObject("http://USER-SERVICE/user/{id}", String.class, id);
}

public String fallback(String id) {
// 尝试从缓存获取
String cachedUser = userCache.get(id);
if (cachedUser != null) {
return cachedUser;
}
// 返回默认值
return "{\"id\":\"" + id + "\",\"name\":\"Default User\",\"message\":\"Service unavailable\"}";
}
}

注意事项

  • Hystrix已经进入维护模式,推荐使用Resilience4j或Sentinel
  • 熔断器的配置应根据实际业务场景进行调整
  • 应避免在fallback方法中调用可能失败的服务
  • 应合理设置超时时间,避免服务调用等待时间过长
  • 应监控熔断器的状态,及时发现和解决问题

3.5 什么是API网关?Spring Cloud中如何实现?

API网关是微服务架构中的一个核心组件,它作为所有外部请求的统一入口,负责请求的路由、过滤、限流、认证等功能。

API网关的作用

  • 统一入口:所有外部请求都通过API网关进入系统
  • 路由转发:根据请求路径将请求转发到对应的微服务
  • 请求过滤:对请求进行验证、转换、日志记录等处理
  • 限流熔断:限制请求频率,防止系统过载
  • 认证授权:统一处理用户认证和授权
  • 监控统计:监控请求的响应时间、成功率等指标

Spring Cloud中实现API网关的方式

1. Zuul(第一代API网关)

特点

  • 基于Servlet实现,同步阻塞式架构
  • 功能丰富,支持多种过滤器
  • 与Spring Cloud生态集成良好

配置示例

1
2
3
4
5
<!-- 添加依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
1
2
3
4
5
6
7
8
// 启用Zuul
@SpringBootApplication
@EnableZuulProxy
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
1
2
3
4
5
6
7
8
9
10
11
# application.yml配置
zuul:
routes:
user-service:
path: /user/**
serviceId: user-service
order-service:
path: /order/**
serviceId: order-service
ignored-services: '*' # 忽略未配置的服务
prefix: /api # 统一前缀

2. Gateway(新一代API网关)

特点

  • 基于WebFlux实现,响应式非阻塞架构
  • 性能更好,支持高并发
  • 提供更丰富的路由规则
  • 支持过滤器链
  • 与Spring Cloud生态集成良好

配置示例

1
2
3
4
5
<!-- 添加依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
1
2
3
4
5
6
7
// 启用Gateway(不需要额外注解)
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

3. Gateway的配置方式

方式一:YAML配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# application.yml配置
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- StripPrefix=1
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallback
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- StripPrefix=1

方式二:Java代码配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user-service", r -> r.path("/user/**")
.filters(f -> f.stripPrefix(1)
.hystrix(config -> config.setName("fallbackcmd")
.setFallbackUri("forward:/fallback")))
.uri("lb://user-service"))
.route("order-service", r -> r.path("/order/**")
.filters(f -> f.stripPrefix(1))
.uri("lb://order-service"))
.build();
}
}

4. 过滤器

Zuul过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Component
public class AuthFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre"; // pre、route、post、error
}

@Override
public int filterOrder() {
return 0; // 过滤器顺序
}

@Override
public boolean shouldFilter() {
return true; // 是否执行该过滤器
}

@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();

// 验证token
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody("Unauthorized");
}

return null;
}
}

Gateway过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();

// 验证token
String token = request.getHeaders().getFirst("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}

return chain.filter(exchange);
}

@Override
public int getOrder() {
return 0; // 过滤器顺序
}
}

5. 限流配置

Gateway限流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# application.yml配置
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 每秒允许的请求数
redis-rate-limiter.burstCapacity: 20 # 允许的突发请求数
key-resolver: "#{@userKeyResolver}"
1
2
3
4
5
6
7
@Configuration
public class RateLimiterConfig {
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
}

6. 集成Spring Security

1
2
3
4
5
<!-- 添加依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
}

7. API网关的最佳实践

  • 统一路由管理:集中管理所有服务的路由规则
  • 合理使用过滤器:只添加必要的过滤器,避免性能损耗
  • 实现限流熔断:保护后端服务不被过载
  • 统一认证授权:在网关层处理认证和授权,简化服务端逻辑
  • 监控和日志:记录请求的详细信息,便于问题排查
  • 高可用部署:部署多个网关实例,使用负载均衡

8. API网关的部署架构

  • 单实例部署:适用于开发和测试环境
  • 集群部署:适用于生产环境,通过负载均衡器分发请求
  • 混合部署:将API网关部署在边缘节点,靠近用户

9. 替代方案

  • Kong:基于Nginx的API网关,功能丰富,支持插件扩展
  • Apigee:Google云提供的API管理平台
  • AWS API Gateway:AWS提供的托管API网关服务
  • Azure API Management:Azure提供的API管理服务

10. 注意事项

  • API网关是系统的入口,应确保其高可用性和性能
  • 应合理配置路由规则,避免路由冲突
  • 应实现有效的限流策略,防止系统过载
  • 应保护API网关免受攻击,如DDoS攻击
  • 应定期监控API网关的性能和健康状态

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 完整的Gateway配置示例
spring:
cloud:
gateway:
routes:
# 用户服务路由
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- StripPrefix=2
- name: Hystrix
args:
name: userFallback
fallbackUri: forward:/fallback/user
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@userKeyResolver}"

# 订单服务路由
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- StripPrefix=2
- name: Hystrix
args:
name: orderFallback
fallbackUri: forward:/fallback/order

# 静态资源路由
- id: static-resources
uri: http://localhost:8080
predicates:
- Path=/static/**

# 熔断降级配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000

# 服务发现配置
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/

总结

  • API网关是微服务架构中的重要组件,负责统一管理外部请求
  • Spring Cloud提供了Zuul和Gateway两种实现方式
  • Gateway是新一代API网关,基于响应式架构,性能更好
  • API网关应实现路由、过滤、限流、认证等功能
  • 应根据实际业务场景选择合适的API网关实现

4. MyBatis

4.1 什么是MyBatis?它的核心功能是什么?

MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集,是Java生态中最流行的持久层框架之一。

MyBatis的核心功能

功能 描述 特点
SQL映射 将SQL语句映射到Java方法 支持XML和注解两种配置方式,支持参数映射和结果映射
动态SQL 根据条件动态生成SQL语句 支持if、choose、foreach等标签,提高SQL的灵活性
高级映射 复杂对象的映射关系 支持一对一、一对多、多对多映射,支持延迟加载
缓存机制 提高查询性能 一级缓存(SqlSession级别)、二级缓存(SqlSessionFactory级别)
批量操作 批量处理数据 支持批量插入、更新、删除,提高操作效率
存储过程支持 调用数据库存储过程 支持获取存储过程的输出参数
类型处理器 Java类型与数据库类型的转换 支持自定义类型处理器
插件机制 扩展MyBatis功能 可以拦截Executor、StatementHandler等组件

详细介绍

1. SQL映射

  • XML配置:通过XML文件定义SQL语句和映射关系
  • 注解配置:通过注解直接在接口方法上定义SQL语句
  • 参数映射:支持基本类型、对象、Map等参数类型
  • 结果映射:支持将查询结果映射到对象、List、Map等

2. 动态SQL

  • if:条件判断,根据条件生成SQL片段
  • choose/when/otherwise:类似switch-case语句
  • foreach:循环处理集合参数
  • sql/include:定义可重用的SQL片段
  • trim/where/set:智能处理SQL语句的拼接

3. 高级映射

  • association:一对一映射,处理对象关联
  • collection:一对多映射,处理集合关联
  • 多对多映射:通过中间表实现
  • 延迟加载:按需加载关联对象,提高性能

4. 缓存机制

  • 一级缓存:SqlSession级别的缓存,默认开启
  • 二级缓存:SqlSessionFactory级别的缓存,需要手动开启
  • 缓存配置:可以配置缓存的大小、过期时间等
  • 自定义缓存:支持集成第三方缓存框架(如Redis)

5. 批量操作

  • 批量插入:一次执行多条插入语句
  • 批量更新:一次执行多条更新语句
  • 批量删除:一次执行多条删除语句
  • ExecutorType.BATCH:使用批处理执行器提高性能

6. 存储过程支持

  • 调用存储过程:通过call语句调用存储过程
  • 输出参数:支持获取存储过程的输出参数
  • 游标参数:支持处理存储过程返回的游标

7. 类型处理器

  • 内置类型处理器:处理常见的Java类型与数据库类型的转换
  • 自定义类型处理器:处理特殊类型的转换
  • 类型别名:简化类型名称的配置

8. 插件机制

  • 拦截点:Executor、StatementHandler、ParameterHandler、ResultSetHandler
  • 插件开发:实现Interceptor接口,重写intercept方法
  • 插件配置:在MyBatis配置文件中注册插件

MyBatis的优势

  • 灵活性高:可以编写复杂的SQL语句,适合各种复杂场景
  • 性能优异:直接操作SQL,避免了ORM框架的性能损耗
  • 易于学习:API简单,配置直观,学习曲线平缓
  • 与Spring集成:可以与Spring框架无缝集成,支持Spring Boot
  • 支持动态SQL:可以根据条件动态生成SQL,提高代码的灵活性
  • 开源社区活跃:拥有庞大的社区支持和丰富的文档

MyBatis的适用场景

  • 复杂SQL场景:需要编写复杂SQL语句的项目,如报表系统
  • 性能要求高:对性能要求较高的项目,如电商系统
  • 遗留系统:需要与遗留系统集成的项目
  • 多数据库:需要支持多种数据库的项目
  • 需要精细控制SQL:需要对SQL语句进行精细控制的项目

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 定义Mapper接口
public interface UserMapper {
// 根据ID查询用户
User selectById(Long id);

// 查询所有用户
List<User> selectAll();

// 插入用户
int insert(User user);

// 更新用户
int update(User user);

// 删除用户
int delete(Long id);

// 根据条件查询用户
List<User> selectByCondition(UserCondition condition);

// 批量插入用户
int batchInsert(List<User> users);

// 根据ID列表查询用户
List<User> selectByIds(List<Long> ids);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 结果映射 -->
<resultMap id="UserMap" type="com.example.entity.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="email" column="email"/>
<!-- 一对一映射 -->
<association property="department" column="dept_id" javaType="com.example.entity.Department">
<id property="id" column="dept_id"/>
<result property="name" column="dept_name"/>
</association>
<!-- 一对多映射 -->
<collection property="orders" column="id" ofType="com.example.entity.Order">
<id property="id" column="order_id"/>
<result property="orderNo" column="order_no"/>
<result property="amount" column="amount"/>
</collection>
</resultMap>

<!-- SQL片段 -->
<sql id="Base_Column_List">
id, name, age, email, dept_id
</sql>

<!-- 根据ID查询用户 -->
<select id="selectById" resultMap="UserMap">
SELECT
<include refid="Base_Column_List"/>,
d.id as dept_id, d.name as dept_name,
o.id as order_id, o.order_no, o.amount
FROM user u
LEFT JOIN department d ON u.dept_id = d.id
LEFT JOIN order o ON u.id = o.user_id
WHERE u.id = #{id}
</select>

<!-- 查询所有用户 -->
<select id="selectAll" resultMap="UserMap">
SELECT
<include refid="Base_Column_List"/>
FROM user
</select>

<!-- 插入用户 -->
<insert id="insert" parameterType="com.example.entity.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, age, email, dept_id)
VALUES (#{name}, #{age}, #{email}, #{deptId})
</insert>

<!-- 更新用户 -->
<update id="update" parameterType="com.example.entity.User">
UPDATE user
<set>
<if test="name != null">name = #{name},</if>
<if test="age != null">age = #{age},</if>
<if test="email != null">email = #{email},</if>
<if test="deptId != null">dept_id = #{deptId},</if>
</set>
WHERE id = #{id}
</update>

<!-- 删除用户 -->
<delete id="delete" parameterType="java.lang.Long">
DELETE FROM user WHERE id = #{id}
</delete>

<!-- 根据条件查询用户 -->
<select id="selectByCondition" resultMap="UserMap" parameterType="com.example.entity.UserCondition">
SELECT
<include refid="Base_Column_List"/>
FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="deptId != null">
AND dept_id = #{deptId}
</if>
</where>
ORDER BY id DESC
</select>

<!-- 批量插入用户 -->
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO user (name, age, email, dept_id)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age}, #{user.email}, #{user.deptId})
</foreach>
</insert>

<!-- 根据ID列表查询用户 -->
<select id="selectByIds" resultMap="UserMap" parameterType="java.util.List">
SELECT
<include refid="Base_Column_List"/>
FROM user
WHERE id IN
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
</mapper>

注解方式示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Mapper
public interface UserMapper {
// 根据ID查询用户
@Select("SELECT id, name, age, email FROM user WHERE id = #{id}")
User selectById(Long id);

// 查询所有用户
@Select("SELECT id, name, age, email FROM user")
List<User> selectAll();

// 插入用户
@Insert("INSERT INTO user (name, age, email) VALUES (#{name}, #{age}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);

// 更新用户
@Update("UPDATE user SET name = #{name}, age = #{age}, email = #{email} WHERE id = #{id}")
int update(User user);

// 删除用户
@Delete("DELETE FROM user WHERE id = #{id}")
int delete(Long id);

// 根据条件查询用户
@Select("SELECT id, name, age, email FROM user WHERE 1=1 " +
"<if test='name != null'>AND name LIKE CONCAT('%', #{name}, '%')</if> " +
"<if test='age != null'>AND age = #{age}</if>")
List<User> selectByCondition(UserCondition condition);
}
  • 注解方式:使用@Select、@Insert、@Update、@Delete等注解
  • XML方式:使用XML配置文件定义SQL
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 注解方式
    public interface UserMapper {
    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectById(Long id);

    @Insert("INSERT INTO user (name, age, email) VALUES (#{name}, #{age}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
    }

4. Mapper XML

  • 作用:配置SQL语句和结果集映射
  • 主要元素
    • <mapper>:根元素,指定命名空间
    • <select>:查询语句
    • <insert>:插入语句
    • <update>:更新语句
    • <delete>:删除语句
    • <resultMap>:结果集映射
    • <parameterMap>:参数映射(已废弃)
    • <sql>:SQL片段
    • <include>:引用SQL片段
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <mapper namespace="com.example.mapper.UserMapper">
    <resultMap id="UserMap" type="com.example.entity.User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <result property="email" column="email"/>
    </resultMap>

    <select id="selectById" resultMap="UserMap">
    SELECT id, name, age, email
    FROM user
    WHERE id = #{id}
    </select>
    </mapper>

5. Executor

  • 作用:执行SQL的执行器,是MyBatis的核心组件之一
  • 类型
    • SimpleExecutor:简单执行器,每次执行SQL都创建新的Statement
    • ReuseExecutor:重用执行器,重用Statement
    • BatchExecutor:批量执行器,批量处理SQL
  • 示例
    1
    2
    // MyBatis内部使用,通常不需要手动创建
    Executor executor = configuration.newExecutor(tx);

6. StatementHandler

  • 作用:处理SQL语句的执行
  • 类型
    • SimpleStatementHandler:处理简单SQL语句
    • PreparedStatementHandler:处理预处理SQL语句
    • CallableStatementHandler:处理存储过程
  • 主要方法
    • prepare():预处理SQL语句
    • parameterize():设置参数
    • execute():执行SQL语句
    • query():执行查询

7. ResultSetHandler

  • 作用:处理结果集的映射
  • 主要方法
    • handleResultSets():处理结果集
    • handleOutputParameters():处理存储过程的输出参数
  • 示例
    1
    2
    // MyBatis内部使用,通常不需要手动创建
    ResultSetHandler resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

8. ParameterHandler

  • 作用:处理SQL语句的参数
  • 主要方法
    • setParameters():设置参数值
  • 示例
    1
    2
    // MyBatis内部使用,通常不需要手动创建
    ParameterHandler parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);

9. TypeHandler

  • 作用:处理Java类型与数据库类型的转换
  • 内置类型处理器:处理常见的Java类型和数据库类型的转换
  • 自定义类型处理器:处理自定义类型的转换
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // 自定义类型处理器
    @MappedTypes(Date.class)
    @MappedJdbcTypes(JdbcType.TIMESTAMP)
    public class DateTypeHandler extends BaseTypeHandler<Date> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
    ps.setTimestamp(i, new Timestamp(parameter.getTime()));
    }

    @Override
    public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
    Timestamp timestamp = rs.getTimestamp(columnName);
    return timestamp != null ? new Date(timestamp.getTime()) : null;
    }

    @Override
    public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    Timestamp timestamp = rs.getTimestamp(columnIndex);
    return timestamp != null ? new Date(timestamp.getTime()) : null;
    }

    @Override
    public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    Timestamp timestamp = cs.getTimestamp(columnIndex);
    return timestamp != null ? new Date(timestamp.getTime()) : null;
    }
    }

10. Plugin

  • 作用:插件机制,用于拦截MyBatis的核心组件
  • 可以拦截的组件:Executor、StatementHandler、ParameterHandler、ResultSetHandler
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // 自定义插件
    @Intercepts({
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
    })
    public class LoggingPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    // 执行前处理
    System.out.println("Before executing update");

    // 执行原始方法
    Object result = invocation.proceed();

    // 执行后处理
    System.out.println("After executing update");

    return result;
    }

    @Override
    public Object plugin(Object target) {
    return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    // 设置插件属性
    }
    }

MyBatis的执行流程

  1. 加载配置:加载MyBatis的配置文件和映射文件
  2. 创建SqlSessionFactory:通过SqlSessionFactoryBuilder创建SqlSessionFactory
  3. 创建SqlSession:通过SqlSessionFactory创建SqlSession
  4. 获取Mapper:通过SqlSession获取Mapper接口的代理实现
  5. 执行SQL:调用Mapper接口的方法执行SQL
  6. 处理结果:将SQL执行结果转换为Java对象
  7. 提交事务:如果是写操作,提交事务
  8. 关闭SqlSession:关闭SqlSession,释放资源

注意事项

  • SqlSessionFactory应该是单例的,在应用启动时创建
  • SqlSession应该在每次操作后关闭,避免资源泄漏
  • Mapper接口应该保持简洁,只定义SQL操作方法
  • 应该合理使用插件,避免过度使用影响性能
  • 应该合理配置类型处理器,确保类型转换正确

4.3 MyBatis的映射方式有哪些?

MyBatis的映射方式是指如何将SQL语句与Java方法进行关联,以及如何将查询结果映射到Java对象。MyBatis提供了多种映射方式,以满足不同场景的需求。

1. XML映射

  • 特点:通过XML文件配置SQL语句和结果集映射,是MyBatis最传统、最灵活的映射方式
  • 优势
    • 支持复杂的SQL语句
    • 支持动态SQL
    • 支持高级结果集映射
    • 便于维护和管理
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    <!-- UserMapper.xml -->
    <mapper namespace="com.example.mapper.UserMapper">
    <!-- 结果映射 -->
    <resultMap id="UserMap" type="com.example.entity.User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <result property="email" column="email"/>
    <!-- 一对一映射 -->
    <association property="address" column="address_id" javaType="com.example.entity.Address">
    <id property="id" column="address_id"/>
    <result property="street" column="street"/>
    <result property="city" column="city"/>
    </association>
    <!-- 一对多映射 -->
    <collection property="orders" column="id" ofType="com.example.entity.Order">
    <id property="id" column="order_id"/>
    <result property="orderNo" column="order_no"/>
    <result property="amount" column="amount"/>
    </collection>
    </resultMap>

    <!-- 查询所有用户 -->
    <select id="selectAll" resultMap="UserMap">
    SELECT u.id, u.name, u.age, u.email,
    a.id AS address_id, a.street, a.city,
    o.id AS order_id, o.order_no, o.amount
    FROM user u
    LEFT JOIN address a ON u.address_id = a.id
    LEFT JOIN order o ON u.id = o.user_id
    </select>

    <!-- 动态SQL -->
    <select id="selectByCondition" parameterType="com.example.condition.UserCondition" resultMap="UserMap">
    SELECT * FROM user
    <where>
    <if test="name != null and name != ''">
    AND name LIKE CONCAT('%', #{name}, '%')
    </if>
    <if test="age != null">
    AND age = #{age}
    </if>
    </where>
    </select>
    </mapper>

2. 注解映射

  • 特点:通过Java注解配置SQL语句,简洁直观
  • 优势
    • 代码量少,配置简单
    • 不需要额外的XML文件
    • 适合简单的SQL语句
  • 劣势
    • 不支持复杂的SQL语句
    • 不支持动态SQL(需要使用XML或混合映射)
    • 不支持高级结果集映射
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    public interface UserMapper {
    // 简单查询
    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectById(Long id);

    // 插入操作
    @Insert("INSERT INTO user (name, age, email) VALUES (#{name}, #{age}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);

    // 更新操作
    @Update("UPDATE user SET name = #{name}, age = #{age}, email = #{email} WHERE id = #{id}")
    int update(User user);

    // 删除操作
    @Delete("DELETE FROM user WHERE id = #{id}")
    int delete(Long id);

    // 复杂查询(使用Provider)
    @SelectProvider(type = UserSqlProvider.class, method = "selectByCondition")
    List<User> selectByCondition(UserCondition condition);
    }

    // SQL Provider
    public class UserSqlProvider {
    public String selectByCondition(UserCondition condition) {
    StringBuilder sql = new StringBuilder("SELECT * FROM user WHERE 1=1");

    if (condition.getName() != null && !condition.getName().isEmpty()) {
    sql.append(" AND name LIKE CONCAT('%', #{name}, '%')");
    }

    if (condition.getAge() != null) {
    sql.append(" AND age = #{age}");
    }

    return sql.toString();
    }
    }

3. 混合映射

  • 特点:同时使用XML和注解进行映射
  • 优势
    • 结合了XML和注解的优点
    • 简单的SQL使用注解,复杂的SQL使用XML
    • 灵活度高
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public interface UserMapper {
    // 简单查询使用注解
    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectById(Long id);

    // 复杂查询使用XML
    List<User> selectByCondition(UserCondition condition);

    // 插入操作使用注解
    @Insert("INSERT INTO user (name, age, email) VALUES (#{name}, #{age}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <!-- UserMapper.xml -->
    <mapper namespace="com.example.mapper.UserMapper">
    <select id="selectByCondition" parameterType="com.example.condition.UserCondition" resultType="com.example.entity.User">
    SELECT * FROM user
    <where>
    <if test="name != null and name != ''">
    AND name LIKE CONCAT('%', #{name}, '%')
    </if>
    <if test="age != null">
    AND age = #{age}
    </if>
    <if test="email != null and email != ''">
    AND email LIKE CONCAT('%', #{email}, '%')
    </if>
    </where>
    <order by>
    <if test="orderBy != null and orderBy != ''">
    #{orderBy}
    </if>
    <otherwise>
    id DESC
    </otherwise>
    </order>
    </select>
    </mapper>

4. 结果集映射

(1) 简单结果映射

  • 使用resultType指定返回类型
  • MyBatis会自动将查询结果映射到指定类型
  • 适用于简单的查询

(2) 高级结果映射

  • 使用resultMap定义复杂的结果映射
  • 支持一对一、一对多、多对多映射
  • 支持延迟加载
  • 适用于复杂的查询

5. 参数映射

(1) 基本类型参数

  • 直接使用#{parameter}引用参数
  • 支持设置参数类型和jdbcType

(2) 对象参数

  • 使用#{property}引用对象的属性
  • 支持嵌套属性,如#{user.name}

(3) 集合参数

  • 使用foreach标签遍历集合
  • 支持List、Set、Map等集合类型

(4) 多参数

  • 使用@Param注解指定参数名称
  • 使用Map传递多个参数
  • 使用自定义对象传递多个参数

6. 动态SQL映射

(1) if

  • 条件判断,根据条件生成SQL

**(2) choose (when, otherwise)**:

  • 选择条件,类似于Java的switch语句

**(3) trim (where, set)**:

  • 处理SQL语句的前缀和后缀
  • where:自动处理WHERE子句
  • set:自动处理SET子句

(4) foreach

  • 遍历集合,生成IN语句或批量操作

(5) bind

  • 绑定变量,用于复杂的表达式

7. 最佳实践

  • 简单SQL:使用注解映射
  • 复杂SQL:使用XML映射
  • 混合使用:简单SQL使用注解,复杂SQL使用XML
  • 结果集映射:简单结果使用resultType,复杂结果使用resultMap
  • 参数映射:使用@Param注解或自定义对象传递参数
  • 动态SQL:使用XML的动态SQL标签

8. 注意事项

  • XML映射文件的命名空间必须与Mapper接口的全限定名一致
  • XML映射文件中的id必须与Mapper接口中的方法名一致
  • 注解映射和XML映射可以共存,但XML映射的优先级更高
  • 应该合理选择映射方式,根据SQL的复杂度和维护性
  • 应该避免在注解中编写复杂的SQL语句,影响代码可读性

4.4 MyBatis的缓存机制?

MyBatis的缓存机制是MyBatis提高性能的重要手段,它通过缓存查询结果,减少数据库访问次数,从而提高系统性能。

1. 一级缓存

  • 作用范围:SqlSession级别的缓存,每个SqlSession有自己的缓存
  • 默认状态:默认开启,不需要额外配置
  • 工作原理
    • 当执行查询操作时,MyBatis会先在一级缓存中查找
    • 如果找到,直接返回缓存的结果
    • 如果没有找到,执行SQL查询,将结果存入一级缓存
    • 当执行增删改操作时,一级缓存会被清空
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    // 第一次查询,从数据库获取
    User user1 = userMapper.selectById(1L);
    System.out.println(user1);

    // 第二次查询,从一级缓存获取
    User user2 = userMapper.selectById(1L);
    System.out.println(user2);

    // user1和user2是同一个对象
    System.out.println(user1 == user2); // true

    // 执行更新操作,清空一级缓存
    userMapper.update(user1);
    sqlSession.commit();

    // 第三次查询,从数据库获取
    User user3 = userMapper.selectById(1L);
    System.out.println(user3);

    // user1和user3是不同的对象
    System.out.println(user1 == user3); // false
    } finally {
    sqlSession.close();
    }

2. 二级缓存

  • 作用范围:Mapper级别的缓存,多个SqlSession共享
  • 默认状态:默认关闭,需要手动开启
  • 开启方式
    • 在MyBatis配置文件中开启全局二级缓存
    • 在Mapper XML文件中开启二级缓存
    • 实体类需要实现Serializable接口
  • 工作原理
    • 当执行查询操作时,MyBatis会先在一级缓存中查找
    • 如果一级缓存中没有,再在二级缓存中查找
    • 如果二级缓存中也没有,执行SQL查询,将结果存入一级缓存和二级缓存
    • 当执行增删改操作时,二级缓存会被清空
  • 示例
    1
    2
    3
    4
    5
    6
    7
    <!-- mybatis-config.xml -->
    <configuration>
    <!-- 开启全局二级缓存 -->
    <settings>
    <setting name="cacheEnabled" value="true"/>
    </settings>
    </configuration>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- UserMapper.xml -->
    <mapper namespace="com.example.mapper.UserMapper">
    <!-- 开启二级缓存 -->
    <cache
    eviction="LRU"
    flushInterval="60000"
    size="1024"
    readOnly="false"/>

    <!-- 其他映射配置 -->
    </mapper>
    1
    2
    3
    4
    5
    6
    7
    8
    // 实体类需要实现Serializable接口
    public class User implements Serializable {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    // getter和setter
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    // 测试二级缓存
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();

    try {
    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

    // 第一次查询,从数据库获取
    User user1 = mapper1.selectById(1L);
    System.out.println(user1);

    // 提交事务,将结果存入二级缓存
    sqlSession1.commit();

    // 第二次查询,从二级缓存获取
    User user2 = mapper2.selectById(1L);
    System.out.println(user2);

    // user1和user2是不同的对象,但内容相同
    System.out.println(user1 == user2); // false
    System.out.println(user1.equals(user2)); // true

    // 执行更新操作,清空二级缓存
    mapper1.update(user1);
    sqlSession1.commit();

    // 第三次查询,从数据库获取
    User user3 = mapper2.selectById(1L);
    System.out.println(user3);

    // user1和user3是不同的对象,内容也不同
    System.out.println(user1.equals(user3)); // false
    } finally {
    sqlSession1.close();
    sqlSession2.close();
    }

3. 缓存实现

(1) 内置缓存实现

  • PerpetualCache:默认缓存实现,使用HashMap存储缓存数据
  • LruCache:基于LRU(最近最少使用)算法的缓存实现
  • FifoCache:基于FIFO(先进先出)算法的缓存实现
  • SoftCache:基于软引用的缓存实现,当内存不足时会被垃圾回收
  • WeakCache:基于弱引用的缓存实现,随时可能被垃圾回收

(2) 自定义缓存实现

  • 实现Cache接口
  • 在Mapper XML文件中配置自定义缓存
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    public class MyCache implements Cache {
    private String id;
    private Map<Object, Object> cache;

    public MyCache(String id) {
    this.id = id;
    this.cache = new ConcurrentHashMap<>();
    }

    @Override
    public String getId() {
    return id;
    }

    @Override
    public int getSize() {
    return cache.size();
    }

    @Override
    public void putObject(Object key, Object value) {
    cache.put(key, value);
    }

    @Override
    public Object getObject(Object key) {
    return cache.get(key);
    }

    @Override
    public Object removeObject(Object key) {
    return cache.remove(key);
    }

    @Override
    public void clear() {
    cache.clear();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
    return null; // 不需要锁
    }
    }
    1
    2
    3
    4
    5
    6
    7
    <!-- UserMapper.xml -->
    <mapper namespace="com.example.mapper.UserMapper">
    <!-- 使用自定义缓存 -->
    <cache type="com.example.cache.MyCache"/>

    <!-- 其他映射配置 -->
    </mapper>

4. 缓存配置

(1) 全局缓存配置

1
2
3
4
5
6
7
8
<settings>
<!-- 开启全局二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 延迟加载的全局开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 开启侵入式延迟加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

(2) Mapper缓存配置

1
2
3
4
5
6
7
<cache
eviction="LRU" <!-- 缓存淘汰策略 -->
flushInterval="60000" <!-- 缓存刷新间隔,单位毫秒 -->
size="1024" <!-- 缓存大小 -->
readOnly="false" <!-- 是否只读 -->
type="com.example.cache.MyCache" <!-- 自定义缓存实现 -->
/>

(3) 缓存淘汰策略

  • LRU:最近最少使用,移除最长时间不被使用的对象
  • FIFO:先进先出,按对象进入缓存的顺序移除
  • SOFT:软引用,基于垃圾回收器状态和软引用规则移除对象
  • WEAK:弱引用,基于垃圾回收器状态和弱引用规则移除对象

5. 缓存失效

  • 增删改操作:当执行insert、update、delete操作时,缓存会被清空
  • 手动清空:调用SqlSession.clearCache()清空一级缓存
  • 缓存过期:当缓存达到刷新间隔或缓存大小限制时,缓存会被清空
  • 序列化问题:当实体类未实现Serializable接口时,二级缓存会失效

6. 缓存的使用场景

  • 适合使用缓存的场景

    • 查询频率高,数据变化频率低的场景
    • 数据一致性要求不高的场景
    • 系统性能要求高的场景
  • 不适合使用缓存的场景

    • 数据变化频率高的场景
    • 数据一致性要求高的场景
    • 内存资源有限的场景

7. 缓存的最佳实践

  • 合理配置缓存:根据实际业务场景配置缓存的大小、刷新间隔和淘汰策略
  • 使用二级缓存:对于频繁查询的数据,使用二级缓存提高性能
  • 注意序列化:使用二级缓存时,实体类必须实现Serializable接口
  • 避免缓存穿透:对于不存在的数据,也应该缓存,避免每次都查询数据库
  • 避免缓存雪崩:设置不同的缓存过期时间,避免缓存同时过期
  • 监控缓存:监控缓存的命中率和内存使用情况,及时调整缓存策略

8. 缓存的优缺点

优点

  • 减少数据库访问次数,提高系统性能
  • 减轻数据库压力
  • 提高用户体验

缺点

  • 占用内存资源
  • 可能导致数据不一致
  • 增加系统复杂度

9. 注意事项

  • 一级缓存是SqlSession级别的,不能跨SqlSession共享
  • 二级缓存是Mapper级别的,可以跨SqlSession共享
  • 使用二级缓存时,实体类必须实现Serializable接口
  • 当执行增删改操作时,缓存会被清空
  • 应该根据实际业务场景选择合适的缓存策略
  • 应该避免缓存大量数据,导致内存溢出
  • 应该监控缓存的使用情况,及时调整缓存配置

4.5 MyBatis的动态SQL有哪些?

MyBatis的动态SQL是MyBatis的核心特性之一,它允许根据条件动态生成SQL语句,提高了SQL语句的灵活性和可维护性。

1. if

  • 作用:条件判断,根据条件生成SQL片段
  • 语法
    1
    2
    3
    <if test="条件">
    SQL片段
    </if>
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <select id="selectByCondition" parameterType="com.example.condition.UserCondition" resultType="com.example.entity.User">
    SELECT * FROM user
    WHERE 1=1
    <if test="name != null and name != ''">
    AND name LIKE CONCAT('%', #{name}, '%')
    </if>
    <if test="age != null">
    AND age = #{age}
    </if>
    <if test="email != null and email != ''">
    AND email LIKE CONCAT('%', #{email}, '%')
    </if>
    </select>

**2. choose (when, otherwise)**:

  • 作用:选择条件,类似于Java的switch语句
  • 语法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <choose>
    <when test="条件1">
    SQL片段1
    </when>
    <when test="条件2">
    SQL片段2
    </when>
    <otherwise>
    SQL片段3
    </otherwise>
    </choose>
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <select id="selectByCondition" parameterType="com.example.condition.UserCondition" resultType="com.example.entity.User">
    SELECT * FROM user
    WHERE 1=1
    <choose>
    <when test="name != null and name != ''">
    AND name LIKE CONCAT('%', #{name}, '%')
    </when>
    <when test="age != null">
    AND age = #{age}
    </when>
    <otherwise>
    AND status = 'active'
    </otherwise>
    </choose>
    </select>

**3. trim (where, set)**:

  • 作用:处理SQL语句的前缀和后缀
  • 语法
    1
    2
    3
    <trim prefix="前缀" suffix="后缀" prefixOverrides="前缀覆盖" suffixOverrides="后缀覆盖">
    SQL片段
    </trim>
  • where:自动处理WHERE子句,移除多余的AND或OR
  • set:自动处理SET子句,移除多余的逗号
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <!-- where示例 -->
    <select id="selectByCondition" parameterType="com.example.condition.UserCondition" resultType="com.example.entity.User">
    SELECT * FROM user
    <where>
    <if test="name != null and name != ''">
    AND name LIKE CONCAT('%', #{name}, '%')
    </if>
    <if test="age != null">
    AND age = #{age}
    </if>
    </where>
    </select>

    <!-- set示例 -->
    <update id="update" parameterType="com.example.entity.User">
    UPDATE user
    <set>
    <if test="name != null and name != ''">
    name = #{name},
    </if>
    <if test="age != null">
    age = #{age},
    </if>
    <if test="email != null and email != ''">
    email = #{email},
    </if>
    </set>
    WHERE id = #{id}
    </update>

4. foreach

  • 作用:遍历集合,生成IN语句或批量操作
  • 语法
    1
    2
    3
    <foreach collection="集合" item="元素" index="索引" open="开始" close="结束" separator="分隔符">
    #{元素}
    </foreach>
  • 属性
    • collection:集合的名称
    • item:集合元素的别名
    • index:集合元素的索引
    • open:SQL片段的开始
    • close:SQL片段的结束
    • separator:元素之间的分隔符
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- IN语句示例 -->
    <select id="selectByIds" parameterType="java.util.List" resultType="com.example.entity.User">
    SELECT * FROM user
    WHERE id IN
    <foreach collection="list" item="id" open="(" close=")" separator=",">
    #{id}
    </foreach>
    </select>

    <!-- 批量插入示例 -->
    <insert id="batchInsert" parameterType="java.util.List">
    INSERT INTO user (name, age, email)
    VALUES
    <foreach collection="list" item="user" separator=",">
    (#{user.name}, #{user.age}, #{user.email})
    </foreach>
    </insert>

5. bind

  • 作用:绑定变量,用于复杂的表达式
  • 语法
    1
    <bind name="变量名" value="表达式"/>
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <select id="selectByCondition" parameterType="com.example.condition.UserCondition" resultType="com.example.entity.User">
    <bind name="nameLike" value="'%' + name + '%'"/>
    <bind name="emailLike" value="'%' + email + '%'"/>
    SELECT * FROM user
    <where>
    <if test="name != null and name != ''">
    AND name LIKE #{nameLike}
    </if>
    <if test="email != null and email != ''">
    AND email LIKE #{emailLike}
    </if>
    </where>
    </select>

6. sql和include

  • 作用:定义SQL片段,提高代码复用性
  • 语法
    1
    2
    3
    4
    5
    6
    7
    <!-- 定义SQL片段 -->
    <sql id="sql片段ID">
    SQL片段
    </sql>

    <!-- 引用SQL片段 -->
    <include refid="sql片段ID"/>
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!-- 定义SQL片段 -->
    <sql id="userColumns">
    id, name, age, email
    </sql>

    <!-- 引用SQL片段 -->
    <select id="selectById" parameterType="java.lang.Long" resultType="com.example.entity.User">
    SELECT <include refid="userColumns"/>
    FROM user
    WHERE id = #{id}
    </select>

    <select id="selectAll" resultType="com.example.entity.User">
    SELECT <include refid="userColumns"/>
    FROM user
    </select>

7. 动态SQL的最佳实践

  • 使用where标签:自动处理WHERE子句,避免多余的AND或OR
  • 使用set标签:自动处理SET子句,避免多余的逗号
  • 使用foreach标签:处理集合参数,生成IN语句或批量操作
  • 使用bind标签:处理复杂的表达式,提高代码可读性
  • 使用sql和include标签:定义可复用的SQL片段,提高代码复用性
  • 合理使用choose标签:处理多条件选择场景
  • 避免嵌套过深:动态SQL嵌套过深会影响代码可读性和性能

8. 动态SQL的优缺点

优点

  • 提高SQL语句的灵活性
  • 减少代码重复
  • 提高代码可维护性
  • 支持复杂的条件查询

缺点

  • 可能导致SQL语句过于复杂
  • 可能影响SQL语句的性能
  • 调试难度较大

9. 注意事项

  • 动态SQL的条件判断应使用OGNL表达式
  • 应避免在动态SQL中使用复杂的业务逻辑
  • 应注意SQL注入问题,使用#{ }占位符
  • 应合理使用动态SQL,避免过度使用
  • 应测试动态SQL生成的SQL语句,确保其正确性

10. 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!-- 综合示例 -->
<select id="selectByCondition" parameterType="com.example.condition.UserCondition" resultType="com.example.entity.User">
<bind name="nameLike" value="'%' + name + '%'"/>
SELECT id, name, age, email
FROM user
<where>
<if test="id != null">
AND id = #{id}
</if>
<if test="name != null and name != ''">
AND name LIKE #{nameLike}
</if>
<if test="age != null">
<choose>
<when test="age == 0">
AND age = 0
</when>
<otherwise>
AND age = #{age}
</otherwise>
</choose>
</if>
<if test="ids != null and ids.size() > 0">
AND id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</if>
</where>
<if test="orderBy != null and orderBy != ''">
ORDER BY #{orderBy}
</if>
<if test="limit != null">
LIMIT #{limit}
</if>
</select>

总结

  • MyBatis的动态SQL是其核心特性之一,允许根据条件动态生成SQL语句
  • 常用的动态SQL标签包括:if、choose、when、otherwise、trim、where、set、foreach、bind、sql、include
  • 动态SQL可以提高SQL语句的灵活性和可维护性
  • 应合理使用动态SQL,避免过度使用导致SQL语句过于复杂
  • 应注意动态SQL的性能和安全性

5. 其他框架相关问题

5.1 Spring MVC的工作原理?

Spring MVC的工作原理:

  1. 客户端发送请求到DispatcherServlet
  2. DispatcherServlet根据请求路径查找HandlerMapping
  3. HandlerMapping返回HandlerExecutionChain
  4. DispatcherServlet调用HandlerAdapter执行Handler
  5. Handler执行完毕返回ModelAndView
  6. DispatcherServlet根据ViewName查找ViewResolver
  7. ViewResolver返回View
  8. DispatcherServlet渲染View并返回响应

5.2 什么是ORM?常见的ORM框架有哪些?

ORM(Object-Relational Mapping)是一种将对象与关系数据库映射的技术。常见的ORM框架包括:

  • Hibernate:全自动化ORM框架
  • MyBatis:半自动化ORM框架
  • JPA:Java持久化API
  • Spring Data JPA:基于JPA的框架

5.3 什么是依赖注入?依赖注入的方式有哪些?

依赖注入是指将依赖的对象注入到需要的对象中。依赖注入的方式包括:

  • 构造函数注入:通过构造函数注入依赖
  • setter方法注入:通过setter方法注入依赖
  • 字段注入:通过字段注入依赖
  • 接口注入:通过接口注入依赖

5.4 什么是控制反转?控制反转的实现方式有哪些?

控制反转是指将对象的创建和管理交给容器,而不是由对象自己创建和管理依赖。控制反转的实现方式包括:

  • 依赖注入:通过构造函数、setter方法或字段注入依赖
  • 服务定位器:通过服务定位器获取依赖
  • 工厂模式:通过工厂创建对象
  • 模板方法:通过模板方法定义算法骨架