SpringBoot常用注解详解

news2025/5/17 17:00:58

文章目录

    • 1. 前言
    • 2. 核心注解
      • 2.1 @SpringBootApplication
      • 2.2 @Configuration
      • 2.3 @EnableAutoConfiguration
      • 2.4 @ComponentScan
      • 2.5 @Bean
      • 2.6 @Autowired
      • 2.7 @Qualifier
      • 2.8 @Primary
      • 2.9 @Value
      • 2.10 @PropertySource
      • 2.11 @ConfigurationProperties
      • 2.12 @Profile
    • 3. Web开发相关注解
      • 3.1 @Controller
      • 3.2 @RestController
      • 3.3 @RequestMapping
      • 3.4 @GetMapping、@PostMapping等
      • 3.5 @PathVariable
      • 3.6 @RequestParam
      • 3.7 @RequestBody
      • 3.8 @ResponseBody
      • 3.9 @ResponseStatus
      • 3.10 @ExceptionHandler
      • 3.11 @ControllerAdvice / @RestControllerAdvice
    • 4. 数据访问相关注解
      • 4.1 @Repository
      • 4.2 @Entity
      • 4.3 @Table
      • 4.4 @Id和@GeneratedValue
      • 4.5 @Column
      • 4.6 @Transactional
      • 4.7 Spring Data JPA相关注解
        • 4.7.1 @Query
        • 4.7.2 @Param
        • 4.7.3 @Modifying
      • 4.8 Mybatis相关注解
        • 4.8.1 @Mapper
        • 4.8.2 @Select、@Insert、@Update、@Delete
        • 4.8.3 @Results和@Result
    • 5. 安全相关注解
      • 5.1 @EnableWebSecurity
      • 5.2 @PreAuthorize和@PostAuthorize
      • 5.3 @Secured
    • 6. 测试相关注解
      • 6.1 @SpringBootTest
      • 6.2 @WebMvcTest
      • 6.3 @DataJpaTest
      • 6.4 @MockBean
      • 6.5 @SpyBean
    • 7. 总结
    • 8. 参考资料

1. 前言

Spring Boot是基于Spring框架的快速开发套件,大量使用注解来简化配置和开发。本文档详细介绍SpringBoot中常用的注解,对每个注解的功能、使用场景和示例代码进行全面解析,帮助新手开发者快速掌握SpringBoot开发。

注解是Java代码的元数据,不会直接影响程序执行,但会被框架读取并用于控制应用行为。SpringBoot注解极大地减少了XML配置的繁琐,使代码更加简洁优雅。

2. 核心注解

2.1 @SpringBootApplication

这是SpringBoot最核心的注解,用于标记SpringBoot应用程序的入口类。它结合了三个注解的功能:

  • @Configuration:将类标记为应用上下文的Bean定义源
  • @EnableAutoConfiguration:启用SpringBoot的自动配置机制
  • @ComponentScan:启用组件扫描,自动发现和注册Bean

语法

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

详细解析
@SpringBootApplication使用时可以自定义属性来调整行为:

@SpringBootApplication(
    scanBasePackages = {"com.example.project", "org.example.another"},
    exclude = {DataSourceAutoConfiguration.class},
    excludeName = {"org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration"}
)
public class CustomizedApplication {
    public static void main(String[] args) {
        SpringApplication.run(CustomizedApplication.class, args);
    }
}
  • scanBasePackages:指定要扫描的包路径,默认只扫描注解所在类的包及其子包
  • exclude:排除特定的自动配置类
  • excludeName:通过类名排除特定的自动配置类

实践建议

  • @SpringBootApplication注解的类保持简洁,仅包含必要的启动代码
  • 入口类通常放在根包(其他类的父包)下,这样默认组件扫描会涵盖所有项目类
  • 如果需要自定义扫描范围或排除某些配置,使用注解的属性而不是创建单独的配置类

2.2 @Configuration

标记一个类作为Bean定义的来源,类似于传统Spring的XML配置文件。

语法

@Configuration
public class AppConfig {
    
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

详细解析
@Configuration类中的@Bean方法会被Spring容器处理,创建并管理返回的对象实例。配置类本身也会作为Bean管理。

默认情况下,@Configuration类使用CGLIB代理增强,确保@Bean方法之间的引用始终返回同一个实例:

@Configuration
public class DatabaseConfig {
    
    @Bean
    public DataSource dataSource() {
        BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName("org.postgresql.Driver");
        ds.setUrl("jdbc:postgresql://localhost:5432/mydb");
        ds.setUsername("user");
        ds.setPassword("pass");
        return ds;
    }
    
    @Bean
    public JdbcTemplate jdbcTemplate() {
        // 多次调用dataSource()将返回相同实例
        return new JdbcTemplate(dataSource());
    }
}

特殊选项

  • @Configuration(proxyBeanMethods = false):使用lite模式,不代理方法,提高性能但不保证Bean引用的一致性

使用场景

  • 创建第三方库中类的Beans
  • 定义复杂的Bean初始化逻辑
  • 管理Bean之间的依赖关系

2.3 @EnableAutoConfiguration

启用SpringBoot的自动配置机制,根据类路径上的依赖自动配置Spring应用。

语法

@EnableAutoConfiguration
public class MyConfig {
    // ...
}

通常不直接使用,而是作为@SpringBootApplication的一部分。

工作原理

  1. 搜索类路径下所有的META-INF/spring.factories文件
  2. 查找org.springframework.boot.autoconfigure.EnableAutoConfiguration属性定义的配置类
  3. 应用这些配置类,但会根据条件注解(如@ConditionalOnClass)决定是否启用

自定义

@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class CustomAutoConfig {
    // ...
}

2.4 @ComponentScan

配置组件扫描指令,自动发现并注册标有@Component@Service@Repository@Controller等注解的类。

语法

@ComponentScan(basePackages = "com.example")
public class AppConfig {
    // ...
}

详细解析
可以指定多种扫描属性:

@ComponentScan(
    basePackages = {"com.example.service", "com.example.repository"},
    basePackageClasses = {UserService.class, ProductRepository.class},
    includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*ServiceImpl"),
    excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = LegacyService.class)
)
public class DetailedScanConfig {
    // ...
}
  • basePackages:要扫描的包名
  • basePackageClasses:标记性类,将扫描这些类所在的包
  • includeFilters:指定包含哪些类
  • excludeFilters:指定排除哪些类
  • useDefaultFilters:是否使用默认过滤器(默认为true)

2.5 @Bean

@Configuration类中标记方法,告诉Spring该方法将返回一个应注册为Spring容器中Bean的对象。

语法

@Configuration
public class AppConfig {
    
    @Bean
    public MyBean myBean() {
        return new MyBean();
    }
}

详细属性

@Configuration
public class DetailedBeanConfig {
    
    @Bean(
        name = "customBeanName",
        initMethod = "init",
        destroyMethod = "cleanup",
        autowireCandidate = true
    )
    @Description("这是一个示例Bean")
    @Scope("prototype")
    public ComplexBean complexBean() {
        return new ComplexBean();
    }
}
  • name/value:指定Bean的名称(别名)
  • initMethod:初始化方法名称
  • destroyMethod:销毁方法名称(默认会自动检测close或shutdown方法)
  • autowireCandidate:是否作为自动装配的候选
  • 可与@Scope@Description@Lazy等注解组合使用

实践示例

@Configuration
public class ServiceConfig {
    
    @Autowired
    private DatabaseProperties dbProps;
    
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(dbProps.getUrl());
        config.setUsername(dbProps.getUsername());
        config.setPassword(dbProps.getPassword());
        config.setMaximumPoolSize(dbProps.getMaxPoolSize());
        return new HikariDataSource(config);
    }
    
    @Bean
    @ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("users", "transactions");
    }
}

