01.Spring 框架的演化:从 XML 配置到注解驱动

Spring 框架的演化:从 XML 配置到注解驱动

本文参考自: https://www.pdai.tech/md/spring/spring-x-framework-helloworld.html

Spring 框架演化历程

Spring 框架自诞生以来经历了显著的演变,主要体现在配置方式的变革上。这一演化过程从 XML 配置、XML+注解混合配置、Java 配置到纯注解配置,每一步都使开发变得更加简洁高效。通过分析 AnnotationConfigApplicationContext 的参数变化,我们可以清晰地看到这一演进过程。

从配置方式角度看 Spring 演化

为了直观展示 Spring 框架的演化,我们将使用一个简单的用户管理系统作为贯穿始终的示例。该系统包含以下组件:

  1. User:用户实体类
  2. UserDao:数据访问层接口及实现
  3. UserService:业务服务层接口及实现

这样,我们可以清晰地观察到相同业务逻辑在不同配置方式下的实现差异。

共用的基础类

为了便于理解,先看一下贯穿所有示例的基础类:

// 用户实体类 使用lombok注解 会自动生成getter和setter等方法
@Data
public class User {
    private Long id;
    private String username;
    private String email;

    // 构造函数、getter和setter方法略
}

// 数据访问层接口
public interface UserDao {
    User findById(Long id);
    List<User> findAll();
    void save(User user);
}

// 服务层接口
public interface UserService {
    User getUserById(Long id);
    List<User> getAllUsers();
    void createUser(User user);
}

1. XML 配置时代 (Spring 1.x - Spring 2.x 早期)

在 Spring 最初的版本中,所有配置都必须通过 XML 文件完成,包括 Bean 定义、依赖注入、AOP 配置等。这种方式将配置与代码完全分离。

UserDaoImpl 实现类

public class UserDaoImpl implements UserDao {
    // 模拟数据库操作
    private Map<Long, User> userDb = new HashMap<>();

    @Override
    public User findById(Long id) {
        return userDb.get(id);
    }

    @Override
    public List<User> findAll() {
        return new ArrayList<>(userDb.values());
    }

    @Override
    public void save(User user) {
        userDb.put(user.getId(), user);
    }
}

UserServiceImpl 实现类

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    // setter方法,用于XML中的依赖注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

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

    @Override
    public List<User> getAllUsers() {
        return userDao.findAll();
    }

    @Override
    public void createUser(User user) {
        userDao.save(user);
    }
}

XML 配置示例


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

     
    
    <bean id="userDao" class="com.example.dao.UserDaoImpl" />

    
    
    <bean id="userService" class="com.example.service.UserServiceImpl">
        <property name="userDao" ref="userDao" />
    bean>

    
    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        
    bean>
beans>

应用上下文初始化

// 通过XML配置文件初始化上下文
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 通过Bean ID获取Bean
UserService userService = (UserService) context.getBean("userService");

// 使用服务
User user = userService.getUserById(1L);

特点

  • 配置与代码完全分离
  • 所有 Bean 关系在 XML 中显式定义
  • XML 文件可能变得庞大而难以管理
  • 无类型安全检查,容易出现拼写错误
  • 重构代码时需要同步修改 XML

2. XML+注解混合配置 (Spring 2.x 后期 - Spring 3.x 初期)

随着 Spring 2.5 的发布,引入了组件扫描和注解支持,但仍需在 XML 中启用这些功能。这种混合方式既保留了 XML 的集中配置能力,又利用了注解的便捷性。

UserDaoImpl 带注解的实现

@Repository
public class UserDaoImpl implements UserDao {
    // 模拟数据库操作
    private Map<Long, User> userDb = new HashMap<>();

    @Override
    public User findById(Long id) {
        return userDb.get(id);
    }

    @Override
    public List<User> findAll() {
        return new ArrayList<>(userDb.values());
    }

    @Override
    public void save(User user) {
        userDb.put(user.getId(), user);
    }
}

UserServiceImpl 带注解的实现

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

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

    @Override
    public List<User> getAllUsers() {
        return userDao.findAll();
    }

    @Override
    public void createUser(User user) {
        userDao.save(user);
    }
}

XML 配置启用注解


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">

    
    <context:annotation-config />

    
    
    <context:component-scan base-package="com.example" />

    
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/userdb" />
        
    bean>

    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    bean>
beans>

应用上下文初始化

// 仍使用XML应用上下文,但内部Bean使用了注解定义
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 可以按类型获取Bean,无需知道具体的Bean ID
UserService userService = context.getBean(UserService.class);

// 使用服务
List<User> users = userService.getAllUsers();

