从配置文件迷茫到项目实战Spring框架学习路径帮你掌握企业级Java开发核心技术

嘿,朋友,是不是也被Spring框架折腾过?我第一次接触Spring那会儿,满屏的XML配置文件看得我头晕眼花,什么applicationContext.xml<bean>标签、<property>注入,感觉像是在解一道没有提示的谜题。

如果你也经历过这段迷茫期,那这篇文章就是为你准备的。我会带你从零开始,一步步走出配置的迷雾,最终能够独立搭建企业级项目。咱们不废话,直接上干货。

Spring是什么?用大白话先搞懂核心概念

Spring框架本质上就是一个大管家。想象一下你要开一家餐厅,需要采购、烹饪、服务、收银等各个环节配合,如果你亲力亲为,累死也管不好。Spring就是那个帮你统筹协调所有模块的大管家,你只需要关注业务本身。

在Spring里,有三个最核心的概念需要你先理解透:

IoC(控制反转) 这个概念听起来玄乎,但其实很简单。以前你要创建一个对象,得自己用new关键字去实例化,比如:

UserService userService = new UserServiceImpl();

这样写有什么问题呢?耦合太重了——UserServiceUserServiceImpl绑死在一起,将来换实现类就得改代码。IoC就是把这个权利交给Spring容器,你只需要声明”我需要谁”,Spring会自动帮你创建并注入,代码变成这样:

@Autowired
private UserService userService;

DI(依赖注入) 其实是IoC的一种实现方式。Spring在运行时把依赖的对象”注入”到你的类里,常见的注入方式有三种:构造器注入、setter注入、注解注入。我们后面会详细聊每种方式的优缺点。

AOP(面向切面编程) 这个概念也很重要。简单说,就是把你代码中重复的逻辑抽离出来,统一处理。比如日志记录、事务管理、权限校验这些,每个方法都需要,但写起来又很麻烦。用AOP的话,你只需要写一次,Spring会自动在所有需要的方法前后执行这些逻辑。

// 这是一个简单的AOP切面示例
@Aspect
@Component
public class LogAspect {
    
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("方法开始执行:" + joinPoint.getSignature().getName());
    }
}

理解这三个概念,你就掌握了Spring的精髓,后面的学习会轻松很多。

第一阶段:搭建环境,从HelloWorld开始

很多初学者一开始就陷入复杂的配置中,其实没必要。我们用最简单的方式,先让Spring跑起来,建立信心。

创建第一个Spring项目

我用IntelliJ IDEA来演示,这是目前最流行的Java开发工具。

第一步,新建项目,选择Maven项目。在pom.xml中添加Spring依赖:

<dependencies>
    <!-- Spring核心容器 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.23</version>
    </dependency>
    
    <!-- 日志依赖 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.36</version>
    </dependency>
</dependencies>

第二步,创建一个简单的接口和实现类:

// 接口定义
public interface GreetingService {
    String sayHello(String name);
}

// 实现类
@Component
public class GreetingServiceImpl implements GreetingService {
    
    @Override
    public String sayHello(String name) {
        return "Hello, " + name + "! 欢迎来到Spring世界";
    }
}

注意那个@Component注解,它告诉Spring:”我是一个组件,请帮我管理。”

第三步,创建Spring配置类。现在Spring官方推荐用Java Config而不是XML:

@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
    // 这里可以添加更多的Bean配置
    // 比如数据源、事务管理等
}

第四步,写一个测试类验证Spring是否工作:

public class Main {
    public static void main(String[] args) {
        // 启动Spring容器
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        
        // 从容器中获取Bean
        GreetingService service = context.getBean(GreetingService.class);
        
        // 调用方法
        String message = service.sayHello("小明");
        System.out.println(message);
    }
}

当你看到控制台输出”Hello, 小明! 欢迎来到Spring世界”时,恭喜你,你已经成功跑通了第一个Spring应用!

这个过程其实不难对吧?关键在于理解Spring容器的工作方式——它像一个工厂,你告诉它去哪里找组件,它就帮你创建和管理这些对象。

第二阶段:深入理解Bean管理,告别XML配置

很多老项目还在用XML配置Bean,初学者看到那些<bean>标签就头大。其实现在Spring官方强烈推荐用注解方式,简单直观。不过了解XML配置还是很有必要的,因为面试和工作维护老项目时经常会碰到。