2.6 @Autowired

自动装配依赖的Bean。Spring会在应用上下文中查找匹配的Bean并注入。

语法

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    // 方法也可以使用@Autowired
    @Autowired
    public void setMessageService(MessageService messageService) {
        this.messageService = messageService;
    }
}

详细用法

  1. 构造函数注入(推荐):
@Service
public class ProductService {
    
    private final ProductRepository repository;
    private final PriceCalculator calculator;
    
    @Autowired // 在Spring 4.3+,如果只有一个构造函数,@Autowired可以省略
    public ProductService(ProductRepository repository, PriceCalculator calculator) {
        this.repository = repository;
        this.calculator = calculator;
    }
}
  1. 字段注入
@Controller
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    @Qualifier("mainAuthenticator") // 当有多个同类型Bean时,指定具体实现
    private Authenticator authenticator;
}
  1. 可选依赖
@Component
public class OptionalDependencyExample {
    
    @Autowired(required = false) // 依赖不存在也不会报错
    private OptionalService optionalService;
    
    public void doWork() {
        if (optionalService != null) {
            optionalService.perform();
        } else {
            // 备选逻辑
        }
    }
}

最佳实践

  • 使用构造函数注入,而不是字段注入
  • 对于必需的依赖,构造函数注入可以确保在创建Bean时就完成注入
  • 使用final字段防止依赖被修改
  • 当有多个相同类型的Bean时,使用@Qualifier@Primary解决冲突

2.7 @Qualifier

当有多个相同类型的Bean时,@Qualifier用于指定要注入的特定Bean。

语法

@Component
public class PaymentProcessor {
    
    @Autowired
    @Qualifier("visaPaymentGateway")
    private PaymentGateway paymentGateway;
}

对应的Bean定义

@Component("visaPaymentGateway")
public class VisaPaymentGateway implements PaymentGateway {
    // 实现
}

@Component("paypalPaymentGateway")
public class PayPalPaymentGateway implements PaymentGateway {
    // 实现
}

构造函数参数限定

@Service
public class CheckoutService {
    
    private final PaymentGateway paymentGateway;
    private final ShippingCalculator shippingCalculator;
    
    @Autowired
    public CheckoutService(
            @Qualifier("visaPaymentGateway") PaymentGateway paymentGateway,
            @Qualifier("internationalShipping") ShippingCalculator shippingCalculator) {
        this.paymentGateway = paymentGateway;
        this.shippingCalculator = shippingCalculator;
    }
}

2.8 @Primary

标记首选的Bean,当存在多个相同类型的Bean时,优先注入带有@Primary注解的Bean。

语法

@Component
@Primary
public class MainUserRepository implements UserRepository {
    // 优先使用的实现
}

@Component
public class SecondaryUserRepository implements UserRepository {
    // 备用实现
}

自动注入时

@Service
public class DefaultUserService {
    
    private final UserRepository userRepository;
    
    @Autowired
    public DefaultUserService(UserRepository userRepository) {
        // 将自动注入带有@Primary的MainUserRepository
        this.userRepository = userRepository;
    }
}

与@Qualifier结合
@Primary提供了系统默认值,而@Qualifier允许在特定情况下覆盖默认行为:

@Service
public class SpecialUserService {
    
    private final UserRepository userRepository;
    
    @Autowired
    public SpecialUserService(@Qualifier("secondaryUserRepository") UserRepository userRepository) {
        // 明确指定使用非主要的实现
        this.userRepository = userRepository;
    }
}

2.9 @Value

注入外部化配置值,如属性文件、环境变量或系统属性中的值。

语法

@Component
public class DatabaseSettings {
    
    @Value("${database.url}")
    private String databaseUrl;
    
    @Value("${database.username:root}") // 默认值为root
    private String username;
    
    @Value("#{systemProperties['java.version']}") // SpEL表达式
    private String javaVersion;
    
    @Value("#{new java.util.Random().nextInt(100)}") // 计算值
    private int randomNumber;
}

支持的用法

  1. 属性占位符
@Value("${app.timeout:30}") // 从配置中读取app.timeout,默认值为30
private int timeout;
  1. SpEL表达式
@Value("#{environment['USER'] ?: 'Unknown'}") // 环境变量USER,如不存在则为'Unknown'
private String user;

@Value("#{configProperties.getProperty('app.max-connections')}") // 调用方法
private Integer maxConnections;
  1. 直接值
@Value("true") // 布尔值
private boolean enabled;

@Value("100") // 整数
private int max;
  1. 数组和集合
@Value("${app.servers:localhost:8080,localhost:8081}")
private String[] serverArray;

@Value("#{'${app.regions}'.split(',')}")
private List<String> regionList;

配置文件示例(application.properties):

database.url=jdbc:mysql://localhost:3306/myapp
database.username=admin
app.timeout=60
app.servers=server1.example.com,server2.example.com
app.regions=US,EU,ASIA

2.10 @PropertySource

指定属性文件的位置,用于加载外部配置。

语法

@Configuration
@PropertySource("classpath:database.properties")
public class DatabaseConfig {
    
    @Autowired
    private Environment environment;
    
    @Value("${db.driver}")
    private String driver;
    
    @Bean
    public DataSource dataSource() {
        BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName(environment.getProperty("db.driver"));
        ds.setUrl(environment.getProperty("db.url"));
        ds.setUsername(environment.getProperty("db.username"));
        ds.setPassword(environment.getProperty("db.password"));
        return ds;
    }
}

多属性源

@Configuration
@PropertySource({
    "classpath:database.properties",
    "classpath:application-${spring.profiles.active}.properties",
    "file:/opt/config/external.properties"
})
public class MultiplePropertiesConfig {
    // ...
}

自定义编码和工厂

@Configuration
@PropertySource(
    value = "classpath:i18n/messages_zh_CN.properties",
    encoding = "UTF-8",
    factory = YamlPropertySourceFactory.class
)
public class InternationalConfig {
    // ...
}

使用环境

@Component
public class DatabaseService {
    
    private final Environment env;
    
    @Autowired
    public DatabaseService(Environment env) {
        this.env = env;
    }
    
    public void printConfig() {
        System.out.println("Driver: " + env.getProperty("db.driver"));
        System.out.println("URL: " + env.getProperty("db.url"));
        System.out.println("Timeout: " + env.getProperty("db.timeout", Integer.class, 30));
    }
}

2.11 @ConfigurationProperties

将配置属性绑定到结构化的Java对象。

语法

@Component
@ConfigurationProperties(prefix = "app.mail")
public class MailProperties {
    
    private String host;
    private int port = 25; // 默认值
    private String username;
    private String password;
    private boolean ssl = false;
    private List<String> recipients = new ArrayList<>();
    private Map<String, String> headers = new HashMap<>();
    
    // Getters and setters...
}

对应的配置(application.yml):

app:
  mail:
    host: smtp.example.com
    port: 587
    username: user@example.com
    password: secret
    ssl: true
    recipients:
      - admin@example.com
      - support@example.com
    headers:
      X-Priority: High
      X-Mailer: MyApp

与@Bean结合使用

@Configuration
public class AppConfig {
    
    @Bean
    @ConfigurationProperties(prefix = "datasource")
    public HikariConfig hikariConfig() {
        return new HikariConfig();
    }
    
    @Bean
    public DataSource dataSource(HikariConfig config) {
        return new HikariDataSource(config);
    }
}

嵌套属性类

@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
    
    private String name;
    private String description;
    private final Security security = new Security();
    
    // Getters and setters...
    
    public static class Security {
        private boolean enabled;
        private String tokenSecret;
        private int tokenExpirySeconds = 3600;
        
        // Getters and setters...
    }
}