特点

  • 减少了 XML 配置量
  • 组件和依赖关系通过注解在类上直接表达
  • 业务代码与配置部分融合
  • 仍然需要 XML 启用注解功能
  • 适合逐步从 XML 迁移到注解的过渡阶段

3. Java 配置方式 (Spring 3.x)

Spring 3.0 引入了@Configuration 和@Bean 注解,使得开发者可以完全使用 Java 代码进行配置,不再依赖 XML。这种方式保留了集中配置的思想,但使用 Java 代码增强了类型安全。

UserDaoImpl 实现类

// 可以不带注解,由配置类负责实例化
public class UserDaoImpl implements UserDao {
    // 模拟数据库操作
    private Map<Long, User> userDb = new HashMap<>();

    @Override
    public User findById(Long id) {
        return userDb.get(id);
    }

    @Override
    public List<User> findAll() {
        return new ArrayList<>(userDb.values());
    }

    @Override
    public void save(User user) {
        userDb.put(user.getId(), user);
    }
}

UserServiceImpl 实现类

// 可以不带注解,由配置类负责实例化
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    // 构造器注入
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

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

    @Override
    public List<User> getAllUsers() {
        return userDao.findAll();
    }

    @Override
    public void createUser(User user) {
        userDao.save(user);
    }
}

Java 配置类

@Configuration
public class AppConfig {

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

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

    @Bean
    public UserService userService() {
        // 显式注入依赖
        return new UserServiceImpl(userDao());
    }

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

关于 UserServiceImpl 是否需要注解的说明

在纯 Java 配置方式中,有两种处理 UserServiceImpl 的方法:

  1. 不使用组件注解:当使用 @Bean 方法在配置类中显式创建和配置 UserServiceImpl 实例时,该类本身不需要添加 @Service 或其他组件注解。配置类中的 @Bean 方法已经向 Spring 容器注册了这个 Bean。

  2. 混合使用组件扫描:如果同时启用了组件扫描(@ComponentScan),则可以在 UserServiceImpl 类上添加 @Service 注解,并使用 @Autowired 进行依赖注入,而不需要在配置类中显式定义 @Bean 方法。

@Configuration
@ComponentScan("com.example")  // 启用组件扫描
public class AppConfig {
    @Bean
    public DataSource dataSource() {
        // 数据源配置
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/userdb");
        return dataSource;
    }
}

@Service  // 通过组件扫描发现并注册为 Bean
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    // 业务方法实现
    @Override
    public User getUserById(Long id) {
        return userDao.findById(id);
    }

    // 其他方法...
}

应用上下文初始化

// 使用基于Java配置的应用上下文
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);

// 使用服务
userService.createUser(new User(1L, "john", "[email protected]"));

特点

  • 完全不需要 XML 配置
  • 配置代码具有类型安全性
  • 可以使用 Java 的所有功能(条件逻辑、循环等)
  • 配置集中在配置类中,便于管理
  • IDE 提供更好的支持(代码补全、重构)
  • 允许编程方式构建配置

4. 纯注解驱动 (Spring 4.x - Spring 5.x+)

Spring 4.x 进一步增强了注解驱动功能。通过组合注解和条件化配置等特性使配置更加精简。Spring Boot 的出现则将注解驱动发挥到极致,通过自动配置大幅减少了样板代码。

4.1 基于配置类的组件扫描

早期的纯注解驱动方式仍然需要一个配置类作为入口:

带注解的 UserDaoImpl

@Repository
public class UserDaoImpl implements UserDao {
    // 使用JdbcTemplate进行数据库操作
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public User findById(Long id) {
        return jdbcTemplate.queryForObject(
            "SELECT id, username, email FROM users WHERE id = ?",
            new Object[]{id},
            (rs, rowNum) -> new User(
                rs.getLong("id"),
                rs.getString("username"),
                rs.getString("email")
            )
        );
    }

    @Override
    public List<User> findAll() {
        return jdbcTemplate.query(
            "SELECT id, username, email FROM users",
            (rs, rowNum) -> new User(
                rs.getLong("id"),
                rs.getString("username"),
                rs.getString("email")
            )
        );
    }

    @Override
    public void save(User user) {
        jdbcTemplate.update(
            "INSERT INTO users (id, username, email) VALUES (?, ?, ?)",
            user.getId(), user.getUsername(), user.getEmail()
        );
    }
}

带注解的 UserServiceImpl

@Service
@Transactional
public class UserServiceImpl implements UserService {
    private final UserDao userDao;