注解方式创建Bean

Spring提供了四个常用的注解:

  • @Component:通用组件,用于普通Bean
  • @Service:服务层Bean,语义更清晰
  • @Repository:数据访问层Bean,同时具备异常转换功能
  • @Controller:控制层Bean,用于Web MVC
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public User findById(Long id) {
        return userRepository.findById(id);
    }
    
    public void save(User user) {
        userRepository.save(user);
    }
}

依赖注入的三种方式

依赖注入是Spring最核心的功能,掌握了它,你就掌握了Spring的半壁江山。

构造器注入是最推荐的方式,因为它能确保依赖不为null:

@Service
public class OrderService {
    
    private final PaymentService paymentService;
    private final InventoryService inventoryService;
    
    // 构造器注入,参数越多优势越明显
    @Autowired
    public OrderService(PaymentService paymentService, 
                       InventoryService inventoryService) {
        this.paymentService = paymentService;
        this.inventoryService = inventoryService;
    }
    
    public void placeOrder(Order order) {
        // 业务逻辑...
        paymentService.processPayment(order);
        inventoryService.reduceStock(order);
    }
}

setter注入适合可选依赖:

@Service
public class ReportService {
    
    private DataSource dataSource;
    private EmailService emailService;
    
    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    
    @Autowired
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }
}

字段注入最简单但可读性稍差:

@Service
public class NotificationService {
    
    @Autowired
    private SmsService smsService;
    
    @Autowired
    private EmailService emailService;
}

我个人的建议是:核心依赖用构造器注入,可选依赖用setter注入,尽量不要用字段注入。这样代码更健壮,也更容易测试。

作用域和生命周期

Spring Bean默认是单例的,也就是整个应用只有一个实例。但有些场景需要多实例,比如保存用户会话信息:

@Scope("prototype")
@Component
public class SessionContext {
    private String userId;
    private String username;
    // getter and setter...
}

常用的作用域有:

  • singleton:单例,全局只有一个(默认)
  • prototype:原型,每次获取都创建新实例
  • request:请求作用域,每个HTTP请求创建一个实例
  • session:会话作用域,每个HTTP会话创建一个实例

了解Bean的生命周期也很重要:创建 → 属性赋值 → 初始化 → 使用 → 销毁。你可以用@PostConstruct@PreDestroy注解来定义初始化和销毁逻辑:

@Component
public class DatabaseConnection {
    
    @PostConstruct
    public void init() {
        System.out.println("数据库连接初始化");
        connectToDatabase();
    }
    
    @PreDestroy
    public void destroy() {
        System.out.println("数据库连接关闭");
        closeConnection();
    }
}

第三阶段:Spring MVC,构建Web应用

学完了基础,现在让我们来构建一个真正的Web应用。Spring MVC是Spring框架的Web模块,它的核心思想是前端控制器模式。

项目结构搭建

用Spring Initializr快速创建项目是个好习惯,网址是start.spring.io。选择Spring Web、Spring Data JPA、MySQL Driver等依赖,生成项目后导入IDE。

标准的Spring Boot项目结构是这样的:

src/main/java/com/example/demo/
├── DemoApplication.java          // 启动类
├── config/                       // 配置类
├── controller/                   // 控制器层
├── service/                      // 服务层
├── repository/                   // 数据访问层
├── entity/                       // 实体类
└── dto/                          // 数据传输对象

第一个Controller

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    // 查询所有用户
    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }
    
    // 根据ID查询
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.findById(id);
    }
    
    // 创建用户
    @PostMapping
    public User createUser(@RequestBody CreateUserDTO dto) {
        return userService.create(dto);
    }
    
    // 更新用户
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, 
                          @RequestBody UpdateUserDTO dto) {
        return userService.update(id, dto);
    }
    
    // 删除用户
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.delete(id);
    }
}

注意几个关键点:@RestController等价于@Controller@ResponseBody,表示这个类处理API请求并直接返回JSON数据。@RequestMapping用于定义请求映射,@GetMapping@PostMapping等是它的变体,语义更清晰。

请求处理流程

理解Spring MVC的请求处理流程很重要。当一个HTTP请求进来时:

  1. DispatcherServlet(前端控制器)接收请求
  2. HandlerMapping根据URL找到对应的Controller方法
  3. Controller处理方法,调用Service
  4. Service处理业务逻辑,调用Repository
  5. Repository访问数据库
  6. 数据逐层返回,最终由DispatcherServlet返回给客户端