对应的配置

app:
  name: MyApplication
  description: Sample SpringBoot Application
  security:
    enabled: true
    token-secret: abc123xyz789
    token-expiry-seconds: 7200

验证

@Component
@ConfigurationProperties(prefix = "app.server")
@Validated
public class ServerProperties {
    
    @NotEmpty
    private String host;
    
    @Min(1024)
    @Max(65535)
    private int port;
    
    @Pattern(regexp = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")
    private String ipAddress;
    
    // Getters and setters...
}

启用配置属性支持
在启动类上使用:

@SpringBootApplication
@EnableConfigurationProperties
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

2.12 @Profile

指定组件在特定的环境配置文件激活时才加载。

语法

@Configuration
@Profile("development")
public class DevelopmentConfig {
    
    @Bean
    public DataSource devDataSource() {
        // 返回开发环境的数据源配置
    }
}

@Configuration
@Profile("production")
public class ProductionConfig {
    
    @Bean
    public DataSource prodDataSource() {
        // 返回生产环境的数据源配置
    }
}

在Bean级别使用

@Configuration
public class AppConfig {
    
    @Bean
    @Profile("development")
    public EmailService mockEmailService() {
        return new MockEmailService();
    }
    
    @Bean
    @Profile("production")
    public EmailService smtpEmailService() {
        return new SmtpEmailService();
    }
}

使用表达式

@Component
@Profile("!development") // 非开发环境
public class RealPaymentProcessor implements PaymentProcessor {
    // 实现
}

@Component
@Profile("development | test") // 开发或测试环境
public class MockPaymentProcessor implements PaymentProcessor {
    // 实现
}

设置激活的配置文件

  1. application.properties 文件:
spring.profiles.active=development
  1. 命令行参数:
java -jar myapp.jar --spring.profiles.active=production
  1. 环境变量:
export SPRING_PROFILES_ACTIVE=production
java -jar myapp.jar
  1. 编程方式:
public static void main(String[] args) {
    SpringApplication app = new SpringApplication(MyApplication.class);
    app.setAdditionalProfiles("production");
    app.run(args);
}

3. Web开发相关注解

3.1 @Controller

标记一个类作为Spring MVC控制器,处理HTTP请求。

语法

@Controller
public class UserController {
    
    @GetMapping("/users")
    public String listUsers(Model model) {
        model.addAttribute("users", userService.findAll());
        return "users/list";  // 返回视图名称
    }
}

详细解析
@Controller注解的类会被自动检测并注册为Spring Bean。它主要用于处理视图和模型,默认方法返回视图名称。

3.2 @RestController

结合了@Controller@ResponseBody,专门用于构建RESTful API。

语法

@RestController
@RequestMapping("/api/users")
public class UserRestController {
    
    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();  // 直接返回对象,会自动转换为JSON
    }
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
}

详细解析

  • @RestController中的方法返回值会直接序列化到HTTP响应体中,通常是JSON格式
  • 不需要在每个方法上添加@ResponseBody注解
  • 适用于构建API而不是返回视图的应用

3.3 @RequestMapping

映射HTTP请求到控制器的方法。

语法

@Controller
@RequestMapping("/users")  // 类级别的基本路径
public class UserController {
    
    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public String listUsers(Model model) {
        // 处理 GET /users/list
        return "users/list";
    }
    
    @RequestMapping(
        path = "/save",
        method = RequestMethod.POST,
        consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
        produces = MediaType.TEXT_HTML_VALUE
    )
    public String saveUser(User user) {
        // 处理 POST /users/save
        return "redirect:/users/list";
    }
}

详细属性

  • value/path:URL路径
  • method:HTTP方法(GET、POST、PUT、DELETE等)
  • params:请求必须包含的参数
  • headers:请求必须包含的头信息
  • consumes:请求的内容类型
  • produces:响应的内容类型

特殊用法

@Controller
public class AdvancedMappingController {
    
    // 多路径映射
    @RequestMapping({"/home", "/index", "/"})
    public String home() {
        return "home";
    }
    
    // 参数条件
    @RequestMapping(path = "/search", params = "type=user")
    public String searchUsers() {
        return "users/search";
    }
    
    // 请求头条件
    @RequestMapping(path = "/api/data", headers = "X-API-VERSION=1")
    @ResponseBody
    public ApiData getDataV1() {
        return new ApiData("v1");
    }
}

3.4 @GetMapping、@PostMapping等

@RequestMapping的HTTP方法特定变体,让代码更加简洁明了。

语法

@RestController
@RequestMapping("/api/products")
public class ProductController {
    
    // 等同于 @RequestMapping(method = RequestMethod.GET)
    @GetMapping
    public List<Product> getAllProducts() {
        return productService.findAll();
    }
    
    // 等同于 @RequestMapping(path = "/{id}", method = RequestMethod.GET)
    @GetMapping("/{id}")
    public Product getProduct(@PathVariable Long id) {
        return productService.findById(id);
    }
    
    // POST请求
    @PostMapping
    public Product createProduct(@RequestBody Product product) {
        return productService.save(product);
    }
    
    // PUT请求
    @PutMapping("/{id}")
    public Product updateProduct(@PathVariable Long id, @RequestBody Product product) {
        product.setId(id);
        return productService.update(product);
    }
    
    // DELETE请求
    @DeleteMapping("/{id}")
    public void deleteProduct(@PathVariable Long id) {
        productService.deleteById(id);
    }
    
    // PATCH请求
    @PatchMapping("/{id}")
    public Product patchProduct(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
        return productService.partialUpdate(id, updates);
    }
}

可用注解

  • @GetMapping:处理GET请求,通常用于获取资源
  • @PostMapping:处理POST请求,通常用于创建资源
  • @PutMapping:处理PUT请求,通常用于完全更新资源
  • @DeleteMapping:处理DELETE请求,通常用于删除资源
  • @PatchMapping:处理PATCH请求,通常用于部分更新资源

详细配置

@GetMapping(
    path = "/export",
    params = "format=pdf",
    produces = MediaType.APPLICATION_PDF_VALUE
)
public ResponseEntity<byte[]> exportPdf() {
    byte[] content = generatePdfContent();
    HttpHeaders headers = new HttpHeaders();
    headers.setContentDispositionFormData("attachment", "report.pdf");
    return new ResponseEntity<>(content, headers, HttpStatus.OK);
}

3.5 @PathVariable

将URL路径变量绑定到方法参数。

语法

@GetMapping("/users/{id}/posts/{postId}")
public Post getUserPost(
    @PathVariable Long id,
    @PathVariable Long postId
) {
    return postService.findByUserIdAndPostId(id, postId);
}

详细用法

  1. 自定义变量名
@GetMapping("/users/{userId}/roles/{roleId}")
public String assignRole(
    @PathVariable("userId") Long id,
    @PathVariable("roleId") Long roleId
) {
    userService.assignRole(id, roleId);
    return "redirect:/users";
}
  1. 可选路径变量
@GetMapping({"/orders/{id}", "/orders"})
public List<Order> getOrders(@PathVariable(required = false) Long id) {
    if (id != null) {
        return Collections.singletonList(orderService.findById(id));
    }
    return orderService.findAll();
}
  1. Map中捕获所有变量
@GetMapping("/users/{country}/{city}/{name}")
public User findUser(@PathVariable Map<String, String> pathVars) {
    String country = pathVars.get("country");
    String city = pathVars.get("city");
    String name = pathVars.get("name");
    return userService.findByCountryAndCityAndName(country, city, name);
}

注意事项

  • 路径变量名必须与URL路径占位符匹配,除非使用name属性明确指定
  • 如果URL模板中的某个变量不是必需的,确保使用多个@GetMapping路径或将required设置为false
  • Spring会自动转换简单类型(如String、Integer、Long)的路径变量

