Spring 全注解开发详解
一、什么是全注解开发
全注解开发是指完全摆脱 XML 配置文件,用 Java 类 + 注解来构建 Spring 应用。配置类替代了 applicationContext.xml,注解替代了 <bean>、<property>、<constructor-arg> 等标签。
传统 XML 方式需要维护多个配置文件,配置和代码分离,修改配置要改 XML。全注解方式把配置和代码放在一起,类上标注解就能被 Spring 管理,方法参数自动注入依赖,配置类替代 XML 文件。
Spring Boot 就是全注解开发的极致体现,但理解全注解开发,需要先掌握 Spring Framework 层面的注解体系。
二、从 XML 到注解的演进
XML 时代:
xml
<bean id="userDao" class="com.example.UserDao"/>
<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao"/>
</bean>
注解时代:
java
@Repository
public class UserDao { }
@Service
public class UserService {
@Autowired
private UserDao userDao;
}
配置从 XML 转移到了 Java 类上。类的职责和依赖关系一目了然,不需要在 XML 和 Java 之间来回切换。
三、核心注解体系
1. 声明 Bean
| 注解 | 语义 | 适用层 |
|---|---|---|
@Component |
通用组件 | 任意 |
@Service |
业务层 | Service |
@Repository |
数据访问层 | Dao |
@Controller |
控制层 | Controller |
@RestController |
REST 控制器 | Controller |
@Configuration |
配置类 | 配置 |
java
@Service
public class UserService { }
@Repository
public class UserDao { }
Bean 名称默认是类名首字母小写,可以显式指定:
java
@Service("userService")
public class UserService { }
2. 配置类与 @Bean
@Configuration 标记一个类为配置类,替代 XML 文件。@Bean 标记方法,把返回值注册为 Bean。
java
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
return ds;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
@Configuration 类会被 CGLIB 代理,@Bean 方法之间互相调用返回容器中的单例。@Component 不会被代理,方法间调用直接执行方法体。
3. 组件扫描
java
@Configuration
@ComponentScan("com.example")
public class AppConfig { }
@SpringBootApplication 已经包含了 @ComponentScan,默认扫描启动类所在包及其子包。
4. 依赖注入
java
@Service
public class UserService {
private final UserDao userDao;
private final EmailService emailService;
public UserService(UserDao userDao, EmailService emailService) {
this.userDao = userDao;
this.emailService = emailService;
}
}
构造器注入是 Spring 官方推荐的方式。类只有一个构造方法时,@Autowired 可以省略。
5. 配置值注入
java
@Component
public class AppConfig {
@Value("${app.name}")
private String appName;
@Value("${app.timeout:30}")
private int timeout;
}
或者用 @ConfigurationProperties 批量绑定:
java
@Component
@ConfigurationProperties(prefix = "app")
@Getter
@Setter
public class AppProperties {
private String name;
private int timeout;
private List<String> servers;
}
四、配置类详解
1. @Configuration 的本质
@Configuration 本身被 @Component 标注,所以它也是一个 Bean。但 Spring 对 @Configuration 类做了特殊处理------通过 CGLIB 生成子类,拦截 @Bean 方法的调用。
java
@Configuration
public class AppConfig {
@Bean
public UserDao userDao() {
return new UserDao();
}
@Bean
public UserService userService() {
// 由于 CGLIB 代理,userDao() 返回容器中的单例
return new UserService(userDao());
}
}
如果换成 @Component,userDao() 每次调用都会执行方法体,创建新实例。
2. @Bean 方法参数注入
java
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
Spring 会自动从容器中查找 DataSource 类型的 Bean 注入到方法参数。
3. @Bean 的生命周期回调
java
@Bean(initMethod = "init", destroyMethod = "cleanup")
public MyBean myBean() {
return new MyBean();
}
也可以在 Bean 类中用 @PostConstruct 和 @PreDestroy。
4. 条件化配置
java
@Configuration
public class AppConfig {
@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
@Bean
@ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")
public CacheManager cacheManager() {
return new CaffeineCacheManager();
}
}
5. 环境化配置
java
@Configuration
@Profile("dev")
public class DevConfig {
@Bean
public DataSource devDataSource() { ... }
}
@Configuration
@Profile("prod")
public class ProdConfig {
@Bean
public DataSource prodDataSource() { ... }
}
五、AOP 全注解
1. 开启 AOP
java
@Configuration
@EnableAspectJAutoProxy
public class AopConfig { }
Spring Boot 中引入 spring-boot-starter-aop 后自动生效。
2. 定义切面
java
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
@Around("serviceLayer()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long cost = System.currentTimeMillis() - start;
System.out.println(pjp.getSignature().getName() + " 耗时:" + cost + "ms");
return result;
}
}
五种通知注解:@Before、@After、@AfterReturning、@AfterThrowing、@Around。
3. 自定义注解 + AOP
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
@Aspect
@Component
public class LogAspect {
@Around("@annotation(log)")
public Object log(ProceedingJoinPoint pjp, Log log) throws Throwable {
System.out.println("执行:" + log.value());
return pjp.proceed();
}
}
六、事务全注解
java
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
业务方法上使用 @Transactional:
java
@Service
public class OrderService {
@Transactional(rollbackFor = Exception.class, timeout = 30)
public void createOrder(Order order) {
orderDao.save(order);
inventoryDao.decrease(order.getProductId(), order.getQuantity());
}
}
Spring Boot 中 DataSourceTransactionManager 自动配置,不需要手动声明。
七、Web 层全注解
1. 传统 Spring MVC 配置
XML 时代需要 web.xml + spring-mvc.xml。全注解方式用 WebApplicationInitializer 替代 web.xml:
java
public class WebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
// 创建 Spring 根容器
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(AppConfig.class);
// 创建 Spring MVC 容器
AnnotationConfigWebApplicationContext webContext =
new AnnotationConfigWebApplicationContext();
webContext.register(WebConfig.class);
// 注册 DispatcherServlet
ServletRegistration.Dynamic dispatcher = servletContext.addServlet(
"dispatcher", new DispatcherServlet(webContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
2. WebMvcConfigurer
替代 spring-mvc.xml:
java
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login", "/register");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter());
}
}
3. 控制器
java
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getById(id);
}
@PostMapping
public User create(@RequestBody @Valid User user) {
return userService.save(user);
}
}
4. 全局异常处理
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusiness(BusinessException e) {
return ResponseEntity.badRequest()
.body(new ErrorResponse(e.getMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e) {
return ResponseEntity.status(500)
.body(new ErrorResponse("服务器内部错误"));
}
}
八、全注解项目结构
src/main/java/com/example/
├── config/
│ ├── AppConfig.java # 根配置
│ ├── WebConfig.java # Web 配置
│ ├── AopConfig.java # AOP 配置
│ └── TransactionConfig.java # 事务配置
├── controller/
│ └── UserController.java
├── service/
│ ├── UserService.java
│ └── impl/
│ └── UserServiceImpl.java
├── dao/
│ └── UserDao.java
├── entity/
│ └── User.java
└── WebAppInitializer.java # 替代 web.xml
九、全注解 vs XML
| 对比维度 | 全注解 | XML |
|---|---|---|
| 配置位置 | 代码内 | 独立文件 |
| 可读性 | 类与配置合一 | 配置集中但分离 |
| 修改成本 | 需重新编译 | 改 XML 即可 |
| 类型安全 | 编译期检查 | 运行时才发现错误 |
| 重构支持 | IDE 自动重构 | 手动修改 |
| 适用场景 | 业务类、Controller、Service | 第三方类、动态配置 |
| 现代趋势 | 主流 | 逐步淘汰 |
混合使用 :自己写的类用注解,第三方类用 @Bean 方法或 XML。全注解不等于完全不用 XML,而是以注解为主。
十、常见问题
1. @Configuration 与 @Component 混用
@Configuration 类会被 CGLIB 代理,@Bean 方法间调用返回单例。@Component 不会被代理,方法间调用创建新实例。配置类一律用 @Configuration。
2. @Bean 方法返回类型与注入类型不匹配
java
@Bean
public DataSource dataSource() {
return new HikariDataSource(); // 返回 HikariDataSource,注册为 DataSource 类型
}
注入时按 DataSource 类型查找可以找到,按 HikariDataSource 查找也可以(Spring 会判断实际类型)。
3. 组件扫描范围
@ComponentScan 默认扫描当前类所在包及其子包。启动类放在根包下,确保所有组件都在扫描范围内。
4. 循环依赖
构造器注入的循环依赖无法解决,启动报 BeanCurrentlyInCreationException。字段注入可以通过三级缓存解决,但应通过重构消除循环依赖。
5. @EnableWebMvc 与 Spring Boot
Spring Boot 中不要手动加 @EnableWebMvc,它会关闭 Spring Boot 的 Web 自动配置。需要自定义 Web 配置时,实现 WebMvcConfigurer 接口即可。
十一、总结
| 分类 | 核心注解 |
|---|---|
| 声明 Bean | @Component、@Service、@Repository、@Controller、@RestController |
| 配置类 | @Configuration、@Bean、@ComponentScan、@Import |
| 依赖注入 | @Autowired、@Qualifier、@Resource、@Value |
| 配置绑定 | @ConfigurationProperties、@PropertySource |
| 条件化 | @Conditional、@ConditionalOnXxx、@Profile |
| AOP | @Aspect、@Pointcut、@Before、@Around |
| 事务 | @EnableTransactionManagement、@Transactional |
| Web | @EnableWebMvc、WebMvcConfigurer、@RestControllerAdvice |
| 测试 | @SpringBootTest、@MockBean |
全注解开发是 Spring 现代开发的主流方式。它把配置从 XML 转移到 Java 类和注解上,让代码和配置合一,类型安全,重构友好。Spring Boot 在此基础上进一步简化,通过自动配置和 Starter 机制,让开发者几乎不需要写配置类。理解全注解开发,是掌握 Spring Boot 自动配置原理的基础。