说实话,刚开始学Spring的时候,我跟你一样懵。满屏的注解、层层叠叠的配置,还有那些看不懂的依赖注入概念,简直让人头大。但别担心,今天我不给你讲枯燥的理论,咱们像老朋友聊天一样,把Spring这头“大象”一步步解剖清楚。我会带你从最基础的Java知识开始,一直到能独立搭建一个像样的Web项目,中间会穿插很多我踩过的坑和实用的技巧。
先别急着碰Spring,打好地基更重要
在我见过的大多数人里,有相当一部分是直接跳到Spring学习的,结果遇到任何问题都查半天,因为基础不牢。Spring说到底还是Java,它跑在JVM上,所以你的Java基本功直接决定了你学Spring的上限。
面向对象是Spring的灵魂
Spring的设计思想全是面向对象(OOP)的精髓。你得彻底理解这几个概念:
- 封装:为什么Spring要帮我们管理对象?就是因为我们不应该直接
new一个对象然后到处传参,这样耦合太高。 - 继承:Spring的很多类都是通过继承来实现功能扩展的,比如
AbstractApplicationContext。 - 多态:这是Spring最强大的地方。接口和实现分离,同一个接口可以有多种实现,Spring容器负责在运行时决定用哪个实现。
- 抽象:Spring大量使用抽象类和接口来定义规范。
举个例子,如果你不懂多态,你就无法理解为什么可以这样写代码:
@Autowired
private UserService userService;
UserService是一个接口,但你注入的可能是UserServiceImpl、UserServiceImplV2或者其他任何实现了该接口的类。Spring在后台做了多态的转换,这才是依赖注入的核心。
Java集合和泛型必须熟练
Spring内部大量使用Map、List、Set等集合。你需要非常熟悉:
HashMap的原理和常用操作ArrayList和LinkedList的区别- 泛型的用法,比如
Map<String, Object>
还有反射,这是Spring的底层基石。Spring通过反射来实例化Bean、调用方法、读取注解。你不需要成为反射专家,但至少要知道Class.forName()、getDeclaredMethod()、newInstance()这些基础用法是怎么工作的。
了解Java EE基础
Spring最初就是为了解决Java EE(企业版Java)繁琐的配置而生的。虽然现在的Spring Boot已经简化了很多,但了解以下概念还是有帮助的:
- Servlet:Web请求处理的基石
- HTTP协议:请求方法、状态码、Header等
- Maven/Gradle:构建工具,管理依赖
第一步:理解IoC容器,这是Spring的起点
什么是IoC?
IoC全称是控制反转(Inversion of Control)。听起来很玄乎,其实很简单:
以前,你需要某个对象时,自己new出来:
public class OrderService {
private UserDao userDao = new UserDaoImpl(); // 自己创建依赖
}
有了IoC,对象由Spring容器来创建和管理:
public class OrderService {
@Autowired
private UserDao userDao; // 从Spring容器获取依赖
}
控制权从你自己手里交到了Spring容器手里,所以叫”控制反转”。
Spring IoC容器的工作原理
Spring IoC容器本质上是一个巨大的”对象工厂”。它的核心组件是:
- BeanDefinition:描述Bean的配置信息,包括类名、作用域、初始化方法等
- BeanFactory:容器的核心接口,负责创建和管理Bean
- ApplicationContext:BeanFactory的子接口,提供了更多企业级功能
// 传统方式创建Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取Bean
UserService userService = context.getBean(UserService.class);
三种创建Bean的方式
方式一:XML配置(传统方式)
<!-- applicationContext.xml -->
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<bean id="userService" class="com.example.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
方式二:Java Config(现代方式)
@Configuration
public class AppConfig {
@Bean
public UserDao userDao() {
return new UserDaoImpl();
}
@Bean
public UserService userService() {
UserService service = new UserService();
service.setUserDao(userDao());
return service;
}
}
方式三:注解驱动(最常用)
@Component
public class UserDaoImpl implements UserDao {
// Spring会自动扫描并注册这个Bean
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
}
Bean的作用域
Spring Bean有几种不同的作用域,理解它们对设计系统很重要:
@Component
@Scope("singleton") // 默认,整个应用只有一个实例
public class SingletonBean {
}
@Component
@Scope("prototype") // 每次获取都创建新实例
public class PrototypeBean {
}
@Component
@Scope("request") // 每个HTTP请求创建一个实例(Web应用)
public class RequestBean {
}
@Component
@Scope("session") // 每个HTTP会话创建一个实例(Web应用)
public class SessionBean {
}
实际建议:除非你有明确需求,否则大部分Bean都用默认的singleton作用域。只有那些需要保持用户状态或者有外部连接池的场景才用其他作用域。
第二步:掌握DI依赖注入,让代码更灵活
DI和IoC的关系
很多人搞混这两个概念。其实它们是一体两面:
- IoC是设计思想:把创建对象的权力交给容器
- DI是实现手段:容器通过注入依赖来实现IoC
三种注入方式
1. 构造函数注入(推荐)
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
// 构造函数注入
@Autowired
public OrderService(OrderRepository orderRepository,
PaymentService paymentService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
}
}
优点:
- 依赖关系明确,一眼就能看出这个类依赖什么
- 可以在构造函数中做参数校验
- 不可变对象,线程安全
- 方便单元测试
2. Setter注入
@Service
public class OrderService {
private OrderRepository orderRepository;
@Autowired
public void setOrderRepository(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
}
适用场景:依赖不是必须的,或者需要动态替换。
3. 字段注入(不推荐)
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository; // 这种写法有问题
}
为什么我不推荐:
- 依赖关系隐藏在字段里,看不出来
- 无法保证不可变性
- 单元测试麻烦
- 代码可读性差
按类型注入 vs 按名称注入
// 按类型注入(默认)
@Autowired
private UserDao userDao;
// 当有多个实现类时,按名称注入
@Autowired
@Qualifier("mysqlUserDao")
private UserDao userDao;
可选注入
有些依赖不是必须的,可以用@Autowired(required = false):
@Autowired(required = false)
private CacheService cacheService; // 如果容器里没有这个Bean,不会报错
或者用Optional包装:
@Autowired
private Optional<CacheService> cacheService;
第三步:理解AOP面向切面编程,解耦横切关注点
什么是AOP?
AOP全称面向切面编程(Aspect-Oriented Programming)。
想象一下,你的应用有多个功能模块:订单服务、用户服务、支付服务等。每个服务都可能需要:
- 日志记录
- 事务管理
- 权限检查
- 性能监控
如果没有AOP,你必须在每个方法里重复写这些代码:
public class OrderServiceImpl implements OrderService {
public void createOrder(Order order) {
// 日志开始
log.info("开始创建订单");
// 权限检查
if (!hasPermission(order)) {
throw new RuntimeException("无权限");
}
// 事务开始
Transaction tx = null;
try {
tx = beginTransaction();
// 业务逻辑
orderRepository.save(order);
tx.commit();
} catch (Exception e) {
if (tx != null) tx.rollback();
throw e;
}
// 日志结束
log.info("订单创建完成");
}
}
每个服务都要写一遍,又臭又长,还容易出错。
有了AOP,我们可以把这些横切关注点抽离出来:
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
log.info("方法执行前: " + joinPoint.getSignature().getName());
}
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
log.info("方法执行后: " + joinPoint.getSignature().getName());
}
}
核心概念
| 概念 | 说明 | 示例 |
|---|---|---|
| Aspect(切面) | 横切关注点的模块化 | LoggingAspect、TransactionAspect |
| Join Point(连接点) | 程序执行的某个特定位置 | 方法调用、异常抛出 |
| Pointcut(切入点) | 匹配连接点的表达式 | execution(* com.example.*.*(..)) |
| Advice(通知) | 在特定连接点执行的动作 | @Before、@After、@Around |
| Weaving(织入) | 把切面应用到目标对象的过程 | Spring在运行时动态代理 |
五种通知类型
@Aspect
@Component
public class PerformanceAspect {
// 前置通知:方法执行前
@Before("execution(* com.example.service.*.*(..))")
public void before(JoinPoint joinPoint) {
System.out.println("方法开始前");
}
// 后置通知:方法执行后(无论是否异常)
@After("execution(* com.example.service.*.*(..))")
public void after(JoinPoint joinPoint) {
System.out.println("方法结束后");
}
// 返回通知:方法正常返回后
@AfterReturning(value = "execution(* com.example.service.*.*(..))",
returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
System.out.println("方法返回: " + result);
}
// 异常通知:方法抛出异常后
@AfterThrowing(value = "execution(* com.example.service.*.*(..))",
throwing = "exception")
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
System.out.println("方法异常: " + exception.getMessage());
}
// 环绕通知:包围方法执行
@Around("execution(* com.example.service.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 执行目标方法
long end = System.currentTimeMillis();
System.out.println("执行时间: " + (end - start) + "ms");
return result;
}
}
切入点表达式详解
Spring AOP使用AspectJ的切入点表达式语言:
execution(Modifier? return-type declaring-type? name(param-types) throws?)
常见示例:
// 匹配所有public方法
@Pointcut("execution(public * *(..))")
// 匹配com.example包及其子包下的所有方法
@Pointcut("execution(* com.example..*.*(..))")
// 匹配所有以save开头的方法
@Pointcut("execution(* save*(..))")
// 匹配参数为String的方法
@Pointcut("execution(* *(String))")
// 匹配参数个数任意但第一个参数是String的方法
@Pointcut("execution(* *(String, ..))")
// 匹配OrderService类的所有方法
@Pointcut("execution(* com.example.service.OrderService.*(..))")
AOP的实现原理
Spring AOP默认使用JDK动态代理,如果被代理类没有实现接口,则使用CGLIB。
// 有接口的情况:使用JDK动态代理
public interface UserService {
void createUser(User user);
}
@Component
public class UserServiceImpl implements UserService {
public void createUser(User user) {
// 业务逻辑
}
}
// 没有接口的情况:使用CGLIB
@Component
public class OrderService {
public void createOrder(Order order) {
// 业务逻辑
}
}
实际应用场景
1. 日志记录
@Aspect
@Component
@Slf4j
public class LogAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
log.info("开始执行: {},参数: {}", methodName, Arrays.toString(args));
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long elapsed = System.currentTimeMillis() - start;
log.info("执行完成: {},耗时: {}ms", methodName, elapsed);
return result;
}
}
2. 事务管理
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryService inventoryService;
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
// 保存订单
orderRepository.save(order);
// 扣减库存(如果库存不足会抛出异常,事务回滚)
inventoryService.decreaseStock(order.getItemId(), order.getQuantity());
}
}
3. 权限校验
@Aspect
@Component
public class SecurityAspect {
@Before("execution(* com.example.controller.*.*(..)) && @annotation(com.example.annotation.RequiresPermission)")
public void checkPermission(JoinPoint joinPoint) {
// 获取方法上的注解
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RequiresPermission annotation = method.getAnnotation(RequiresPermission.class);
// 检查权限
String permission = annotation.value();
if (!hasPermission(permission)) {
throw new AccessDeniedException("权限不足");
}
}
}
第四步:Spring MVC,构建Web应用
Spring MVC的工作原理
Spring MVC是Spring框架的Web模块,基于请求-响应模型。
核心流程:
用户请求 → DispatcherServlet(前端控制器) → HandlerMapping(处理器映射)
→ Controller(控制器) → Service(业务逻辑) → Repository(数据访问)
→ 返回ModelAndView → ViewResolver(视图解析) → 视图 → 响应给用户
配置Spring MVC
方式一:Java Config(推荐)
@Configuration
@EnableWebMvc
@ComponentScan("com.example")
public class WebConfig implements WebMvcConfigurer {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
}
方式二:Spring Boot自动配置
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Spring Boot会自动配置Spring MVC,你只需要关注业务逻辑。
控制器
基本控制器
@Controller
public class UserController {
// 处理GET请求
@GetMapping("/users/{id}")
public String getUser(@PathVariable Long id, Model model) {
User user = userService.findById(id);
model.addAttribute("user", user);
return "user/detail"; // 返回视图名称
}
// 处理POST请求
@PostMapping("/users")
public String createUser(@Valid @RequestBody User user,
BindingResult result) {
if (result.hasErrors()) {
return "user/create";
}
userService.save(user);
return "redirect:/users/" + user.getId();
}
// 处理JSON请求(RESTful API)
@RestController
public class ApiUserController {
@GetMapping("/api/users")
public List<User> getUsers() {
return userService.findAll();
}
@PostMapping("/api/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
}
常用注解
| 注解 | 用途 | 示例 |
|---|---|---|
@Controller |
标记控制器类 | @Controller |
@RestController |
标记RESTful控制器(返回JSON) | @RestController |
@GetMapping |
处理GET请求 | @GetMapping("/users") |
@PostMapping |
处理POST请求 | @PostMapping("/users") |
@PutMapping |
处理PUT请求 | @PutMapping("/users/{id}") |
@DeleteMapping |
处理DELETE请求 | @DeleteMapping("/users/{id}") |
@RequestMapping |