3.6 @RequestParam

将请求参数绑定到方法参数。

语法

@GetMapping("/search")
public List<Product> searchProducts(
    @RequestParam String query,
    @RequestParam(defaultValue = "0") int page,
    @RequestParam(defaultValue = "10") int size
) {
    return productService.search(query, page, size);
}

详细用法

  1. 自定义参数名
@GetMapping("/filter")
public List<User> filterUsers(
    @RequestParam("q") String query,
    @RequestParam("pg") int page
) {
    return userService.filter(query, page);
}
  1. 可选参数
@GetMapping("/products")
public List<Product> getProducts(
    @RequestParam(required = false) String category,
    @RequestParam(required = false) Double minPrice
) {
    return productService.findByCriteria(category, minPrice);
}
  1. 设置默认值
@GetMapping("/employees")
public List<Employee> getEmployees(
    @RequestParam(defaultValue = "name") String sortBy,
    @RequestParam(defaultValue = "asc") String order
) {
    return employeeService.findAllSorted(sortBy, order);
}
  1. 映射所有参数
@PostMapping("/process")
public void processForm(@RequestParam Map<String, String> formParams) {
    formService.process(formParams);
}
  1. 多值参数
@GetMapping("/filter")
public List<Product> filterProducts(
    @RequestParam List<String> categories,
    @RequestParam(defaultValue = "0,100") List<Double> priceRange
) {
    return productService.filterByCategoriesAndPriceRange(categories, priceRange);
}

URL示例

/filter?categories=electronics&categories=books&priceRange=10&priceRange=50

3.7 @RequestBody

将HTTP请求体绑定到方法参数。

语法

@PostMapping("/users")
public User createUser(@RequestBody User user) {
    return userService.save(user);
}

详细用法

  1. 基本使用
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest orderRequest) {
    Order createdOrder = orderService.createOrder(orderRequest);
    return ResponseEntity.created(URI.create("/orders/" + createdOrder.getId()))
            .body(createdOrder);
}
  1. 请求验证
@PostMapping("/register")
public User registerUser(@Valid @RequestBody UserRegistration registration) {
    return userService.register(registration);
}
  1. 可选请求体
@PutMapping("/users/{id}")
public User updateUser(
    @PathVariable Long id,
    @RequestBody(required = false) UserUpdateRequest updateRequest
) {
    if (updateRequest == null) {
        throw new BadRequestException("Request body is required");
    }
    return userService.update(id, updateRequest);
}

工作原理

  • 使用HTTP消息转换器(HttpMessageConverter)将请求体转换为对象
  • 根据Content-Type头选择适当的转换器(如JSON转换为Java对象)
  • 可与Bean验证(@Valid)结合使用

常见问题

  • 客户端必须设置Content-Type头(如application/json
  • 请求体必须是有效的JSON/XML等(根据Content-Type)
  • 如果请求体无法解析,Spring会返回400 Bad Request
  • 默认情况下,@RequestBody是必需的,可以通过required=false设置为可选

3.8 @ResponseBody

表示方法的返回值应该直接写入HTTP响应体,而不是视图名。

语法

@Controller
public class ApiController {
    
    @GetMapping("/api/data")
    @ResponseBody
    public Map<String, Object> getData() {
        Map<String, Object> data = new HashMap<>();
        data.put("key", "value");
        return data; // 自动转换为JSON并写入响应体
    }
}

详细解析

  • 通常与@Controller一起使用,将方法返回值直接写入响应体
  • @RestController已经包含了@ResponseBody,无需再添加
  • 根据Accept头或配置选择适当的HttpMessageConverter进行序列化

自定义响应

@Controller
public class FileController {
    
    @GetMapping("/download/{filename}")
    @ResponseBody
    public ResponseEntity<byte[]> downloadFile(@PathVariable String filename) {
        byte[] fileContent = fileService.getFileContent(filename);
        
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentDispositionFormData("attachment", filename);
        
        return new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
    }
}

3.9 @ResponseStatus

指定HTTP响应状态码。

语法

@PostMapping("/users")
@ResponseStatus(HttpStatus.CREATED)
public User createUser(@RequestBody User user) {
    return userService.save(user);
}

详细用法

  1. 方法级别
@DeleteMapping("/users/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) {
    userService.delete(id);
}
  1. 类级别(适用于所有方法):
@RestController
@ResponseStatus(HttpStatus.OK)
public class HealthController {
    
    @GetMapping("/health")
    public Map<String, String> checkHealth() {
        return Collections.singletonMap("status", "UP");
    }
}
  1. 异常类
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "User not found")
public class UserNotFoundException extends RuntimeException {
    
    public UserNotFoundException(Long id) {
        super("User with ID " + id + " not found");
    }
}

当抛出此异常时,Spring会返回404状态码:

@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
    return userService.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
}

3.10 @ExceptionHandler

处理控制器中的异常。

语法

@RestController
public class ProductController {
    
    @GetMapping("/products/{id}")
    public Product getProduct(@PathVariable Long id) {
        Product product = productService.findById(id);
        if (product == null) {
            throw new ProductNotFoundException(id);
        }
        return product;
    }
    
    @ExceptionHandler(ProductNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Map<String, String> handleProductNotFound(ProductNotFoundException ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", "Product not found");
        error.put("message", ex.getMessage());
        return error;
    }
}

详细用法

  1. 处理多种异常
@ExceptionHandler({ResourceNotFoundException.class, AccessDeniedException.class})
public ResponseEntity<ErrorResponse> handleResourceErrors(Exception ex) {
    ErrorResponse error = new ErrorResponse();
    
    if (ex instanceof ResourceNotFoundException) {
        error.setStatus(HttpStatus.NOT_FOUND.value());
        error.setMessage("Resource not found");
    } else if (ex instanceof AccessDeniedException) {
        error.setStatus(HttpStatus.FORBIDDEN.value());
        error.setMessage("Access denied");
    }
    
    error.setTimestamp(new Date());
    error.setDetails(ex.getMessage());
    
    return new ResponseEntity<>(error, HttpStatus.valueOf(error.getStatus()));
}
  1. 获取请求详情
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationErrorResponse> handleValidationError(
    MethodArgumentNotValidException ex,
    WebRequest request
) {
    ValidationErrorResponse error = new ValidationErrorResponse();
    error.setTimestamp(new Date());
    error.setStatus(HttpStatus.BAD_REQUEST.value());
    error.setMessage("Validation error");
    error.setPath(request.getDescription(false));
    
    // 提取验证错误
    List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(e -> e.getField() + ": " + e.getDefaultMessage())
            .collect(Collectors.toList());
    
    error.setErrors(errors);
    
    return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}

作用范围

  • 控制器类内定义的@ExceptionHandler方法只能处理该控制器内抛出的异常
  • 要处理全局异常,请使用@ControllerAdvice@RestControllerAdvice

3.11 @ControllerAdvice / @RestControllerAdvice

定义全局异常处理器、绑定数据或初始化绑定器等,适用于所有@Controller类。

语法

@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ResponseBody
    public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex) {
        ErrorResponse error = new ErrorResponse();
        error.setStatus(HttpStatus.NOT_FOUND.value());
        error.setMessage(ex.getMessage());
        error.setTimestamp(new Date());
        return error;
    }
    
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorResponse handleGeneralError(Exception ex) {
        ErrorResponse error = new ErrorResponse();
        error.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        error.setMessage("An unexpected error occurred");
        error.setTimestamp(new Date());
        return error;
    }
}

@RestControllerAdvice
结合了@ControllerAdvice@ResponseBody,类似于@RestController