这个流程看着复杂,其实Spring都帮你封装好了。你只需要关注Controller和Service层的业务逻辑。

第四阶段:Spring Data JPA,轻松操作数据库

数据库操作是Web应用的核心,Spring Data JPA帮你简化了大部分工作。

定义实体类

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 50)
    private String name;
    
    @Column(unique = true, nullable = false)
    private String email;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    // 构造函数、getter、setter...
    
    @PrePersist
    public void prePersist() {
        this.createdAt = LocalDateTime.now();
    }
}

@Entity表示这是一个JPA实体,@Table指定表名,@Column指定列名和约束。@PrePersist表示在保存前执行的方法。

创建Repository

public interface UserRepository extends JpaRepository<User, Long> {
    
    // 自动实现:根据email查询
    User findByEmail(String email);
    
    // 自动实现:根据name模糊查询
    List<User> findByNameContaining(String keyword);
    
    // 自定义查询,使用JPQL
    @Query("SELECT u FROM User u WHERE u.createdAt > :date")
    List<User> findUsersAfterDate(@Param("date") LocalDateTime date);
}

这就是Spring Data JPA的魔力——你只需要定义接口,不用写任何实现,Spring会自动帮你生成代码。方法名遵循一定的命名规范,比如findByXxxcountByXxxdeleteByXxx等。

配置数据源

application.yml中配置数据库连接:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  
  jpa:
    hibernate:
      ddl-auto: update  # 自动创建或更新表结构
    show-sql: true      # 打印SQL语句,方便调试
    properties:
      hibernate:
        format_sql: true

第五阶段:事务管理,保证数据一致性

事务管理是企业级开发的重中之重。想象一下用户下单的场景:需要扣库存、创建订单、扣减余额,这三个操作要么全部成功,要么全部失败。这就是事务的典型应用场景。

声明式事务

Spring提供了声明式事务,你只需要加一个注解:

@Service
public class OrderService {
    
    @Autowired
    private InventoryRepository inventoryRepository;
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private AccountRepository accountRepository;
    
    // 添加@Transactional,Spring会自动管理事务
    @Transactional(rollbackFor = Exception.class)
    public Order placeOrder(Long userId, Long itemId, int quantity) {
        // 1. 扣减库存
        Inventory inventory = inventoryRepository.findById(itemId)
            .orElseThrow(() -> new RuntimeException("商品不存在"));
        
        if (inventory.getStock() < quantity) {
            throw new RuntimeException("库存不足");
        }
        inventory.setStock(inventory.getStock() - quantity);
        inventoryRepository.save(inventory);
        
        // 2. 扣减余额
        Account account = accountRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        int totalAmount = inventory.getPrice() * quantity;
        if (account.getBalance() < totalAmount) {
            throw new RuntimeException("余额不足");
        }
        account.setBalance(account.getBalance() - totalAmount);
        accountRepository.save(account);
        
        // 3. 创建订单
        Order order = new Order();
        order.setUserId(userId);
        order.setItemId(itemId);
        order.setQuantity(quantity);
        order.setTotalAmount(totalAmount);
        order.setStatus("PENDING");
        return orderRepository.save(order);
        
        // 如果任何一步抛出异常,前面的操作都会回滚
    }
}

关键点:rollbackFor = Exception.class表示遇到任何异常都回滚。默认情况下,只有运行时异常才会回滚, checked异常不会回滚,所以这个配置很重要。

事务传播行为

有时候你需要在已有事务的方法中调用另一个事务方法,这时就要考虑传播行为:

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    // 如果有事务就加入,没有就新建
    methodB();
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    // 挂起当前事务,新建一个独立事务
    // methodB成功或失败不影响methodA的事务
}

常用的传播行为有:

  • REQUIRED:默认值,有事务就加入,没有就新建
  • REQUIRES_NEW:新建事务,挂起当前事务
  • SUPPORTS:有事务就加入,没有就以非事务方式执行
  • NOT_SUPPORTED:以非事务方式执行,挂起当前事务
  • MANDATORY:必须有事务,否则抛异常
  • NEVER:必须没有事务,否则抛异常
  • NESTED:嵌套事务,类似SAVEPOINT

第六阶段:实战项目,从零到部署

