Spring 全注解开发详解

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 自动配置原理的基础。

相关推荐
长河1 小时前
Java的GC(垃圾回收)中,标记无用对象的核心方法是可达性分析算法
java·jvm·算法
code2cat2 小时前
Java进阶篇之LongAdder:把统计更新分散,再读取总和
java·并发编程·原子类·longadder
凯哥Java2 小时前
写代码怎么避免逻辑漏洞?
java·开发语言·人工智能·自动化
Ivanqhz2 小时前
正余弦位置编码(Sinusoidal Positional Encoding)
java·linux·数据库
BD_Marathon2 小时前
消息对象中字段的说明
java·前端·python
zlpzlpzyd2 小时前
nacos的openapi地址
java
落魄实习生3 小时前
Agent Scope Java 2.x 系列【10】Middleware
java·开发语言·ai
IT_陈寒4 小时前
SpringBoot启动慢得像蜗牛?原来是这个配置在捣鬼
前端·人工智能·后端
ZealSinger4 小时前
Spring7 RestTemplate弃用迁移
java·spring·后端开发·restclient