Spring框架常用注解详解

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字符串中。这对于保护用户隐私和避免敏感信息泄露非常重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值