@RestControllerAdvice
public class GlobalRestExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex) {
        // 与上面相同,但不需要@ResponseBody
    }
}

详细属性

@ControllerAdvice(
    basePackages = {"com.example.controllers"},
    assignableTypes = {UserController.class, ProductController.class},
    annotations = {RestController.class}
)
public class SpecificExceptionHandler {
    // 只适用于指定包、类或注解的控制器
}
  • basePackages:适用于指定包及其子包中的控制器
  • assignableTypes:适用于指定类型的控制器
  • annotations:适用于带有指定注解的控制器

全局数据绑定

@ControllerAdvice
public class GlobalModelAttributes {
    
    @ModelAttribute("globalSettings")
    public Map<String, String> globalSettings() {
        Map<String, String> settings = new HashMap<>();
        settings.put("siteName", "My Awesome Site");
        settings.put("copyrightYear", String.valueOf(Year.now().getValue()));
        return settings;
    }
    
    @ModelAttribute
    public void addAttributes(Model model) {
        model.addAttribute("version", "1.0.0");
        model.addAttribute("lastUpdated", new Date());
    }
}

全局数据绑定器

@ControllerAdvice
public class GlobalBindingInitializer {
    
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
        
        // 禁止修改特定字段
        binder.setDisallowedFields("id", "createdAt");
    }
} 

4. 数据访问相关注解

4.1 @Repository

标记数据访问层组件(DAO层)。

语法

@Repository
public class JpaUserRepository implements UserRepository {
    
    @PersistenceContext
    private EntityManager entityManager;
    
    @Override
    public User findById(Long id) {
        return entityManager.find(User.class, id);
    }
    
    @Override
    public List<User> findAll() {
        return entityManager.createQuery("SELECT u FROM User u", User.class)
                .getResultList();
    }
}

详细解析

  • @Repository@Component的特化,用于数据访问对象
  • 自动被组件扫描检测并注册为Bean
  • 提供数据访问异常转换,将特定于持久化技术的异常(如JDBC、JPA等)转换为Spring的DataAccessException层次结构

实际应用示例

@Repository
public class ProductRepositoryImpl implements ProductRepository {
    
    private final JdbcTemplate jdbcTemplate;
    
    @Autowired
    public ProductRepositoryImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    
    @Override
    public Product findById(Long id) {
        try {
            return jdbcTemplate.queryForObject(
                    "SELECT id, name, price, description FROM products WHERE id = ?",
                    new Object[]{id},
                    (rs, rowNum) -> {
                        Product product = new Product();
                        product.setId(rs.getLong("id"));
                        product.setName(rs.getString("name"));
                        product.setPrice(rs.getBigDecimal("price"));
                        product.setDescription(rs.getString("description"));
                        return product;
                    }
            );
        } catch (EmptyResultDataAccessException e) {
            // 这里的异常已由Spring转换为DataAccessException层次结构
            return null;
        }
    }
    
    // 其他实现...
}

4.2 @Entity

将Java类标记为JPA实体。

语法

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "username", unique = true, nullable = false, length = 50)
    private String username;
    
    @Column(name = "email", unique = true, nullable = false)
    private String email;
    
    @Column(name = "password", nullable = false)
    private String password;
    
    @Column(name = "created_at")
    @Temporal(TemporalType.TIMESTAMP)
    private Date createdAt;
    
    // Getters and setters...
}

详细属性

  • name:实体名称,默认为类名
  • 需结合@Table@Id等JPA注解使用

关系映射

@Entity
@Table(name = "orders")
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "order_number", nullable = false, unique = true)
    private String orderNumber;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id", nullable = false)
    private Customer customer;
    
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderItem> items = new ArrayList<>();
    
    @ManyToMany
    @JoinTable(
        name = "order_promotions",
        joinColumns = @JoinColumn(name = "order_id"),
        inverseJoinColumns = @JoinColumn(name = "promotion_id")
    )
    private Set<Promotion> promotions = new HashSet<>();
    
    // Getters and setters...
}

4.3 @Table

指定实体映射的数据库表。

语法

@Entity
@Table(
    name = "products",
    schema = "inventory",
    uniqueConstraints = {
        @UniqueConstraint(columnNames = {"sku", "vendor_id"}),
        @UniqueConstraint(columnNames = {"name", "vendor_id"})
    },
    indexes = {
        @Index(name = "idx_product_name", columnList = "name"),
        @Index(name = "idx_product_price", columnList = "price")
    }
)
public class Product {
    // Class implementation...
}

详细属性

  • name:表名,默认为实体类名
  • schema/catalog:数据库schema或catalog
  • uniqueConstraints:唯一约束
  • indexes:表索引
  • quoteStrategy:包围标识符的策略(如表名、列名)

4.4 @Id和@GeneratedValue

@Id标记实体的主键属性,@GeneratedValue定义主键生成策略。

语法

@Entity
public class Employee {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    // 其他属性...
}

生成策略

// 自增主键(MySQL、SQL Server、PostgreSQL)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// 序列(Oracle、PostgreSQL)
@Id
@GeneratedValue(
    strategy = GenerationType.SEQUENCE,
    generator = "employee_seq"
)
@SequenceGenerator(
    name = "employee_seq",
    sequenceName = "employee_sequence",
    allocationSize = 1
)
private Long id;

// 表(通用)
@Id
@GeneratedValue(
    strategy = GenerationType.TABLE,
    generator = "employee_gen"
)
@TableGenerator(
    name = "employee_gen",
    table = "id_generator",
    pkColumnName = "gen_name",
    valueColumnName = "gen_value",
    allocationSize = 1
)
private Long id;

// UUID(通用,需要手动设置)
@Id
@Column(length = 36)
private String id;

public Employee() {
    this.id = UUID.randomUUID().toString();
}

4.5 @Column

指定实体属性映射的表列详情。

语法

@Entity
public class Customer {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(
        name = "full_name",
        nullable = false,
        length = 100
    )
    private String fullName;
    
    @Column(
        name = "email",
        unique = true,
        nullable = false,
        length = 150
    )
    private String email;
    
    @Column(
        name = "credit_limit",
        precision = 10,
        scale = 2
    )
    private BigDecimal creditLimit;
    
    @Column(
        name = "is_active",
        columnDefinition = "BOOLEAN DEFAULT true"
    )
    private boolean active;
    
    @Column(
        name = "notes",
        length = 1000
    )
    private String notes;
    
    // Getters and setters...
}

详细属性

  • name:列名,默认为属性名
  • unique:是否唯一
  • nullable:是否允许为空
  • insertable/updatable:是否包含在INSERT/UPDATE语句中
  • columnDefinition:列的SQL DDL片段
  • length:字符串列长度
  • precision/scale:数值精度和小数位

4.6 @Transactional

标记事务方法或类。

语法

@Service
public class OrderService {
    
    private final OrderRepository orderRepository;
    private final PaymentService paymentService;
    private final InventoryService inventoryService;
    
    @Autowired
    public OrderService(
            OrderRepository orderRepository,
            PaymentService paymentService,
            InventoryService inventoryService) {
        this.orderRepository = orderRepository;
        this.paymentService = paymentService;
        this.inventoryService = inventoryService;
    }
    
    @Transactional
    public Order placeOrder(Order order) {
        // 1. 保存订单
        Order savedOrder = orderRepository.save(order);
        
        // 2. 处理支付
        paymentService.processPayment(order);
        
        // 3. 更新库存
        inventoryService.updateStock(order.getItems());
        
        return savedOrder;
    }
}

详细属性

@Service
public class AdvancedTransactionService {
    