学完了各个模块,现在让我们把它们组合起来,做一个完整的项目。我们来做一个人人网社交系统,包含用户管理、日志系统、权限控制等核心功能。

项目架构设计

src/main/java/com/example/social/
├── SocialApplication.java          // 启动类
├── config/
│   ├── SecurityConfig.java         // 安全配置
│   ├── DataSourceConfig.java       // 数据源配置
│   └── SwaggerConfig.java          // API文档配置
├── controller/
│   ├── UserController.java         // 用户控制器
│   ├── PostController.java         // 帖子控制器
│   └── CommentController.java      // 评论控制器
├── service/
│   ├── UserService.java
│   ├── PostService.java
│   └── CommentService.java
├── repository/
│   ├── UserRepository.java
│   ├── PostRepository.java
│   └── CommentRepository.java
├── entity/
│   ├── User.java
│   ├── Post.java
│   └── Comment.java
├── dto/
│   ├── CreateUserDTO.java
│   ├── UpdateUserDTO.java
│   └── PostDTO.java
├── aspect/
│   └── LogAspect.java              // 日志切面
└── exception/
    ├── GlobalExceptionHandler.java // 全局异常处理
    └── BusinessException.java      // 业务异常

实体类设计

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(unique = true, nullable = false)
    private String username;
    
    @Column(nullable = false)
    private String password;
    
    @Column(unique = true)
    private String email;
    
    private String avatar;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    // 好友关系,使用JoinTable实现多对多
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
        name = "user_friends",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "friend_id")
    )
    private Set<User> friends = new HashSet<>();
    
    // 帖子关系
    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL, 
               orphanRemoval = true)
    private List<Post> posts = new ArrayList<>();
    
    @PrePersist
    public void prePersist() {
        this.createdAt = LocalDateTime.now();
    }
}
@Entity
@Table(name = "posts")
public class Post {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(columnDefinition = "TEXT")
    private String content;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    // 关联用户
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id", nullable = false)
    private User author;
    
    // 评论关系
    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL,
               orphanRemoval = true)
    private List<Comment> comments = new ArrayList<>();
    
    @PrePersist
    public void prePersist() {
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    @PreUpdate
    public void preUpdate() {
        this.updatedAt = LocalDateTime.now();
    }
}

Service层实现

@Service
public class PostService {
    
    @Autowired
    private PostRepository postRepository;
    
    @Autowired
    private UserRepository userRepository;
    
    // 发布帖子
    @Transactional
    public Post createPost(Long authorId, String content) {
        User author = userRepository.findById(authorId)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        
        Post post = new Post();
        post.setContent(content);
        post.setAuthor(author);
        
        return postRepository.save(post);
    }
    
    // 查询用户的所有帖子(包含评论数)
    public Page<PostDTO> getUserPosts(Long userId, int page, int size) {
        Page<Post> posts = postRepository.findByAuthorId(userId, 
            PageRequest.of(page, size, Sort.by("createdAt").descending()));
        
        return posts.map(post -> {
            PostDTO dto = new PostDTO();
            dto.setId(post.getId());
            dto.setContent(post.getContent());
            dto.setCreatedAt(post.getCreatedAt());
            dto.setAuthorName(post.getAuthor().getUsername());
            dto.setCommentCount(post.getComments().size());
            return dto;
        });
    }
    
    // 删除帖子(级联删除评论)
    @Transactional
    public void deletePost(Long postId) {
        Post post = postRepository.findById(postId)
            .orElseThrow(() -> new BusinessException("帖子不存在"));
        postRepository.delete(post);
    }
}

切面编程:统一的日志记录

@Aspect
@Component
public class LogAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
    
    // 匹配所有Service层的方法
    @Pointcut("execution(* com.example.social.service.*.*(..))")
    public void serviceLayer() {}
    
    @Before("serviceLayer()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        logger.info("方法开始: {}, 参数: {}", methodName, Arrays.toString(args));
    }
    
    @AfterReturning(value = "serviceLayer()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        logger.info("方法结束: {}, 结果: {}", methodName, result);
    }
    
    @AfterThrowing(value = "serviceLayer()", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        logger.error("方法异常: {}, 异常信息: {}", methodName, ex.getMessage());
    }
}

这个切面会自动记录所有Service方法执行的日志,包括参数、返回值和异常信息。你不需要在每个方法里手动写日志代码,这就是AOP的威力。

