Skip to content

注解驱动开发

Spring Framework支持基于注解的配置,相比XML配置,注解提供了更简洁、更靠近代码的方式来配置Spring应用。本文将介绍Spring中的核心注解及其用法。

启用注解支持

要在Spring中启用注解支持,需要在XML配置文件中添加以下配置:

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

或者使用Java配置:

java
@Configuration
@ComponentScan("com.example")
public class AppConfig {
    // 配置类内容
}

组件扫描与自动装配

组件注解

Spring提供了几个用于标识组件的注解:

  1. @Component:通用的组件注解,表示该类是Spring管理的组件
  2. @Service:标识服务层组件
  3. @Repository:标识数据访问层组件
  4. @Controller:标识表现层组件
  5. @RestController:组合了@Controller和@ResponseBody,用于RESTful Web服务
java
@Component
public class GenericComponent {
    // 组件实现
}

@Service
public class UserService {
    // 服务实现
}

@Repository
public class UserRepository {
    // 数据访问实现
}

@Controller
public class UserController {
    // 控制器实现
}

依赖注入注解

  1. @Autowired:自动装配依赖,默认按类型匹配
  2. @Qualifier:与@Autowired结合使用,按名称匹配
  3. @Resource:JSR-250注解,默认按名称匹配
  4. @Value:注入简单的值,包括属性占位符
java
@Service
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    @Qualifier("emailService")
    private NotificationService notificationService;
    
    @Resource(name = "auditService")
    private AuditService auditService;
    
    @Value("${app.timeout}")
    private int timeout;
    
    @Value("#{systemProperties['user.region']}")
    private String region;
}

Java配置

Spring 3.0引入了基于Java的配置,使用@Configuration和@Bean注解:

java
@Configuration
public class AppConfig {
    
    @Bean
    public DataSource dataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:mem:testdb");
        dataSource.setUsername("sa");
        dataSource.setPassword("");
        return dataSource;
    }
    
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    
    @Bean
    @Scope("prototype")
    public UserService userService() {
        return new UserServiceImpl();
    }
}

@Configuration注解

标识一个类为配置类,相当于XML配置文件。配置类包含Bean定义和特定于应用的配置。

@Bean注解

标识一个方法,该方法返回一个应注册为Spring上下文中bean的对象。相当于XML配置中的<bean>元素。

@Import注解

用于导入其他配置类:

java
@Configuration
@Import({DataSourceConfig.class, SecurityConfig.class})
public class AppConfig {
    // 配置
}

@PropertySource注解

加载属性文件:

java
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig {
    
    @Autowired
    private Environment env;
    
    @Bean
    public DataSource dataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUrl(env.getProperty("jdbc.url"));
        // 其他配置
        return dataSource;
    }
}

条件注解

Spring 4引入了条件注解,允许基于特定条件创建bean:

@Conditional

java
@Configuration
public class AppConfig {
    
    @Bean
    @Conditional(WindowsCondition.class)
    public FileService windowsFileService() {
        return new WindowsFileService();
    }
    
    @Bean
    @Conditional(LinuxCondition.class)
    public FileService linuxFileService() {
        return new LinuxFileService();
    }
}

public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("os.name").contains("Windows");
    }
}

内置条件注解

Spring Boot提供了多个内置的条件注解:

  • @ConditionalOnBean:当存在指定Bean时
  • @ConditionalOnMissingBean:当不存在指定Bean时
  • @ConditionalOnClass:当类路径上有指定类时
  • @ConditionalOnMissingClass:当类路径上没有指定类时
  • @ConditionalOnProperty:当配置了指定属性时
  • @ConditionalOnWebApplication:当是Web应用时
  • @ConditionalOnNotWebApplication:当不是Web应用时
java
@Configuration
public class CacheConfig {
    
    @Bean
    @ConditionalOnClass(name = "redis.clients.jedis.Jedis")
    public CacheManager redisCacheManager() {
        // 配置Redis缓存管理器
        return new RedisCacheManager();
    }
    
    @Bean
    @ConditionalOnMissingBean(CacheManager.class)
    public CacheManager simpleCacheManager() {
        // 配置简单缓存管理器作为后备
        return new SimpleCacheManager();
    }
}

生命周期注解

初始化和销毁方法

  1. @PostConstruct:在依赖注入完成后执行初始化
  2. @PreDestroy:在bean销毁前执行清理
java
@Component
public class DatabaseService {
    
    private Connection connection;
    