    @Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.READ_COMMITTED,
        timeout = 30,
        readOnly = false,
        rollbackFor = {SQLException.class, DataAccessException.class},
        noRollbackFor = {IllegalArgumentException.class},
        label = "orderProcessing"
    )
    public void complexOperation() {
        // 事务处理...
    }
}
  • propagation:事务传播行为

    • REQUIRED(默认):如果存在事务,则加入;否则创建新事务
    • REQUIRES_NEW:创建新事务,挂起当前事务
    • SUPPORTS:如果存在事务,则加入;否则以非事务方式执行
    • NOT_SUPPORTED:以非事务方式执行,挂起当前事务
    • MANDATORY:必须在事务中执行,否则抛出异常
    • NEVER:必须在非事务中执行,否则抛出异常
    • NESTED:如果存在事务,则创建嵌套事务;否则类似REQUIRED
  • isolation:事务隔离级别

    • DEFAULT:使用数据库默认隔离级别
    • READ_UNCOMMITTED:最低隔离级别,可能读取未提交数据
    • READ_COMMITTED:只读取已提交数据,可能出现不可重复读
    • REPEATABLE_READ:确保相同查询返回相同结果,但可能出现幻读
    • SERIALIZABLE:最高隔离级别,完全串行化执行
  • timeout:事务超时时间(秒)

  • readOnly:是否只读事务(只允许读操作)

  • rollbackFor/noRollbackFor:指定回滚或不回滚的异常类型

示例场景

@Service
public class BankingService {
    
    @Autowired
    private AccountRepository accountRepository;
    
    @Transactional
    public void transfer(String fromId, String toId, BigDecimal amount) {
        Account from = accountRepository.findById(fromId)
                .orElseThrow(() -> new AccountNotFoundException(fromId));
        
        Account to = accountRepository.findById(toId)
                .orElseThrow(() -> new AccountNotFoundException(toId));
        
        if (from.getBalance().compareTo(amount) < 0) {
            throw new InsufficientFundsException(fromId);
        }
        
        from.setBalance(from.getBalance().subtract(amount));
        to.setBalance(to.getBalance().add(amount));
        
        accountRepository.save(from);
        accountRepository.save(to);
        
        // 如果这里抛出异常,整个转账操作会回滚
    }
    
    @Transactional(readOnly = true)
    public BigDecimal getBalance(String accountId) {
        return accountRepository.findById(accountId)
                .map(Account::getBalance)
                .orElseThrow(() -> new AccountNotFoundException(accountId));
    }
}

4.7 Spring Data JPA相关注解

4.7.1 @Query

自定义查询语句。

语法

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    @Query("SELECT u FROM User u WHERE u.email = ?1")
    User findByEmail(String email);
    
    @Query(
        value = "SELECT * FROM users WHERE last_name = :lastName",
        nativeQuery = true
    )
    List<User> findByLastName(@Param("lastName") String lastName);
    
    @Query("SELECT u FROM User u WHERE u.status = :status AND u.name LIKE %:namePart%")
    List<User> findByStatusAndNameContaining(
            @Param("status") UserStatus status,
            @Param("namePart") String namePart);
    
    @Query(value = "SELECT u FROM User u WHERE u.createdDate > :date")
    List<User> findUsersCreatedAfter(@Param("date") Date date, Pageable pageable);
    
    @Query(
        value = "UPDATE User u SET u.status = :status WHERE u.lastLoginDate < :date"
    )
    @Modifying
    int updateStatusForInactiveUsers(
            @Param("status") UserStatus status,
            @Param("date") Date date);
}

详细属性

  • value:JPQL查询语句(或nativeQuery=true时的SQL语句)
  • nativeQuery:是否使用原生SQL(默认false)
  • countQuery:用于分页查询的计数查询
  • countProjection:用于分页的计数投影
4.7.2 @Param

@Query中的命名参数提供映射。

语法

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    
    @Query("SELECT p FROM Product p WHERE p.price BETWEEN :minPrice AND :maxPrice AND p.category = :category")
    List<Product> findByPriceRangeAndCategory(
            @Param("minPrice") BigDecimal minPrice, 
            @Param("maxPrice") BigDecimal maxPrice, 
            @Param("category") String category);
}
4.7.3 @Modifying

标记更新或删除操作的查询。

语法

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    
    @Modifying
    @Transactional
    @Query("UPDATE Order o SET o.status = :status WHERE o.id = :id")
    int updateOrderStatus(@Param("id") Long id, @Param("status") OrderStatus status);
    
    @Modifying
    @Transactional
    @Query("DELETE FROM Order o WHERE o.createdDate < :date")
    int deleteOldOrders(@Param("date") Date date);
    
    @Modifying(clearAutomatically = true, flushAutomatically = true)
    @Query("UPDATE Product p SET p.price = p.price * :factor WHERE p.category = :category")
    int updatePricesByCategory(@Param("category") String category, @Param("factor") double factor);
}

详细属性

  • clearAutomatically:查询后是否自动清除持久化上下文
  • flushAutomatically:查询前是否自动刷新持久化上下文

4.8 Mybatis相关注解

4.8.1 @Mapper

标记MyBatis映射器接口,用于与数据库交互。

语法

@Mapper
public interface UserMapper {
    
    @Select("SELECT * FROM users WHERE id = #{id}")
    User findById(@Param("id") Long id);
    
    @Select("SELECT * FROM users")
    List<User> findAll();
    
    @Insert("INSERT INTO users(name, email, password) VALUES(#{name}, #{email}, #{password})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insert(User user);
    
    @Update("UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}")
    void update(User user);
    
    @Delete("DELETE FROM users WHERE id = #{id}")
    void delete(@Param("id") Long id);
}
4.8.2 @Select、@Insert、@Update、@Delete

MyBatis操作数据库的注解。

语法

@Mapper
public interface OrderMapper {
    
    @Select("SELECT * FROM orders WHERE id = #{id}")
    @Results({
        @Result(property = "id", column = "id"),
        @Result(property = "orderNumber", column = "order_number"),
        @Result(property = "customer", column = "customer_id", 
                one = @One(select = "com.example.mapper.CustomerMapper.findById")),
        @Result(property = "items", column = "id", 
                many = @Many(select = "com.example.mapper.OrderItemMapper.findByOrderId"))
    })
    Order findById(@Param("id") Long id);
    
    @Insert({
        "INSERT INTO orders(order_number, customer_id, order_date, total_amount) ",
        "VALUES(#{orderNumber}, #{customer.id}, #{orderDate}, #{totalAmount})"
    })
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(Order order);
    
    @Update({
        "UPDATE orders ",
        "SET status = #{status}, ",
        "updated_at = #{updatedAt} ",
        "WHERE id = #{id}"
    })
    int updateStatus(Order order);
    
    @Delete("DELETE FROM orders WHERE id = #{id}")
    int delete(@Param("id") Long id);
}
4.8.3 @Results和@Result

映射查询结果到Java对象。

语法

@Mapper
public interface ProductMapper {
    
    @Select("SELECT p.*, c.name as category_name FROM products p " +
            "LEFT JOIN categories c ON p.category_id = c.id " +
            "WHERE p.id = #{id}")
    @Results({
        @Result(property = "id", column = "id", id = true),
        @Result(property = "name", column = "name"),
        @Result(property = "price", column = "price"),
        @Result(property = "description", column = "description"),
        @Result(property = "categoryId", column = "category_id"),
        @Result(property = "categoryName", column = "category_name"),
        @Result(property = "tags", column = "id", 
                many = @Many(select = "com.example.mapper.TagMapper.findByProductId"))
    })
    Product findById(@Param("id") Long id);
}

5. 安全相关注解

5.1 @EnableWebSecurity

启用Spring Security的Web安全支持。

语法

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }
}

5.2 @PreAuthorize和@PostAuthorize

基于表达式的方法级安全性注解。

语法