    // 构造器注入
    // 依赖于UserDao的Bean 使用@Autowired注解 会自动将UserDao的Bean注入到UserServiceImpl中
    @Autowired // Spring 4.3+可以省略此注解
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    @Transactional(readOnly = true)
    public User getUserById(Long id) {
        return userDao.findById(id);
    }

    @Override
    @Transactional(readOnly = true)
    public List<User> getAllUsers() {
        return userDao.findAll();
    }

    @Override
    public void createUser(User user) {
        userDao.save(user);
    }
}

组件扫描配置类

@Configuration
@ComponentScan("com.example")
@EnableTransactionManagement
public class AppConfig {
    // 基础设施Bean的配置
    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schema.sql")
            .build();
    }

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

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

应用上下文初始化

// 直接使用配置类初始化上下文
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);

// 使用服务
User user = new User(1L, "alice", "[email protected]");
userService.createUser(user);
4.2 完全基于注解的应用入口(无独立配置类)

随着注解驱动的成熟,Spring 允许直接在应用入口类上添加必要的注解,无需独立的配置类:

// 应用入口类直接包含配置和启动
@Configuration
@ComponentScan("com.example")
@EnableTransactionManagement
public class Application {
    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schema.sql")
            .build();
    }

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

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

    public static void main(String[] args) {
        // 应用类自身作为配置源
        ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        UserService userService = context.getBean(UserService.class);

        // 使用服务创建用户
        User user = new User(1L, "bob", "[email protected]");
        userService.createUser(user);

        // 查询并显示所有用户
        List<User> users = userService.getAllUsers();
        users.forEach(u -> System.out.println(u.getUsername()));
    }
}

这种方式将应用入口和配置合二为一,进一步简化了应用结构。组件还是通过各自的注解(@Service、@Repository 等)被自动发现和注册。

4.3 直接使用包路径的注解配置方式

Spring 3.1 以后,AnnotationConfigApplicationContext 还支持直接传入包路径字符串的方式,这比传入配置类更加简洁:

public class Application {
    public static void main(String[] args) {
        // 直接使用包路径初始化上下文,自动扫描该包及其子包中的组件
        ApplicationContext context = new AnnotationConfigApplicationContext("com.example");
        UserService userService = context.getBean(UserService.class);

        // 使用服务
        User user = new User(1L, "charlie", "[email protected]");
        userService.createUser(user);
    }
}

在这种方式下,你需要确保所有必要的组件都带有适当的注解,而且基础设施 Bean(如 DataSource、TransactionManager 等)也需要在组件中定义:

@Configuration
public class DataConfig {
    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schema.sql")
            .build();
    }

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

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

可以同时扫描多个包:

ApplicationContext context = new AnnotationConfigApplicationContext(
    "com.example.service", "com.example.repository", "com.example.config");
4.4 Spring Boot 方式

Spring Boot 通过@SpringBootApplication 注解进一步简化了配置,这个注解组合了@Configuration、@ComponentScan 和@EnableAutoConfiguration:

@SpringBootApplication
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

Spring Boot 的核心理念是"约定优于配置",它自动配置许多组件。例如,在 application.properties 中配置数据源:

# application.properties
spring.datasource.url=jdbc:h2:mem:userdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

控制器直接处理请求:

@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    // 构造器注入
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUserById(id);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void createUser(@RequestBody User user) {
        userService.createUser(user);
    }
}

业务服务和数据访问层的代码与前面的例子类似,只是加上了 Spring Boot 特有的一些功能:

@Service
public class UserServiceImpl implements UserService {
    private final UserRepository userRepository;

    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // 实现方法...
}

// 使用Spring Data JPA简化数据访问
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // 无需实现方法,Spring Data自动提供
}

// 使用JPA注解的实体类
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String email;

    // 构造函数、getter和setter
}
4.5 纯注解驱动的依赖注入方式比较

随着 Spring 版本的演进,依赖注入方式也在变化:

  1. 字段注入(最早普及的方式):
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    // 业务方法...
    @Override
    public User getUserById(Long id) {
        return userDao.findById(id);
    }
}
  1. setter 方法注入
@Service
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    // 业务方法...
    @Override
    public User getUserById(Long id) {
        return userDao.findById(id);
    }
}
  1. 构造器注入(Spring 4.3+推荐的方式):
@Service
public class UserServiceImpl implements UserService {
    private final UserDao userDao;

    // 单一构造器时Spring 4.3+自动注入,无需@Autowired
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    // 业务方法...
    @Override
    public User getUserById(Long id) {
        return userDao.findById(id);
    }
}

构造器注入的优点:

  • 可以将依赖声明为 final,确保不变性
  • 确保依赖在对象创建时就已注入完成
  • 更易于单元测试
  • 避免了循环依赖问题

