@Target({ElementType.METHOD, ElementType.TYPE})
Defines the scope of an annotation (where it can be applied). If used outside this scope, a compilation error occurs.
| Value | Description |
|---|---|
| ElementType.METHOD | Applies to methods |
| ElementType.TYPE | Applies to classes, interfaces (including annotation types), or enum declarations |
| ElementType.LOCAL_VARIABLE | Applies to local variables |
| ElementType.PARAMETER | Applies to parameters |
| ElementType.CONSTRUCTOR | Applies to constructors |
| ElementType.FIELD | Applies to fields |
| ElementType.PACKAGE | Applies to packages |
@Retention(RetentionPolicy.SOURCE)
Specifies the lifecycle of an annotation.
| Value | Description | Scope | Use Case |
|---|---|---|---|
| RetentionPolicy.SOURCE | Retained only in source file, discarded during compilation | Source file | Check-like operations (e.g., @Override, @SuppressWarnings) |
| RetentionPolicy.CLASS | Retained in .class file but discarded at runtime (default) | Class file | Preprocessing at compile time (e.g., generate helper code like ButterKnife) |
| RetentionPolicy.RUNTIME | Retained in .class file and available at runtime | Runtime | Need to dynamically retrieve annotation information at runtime |
For example, Lombok's @AllArgsConstructor has SOURCE retention. As shown in the image, after compilation, the annotation disappears and a full-argument constructor is added.
Validation Annotations
Add Dependency
<!-- Spring Boot Starter Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Code Example
- DTO
import javax.validation.constraints.*;
public class UserDTO {
@NotBlank(message = "Username cannot be empty")
private String username;
@Min(value = 18, message = "Age must be at least 18")
@Max(value = 60, message = "Age cannot exceed 60")
private Integer age;
@Email(message = "Invalid email format")
private String email;
// getters/setters omitted
}
- Controller
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.*;
@RestController
@RequestMapping("/users")
@Validated
public class UserController {
@PostMapping("/create")
public String createUser(@RequestBody @Valid UserDTO dto) {
return "User created: " + dto.getUsername();
}
@GetMapping("/byId")
public String getUserById(@RequestParam("id") @Min(1) Long id) {
return "User ID: " + id;
}
@GetMapping("/search")
public String searchUsers(
@RequestParam("keyword") @NotBlank String keyword,
@RequestParam("limit") @Max(100) int limit) {
return "Search: " + keyword + ", limit: " + limit;
}
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable @Positive Long id) {
return "Deleted user: " + id;
}
}
@Valid and @Validated
- Primitive-type parameters (e.g.,
Long,int,String) require@Validatedon the class level to trigger validation. - Object-type parameters (e.g.,
UserDTO) only need@Valid; class-level@Validatedis optional. - Group validation can be performed with
@Validated(Group.class).
Origin:
| Annotation | Package |
|---|---|
@Valid |
javax.validation.Valid (JSR 303) |
@Validated |
org.springframework.validation.annotation.Validated (Spring) |
Other Common Validation Annotations
| Annotation | Applicable Types | Description |
|---|---|---|
@Null |
Any | Must be null |
@NotNull |
Any | Must not be null |
@NotBlank |
String | Must not be null and contain atleast one non-whitespace character |
@NotEmpty |
String, Collection, Array, Map | Must not be null and have at least one element (or length > 0 for String) |
@Size(min=, max=) |
Collection, Array, String, Map | Size must be within given boundaries |
@Min(value) |
Numeric | Must be >= specified value |
@Max(value) |
Numeric | Must be <= specified value |
@DecimalMin(value) |
BigDecimal, String, etc. | Must be >= specified decimal value |
@DecimalMax(value) |
BigDecimal, String, etc. | Must be <= specified decimal value |
@Pattern(regexp=) |
String | Must match the regex |
@Email |
String | Must be a valid email (requires hibernate-validator) |
@Positive |
Numeric | Must be > 0 |
@PositiveOrZero |
Numeric | Must be >= 0 |
@Negative |
Numeric | Must be < 0 |
@NegativeOrZero |
Numeric | Must be <= 0 |
@Future |
Date (e.g., java.util.Date, java.time.LocalDate) |
Must be a future date |
@FutureOrPresent |
Date | Must be present or future |
@Past |
Date | Must be a past date |
@PastOrPresent |
Date | Must be present or past |
@NoRepositoryBean
Indicates that an interface will not be instantiated as a bean on its own; it only serves as a parent interface for other repositories.
@PathVariable
Binds a method parameter to a URI template variable. Used in RESTful endpoints to extract values from the URL.
// URL: /test1/123
@RequestMapping("/test1/{username}")
public String test1(@PathVariable("username") String name) {
if (name != null && !name.isEmpty()) {
return name;
}
return "null";
}
@RequestParam
Binds a method parameter to a URL query parameter.
// URL: /test1?name=zhangsan
@RequestMapping("/test1")
public String test1(@RequestParam String name) {
if (name != null && !name.isEmpty()) {
return name;
}
return "null";
}
Various writing styles:
1. @RequestParam(value="id", required = true/false)
2. @RequestParam(value="id")
3. @RequestParam("id")
4. @RequestParam // if parameter name matches
5. Omit the annotation entirely if the name matches
Summary for HTTP methods:
@GetMappingworks with@RequestParam,@RequestBody,@PathVariable.@PostMappingworks with@RequestBody(using@RequestParammay cause issues).@RequestParam: for wrappers, primitives, Strings; specify name withvalue.@RequestBody: for objects, Maps, Lists.@PathVariable: for URL placeholders.
@RequestBody
Binds the HTTP request body to a method parameter.
- For form submissions, do not use
@RequestBody; Spring MVC automatically binds form data. - If
@RequestBodyis used, it reads from the request body instead of the form. - POST endpoints require
@RequestBodyto correctly parse parameters; GET endpoints do not.
Example:
@RequestMapping("/test1")
public String test1(@RequestBody User user) {
String name = user.getUsername();
if (name != null && !name.isEmpty()) {
return name;
}
return "null";
}
@ResponseBody
Indicates that the return value of a method should be written directly to the HTTP response body (e.g., as JSON), not parsed as a view path.
@Param
MyBatis annotation to bind method parameters to SQL parameters when names do not match.
public User getUser(@Param("userName") String name, String id);
<select id="getUser" resultMap="User">
select * from user where user_name = #{userName} and user_id = #{id}
</select>
@ModelAttribute
Binds form data (application/x-www-form-urlencoded) to an object. For JSON data, use @RequestBody instead.
Conditional Registration Annotations
@ConditionalOnBean
Registers a bean only if the specified bean exists in the context.
@Bean
@ConditionalOnBean(Emp.class)
public User user(User user) {
return new User("zhangsan");
}
@ConditionalOnProperty
Registers a bean only if the specified property is present in the configuration.
@ConditionalOnProperty(prefix = "person", name = {"name", "age"})
@Bean
public User user(@Value("${person.name}") String name, @Value("${person.pwd}") String pwd) {
System.out.println("name:" + name + ",pwd:" + pwd);
return new User();
}
@ConditionalOnMissingBean
Registers a bean only if the specified bean is missing from the context.
@ConditionalOnMissingBean(User.class)
@Bean
public Dept dept() {
return new Dept();
}
@ConditionalOnClass
Registers a bean only if the specified class is present on the classpath.
@ConditionalOnClass(User.class)
public Dept dept() {
return new Dept();
}
@PostConstruct
Executes after dependency injection is complete. Typically used for initialization logic. Unlike static{} blocks (which cannot use injected beans), @PostConstruct can.
@Component
public class MyBean {
@PostConstruct
public void init() {
// initialization logic
}
}
Execution order (for Spring-managed beans): static{} > Constructor > @Autowired > @PostConstruct > CommandLineRunner.
Reading Configuration Values
@ConfigurationProperties(prefix = "aliyun.oss.file")
Binds properties with a given prefix to a Java object. Requires getters and setters.
@Value
Injects a single property value.
@Value("${aliyun.oss.file.endpoint}")
private String endpoint;
Can also be used on method parameters:
@Bean
public User user(@Value("${person.name}") String name, @Value("${person.pwd}") String pwd) {
System.out.println("name:" + name + ",pwd:" + pwd);
return new User();
}
Global Exception Handling and Cross-Cutting Concerns
@ControllerAdvice
A class-level annotation for global exception handling and model attribute addition in traditional MVC controllers (returning view names).
@RestControllerAdvice
Similar to @ControllerAdvice but for @RestController-based REST APIs, returning JSON/XML responses.
@Bean
Indicates that a method produces a bean to be managed by the Spring container.
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
Method parameters can be auto-injected if beans exist:
@Bean
public User user() {
return new User();
}
@Bean
public Dept dept(User user) {
return new Dept();
}
@Import
Imports additional configuration classes, usually when they are not under the component scan path.
Importing a Configuration Class
@Import(SpringBootConfig.class)
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Importing an ImportSelector Implementation
For bulk imports, implement ImportSelector:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
return new String[]{"com.example.config.SpringBootConfig"};
}
}
Then import via @Import(MyImportSelector.class). The implementation can read from a file (e.g., config.imports) for dynamic loading.
Jackson Annotations
@JsonSerialize(using = ToStringSerializer.class)
Converts Long to String to prevent precision loss when sent to frontend (e.g., large numbers become 0).
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
Formats date/time fields in JSON.
@JsonIgnore
Excludes a field from JSON serialization/deserialization.
public class User {
@JsonIgnore
private String name;
// ...
}
@JsonView
Controls which fields are returned based on the view used in the controller.
Define interfaces:
public class UserVo {
@JsonView(ListView.class)
private String name;
@JsonView(ListView.class)
private String deptName;
@JsonView(InfoView.class)
private String phone;
@JsonView(InfoView.class)
private String email;
public interface ListView {}
public interface InfoView extends ListView {}
}
Use in controller:
@GetMapping("/info")
@JsonView(UserVo.InfoView.class)
public UserVo info() {
// returns all fields
}
@GetMapping("/list")
@JsonView(UserVo.ListView.class)
public UserVo list() {
// returns only name and deptName
}
If using a wrapper class (e.g., R<T>), additional configuration is needed:
- Add
@JsonViewon the wrapper's data field (affects all endpoints). - Use
MappingJacksonValueto set view per request. - Custom serializer (recommended):
public class RSerializer<T> extends JsonSerializer<R<T>> {
private final ObjectMapper mapper;
public RSerializer(ObjectMapper mapper) {
this.mapper = mapper;
}
@Override
public void serialize(R<T> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeNumberField("code", value.getCode());
gen.writeStringField("msg", value.getMsg());
gen.writeFieldName("data");
T data = value.getData();
if (data == null) {
gen.writeNull();
} else {
serializers.defaultSerializeValue(data, gen);
}
gen.writeEndObject();
}
}
Register via configuration:
@Configuration
public class JacksonConfig {
private final ObjectMapper objectMapper;
public JacksonConfig(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@PostConstruct
public void setUp() {
SimpleModule module = new SimpleModule();
module.addSerializer(R.class, new RSerializer<>(objectMapper));
objectMapper.registerModule(module);
}
}
Or via annotation on the wrapper class: @JsonSerialize(using = RSerializer.class).
@JsonNaming
Defines a naming strategy for serialization/deserialization. Common strategies: SnakeCaseStrategy, KebabCaseStrategy, UpperCamelCaseStrategy, LowerCamelCaseStrategy, PascalCaseStrategy, LowerDotCaseStrategy, UpperDotCaseStrategy.
Example:
@JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class)
public class MyClass {}
@JsonRawValue
Serializes a field as raw JSON without escaping quotes.
Without @JsonRawValue: [{"code": 4, "desc": "资料未补全"}]
With @JsonRawValue: [{"code": 4, "desc": "资料未补全"}]
@JsonInclude
Controls inclusion of properties during serialization.
Common values: ALWAYS, NON_NULL, NON_ABSENT, NON_EMPTY, NON_DEFAULT, CUSTOM, USE_DEFAULTS.
Example: @JsonInclude(JsonInclude.Include.NON_EMPTY).
@JsonProperty
Specifies the name of a property in JSON.
@JsonProperty("GPSBrand")
private String GPSBrand;
Handling Unknown Properties
If you configure a custom ObjectMapper, unknown properties may cause errors. Set:
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Timezone Issue with @JsonFormat
If dates appear off by one day, set timezone in ObjectMapper:
objectMapper.setTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
Duplicate Data in JSON
Avoid using both @JsonNaming and @JsonProperty on the same class/field, as it can produce duplicate entries.
MyBatis-Plus Annotations
@TableField(typeHandler = Fastjson2TypeHandler.class)
Auto-converts between database JSON strings and Java objects.
@TableField(typeHandler = Fastjson2TypeHandler.class)
private Object content;
Other Annotations
@DateTimeFormat vs @JsonFormat
@DateTimeFormat: for converting date strings from form/URL parameters (non-JSON).@JsonFormat: for converting date strings in JSON (both incoming and outgoing).
Both can be used on the same field if needed.
Example: @DateTimeFormat(pattern = "yyyy-MM-dd") on a LocalDate field for form parameters.