全局异常处理

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Map<String, Object>> handleBusinessException(
            BusinessException ex) {
        Map<String, Object> result = new HashMap<>();
        result.put("success", false);
        result.put("code", ex.getCode());
        result.put("message", ex.getMessage());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, Object>> handleException(Exception ex) {
        Map<String, Object> result = new HashMap<>();
        result.put("success", false);
        result.put("code", 500);
        result.put("message", "系统内部错误");
        logger.error("系统异常", ex);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
    }
}

这样无论发生什么异常,客户端都能收到统一的JSON格式响应,而不是看到一堆难看的堆栈信息。

第七阶段:性能优化和生产部署

应用跑起来了,但离生产还差得远。我们需要考虑性能优化和部署问题。

缓存优化

Spring提供了统一的缓存抽象,配合Redis等缓存中间件效果显著:

@Service
public class UserService {
    
    @Cacheable(value = "users", key = "#id")
    public User findById(Long id) {
        // 只有缓存未命中时才会执行这个方法
        return userRepository.findById(id).orElse(null);
    }
    
    @CacheEvict(value = "users", key = "#id")
    public void delete(Long id) {
        userRepository.deleteById(id);
    }
    
    @CacheEvict(value = "users", allEntries = true)
    public void refreshCache() {
        // 清空所有缓存
    }
}

启动类记得加@EnableCaching注解。配置Redis连接:

spring:
  cache:
    type: redis
  redis:
    host: localhost
    port: 6379
    password: yourpassword

数据库优化

一些基本的数据库优化技巧:

// 1. 使用@Query指定只查询需要的字段,避免N+1问题
@Query("SELECT p FROM Post p JOIN FETCH p.author WHERE p.id = :id")
Optional<Post> findByIdWithAuthor(@Param("id") Long id);

// 2. 使用分页查询,避免一次性加载大量数据
Page<Post> findByAuthorId(Long authorId, Pageable pageable);

// 3. 批量操作
@Modifying
@Query("UPDATE Post p SET p.status = :status WHERE p.id IN :ids")
int updateStatusBatch(@Param("ids") List<Long> ids, 
                     @Param("status") String status);

打包和部署

Spring Boot项目打包很简单:

# 打包成可执行的JAR
mvn clean package -DskipTests

# 运行应用
java -jar target/social-1.0.0.jar

# 指定端口运行
java -jar target/social-1.0.0.jar --server.port=8080

# 指定配置文件
java -jar target/social-1.0.0.jar --spring.profiles.active=prod

配置文件分环境管理,这是好习惯:

# application.yml(公共配置)
spring:
  profiles:
    active: dev

---
# application-dev.yml(开发环境)
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/social_dev
    username: root
    password: devpassword

---
# application-prod.yml(生产环境)
spring:
  datasource:
    url: jdbc:mysql://prod-server:3306/social
    username: prod_user
    password: prod_password

学习路径总结

好了,我们来梳理一下整个学习路径:

入门阶段(1-2周):理解IoC、DI、AOP三大核心概念,能用注解方式创建和管理Bean,跑通一个HelloWorld项目。

基础阶段(2-4周):掌握Spring MVC的Controller开发,理解请求处理流程,能编写RESTful API。学习Spring Data JPA,完成数据库操作。

进阶阶段(4-8周):深入学习事务管理,理解传播行为和隔离级别。掌握AOP的实际应用,编写切面代码。学习缓存、日志、异常处理等企业级功能。

实战阶段(8-12周):独立完成一个完整的项目,涵盖用户模块、业务模块、权限控制、性能优化等。学习部署和运维基础知识。

学习过程中有几个常见的坑要注意:

  1. 不要过早优化:先让程序跑起来,再考虑性能问题
  2. 理解优先于记忆:不要死记注解的用法,要理解背后的原理
  3. 多动手实践:看十遍不如写一遍,亲手搭一遍项目比看十遍教程有用
  4. 关注官方文档:Spring官方文档是最好的学习资源,有问题先看文档

记住,Spring是一个庞大的生态体系,上面介绍的内容只是冰山一角。但掌握了这些核心概念和常用功能,你就有了继续深入学习的基础。企业级开发中,Spring几乎是标配,学会了它,你的Java开发之路会宽广很多。

有什么问题随时交流,学习路上不孤单。加油!