1. 项目背景与核心目标
在Java企业级开发领域,Spring框架早已成为事实上的标准。作为从业十余年的开发者,我见过太多初学者在刚接触Spring时陷入配置文件的泥潭,或是被各种抽象概念绕得晕头转向。今天我们就用两个最基础的案例——加法计算器和用户登录系统,来揭示Spring的核心工作机制。
为什么选择这两个案例?加法计算器看似简单,但涉及Spring最核心的依赖注入和Bean管理机制;用户登录则能完整展示Spring MVC的工作流程和表单处理能力。通过这两个典型场景,新手可以快速建立对Spring的直观认知,而有经验的开发者也能从中发现一些值得注意的细节。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与项目搭建
2.1 基础环境配置
我推荐使用以下环境组合,这也是目前企业开发中的主流选择:
- JDK 17(LTS长期支持版本)
- Spring Boot 3.1.5(内置Spring Framework 6.0.11)
- Maven 3.9.4
- IntelliJ IDEA 2023.2(社区版即可)
注意:Spring Boot 3.x要求JDK 17+,如果你仍在使用JDK 8,需要先升级JDK或降级到Spring Boot 2.7.x版本。
创建项目时,在Spring Initializr中选择这些依赖:
- Spring Web(包含Spring MVC)
- Thymeleaf(模板引擎)
- Lombok(简化POJO开发)
2.2 项目结构规划
合理的项目结构能大幅提升开发效率。这是我推荐的基础结构:
code复制src/main/java
└── com.example.demo
├── config # 配置类
├── controller # 控制器
├── service # 服务层
├── model # 数据模型
└── DemoApplication.java # 启动类
src/main/resources
├── static # 静态资源
├── templates # 模板文件
└── application.yml # 配置文件
3. 加法计算器实现
3.1 服务层设计
首先创建计算器服务接口及其实现:
java复制public interface CalculatorService {
double add(double a, double b);
}
@Service
public class BasicCalculatorService implements CalculatorService {
@Override
public double add(double a, double b) {
return a + b;
}
}
这里有几个关键点需要注意:
@Service注解将该类声明为Spring管理的Bean- 接口与实现分离是良好的设计实践
- 方法参数使用基本类型而非包装类,避免null值处理
3.2 控制器开发
控制器负责处理HTTP请求:
java复制@Controller
@RequestMapping("/calculator")
public class CalculatorController {
private final CalculatorService calculatorService;
@Autowired
public CalculatorController(CalculatorService calculatorService) {
this.calculatorService = calculatorService;
}
@GetMapping
public String showForm(Model model) {
model.addAttribute("calculation", new Calculation());
return "calculator";
}
@PostMapping("/add")
public String calculate(@ModelAttribute Calculation calculation, Model model) {
double result = calculatorService.add(
calculation.getOperand1(),
calculation.getOperand2()
);
model.addAttribute("result", result);
return "calculator";
}
}
关键设计考量:
- 使用构造器注入而非字段注入(Spring官方推荐方式)
@GetMapping和@PostMapping分别处理不同HTTP方法Calculation是包含operand1和operand2的POJO
3.3 前端页面实现
使用Thymeleaf模板引擎创建calculator.html:
html复制<form th:action="@{/calculator/add}" method="post">
<input type="number" th:field="*{operand1}" step="any" required>
<span> + </span>
<input type="number" th:field="*{operand2}" step="any" required>
<button type="submit">计算</button>
</form>
<div th:if="${result != null}">
结果: <span th:text="${result}"></span>
</div>
4. 用户登录系统实现
4.1 安全配置基础
Spring Security是处理认证授权的标准方案,我们先添加依赖:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
创建基础安全配置类:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/static/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.permitAll()
);
return http.build();
}
}
4.2 用户认证服务
实现自定义用户详情服务:
java复制@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.roles("USER")
.build();
}
}
4.3 登录控制器
处理登录相关请求:
java复制@Controller
public class LoginController {
@GetMapping("/login")
public String loginForm(Model model) {
model.addAttribute("loginRequest", new LoginRequest());
return "login";
}
@GetMapping("/home")
public String home() {
return "home";
}
}
对应的登录页面login.html:
html复制<form th:action="@{/login}" method="post">
<input type="text" th:field="*{username}" placeholder="用户名" required>
<input type="password" th:field="*{password}" placeholder="密码" required>
<button type="submit">登录</button>
</form>
<div th:if="${param.error}">用户名或密码错误</div>
5. 常见问题与调试技巧
5.1 计算器精度问题
在实际测试中,你可能会遇到这样的问题:
java复制@Test
void testAddition() {
assertEquals(0.3, calculatorService.add(0.1, 0.2));
}
这个断言会失败!因为浮点数在计算机中的表示存在精度问题。解决方案:
- 使用BigDecimal进行精确计算
- 或者在断言时允许一定的误差范围
5.2 安全配置陷阱
新手常犯的安全配置错误包括:
- 忘记放行静态资源路径(导致CSS/JS加载失败)
- 未正确配置CSRF保护(导致POST请求被拒绝)
- 密码未加密存储(必须使用PasswordEncoder)
5.3 视图解析问题
如果遇到模板引擎无法解析视图的情况,检查:
- 模板文件是否放在正确的目录(默认是resources/templates)
- 是否添加了正确的模板引擎依赖
- 控制器返回的视图名称是否与模板文件名一致(不含.html后缀)
6. 项目扩展建议
完成基础功能后,可以考虑以下增强点:
6.1 计算器功能扩展
- 添加减法、乘法、除法运算
- 实现计算历史记录
- 增加输入验证和异常处理
6.2 登录系统增强
- 添加记住我功能
- 实现密码重置流程
- 集成OAuth2第三方登录
- 添加验证码支持
6.3 架构优化
- 引入DTO进行层间数据传输
- 添加全局异常处理器
- 实现API版本控制
- 集成Swagger文档
我在实际项目中发现,很多团队在初期忽视了这些架构层面的设计,导致后期重构成本很高。即使是在这样的小demo中,也应该保持好的编码习惯。