@Service
public class UserService {
    
    @PreAuthorize("hasRole('ADMIN')")
    public List<User> getAllUsers() {
        // 只有ADMIN角色才能访问
        return userRepository.findAll();
    }
    
    @PreAuthorize("hasRole('ADMIN') or #username == authentication.principal.username")
    public User getUser(String username) {
        // ADMIN或当前用户本人可以访问
        return userRepository.findByUsername(username);
    }
    
    @PostAuthorize("returnObject.owner == authentication.principal.username")
    public Document getDocument(Long id) {
        // 只有文档的拥有者才能获取到结果
        return documentRepository.findById(id).orElse(null);
    }
    
    @PreAuthorize("hasPermission(#project, 'WRITE')")
    public void updateProject(Project project) {
        // 需要对项目有写权限
        projectRepository.save(project);
    }
}

5.3 @Secured

指定方法安全性所需的角色。

语法

@Service
public class AdminService {
    
    @Secured("ROLE_ADMIN")
    public void deleteUser(Long userId) {
        // 只有管理员可以删除用户
        userRepository.deleteById(userId);
    }
    
    @Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
    public void updateUserStatus(Long userId, UserStatus status) {
        // 管理员或经理可以更新用户状态
        User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException(userId));
        user.setStatus(status);
        userRepository.save(user);
    }
}

6. 测试相关注解

6.1 @SpringBootTest

标记Spring Boot集成测试,加载完整的应用程序上下文。

语法

@SpringBootTest
public class UserServiceIntegrationTest {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    public void testCreateUser() {
        User user = new User();
        user.setUsername("testuser");
        user.setEmail("test@example.com");
        user.setPassword("password");
        
        User savedUser = userService.create(user);
        
        assertNotNull(savedUser.getId());
        assertEquals("testuser", savedUser.getUsername());
        assertEquals("test@example.com", savedUser.getEmail());
    }
}

详细属性

@SpringBootTest(
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
    properties = {"spring.datasource.url=jdbc:h2:mem:testdb", "spring.jpa.hibernate.ddl-auto=create-drop"},
    classes = {TestConfig.class},
    args = {"--debug"}
)
public class AdvancedIntegrationTest {
    // ...
}
  • webEnvironment:web环境配置

    • MOCK:加载WebApplicationContext并提供mock servlet环境
    • RANDOM_PORT:加载WebApplicationContext并提供实际servlet环境,使用随机端口
    • DEFINED_PORT:加载WebApplicationContext并提供实际servlet环境,使用定义的端口
    • NONE:加载ApplicationContext但不提供servlet环境
  • properties:测试期间添加或覆盖的属性

  • classes:明确指定要加载的配置类

  • args:应用程序运行参数

6.2 @WebMvcTest

用于Controller层测试,仅加载Web层组件。

语法

@WebMvcTest(UserController.class)
public class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    public void testGetUser() throws Exception {
        User user = new User();
        user.setId(1L);
        user.setUsername("testuser");
        user.setEmail("test@example.com");
        
        when(userService.findById(1L)).thenReturn(user);
        
        mockMvc.perform(get("/api/users/1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(1))
                .andExpect(jsonPath("$.username").value("testuser"))
                .andExpect(jsonPath("$.email").value("test@example.com"));
    }
    
    @Test
    public void testCreateUser() throws Exception {
        User user = new User();
        user.setUsername("newuser");
        user.setEmail("new@example.com");
        
        User savedUser = new User();
        savedUser.setId(2L);
        savedUser.setUsername("newuser");
        savedUser.setEmail("new@example.com");
        
        when(userService.create(any(User.class))).thenReturn(savedUser);
        
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"username\":\"newuser\",\"email\":\"new@example.com\",\"password\":\"secret\"}"))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").value(2))
                .andExpect(jsonPath("$.username").value("newuser"));
    }
}

6.3 @DataJpaTest

用于仅测试JPA组件(repositories)。

语法

@DataJpaTest
public class UserRepositoryTest {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private TestEntityManager entityManager;
    
    @Test
    public void testFindByEmail() {
        // given
        User user = new User();
        user.setUsername("testuser");
        user.setEmail("test@example.com");
        user.setPassword("password");
        entityManager.persist(user);
        entityManager.flush();
        
        // when
        User found = userRepository.findByEmail("test@example.com");
        
        // then
        assertNotNull(found);
        assertEquals("testuser", found.getUsername());
    }
    
    @Test
    public void testSave() {
        // given
        User user = new User();
        user.setUsername("saveuser");
        user.setEmail("save@example.com");
        user.setPassword("password");
        
        // when
        User saved = userRepository.save(user);
        
        // then
        assertNotNull(saved.getId());
        User found = entityManager.find(User.class, saved.getId());
        assertEquals("saveuser", found.getUsername());
        assertEquals("save@example.com", found.getEmail());
    }
}

6.4 @MockBean

创建并注入一个Mock对象,替换Spring容器中的Bean。

语法

@SpringBootTest
public class UserServiceTest {
    
    @Autowired
    private UserService userService;
    
    @MockBean
    private UserRepository userRepository;
    
    @Test
    public void testFindById() {
        // given
        User user = new User();
        user.setId(1L);
        user.setUsername("testuser");
        
        when(userRepository.findById(1L)).thenReturn(Optional.of(user));
        
        // when
        User found = userService.findById(1L);
        
        // then
        assertNotNull(found);
        assertEquals("testuser", found.getUsername());
        verify(userRepository).findById(1L);
    }
    
    @Test
    public void testFindByIdNotFound() {
        // given
        when(userRepository.findById(99L)).thenReturn(Optional.empty());
        
        // when & then
        assertThrows(UserNotFoundException.class, () -> {
            userService.findById(99L);
        });
        
        verify(userRepository).findById(99L);
    }
}

6.5 @SpyBean

创建并注入一个Spy对象,部分模拟Bean的行为。

语法

@SpringBootTest
public class EmailServiceTest {
    
    @Autowired
    private UserService userService;
    
    @SpyBean
    private EmailService emailService;
    
    @Test
    public void testSendRegistrationEmail() {
        // given
        User user = new User();
        user.setUsername("newuser");
        user.setEmail("new@example.com");
        
        doNothing().when(emailService).sendEmail(anyString(), anyString(), anyString());
        
        // when
        userService.register(user);
        
        // then
        verify(emailService).sendEmail(
                eq("new@example.com"),
                contains("Registration"),
                contains("Welcome")
        );
    }
}

7. 总结

SpringBoot注解大大简化了Java企业级应用的开发,通过这些注解可以专注于业务逻辑而不是繁琐的配置。本文档详细介绍了SpringBoot中常用的核心注解、Web开发注解、数据访问注解以及安全测试注解,覆盖了从项目初始化到测试验证的整个开发流程。

通过学习和掌握这些注解,开发者可以更高效地构建健壮的SpringBoot应用。建议根据实际项目需求,选择合适的注解组合使用,并参考官方文档了解更多高级用法。

8. 参考资料

  • Spring Boot 官方文档
  • Spring Framework 官方文档
  • Spring Data JPA 文档
  • Spring Security 参考文档
  • MyBatis Spring Boot Starter
  • Spring Boot 测试文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2377836.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RK3568-鸿蒙5.1与原生固件-扇区对比分析

编译生成的固件目录地址 ../openharmony/out/rk3568/packages/phone/images鸿蒙OS RK3568固件分析 通过查看提供的信息&#xff0c;分析RK3568开发板固件的各个组件及其用途&#xff1a; 主要固件组件 根据终端输出的文件列表&#xff0c;RK3568固件包含以下关键组件&#x…

常见激活函数——作用、意义、特点及实现