    @PostConstruct
    public void initialize() {
        try {
            connection = DriverManager.getConnection("jdbc:mysql://localhost/db", "user", "pass");
        } catch (SQLException e) {
            throw new RuntimeException("Failed to initialize database connection", e);
        }
    }
    
    @PreDestroy
    public void cleanup() {
        try {
            if (connection != null && !connection.isClosed()) {
                connection.close();
            }
        } catch (SQLException e) {
            // 记录错误但不抛出异常,避免中断销毁过程
            System.err.println("Error closing connection: " + e.getMessage());
        }
    }
}

事务管理注解

@Transactional

声明式事务管理:

java
@Service
public class UserServiceImpl implements UserService {
    
    private final UserRepository userRepository;
    
    @Autowired
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Transactional
    public void registerUser(User user) {
        userRepository.save(user);
        // 其他操作
    }
    
    @Transactional(readOnly = true)
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    @Transactional(rollbackFor = {SQLException.class})
    public void transferFunds(Account from, Account to, BigDecimal amount) {
        // 转账逻辑
    }
}

启用事务管理

在Java配置中启用事务管理:

java
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
    
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

缓存注解

Spring提供了声明式缓存管理:

java
@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(
            new ConcurrentMapCache("users"),
            new ConcurrentMapCache("accounts")
        ));
        return cacheManager;
    }
}

@Service
public class UserServiceImpl implements UserService {
    
    @Cacheable("users")
    public User getUserById(Long id) {
        // 方法结果将被缓存
        return userRepository.findById(id).orElse(null);
    }
    
    @CacheEvict("users")
    public void updateUser(User user) {
        userRepository.save(user);
    }
    
    @CachePut(value = "users", key = "#user.id")
    public User createUser(User user) {
        return userRepository.save(user);
    }
    
    @Caching(evict = {
        @CacheEvict(value = "users", key = "#id"),
        @CacheEvict(value = "userProfiles", key = "#id")
    })
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

调度和异步注解

启用调度和异步

java
@Configuration
@EnableScheduling
@EnableAsync
public class SchedulingConfig {
    // 配置
}

@Scheduled

定时任务注解:

java
@Component
public class ScheduledTasks {
    
    @Scheduled(fixedRate = 60000) // 每分钟执行一次
    public void reportCurrentTime() {
        System.out.println("Current time: " + new Date());
    }
    
    @Scheduled(cron = "0 0 0 * * *") // 每天午夜执行
    public void performDailyBackup() {
        // 备份逻辑
    }
}

@Async

异步方法注解:

java
@Service
public class EmailService {
    
    @Async
    public void sendEmail(String to, String subject, String content) {
        // 异步发送邮件逻辑
    }
    
    @Async
    public Future<Boolean> sendEmailWithResult(String to, String subject, String content) {
        // 异步发送邮件并返回结果
        boolean success = true;
        // 发送逻辑
        return new AsyncResult<>(success);
    }
}

AOP注解

启用AspectJ支持

java
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
    // 配置
}

定义切面

java
@Aspect
@Component
public class LoggingAspect {
    
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before: " + joinPoint.getSignature().getName());
    }
    
    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("Returned: " + result);
    }
    
    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
        System.out.println("Exception: " + error.getMessage());
    }
    
    @Around("@annotation(com.example.annotation.Loggable)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object proceed = joinPoint.proceed();
        long executionTime = System.currentTimeMillis() - start;
        System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
        return proceed;
    }
}

总结:XML对比注解配置

特性XML配置注解配置
集中管理所有配置集中在XML文件中配置分散在各个类中
侵入性非侵入式,不需要修改源代码侵入式,需要在源代码中添加注解
类型安全不是类型安全的类型安全,编译时检查
重构友好重构时可能破坏XML引用IDE可以检测注解引用
复杂性大型项目中XML可能变得庞大难以管理大型项目中更容易管理
适用场景集成第三方库、需要非侵入式配置的场景自己开发的代码、需要简化配置的场景

最佳实践

  1. 混合使用:可以混合使用XML和注解,各取所长
  2. Java配置用于应用配置:@Configuration类适合应用基础架构配置
  3. 组件注解用于业务组件:@Component及其变体适合标识业务组件
  4. 使用原型注解:使用@Controller/@Service/@Repository而非通用的@Component,以表明组件的意图
  5. 中心化配置:使用@PropertySource和环境抽象集中管理配置
  6. 构造器注入:优先使用构造器注入,特别是对必需依赖
  7. 合理分离配置:按功能域分离配置类,避免"超级配置类"

注解驱动开发使Spring应用更简洁、更易于开发和维护,但也需要在适当的场景选择适当的配置方式。