特点

  • 几乎所有配置都通过注解完成
  • 组件自动发现和自动装配
  • 约定优于配置,减少显式配置
  • 注解可以组合使用,创建自定义语义
  • Spring Boot 通过条件化配置进一步简化
  • 代码更简洁、更直观
  • 应用入口类可直接作为配置源,无需独立配置类

各配置方式的对比

配置方面 XML 配置 XML+注解混合 Java 配置 纯注解驱动
配置位置 外部 XML 文件 XML 文件+类的注解 Java 配置类 分散在各组件类中的注解
类型安全 无类型安全,易出错 部分类型安全 完全类型安全 完全类型安全
重构支持 差,修改类需手动更新 XML 较好,但 XML 部分仍需手动更新 良好,重构工具可以检测 优秀,自动跟随代码重构
配置复杂度 高,需要大量 XML 标签 中等,混合使用 XML 和注解 中等,Java 代码但集中 低,分散在业务代码中
运行时验证 启动时验证 启动时验证 编译时部分验证+启动时验证 编译时部分验证+启动时验证
适用场景 遗留系统,第三方集成 过渡期系统,部分现代化 需要编程式配置的复杂场景 现代应用,微服务
Bean 获取方式 主要通过名称 名称或类型 主要通过类型 主要通过类型
学习曲线 陡峭,需要学习 XML Schema 中等,需要了解 XML 和注解 平缓,熟悉 Java 即可 平缓,但需要了解各种注解
测试便利性 较差,需模拟 XML 环境 中等 良好,可以直接测试配置类 优秀,组件可独立测试

AnnotationConfigApplicationContext 参数演化的意义

AnnotationConfigApplicationContext 的参数变化反映了 Spring 框架配置方式的演进:

  • 从字符串路径到 Java 类:从 ClassPathXmlApplicationContext 的 XML 文件路径字符串,到 AnnotationConfigApplicationContext 的 Java 配置类,体现了从外部配置到内部配置的转变
  • 从多步骤到单步骤:从需要 register()和 refresh()的多步骤初始化,到直接通过构造函数参数完成初始化的单步骤过程,简化了配置流程
  • 从外部文件到内部代码:配置从外部 XML 文件转移到 Java 代码中,增强了类型安全性和 IDE 支持
  • 从显式配置到隐式配置:Spring Boot 进一步简化,通过@SpringBootApplication 注解自动配置,无需显式传入配置类

Spring 的发展演变反映了 Java 生态系统向更简洁、更高效开发模式的转变。从冗长的 XML 配置到简洁的注解,再到 Spring Boot 的约定优于配置,每一步都在减少样板代码,提高开发效率。

Spring 从 XML 配置到注解的主要变化

配置方面 XML 配置方式 注解配置方式 变化优势
Bean 定义 @Component, @Service, @Repository, @Controller 代码更简洁,无需编写大量 XML;类型安全;编译时检查
依赖注入 @Autowired, @Resource, @Inject 自动注入减少配置量;直观地在使用位置声明依赖
组件扫描 @ComponentScan("com.example") 配置更简洁;与 Java 代码集成更紧密
AOP 配置 ... @Aspect, @Before, @After 切面定义更直观;与业务逻辑分离更清晰
事务管理 ... @Transactional 直接在方法上声明事务属性;粒度更细
配置文件 多个 XML 文件,命名空间复杂 @Configuration类,@PropertySource 类型安全;IDE 支持更好;便于重构
条件配置 难以实现或需编写自定义逻辑 @Conditional, @Profile 灵活的条件化配置;代码更具表达力

Spring 各层次对应关系

层次 传统 J2EE Spring 注解 职责
表现层 Servlet, JSP, 过滤器等 @Controller, @RestController, @RequestMapping 处理 HTTP 请求,返回响应,与用户交互
业务逻辑层 Service 类 @Service 封装业务逻辑,处理业务需求
数据访问层 DAO 类 @Repository 与数据库交互,执行 CRUD 操作
领域模型层 实体 Bean @Entity, @Component 数据模型,业务对象的表示
配置层 XML 配置文件 @Configuration, @Bean, @ComponentScan 应用程序配置和 Bean 定义
切面层 拦截器,过滤器 @Aspect, @Pointcut, @Before 横切关注点,如日志、事务、安全等

Spring 的发展演变反映了 Java 生态系统向更简洁、更高效开发模式的转变。从冗长的 XML 配置到简洁的注解,再到 Spring Boot 的约定优于配置,每一步都在减少样板代码,提高开发效率。

你可能感兴趣的:(Java,java,开发语言,spring)