文章目录 激活函数的意义常见激活函数及其特点1. Sigmoid&#xff08;Logistic 函数、S型函数&#xff09;2. Tanh&#xff08;双曲正切函数&#xff09;3. ReLU&#xff08;Rectified Linear Unit修正线性单元&#xff09;4. Softmax5. Swish&#xff08;Google 提出&#xff…

基于微信小程序的在线聊天功能实现:WebSocket通信实战

基于微信小程序的在线聊天功能实现&#xff1a;WebSocket通信实战 摘要 本文将详细介绍如何使用微信小程序结合WebSocket协议开发一个实时在线聊天功能。通过完整的代码示例和分步解析&#xff0c;涵盖界面布局、WebSocket连接管理、消息交互逻辑及服务端实现&#xff0c;适合…

小波变换+注意力机制成为nature收割机

小波变换作为一种新兴的信号分析工具&#xff0c;能够高效地提取信号的局部特征&#xff0c;为复杂数据的处理提供了有力支持。然而&#xff0c;它在捕捉数据中最为关键的部分时仍存在局限性。为了弥补这一不足&#xff0c;我们引入了注意力机制&#xff0c;借助其能够强化关注…

【无标题】威灏光电哲讯科技MES项目启动会圆满举行

5月14日&#xff0c;威灏光电与哲讯科技MES项目启动会在威灏光电总部隆重举行。威灏光电董事长江轮、总经理刘明星、哲讯科技总经理崔新华、副总王子文及双方项目组成员共同出席&#xff0c;标志着两家企业在数字化领域的第二次深度合作正式启航。 强强联手&#xff0c;二度合作…

display:grid网格布局属性说明

网格父级 &#xff1a;display:grid&#xff08;块级网格&#xff09;/ inline-grid&#xff08;行内网格&#xff09; 注意&#xff1a;当设置网格布局&#xff0c;column、float、clear、vertical-align的属性是无效的。 HTML: <ul class"ls02 f18 mt50 sysmt30&…

排序算法之高效排序:快速排序,归并排序,堆排序详解

排序算法之高效排序&#xff1a;快速排序、归并排序、堆排序详解 前言一、快速排序&#xff08;Quick Sort&#xff09;1.1 算法原理1.2 代码实现&#xff08;Python&#xff09;1.3 性能分析 二、归并排序&#xff08;Merge Sort&#xff09;2.1 算法原理2.2 代码实现&#xf…

Java 并发编程归纳总结(可重入锁 | JMM | synchronized 实现原理)

1、锁的可重入 一个不可重入的锁&#xff0c;抢占该锁的方法递归调用自己&#xff0c;或者两个持有该锁的方法之间发生调用&#xff0c;都会发生死锁。以之前实现的显式独占锁为例&#xff0c;在递归调用时会发生死锁&#xff1a; public class MyLock implements Lock {/* 仅…

基于对抗性后训练的快速文本到音频生成:stable-audio-open-small 模型论文速读

Fast Text-to-Audio Generation with Adversarial Post-Training 论文解析 一、引言与背景 文本到音频系统的局限性&#xff1a;当前文本到音频生成系统性能虽佳&#xff0c;但推理速度慢&#xff08;需数秒至数分钟&#xff09;&#xff0c;限制了其在创意领域的应用。 研究…

ADC深入——SNR、SFDR、ENOB等概念

目录 SNR&#xff08;Spurious‑Free Dynamic Range 信噪比&#xff09; ENOB&#xff08;Effective Number Of Bits 有效位&#xff09; SFDR&#xff08;Spurious‑Free Dynamic Range&#xff09; 感觉SNR和SFDR差不多&#xff1f;看看下图 输入带宽 混叠 带通采样/欠…

硬件厂商的MIB文档详解 | 如何查询OID? | MIB Browser实战指南-优雅草卓伊凡

硬件厂商的MIB文档详解 | 如何查询OID? | MIB Browser实战指南-优雅草卓伊凡 一、硬件厂商的MIB文档是什么&#xff1f; 1. MIB的本质&#xff1a;设备的”数据字典” MIB&#xff08;Management Information Base&#xff09; 是SNMP协议的核心数据库&#xff0c;定义了设备…

阿里开源通义万相 Wan2.1-VACE,开启视频创作新时代

0.前言 阿里巴巴于2025年5月14日正式开源了其最新的AI视频生成与编辑模型——通义万相Wan2.1-VACE。这一模型是业界功能最全面的视频生成与编辑工具&#xff0c;能够同时支持多种视频生成和编辑任务&#xff0c;包括文生视频、图像参考视频生成、视频重绘、局部编辑、背景延展…

小学数学题批量生成及检查工具

软件介绍 今天给大家介绍一款近期发现的小工具&#xff0c;它非常实用。 软件特点与出题功能 这款软件体积小巧&#xff0c;不足两兆&#xff0c;具备强大的功能&#xff0c;能够轻松实现批量出题。使用时&#xff0c;只需打开软件&#xff0c;输入最大数和最小数&#xff0c…

5.13/14 linux安装centos及一些操作命令随记

一、环境准备 VMware Workstation版本选择建议 CentOS 7 ISO镜像下载指引 虚拟机硬件配置建议&#xff08;内存/处理器/磁盘空间&#xff09; 二、系统基础命令 一、环境准备 1.VMware Workstation版本选择建议 版本选择依据 选择VMware Workstation的版本时&#xff0c…

Baklib加速企业AI数据智理转型

Baklib智理AI数据资产 在AI技术深度渗透业务场景的背景下&#xff0c;Baklib通过构建企业级知识中台架构&#xff0c;重塑了数据资产的治理范式。该平台采用智能分类引擎与语义分析模型&#xff0c;将分散在邮件、文档、数据库中的非结构化数据转化为标准化的知识单元&#xf…

基于协同过滤的文学推荐系统设计【源码+文档+部署】

基于协同过滤的文学推荐系统设计 摘要 随着信息技术的飞速发展和文学阅读需求的日益多样化&#xff0c;构建一个高效、精准的文学推荐系统变得尤为重要。本文采用Spring Boot框架&#xff0c;结合协同过滤算法&#xff0c;设计并实现了一个基于用户借阅行为和社交论坛互动的文学…

数据结构与算法——单链表(续)

单链表&#xff08;续&#xff09; 查找在指定位置之前插入结点在指定位置之后插入结点删除pos位置的结点删除pos位置之后的结点销毁 查找 遍历&#xff1a;pcur指向头结点&#xff0c;循环&#xff0c;当pucr不为空进入循环&#xff0c;pucr里面指向的数据为要查找的值的时候…

全面且深度学习c++类和对象(上)

文章目录 过程和对象类的引入&#xff0c;类的定义类的访问限定符及封装类的访问限定符封装 类的实例化类大小内存对齐规则&#xff1a; this指针this特性 过程和对象 C语言面向过程设计&#xff0c;c面向对象设计&#xff0c; 举例&#xff1a;洗衣服 C语言&#xff1a;放衣服…

开源情报如何成为信息攻防的关键资源

相比于传统情报&#xff0c;开源情报具有情报数量大、情报质量好、情报成本低、情报可用性强等优势。这是开源情报能够成为信息攻防关键资源的主要原因。 海量信息让开源情报具有更大潜力。一是开源情报体量巨大。信息化时代是信息爆炸的时代&#xff0c;网络上发布的各种信息…

【风控】用户特征画像体系

一、体系架构概述 1.1 核心价值定位 风控特征画像体系是通过多维度数据融合分析&#xff0c;构建客户风险全景视图的智能化工具。其核心价值体现在&#xff1a; 全周期覆盖&#xff1a;贯穿客户生命周期的营销、贷前、贷中、贷后四大场景立体化刻画&#xff1a;整合基础数据…