本文参考自: https://www.pdai.tech/md/spring/spring-x-framework-helloworld.html
Spring 框架自诞生以来经历了显著的演变,主要体现在配置方式的变革上。这一演化过程从 XML 配置、XML+注解混合配置、Java 配置到纯注解配置,每一步都使开发变得更加简洁高效。通过分析 AnnotationConfigApplicationContext 的参数变化,我们可以清晰地看到这一演进过程。
为了直观展示 Spring 框架的演化,我们将使用一个简单的用户管理系统作为贯穿始终的示例。该系统包含以下组件:
这样,我们可以清晰地观察到相同业务逻辑在不同配置方式下的实现差异。
为了便于理解,先看一下贯穿所有示例的基础类:
// 用户实体类 使用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);
}
在 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);
特点:
随着 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();
特点:
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 的方法:
不使用组件注解:当使用 @Bean 方法在配置类中显式创建和配置 UserServiceImpl 实例时,该类本身不需要添加 @Service 或其他组件注解。配置类中的 @Bean 方法已经向 Spring 容器注册了这个 Bean。
混合使用组件扫描:如果同时启用了组件扫描(@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]"));
特点:
Spring 4.x 进一步增强了注解驱动功能。通过组合注解和条件化配置等特性使配置更加精简。Spring Boot 的出现则将注解驱动发挥到极致,通过自动配置大幅减少了样板代码。
早期的纯注解驱动方式仍然需要一个配置类作为入口:
带注解的 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);
随着注解驱动的成熟,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 等)被自动发现和注册。
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");
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
}
随着 Spring 版本的演进,依赖注入方式也在变化:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
// 业务方法...
@Override
public User getUserById(Long id) {
return userDao.findById(id);
}
}
@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);
}
}
@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);
}
}
构造器注入的优点:
特点:
配置方面 | XML 配置 | XML+注解混合 | Java 配置 | 纯注解驱动 |
---|---|---|---|---|
配置位置 | 外部 XML 文件 | XML 文件+类的注解 | Java 配置类 | 分散在各组件类中的注解 |
类型安全 | 无类型安全,易出错 | 部分类型安全 | 完全类型安全 | 完全类型安全 |
重构支持 | 差,修改类需手动更新 XML | 较好,但 XML 部分仍需手动更新 | 良好,重构工具可以检测 | 优秀,自动跟随代码重构 |
配置复杂度 | 高,需要大量 XML 标签 | 中等,混合使用 XML 和注解 | 中等,Java 代码但集中 | 低,分散在业务代码中 |
运行时验证 | 启动时验证 | 启动时验证 | 编译时部分验证+启动时验证 | 编译时部分验证+启动时验证 |
适用场景 | 遗留系统,第三方集成 | 过渡期系统,部分现代化 | 需要编程式配置的复杂场景 | 现代应用,微服务 |
Bean 获取方式 | 主要通过名称 | 名称或类型 | 主要通过类型 | 主要通过类型 |
学习曲线 | 陡峭,需要学习 XML Schema | 中等,需要了解 XML 和注解 | 平缓,熟悉 Java 即可 | 平缓,但需要了解各种注解 |
测试便利性 | 较差,需模拟 XML 环境 | 中等 | 良好,可以直接测试配置类 | 优秀,组件可独立测试 |
AnnotationConfigApplicationContext 的参数变化反映了 Spring 框架配置方式的演进:
Spring 的发展演变反映了 Java 生态系统向更简洁、更高效开发模式的转变。从冗长的 XML 配置到简洁的注解,再到 Spring Boot 的约定优于配置,每一步都在减少样板代码,提高开发效率。
配置方面 | 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 |
灵活的条件化配置;代码更具表达力 |
层次 | 传统 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 的约定优于配置,每一步都在减少样板代码,提高开发效率。