Spring 框架作为 Java 开发中最受欢迎的开源框架之一,其强大的功能很大程度上得益于其丰富的注解(Annotation)支持。本文对Spring框架中常用的注解进行详细解析,并通过示例来展示它们的用法和效果。
1. 组件注解
@Component
这是一个泛化的概念,仅仅表示一个组件(Bean),可以作用在任何层次。当不知道一个类归属于哪个层时,可以使用@Component注解标注。
使用案例:
@Component
public class MyBean {
// 类的实现...
}
在这个例子中,MyBean类被@Component注解标注,表明它是一个Spring管理的组件(Bean)。
@Controller
用于标注控制层组件,如Spring MVC中的控制器。
@Controller
@RequestMapping("/hello")
public class HelloController {
@GetMapping("/greet")
public String greet(@RequestParam("name") String name, Model model) {
String message = "Hello, " + name + "!";
model.addAttribute("greeting", message);
return "greet"; // 返回视图名
}
}
在这个例子中,HelloController类被@Controller注解标注,表明它是一个处理HTTP请求的控制器。@RequestMapping和@GetMapping注解用于定义URL请求和控制器方法之间的映射。
@Service
用于标注服务层组件。
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUser(Long id) {
return userRepository.findUserById(id);
}
}
在这个例子中,UserService类被@Service注解标注,表明它是一个服务层组件,负责封装业务逻辑。
@Repository
用于标注数据访问层组件,即DAO组件。
@Repository
public class UserRepository {
public User findUserById(Long id) {
// 数据库查询逻辑
return new User(id, "John Doe");
}
}
在这个例子中,UserRepository类被@Repository注解标注,表明它是一个数据访问层组件,负责与数据库进行交互。
@RestController
是@Controller和@ResponseBody的组合注解,用于标识一个RESTful风格的控制器,其方法的返回值会自动序列化为JSON或XML格式并写入HTTP响应体中。
@RestController
public class MyRestController {
@GetMapping("/hello")
public String sayHello() {
return "Hello, World!";
}
}
2. 注入注解
@Autowired
由Spring提供,用于自动装配bean。可以作用在变量、setter方法、构造函数上。默认情况下,它要求依赖对象必须存在,但可以通过设置其required属性为false来允许依赖对象不存在。
@Autowired
private UserRepository userRepository;
在UserService类中,@Autowired注解用于自动注入UserRepository的实例。
@Qualifier
与@Autowired一起使用,用于指定注入bean的名称。当使用 @Autowired 自动装配时,如果 Spring 容器中存在多个相同类型的 Bean,那么 Spring 就无法判断具体应该注入哪一个 Bean,此时就会抛出异常。为了解决这个问题,可以使用 @Qualifier 注解来明确指定要注入的 Bean 的名称。
@Component
public class MessageSender {
private final MessageService emailService;
@Autowired
public MessageSender(@Qualifier("emailService") MessageService emailService) {
this.emailService = emailService;
}
public void sendEmail(String message) {
emailService.sendMessage(message);
}
}
@Inject
由JSR-330提供,用法与@Autowired类似,但它是Java标准的一部分。
public class MyClass {
@Inject
private MyDependency myDependency;
public void doSomething() {
myDependency.someMethod();
}
}
// 假设MyDependency是一个Spring管理的Bean
@Component
public class MyDependency {
public void someMethod() {
// 方法的实现...
}
}
在Spring环境中,@Inject注解的用法与@Autowired类似,但它是Java标准的一部分。
@Resource
@Resource 注解是Java EE(Jakarta EE)标准的一部分,用于依赖注入。它可以被用于字段、方法(通常是setter方法)和构造函数上,以指示容器需要注入的依赖项。默认按照名称装配注入,只有当找不到与名称匹配的bean时,才会按照类型来装配注入。
import javax.annotation.Resource;
import javax.sql.DataSource;
public class MyService {
// 使用@Resource注解注入DataSource对象
@Resource(name = "myDataSource")
private DataSource dataSource;
// 使用dataSource进行数据库操作的方法
public void someDatabaseOperation() {
// ... 使用dataSource进行操作
}
}
@Lazy
@Lazy 注解在Spring框架中主要用于控制bean的初始化时机,即延迟bean的初始化直到第一次使用时才进行。这对于减少启动时间或解决循环依赖等问题非常有用。
默认情况下,Spring容器在启动时就会初始化所有的单例(Singleton)bean,但如果你对某个bean使用了@Lazy注解,那么该bean的初始化将被延迟。@Lazy注解可以用在构造器、字段以及类上。
假如ServiceA和ServiceB,它们之间存在循环依赖,通过@Lazy来绕过这个问题。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ServiceA {
private final ServiceB serviceB;
@Autowired
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
// 其他方法和逻辑
}
3. 配置类相关注解
@Bean
注解在方法上,声明当前方法的返回值为一个bean,替代xml中的标签。
@Configuration
声明当前类为配置类,相当于xml形式的Spring配置。
@ComponentScan
用于对Component进行扫描,相当于xml中的<context:component-scan>标签。
@Configuration
@ComponentScan("com.example.service")
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
// 其他Bean的配置...
}
在这个例子中,AppConfig类被@Configuration注解标注,表明它是一个配置类。@ComponentScan注解用于指定Spring在哪些包下搜索带有@Component注解的类。@Bean注解用于声明一个Bean的创建方法。
@Import
@Import注解在Spring框架中扮演着重要的角色,主要用于将指定的类、配置类或组件导入到Spring容器中,以便进行管理和使用。
作用:
- 1.模块化配置:在大型项目中,通常需要将配置信息分散到多个配置类中,以便更好地组织和管理。@Import注解允许开发者在一个主配置类中导入其他配置类,从而实现配置的模块化。
- 2.第三方库或组件的集成:当项目中需要集成第三方库或组件时,这些库或组件可能会提供自己的配置类。通过@Import注解,开发者可以轻松地将这些第三方配置类集成到项目的总体配置中。
- 3.条件化配置:@Import注解还可以与条件注解(如@Conditional)结合使用,以实现基于特定条件的配置加载。因此,在不同的环境或情境下,可以加载不同的配置类,从而实现更加灵活和动态的配置管理。
- 4.扩展Spring功能:通过导入实现了特定接口的类(如BeanFactoryPostProcessor、BeanDefinitionRegistrar等),开发者可以扩展Spring框架的功能。
- 5.解决循环依赖问题:在某些情况下,使用@Import注解可以解决因循环依赖而导致的配置问题。通过将相互依赖的配置类分解并使用@Import进行导入,可以打破循环依赖的链条。
@Import注解有三种主要的使用方式:
(1)class数组方式
假设有两个类TestA和TestB需要被导入到Spring容器中。可以在一个配置类上使用@Import注解,并传入这两个类的Class对象数组。
@Import({TestA.class, TestB.class})
@Configuration
public class ImportConfig {
// 配置类内容
}
这样,TestA和TestB就会被自动注册到Spring容器中,它们的bean名称默认为全类名。
(2)ImportSelector方式
如果需要根据某些条件或运行时环境动态地选择要导入的类,可以实现ImportSelector接口。该接口要求实现selectImports方法,该方法返回一个字符串数组,数组中的每个字符串都是需要导入的类的全类名。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 根据条件返回需要导入的类名数组
return new String[]{"com.example.TestC"};
}
}
@Import(MyImportSelector.class)
@Configuration
public class ImportConfig {
// 配置类内容
}
(3)ImportBeanDefinitionRegistrar方式
如果需要更灵活地控制Bean的注册过程,比如自定义Bean的名称或注册过程,可以实现ImportBeanDefinitionRegistrar接口。该接口要求实现registerBeanDefinitions方法,该方法允许在运行时手动注册Bean定义。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 手动注册Bean定义
RootBeanDefinition beanDefinition = new RootBeanDefinition(TestD.class);
registry.registerBeanDefinition("testD", beanDefinition);
}
}
@Import(MyImportBeanDefinitionRegistrar.class)
@Configuration
public class ImportConfig {
// 配置类内容
}
以上三种方式都可以在Spring项目中灵活使用,以满足不同的配置和管理需求。
@Primary
当Spring容器中存在多个相同类型的Bean时,如果没有指定具体的Bean进行注入,Spring可能会因为歧义而无法确定应该注入哪个Bean,使用@Primary注解可以指定一个Bean作为默认选择。
// 接口定义
public interface PaymentService {
void pay();
}
// 使用@Primary注解标记AliPaymentServiceImpl为首选实现
@Service
@Primary
public class AliPaymentServiceImpl implements PaymentService {
@Override
public void pay() {
System.out.println("使用阿里支付");
}
}
// 另一个实现类,未使用@Primary注解
@Service
public class WxPaymentServiceImpl implements PaymentService {
@Override
public void pay() {
System.out.println("使用微信支付");
}
}
// 在需要注入PaymentService的地方
@Component
public class PaymentController {
@Autowired
private PaymentService paymentService; // 这里会自动注入AliPaymentServiceImpl,因为它被@Primary注解标记
public void handlePayment() {
paymentService.pay(); // 输出:使用阿里支付
}
}
在上述示例中,AliPaymentServiceImpl类上使用了@Primary注解,表示它是PaymentService接口的首选实现。
注意事项:
@Primary注解只适用于Bean的类型,不能用于Bean的名称。因此,如果有多个同一类型的Bean,但它们需要以不同的名称被注入,则无法使用@Primary来确定主要实例
4. 生命周期注解
@PostConstruct
在构造函数执行完之后执行,用于初始化方法。
@PreDestroy
在Bean销毁之前执行,用于清理资源。
@Component
public class MyBean {
@PostConstruct
public void init() {
// 初始化代码...
}
@PreDestroy
public void destroy() {
// 清理资源代码...
}
}
在这个例子中,@PostConstruct注解的方法将在Bean的构造函数执行完毕后执行,用于执行初始化操作。@PreDestroy注解的方法将在Bean销毁之前执行,用于清理资源。
5. AOP相关注解
@Aspect
@Aspect 注解用于声明一个类为切面类。切面类用于声明通知(advice)和切点(pointcut),从而将横切关注点(cross-cutting concerns)如日志、事务管理等从业务逻辑中分离出来。
@PointCut
@Pointcut 注解用于定义一个切点表达式,这个表达式用于匹配连接点(如方法执行)。切点定义了哪些方法会被增强(即在这些方法执行时,会执行通知中的代码)。
@Before
@Before 注解用于声明一个前置通知,这个通知会在目标方法执行之前执行。
@After
@After 注解用于声明一个后置通知,这个通知会在目标方法执行之后(无论成功与否)执行。
@Around
@Around 注解用于声明一个环绕通知,这个通知在目标方法执行之前和之后都会执行,并且它可以决定是否继续执行目标方法,以及修改方法的返回值或抛出异常。
@Aspect
@Component
public class LoggingAspect {
// 通知和切点定义
// 匹配com.example.service包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayerExecution(){}
@Before("serviceLayerExecution()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
@After("serviceLayerExecution()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After method: " + joinPoint.getSignature().getName());
}
@Around("serviceLayerExecution()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed(); // 继续执行目标方法
long endTime = System.currentTimeMillis();
System.out.println("Method " + joinPoint.getSignature().getName() + " took " + (endTime - startTime) + " ms");
return result;
} catch (IllegalArgumentException e) {
System.out.println("Illegal argument: " + e.getMessage());
throw e; // 可以选择重新抛出异常或抛出新的异常
}
}
}
@EnableAspectJAutoProxy
@EnableAspectJAutoProxy 注解是 Spring 框架中用于启用 Spring AOP(面向切面编程)的支持,特别是与 AspectJ 代理的集成,告诉 Spring 容器在运行时通过动态代理(默认是 JDK 动态代理或 CGLIB 代理)来自动处理切面的应用。。当你使用基于注解的 AOP 功能时,这个注解是必须的。有一个例外情况,那就是当你在使用Spring Boot时。Spring Boot会自动配置很多东西,包括AOP。即使你没有显式地添加@EnableAspectJAutoProxy注解,Spring Boot也会自动为你启用AspectJ自动代理它
在你的 Spring Boot 应用类或者配置类上添加 @EnableAspectJAutoProxy(exposeProxy = true)。exposeProxy 属性是 @EnableAspectJAutoProxy 注解中的一个可选属性,默认值为 false。当设置为 true 时,它允许目标对象通过 AOP 代理访问自己。这主要用于在同一个类中调用另一个方法时,你也想应用切面的情况。
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
在服务中通过代理调用
@Service
public class MyService {
public void methodA() {
// 直接调用 methodB,不会触发切面
methodB();
// 通过代理调用 methodB,会触发切面
((MyService) AopContext.currentProxy()).methodB();
}
public void methodB() {
System.out.println("Executing methodB");
}
}
在这个例子中,methodA 内的第一个 methodB 调用是直接调用,不会通过 AOP 代理,因此不会触发切面。而通过 AopContext.currentProxy() 获取代理后调用的 methodB 则会触发切面。
6. 事务管理注解
@EnableTransactionManagement
需要声明在启动类或配置类上,配置开启spring 事务管理
@Transactional
用于声明事务管理的方法或类。注解可以应用于类级别或方法级别。如果应用于类级别,则该类中的所有公共方法都将被视为事务性的,除非它们被显式地标记为非事务性的(使用 @Transactional(propagation = Propagation.NOT_SUPPORTED))。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository; // 假设这是一个用于访问数据库的存储库
// 使用 @Transactional 注解来声明这个方法需要事务支持
@Transactional
public void transfer(Long fromAccountId, Long toAccountId, double amount) {
// 从账户中扣除金额
Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow(() -> new RuntimeException("账户不存在"));
fromAccount.setBalance(fromAccount.getBalance() - amount);
// 向账户中增加金额
Account toAccount = accountRepository.findById(toAccountId).orElseThrow(() -> new RuntimeException("账户不存在"));
toAccount.setBalance(toAccount.getBalance() + amount);
// 假设这里发生了异常,如余额不足等
// 抛出异常将导致事务回滚
if (fromAccount.getBalance() < 0) {
throw new RuntimeException("余额不足");
}
// 保存更改
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
}
}
在这个例子中,transfer 方法被 @Transactional 注解标记,表明这个方法需要在事务的上下文中执行。如果方法执行过程中抛出了未捕获的异常(RuntimeException 及其子类),则 Spring 会自动回滚事务,确保数据库状态的一致性。如果方法成功完成,则 Spring 会提交事务。
7. 异步相关注解
@EnableAsync
在配置类上(通常是带有 @Configuration 注解的类)使用,开启对异步任务的支持。
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync
public class AsyncConfig {
// 这里不需要添加任何bean定义,@EnableAsync已经足够开启异步支持
}
@Async
在实际执行的bean方法上使用,声明其是一个异步任务。你可以创建一个服务类,并在其中使用
@Async 注解来标记一个或多个方法作为异步方法。
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncService {
@Async
public void executeAsyncTask(String taskName) {
System.out.println("开始执行异步任务: " + taskName);
try {
// 模拟耗时操作
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("异步任务执行完成: " + taskName);
}
// 注意:在同一个类中,一个异步方法不能直接调用另一个异步方法,因为调用会在同一个线程中同步执行
// 如果需要,可以将另一个异步方法调用放在另一个bean中
}
注意事项:
- 异步方法不能在同一类中直接调用另一个异步方法,因为调用会在同一个线程中同步执行。如果需要,可以将另一个异步方法调用放在另一个bean中。
- 默认情况下,Spring使用SimpleAsyncTaskExecutor来执行异步任务,但它不支持线程池。在生产环境中,你可能希望配置一个ThreadPoolTaskExecutor来更好地管理线程资源。
- 异步方法返回类型可以是void,也可以是Future、CompletableFuture等,以便能够获取异步操作的结果。如果返回void,则无法直接获取异步操作的结果。
手动管理异步任务线程池
在Spring中,为了在生产环境中更好地管理异步任务的线程资源,你可以通过配置ThreadPoolTaskExecutor来替代默认的SimpleAsyncTaskExecutor。ThreadPoolTaskExecutor提供了更丰富的线程池管理功能,如线程池大小、核心线程数、最大线程数、队列容量、线程存活时间等。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 核心线程数
executor.setMaxPoolSize(10); // 最大线程数
executor.setQueueCapacity(25); // 队列容量
executor.initialize(); // 初始化线程池
return executor;
}
}
注意事项:
- 当你在配置类中定义了taskExecutor这个Bean之后,Spring会自动将它用作@Async注解的默认执行器。但如果你想要为不同的异步方法指定不同的执行器,你可以在@Async注解中通过value属性指定执行器的Bean名称。
- initialize()方法在较新版本的Spring中可能不再需要显式调用,因为Spring容器在创建ThreadPoolTaskExecutor Bean时会自动进行初始化。
- 你可以根据需要调整线程池的参数,如核心线程数、最大线程数、队列容量等,以优化应用的性能和资源使用。
8. 定时任务相关注解
@EnableScheduling
在配置类上使用,开启计划任务的支持。通常是一个带有 @Configuration 注解的类,用于定义Spring应用中的bean和其他配置。
@Scheduled
在方法上使用,声明这是一个定时任务。你可以在任何Spring管理的bean中,使用 @Scheduled 注解来声明一个定时任务。这个bean可以是一个组件(@Component)、服务(
@Service)或其他任何Spring管理的bean。
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class ScheduledTasks {
// 使用cron表达式来指定任务的执行计划
// 例如,每天中午12点执行
@Scheduled(cron = "0 0 12 * * ?")
public void reportCurrentTime() {
System.out.println("当前时间:" + System.currentTimeMillis());
}
// 使用fixedRate属性来指定任务执行的固定频率(以毫秒为单位)
// 例如,每5秒执行一次
@Scheduled(fixedRate = 5000)
public void fixedRateTask() {
System.out.println("固定频率任务执行:" + System.currentTimeMillis());
}
// 使用fixedDelay属性来指定任务执行完成后的延迟时间(以毫秒为单位),然后再执行下一次
// 例如,每次任务执行完成后等待2秒再执行下一次
@Scheduled(fixedDelay = 2000)
public void fixedDelayTask() {
System.out.println("固定延迟任务执行:" + System.currentTimeMillis());
try {
// 模拟任务执行时间
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
注意事项
- 确保你的Spring应用已经包含了Spring Task Scheduling的支持。在Spring Boot应用中,这通常是通过添加spring-boot-starter依赖自动完成的。
- @Scheduled 注解的方法不应该有任何参数,并且它们的返回类型应该是void或者Future<?>。
- 你可以使用cron表达式、fixedRate或fixedDelay属性来指定任务的执行计划。cron表达式提供了更灵活的方式来定义任务的执行时间。
- 如果你的应用是多实例部署的(例如,在多个服务器上运行了相同的Spring Boot应用),那么每个实例都会独立地执行这些定时任务。如果你需要跨实例的定时任务同步,你可能需要考虑使用外部调度服务(如Quartz Scheduler配合数据库存储)或其他分布式锁机制。
8. 环境切换和条件注解
@Profile
@Profile 注解用于指定某个组件(Bean)仅在特定环境配置下才会被注册到Spring容器中。这对于在开发、测试和生产环境之间切换配置特别有用。
@Configuration
@Profile("dev")
public class DevDataSourceConfig {
@Bean
public DataSource dataSource() {
// 配置开发环境的数据源
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
}
在这个例子中,dataSource() 方法配置的Bean仅在激活了dev profile时才会被注册到Spring容器中。
@Conditional
@Conditional 是一个更灵活的注解,它允许你通过实现Condition接口并编写自定义逻辑来决定Bean是否应该被注册。这种方式提供了极高的灵活性,可以用于基于各种条件的Bean注册。通过实现Condition接口,并重写matches方法,从而决定该bean是否被实例化。
示例:
假设你想根据一个系统属性来决定是否注册某个Bean:
首先,定义一个实现Condition接口的类:
public class OnPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 假设我们检查的是名为"myapp.feature.enabled"的系统属性
String propertyName = "myapp.feature.enabled";
String propertyValue = context.getEnvironment().getProperty(propertyName);
return Boolean.parseBoolean(propertyValue);
}
}
然后,使用这个Condition来注解你的Bean定义:
@Configuration
public class ConditionalConfig {
@Bean
@Conditional(OnPropertyCondition.class)
public MyFeature myFeature() {
return new MyFeature();
}
}
在这个例子中,MyFeature Bean只有在系统属性myapp.feature.enabled被设置为true时才会被注册到Spring容器中。
注意:
使用@Profile和@Conditional时,应该考虑它们之间的区别和适用场景。@Profile更适合于基于预定义的环境(如开发、测试、生产)来选择性地注册Bean,而@Conditional则提供了更灵活的条件评估能力,可以用于更复杂的场景。注意:
使用@Profile和@Conditional时,应该考虑它们之间的区别和适用场景。@Profile更适合于基于预定义的环境(如开发、测试、生产)来选择性地注册Bean,而@Conditional则提供了更灵活的条件评估能力,可以用于更复杂的场景。
9. 数据绑定与验证注解
@RequestMapping
@RequestMapping是一个用于处理HTTP请求的基本注解,它可以用于类或方法上。当用于类上时,表示类中的所有响应请求的方法都是以该类路径下的URL为父路径。它可以通过其属性来指定请求的方法类型(如GET、POST)、请求参数等。
@RestController
@RequestMapping("/users")
public class UserController {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public User getUserById(@PathVariable("id") Long id) {
// 实现根据id获取用户的逻辑
return new User(id, "John Doe");
}
@RequestMapping(method = RequestMethod.POST)
public User createUser(@RequestBody User user) {
// 实现创建用户的逻辑
return user;
}
}
@GetMapping
@GetMapping是@RequestMapping(method = RequestMethod.GET)的快捷方式,专门用于处理HTTP GET请求。使用@GetMapping可以让代码更加简洁易读。
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public User getUserById(@PathVariable("id") Long id) {
// 实现根据id获取用户的逻辑
return new User(id, "Jane Doe");
}
}
@PostMapping
与@GetMapping类似,@PostMapping是@RequestMapping(method = RequestMethod.POST)的快捷方式,专门用于处理HTTP POST请求。
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public User createUser(@RequestBody User user) {
// 实现创建用户的逻辑
return user;
}
}
@RequestParam
@RequestParam 注解用于将请求参数绑定到你的控制器处理方法的参数上。当客户端发起请求并包含该请求参数时,Spring MVC 会自动将请求参数的值赋给使用该注解的方法参数。
@RestController
public class helloController {
@GetMapping("/hello")
public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
return "Hello, " + name + "!";
}
}
@PathVariable
@PathVariable 注解用于从URL路径中提取变量值,并将其绑定到控制器处理方法的参数上。这通常与@RequestMapping(或其快捷方式如@GetMapping、@PostMapping等)一起使用,用于定义URL路径模板。
@RestController
public class UserController {
@GetMapping("/users/{userId}")
public User getUserById(@PathVariable("userId") Long userId) {
// 假设这里根据userId查找用户
return new User(userId, "John Doe");
}
}
@RequestBody
@RequestBody 注解用于将HTTP请求的正文(Body)绑定到控制器方法的参数上。通常,它用于处理非表单数据(如JSON、XML等)。当请求的内容类型为application/json时,Spring MVC会使用配置的HttpMessageConverter(如MappingJackson2HttpMessageConverter对于JSON)来将请求正文转换为Java对象。
@RestController
public class UserController {
@PostMapping("/users")
public User createUser(@RequestBody User user) {
// 假设这里有一些逻辑来保存用户
return user; // 或者返回一个不同的响应
}
}
10. 测试相关注解
在Spring框架中,JUnit是常用的测试框架之一,用于对Spring应用进行单元测试。@RunWith和@ContextConfiguration是JUnit与Spring集成时常用的两个注解。
@RunWith
@RunWith注解用于改变测试运行器的行为。在Spring的JUnit测试中,通常会使用SpringJUnit4ClassRunner或SpringRunner(Spring 4.2+)作为运行器,以启用Spring的测试支持。
@ContextConfiguration
@ContextConfiguration注解用于加载Spring的ApplicationContext上下文配置。这可以通过指定XML配置文件路径或使用基于Java的配置类来完成。classes属性是当使用基于Java的配置时,用来指定配置类的。
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class) // SpringRunner是SpringJUnit4ClassRunner的别名
@ContextConfiguration(classes = AppConfig.class) // 加载AppConfig配置类
public class MySpringTest {
@Autowired
private MyService myService; // 假设MyService是一个Spring管理的bean
@Test
public void testMyService() {
// 调用myService的方法进行测试
assert(myService.someMethod() == expectedValue);
}
}
在这个示例中,MySpringTest类使用@RunWith(SpringRunner.class)注解来指定测试运行器为SpringRunner,这使得Spring能够管理测试类中的bean。@ContextConfiguration(classes = AppConfig.class)注解告诉Spring要加载AppConfig类作为Spring应用上下文的配置。
11. 元注解
元注解用于定义其他注解的注解,如 @Retention、@Target、@Inherited、@Documented 和 @Repeatable。
@Retention
指定注解的保留策略(SOURCE、CLASS、RUNTIME)。
- SOURCE:注解仅保留在源码中,在编译成.class文件时被丢弃。
- CLASS:注解被保留在.class文件中,但JVM在运行时不可见。
- RUNTIME:注解被保留在.class文件中,并且在运行时可以通过反射被读取。
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
@Target
指定注解可以应用的Java元素类型(如类、方法、字段等)。
- TYPE:类、接口(包括注解类型)或枚举声明
- FIELD:字段声明(包括枚举常量)
- METHOD:方法声明
- PARAMETER:参数声明
- CONSTRUCTOR:构造器声明
- LOCAL_VARIABLE:局部变量声明
- ANNOTATION_TYPE:注解类型声明
- TYPE_PARAMETER:类型参数声明(1.8+)
- TYPE_USE:类型使用声明(1.8+)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Test {
}
@Inherited
表示一个注解类型会被自动继承。如果用户在类声明上使用了某个带@Inherited注解的注解类型,那么子类将自动继承父类(不包括接口)中该类型的注解。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Inheritable {
}
@Inheritable
public class Parent {
}
public class Child extends Parent {
// Child类也会被视为有@Inheritable注解
}
@Documented
表示注解将被包含在javadoc中。默认情况下,javadoc不会包含注解类型的信息,除非该注解类型被@Documented注解。
@Repeatable
表示注解可以在同一个元素上重复使用。Java 8引入,允许在同一个声明类型(如方法)上多次使用同一个注解类型。
首先,需要定义一个容器注解(Containing Annotation):
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Authors {
Author[] value();
}
@Repeatable(Authors.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Author {
String name() default "Unknown";
}
然后,可以这样使用注解:
public class Book {
@Author(name = "天蚕土豆")
@Author(name = "唐家三少")
public void introduction() {
// 方法介绍
}
}
虽然直接看起来@Author被重复使用了,但实际上Java编译器会将其转换为使用@Authors注解,并包含一个Author数组作为值。这是自动完成的,无需手动编写@Authors注解。
12. 其他注解
@Value
@Value 注解是Spring框架中用于属性注入的一个非常强大的注解,它支持多种注入方式,包括但不限于直接注入普通字符串、环境变量、表达式结果、其他Bean的属性、文件内容等。
@Component
public class MyBean {
# 注入普通字符串
@Value("Hello, World!")
private String helloMessage;
# 注入SpEL表达式结果
@Value("#{systemProperties['user.home']}")
private String userHome;
# 注入配置文件中的属性
@Value("${my.property}")
private String myProperty;
// getter 和 setter 省略
}
@JsonIgnore
@JsonIgnore 注解是Jackson库提供的,用于在序列化对象到JSON时忽略某些属性。这在处理敏感信息或不需要在JSON响应中暴露的字段时非常有用。
import com.fasterxml.jackson.annotation.JsonIgnore;
public class User {
private String username;
@JsonIgnore
private String password;
// 构造器、getter 和 setter 省略
}
在这个例子中,即使User对象被序列化为JSON,password字段也不会出现在JSON字符串中。这对于保护用户隐私和避免敏感信息泄露非常重要。