七、测试支持
7.1 单元测试
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class UserServiceTest {
@Autowired
private UserService userService;
@MockBean
private UserDao userDao;
@Test
public void testCreateUser() {
User user = new User("张三", 25);
when(userDao.insert(any(User.class))).thenReturn(1);
User result = userService.create(user);
assertNotNull(result);
assertEquals("张三", result.getUsername());
verify(userDao, times(1)).insert(any(User.class));
}
@Test(expected = BusinessException.class)
public void testCreateUserWithInvalidAge() {
User user = new User("李四", -1);
userService.create(user);
}
@Test
public void testFindUser() {
User expected = new User(1L, "王五", 30);
when(userDao.findById(1L)).thenReturn(expected);
User actual = userService.findById(1L);
assertEquals(expected.getUsername(), actual.getUsername());
}
}
7.2 集成测试
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@Transactional
@Rollback
public class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
public void testGetUser() throws Exception {
mockMvc.perform(get("/api/users/1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.username").value("张三"))
.andDo(print());
}
@Test
public void testCreateUser() throws Exception {
User user = new User("李四", 25);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").exists())
.andExpect(jsonPath("$.username").value("李四"));
}
@Test
public void testValidationError() throws Exception {
User user = new User("", -1);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.code").value(400));
}
}
八、Spring 表达式语言(SpEL)
@Component
public class SpELDemo {
@Value("#{systemProperties['user.name']}")
private String userName;
@Value("#{systemProperties['java.version']}")
private String javaVersion;
@Value("#{T(Math).random() * 100.0}")
private double randomNumber;
@Value("#{userService.findUser(1)?.username ?: 'Unknown'}")
private String defaultUsername;
@Value("#{userService.users.?[age > 25]}")
private List<User> adults;
@Value("#{userService.users.![username]}")
private List<String> usernames;
public void testSpEL() {
ExpressionParser parser = new SpelExpressionParser();
// 字面量
String hello = parser.parseExpression("'Hello World'").getValue(String.class);
// 数学运算
int sum = parser.parseExpression("1 + 2 + 3").getValue(Integer.class);
// 逻辑运算
boolean result = parser.parseExpression("2 > 1 && 3 < 5").getValue(Boolean.class);
// 方法调用
String upper = parser.parseExpression("'hello'.toUpperCase()").getValue(String.class);
// 属性访问
Expression exp = parser.parseExpression("user.username");
User user = new User("张三", 25);
String username = exp.getValue(user, String.class);
// 安全导航
String email = parser.parseExpression("user?.email ?: 'no-email'")
.getValue(user, String.class);
// 集合选择
List<User> users = Arrays.asList(
new User("张三", 30),
new User("李四", 20),
new User("王五", 35)
);
List<User> adults = parser.parseExpression("#root.?[age >= 25]")
.getValue(users, List.class);
// 集合投影
List<String> names = parser.parseExpression("#root.![username]")
.getValue(users, List.class);
}
}
// 在 XML 中使用 SpEL
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="#{systemProperties['db.url']}"/>
<property name="username" value="#{systemProperties['db.username']}"/>
<property name="password" value="#{systemProperties['db.password']}"/>
</bean>
九、Spring 与 Spring Boot
9.1 Spring Boot 简介
Spring Boot 是 Spring 框架的扩展,通过自动配置和起步依赖简化了 Spring 应用的开发。
核心特性:
自动配置:根据类路径中的依赖自动配置 Spring
起步依赖:简化 Maven/Gradle 依赖管理
嵌入式服务器:内置 Tomcat、Jetty 等
生产就绪:提供健康检查、指标监控等
无代码生成和 XML 配置
9.2 Spring Boot 应用
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
hikari:
maximum-pool-size: 10
connection-timeout: 30000
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
redis:
host: localhost
port: 6379
timeout: 2000ms
cache:
type: redis
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
server:
port: 8080
servlet:
context-path: /api
session:
timeout: 1800s
logging:
level:
com.example: DEBUG
org.springframework.web: INFO
file:
name: logs/application.log
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
9.3 Spring Boot 自动配置
// 自定义自动配置
@Configuration
@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
@EnableConfigurationProperties(CacheProperties.class)
public class CustomCacheAutoConfiguration {
@Autowired
private CacheProperties properties;
@Bean
@ConditionalOnMissingBean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(properties.getTtl()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()
)
);
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
// 配置属性类
@ConfigurationProperties(prefix = "cache")
public class CacheProperties {
private long ttl = 60;
private int maxSize = 1000;
// getter/setter
}
Spring 的世界广阔而精彩,愿本文成为你 Spring 学习之路上的重要指南。持续学习,深入实践,你一定能成为 Spring 专家!
来源:
https://app-aakcgtuh1ywx.appmiaoda.com