Appearance
注解驱动开发
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提供了几个用于标识组件的注解:
- @Component:通用的组件注解,表示该类是Spring管理的组件
- @Service:标识服务层组件
- @Repository:标识数据访问层组件
- @Controller:标识表现层组件
- @RestController:组合了@Controller和@ResponseBody,用于RESTful Web服务
java
@Component
public class GenericComponent {
// 组件实现
}
@Service
public class UserService {
// 服务实现
}
@Repository
public class UserRepository {
// 数据访问实现
}
@Controller
public class UserController {
// 控制器实现
}
依赖注入注解
- @Autowired:自动装配依赖,默认按类型匹配
- @Qualifier:与@Autowired结合使用,按名称匹配
- @Resource:JSR-250注解,默认按名称匹配
- @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();
}
}
生命周期注解
初始化和销毁方法
- @PostConstruct:在依赖注入完成后执行初始化
- @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可能变得庞大难以管理 | 大型项目中更容易管理 |
适用场景 | 集成第三方库、需要非侵入式配置的场景 | 自己开发的代码、需要简化配置的场景 |
最佳实践
- 混合使用:可以混合使用XML和注解,各取所长
- Java配置用于应用配置:@Configuration类适合应用基础架构配置
- 组件注解用于业务组件:@Component及其变体适合标识业务组件
- 使用原型注解:使用@Controller/@Service/@Repository而非通用的@Component,以表明组件的意图
- 中心化配置:使用@PropertySource和环境抽象集中管理配置
- 构造器注入:优先使用构造器注入,特别是对必需依赖
- 合理分离配置:按功能域分离配置类,避免"超级配置类"
注解驱动开发使Spring应用更简洁、更易于开发和维护,但也需要在适当的场景选择适当的配置方式。