Roman Kryvolapov Engineering Blog

Java Spring — Анотації Spring

Які основні анотації є у Spring

Annotation
├── Component
│ ├── Repository
│ ├── Service
│ ├── Controller
│ │ └── RestController
├── Configuration
│ └── EnableAutoConfiguration
├── SpringBootApplication
│ └── Configuration
│ └── EnableAutoConfiguration
├── Conditional
│ ├── ConditionalOnBean
│ ├── ConditionalOnMissingBean
│ ├── ConditionalOnClass
│ ├── ConditionalOnMissingClass
│ ├── ConditionalOnProperty
│ ├── ConditionalOnResource
│ ├── ConditionalOnWebApplication
│ └── ConditionalOnNotWebApplication
├── Enable
│ ├── EnableAspectJAutoProxy
│ ├── EnableCaching
│ ├── EnableScheduling
│ └── EnableAsync
├── Transactional
├── Bean
├── Qualifier
├── Autowired
├── Value
├── Primary
├── Lazy
├── Scope

@Configuration
вказує, що клас містить визначення бінів і має бути оброблений контейнером Spring для генерації бінів Spring.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class AppConfig {
@Bean
fun myBean(): MyBean {
return MyBean()
}
}
class MyBean {
fun doSomething() {
println("Doing something...")
}
}

@Bean
вказує, що метод створює бін, керований Spring-контейнером.

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class AppConfig {
@Bean
fun myBean(): MyBean {
return MyBean()
}
}

@Component
вказує, що клас є компонентом Spring, що дозволяє Spring автоматично визначати його для керування в контейнері.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component
import org.springframework.stereotype.Component
@Component
class MyComponent {
fun doWork() {
println("Component is working...")
}
}

@Service
спеціалізована анотація, що використовується для класів сервісного рівня. Це також різновид @Component.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service
import org.springframework.stereotype.Service
@Service
class MyService {
fun performService() {
println("Service is performing...")
}
}

@Repository
спеціалізована анотація для класів доступу до даних (DAO). Це також різновид @Component.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface UserRepository : JpaRepository<User, Long> {
fun findByUsername(
username: String
): User?
}

@Controller
вказує, що клас є контролером Spring MVC. Ця анотація використовується для обробки вебзапитів.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
@Controller
@RequestMapping("/web")
class WebController {
@GetMapping("/hello")
fun hello(model: Model): String {
model.addAttribute(
"message",
"Hello, World!"
)
return "hello"
}
}

@RestController
є комбінацією @Controller і @ResponseBody. Вона вказує, що клас є RESTful контролером, і дані, що повертаються, мають бути напряму записані в HTTP-відповідь у форматі JSON або XML.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api")
class ApiController {
@GetMapping("/greeting")
fun greeting(): String {
return "Hello, API World!"
}
}

@RestControllerAdvice
використовується для глобальної обробки винятків і надання поради контролерам, які працюють з REST API. Це розширення анотації @ControllerAdvice, яке автоматично вмикає @ResponseBody, що робить її придатною для обробки винятків у контролерах REST, повертаючи відповіді у форматі JSON або XML.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
class UserNotFoundException(
message: String
) : RuntimeException(message)
data class ErrorResponse(
val message: String,
val details: String
)
@RestControllerAdvice
class RestExceptionHandler {
@ExceptionHandler(UserNotFoundException::class)
fun handleUserNotFoundException(
ex: UserNotFoundException
): ResponseEntity<ErrorResponse> {
val errorResponse = ErrorResponse(
message = ex.message ?: "User not found",
details = "The user you are looking for does not exist."
)
return ResponseEntity(errorResponse, HttpStatus.NOT_FOUND)
}
@ExceptionHandler(Exception::class)
fun handleGenericException(
ex: Exception
): ResponseEntity<ErrorResponse> {
val errorResponse = ErrorResponse(
message = ex.message ?: "An error occurred",
details = "Please contact support."
)
return ResponseEntity(
errorResponse,
HttpStatus.INTERNAL_SERVER_ERROR
)
}
}
// Контролер, що кидає виняток
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/users")
class UserController {
private val users = mapOf(
1L to "John Doe",
2L to "Jane Doe"
)
@GetMapping("/{id}")
fun getUser(@PathVariable id: Long): String {
return users[id]
?: throw UserNotFoundException(
"User with ID $id not found"
)
}
}

Приклад обробки винятків з використанням @RestControllerAdvice:
При запиті користувача з ID, якого не існує, сервер поверне JSON-відповідь з HTTP-статусом 404 (Not Found) і тілом:

{
"message": "User with ID 3 not found",
"details": "The user you are looking for does not exist."
}

Якщо виникає загальний виняток, сервер поверне JSON-відповідь з HTTP-статусом 500 (Internal Server Error) і тілом:

{
"message": "An error occurred",
"details": "Please contact support."
}

@ResponseBody
робить автоматичну серіалізацію в JSON будь-якого об'єкта, що повертається

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody
@Controller
class PersonController {
@GetMapping("/persons/{id}")
@ResponseBody
fun getData(
@PathVariable id: Long
): String {
return ""
}
}
// те саме, що
@RestController
class PersonController {
@GetMapping("/persons/{id}")
fun getData(
@PathVariable id: Long
): String{
return ""
}
}

@Aspect
спосіб винести повторювану логіку (наприклад, логування або перевірку безпеки) з методів в окремий клас. Він автоматично підключається до потрібних методів і виконує потрібні дії до, після або навколо них. Це зручно, тому що не потрібно дублювати один і той самий код у багатьох місцях.

@Before
вказує, що метод-порада має виконуватися до виклику цільового методу. Використовується, наприклад, для логування входу в метод або перевірки прав доступу.

@After
вказує, що метод-порада має виконуватися після завершення цільового методу — незалежно від того, успішно він завершився чи з винятком. Підходить для очищення ресурсів або логування виходу.

@Around
дозволяє повністю обгорнути виклик цільового методу: виконати код до, викликати сам метод через proceed(), а потім виконати код після. Дає повний контроль над виконанням — можна змінити аргументи, результат, або навіть не викликати сам метод.

@Pointcut
описує, до яких методів застосовувати аспект (за сигнатурами, анотаціями тощо)

@Aspect
@Component
class LoggingAspect {
// Pointcut that matches all methods in the service package
@Pointcut("execution(* com.example.service.*.*(..))")
fun allServiceMethods() {
}
// Runs before the matched method
@Before("allServiceMethods()")
fun logBefore() {
println("Method is about to be called")
}
// Runs after the matched method finishes
@After("allServiceMethods()")
fun logAfter() {
println("Method has finished execution")
}
// Runs around the matched method: before and after
@Around("allServiceMethods()")
fun logAround(joinPoint: ProceedingJoinPoint): Any? {
println("Before method: ${joinPoint.signature.name}")
val result = joinPoint.proceed()
println("After method: ${joinPoint.signature.name}")
return result
}
}

@ModelAttribute
використовується для прив'язки даних моделі до параметрів методу або значень, що повертаються методами контролера. Вона може застосовуватися як до параметрів методу, так і до самих методів, надаючи гнучкий механізм для підготовки та зв'язування даних моделі.

@MatrixVariable
у Spring використовується для прив'язки значень змінних матриці до параметрів методу в контролері. Змінні матриці — це параметри, які включаються в частини шляху URL між крапками з комою (;). Ця анотація дозволяє витягти значення змінних матриці та використати їх у методах контролера.

@InitBinder
використовується для налаштування засобів прив'язки даних на рівні контролера. Ці засоби прив'язки даних обробляють дані, що надходять з HTTP-запитів, і перетворюють їх на об'єкти моделі. Анотація @InitBinder дозволяє вам реєструвати користувацькі редактори властивостей або валідатори для обробки даних перед їх зв'язуванням з об'єктами моделі.

@CookieValue
використовується для прив'язки значення з HTTP cookie до параметра методу в контролері. Це дозволяє вам отримувати значення cookie безпосередньо в методах контролера, що спрощує роботу з ними.

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.CookieValue
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api")
class UserController {
@GetMapping("/token")
fun getToken(
@CookieValue("userToken") token: String
): String {
return "User token: $token"
}
}

@Autowired
використовується для автоматичного впровадження залежностей. Spring автоматично знаходить відповідний бін і впроваджує його в поле, конструктор або метод.

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
@Service
class MainService @Autowired constructor(
private val myComponent: MyComponent
) {
fun execute() {
myComponent.doWork()
}
}

@Transactional
використовується для керування транзакціями. Вона може бути застосована до методів або класів для автоматичного початку, коміту або відкату транзакцій.
Механізм роботи @Transactional у Spring базується на використанні проксі та аспектно-орієнтованого програмування для керування транзакціями. Проксі перехоплює виклики методів і взаємодіє з транзакційним менеджером для початку, підтвердження або відкату транзакцій. Це дозволяє автоматично обробляти транзакції, спрощуючи розробку надійних і узгоджених застосунків.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Reflective
public @interface Transactional
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
class TransactionalService(
private val userRepository: UserRepository
) {
@Transactional
fun processTransaction() {
val data = userRepository
.findByUsername("username")
println("Processing data: $data")
}
}

@EnableTransactionManagement
використовується у Spring для ввімкнення підтримки транзакційного менеджменту, заснованого на анотаціях. Вона автоматично налаштовує необхідні біни та проксі для обробки методів, анотованих @Transactional. Розгляньмо, як це працює і як його використовувати.

Основні функції @EnableTransactionManagement:

Активація підтримки транзакцій:
Вмикає інфраструктуру, необхідну для роботи з транзакціями, заснованими на анотаціях, такими як @Transactional.

Проксі:
Створює проксі для бінів, які мають методи, анотовані @Transactional. Проксі перехоплює виклики цих методів і керує транзакціями.

Конфігурація:
Анотація автоматично налаштовує TransactionManager, який керує початком, підтвердженням і відкатом транзакцій.

Основні атрибути @EnableTransactionManagement:

mode:
Визначає, чи буде використовуватися проксіювання на основі інтерфейсів (JDK) або класів (CGLIB). Значення за замовчуванням — AdviceMode.PROXY.

proxyTargetClass:
Якщо true, то використовується CGLIB проксіювання (за замовчуванням false).

order:
Встановлює порядок виконання транзакційних аспектів.

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.transaction.annotation.EnableTransactionManagement
import org.springframework.transaction.annotation.TransactionManagementConfigurer
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
import org.springframework.transaction.interceptor.TransactionInterceptor
import org.springframework.transaction.interceptor.TransactionProxyFactoryBean
import javax.sql.DataSource
@Configuration
@EnableTransactionManagement
class TransactionConfig(
private val dataSource: DataSource
) : TransactionManagementConfigurer {
@Bean
override fun annotationDrivenTransactionManager(): PlatformTransactionManager {
return DataSourceTransactionManager(dataSource)
}
@Bean
fun transactionInterceptor(): TransactionInterceptor {
val source = AnnotationTransactionAttributeSource()
val transactionManager = annotationDrivenTransactionManager()
return TransactionInterceptor(transactionManager, source)
}
@Bean
fun transactionProxyFactoryBean(): TransactionProxyFactoryBean {
val proxyFactoryBean = TransactionProxyFactoryBean()
proxyFactoryBean.transactionManager = annotationDrivenTransactionManager()
proxyFactoryBean.setTransactionAttributes(
mapOf(
"save*" to "PROPAGATION_REQUIRED",
"delete*" to "PROPAGATION_REQUIRED",
"update*" to "PROPAGATION_REQUIRED",
"*" to "PROPAGATION_REQUIRED,readOnly"
)
)
return proxyFactoryBean
}
}

DataSourceTransactionManager:
Використовується як транзакційний менеджер для керування транзакціями на рівні JDBC.

TransactionInterceptor:
Перехоплює виклики методів і обробляє транзакційну логіку.

TransactionProxyFactoryBean:
Створює проксі для бінів, щоб перехоплювати виклики методів і обробляти транзакційну логіку.

@RequestPart
використовується для прив'язки частини багаточастинного (multipart) запиту до параметра методу контролера. Вона дозволяє обробляти файли та дані форми, надіслані з використанням форми з атрибутом enctype=”multipart/form-data”.

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestPart
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestPart
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile
import java.nio.file.Files
import java.nio.file.Paths
@RestController
@RequestMapping("/api")
class FileUploadController {
@PostMapping("/upload")
fun handleFileUpload(
@RequestPart("file") file: MultipartFile
): String {
val uploadDir = Paths.get("uploads")
Files.createDirectories(uploadDir)
val uploadFilePath = uploadDir.resolve(
file.originalFilename ?: "uploadedFile"
)
file.inputStream.use { input ->
Files.copy(input, uploadFilePath)
}
return "File uploaded successfully: ${file.originalFilename}"
}
}
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestPart
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile
data class User(
val name: String,
val email: String
)
@RestController
@RequestMapping("/api")
class UserUploadController {
@PostMapping("/upload-user")
fun handleUserUpload(
@RequestPart("user") user: User,
@RequestPart("file") file: MultipartFile
): String {
return "User: ${user.name}, File: ${file.originalFilename}"
}
}

@Value
використовується для впровадження значень з файлу властивостей у поля.

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Configuration
@Configuration
class AppConfig {
@Value("\${my.property}")
lateinit var myProperty: String
fun showProperty() {
println("Property value: $myProperty")
}
}

@RequestMapping
додає перед qrl методу те, що в нього передано

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
@Reflective({ControllerMappingReflectiveProcessor.class})
public @interface RequestMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api")
class ApiController {
@RequestMapping("/all")
fun getAll(): String {
return "Get all data"
}
}

@GetMapping і @PostMapping
є спеціалізованими версіями @RequestMapping для обробки GET і POST запитів відповідно.
Є також @DeleteMapping, @PutMapping

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = {RequestMethod.GET})
public @interface GetMapping
@RestController
@RequestMapping("/api")
class ApiController {
@GetMapping("/get")
fun getData(): String {
return "Get data"
}
@PostMapping("/post")
fun postData(
@RequestParam data: String
): String {
return "Posted data: $data"
}
}

@RequestParam
використовується для витягання параметрів запиту, тоді як @PathVariable використовується для витягання змінних шляху з URL

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api")
class ParameterController {
@GetMapping("/request")
fun requestParam(
@RequestParam name: String
): String {
return "Request param: $name"
}
@GetMapping("/path/{id}")
fun pathVariable(
@PathVariable id: Int
): String {
return "Path variable: $id"
}
}

@RequestBody
серіалізує клас у JSON

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
data class User(
val username: String,
val password: String
)
@RestController
@RequestMapping("/api")
class UserController {
@PostMapping("/user")
fun createUser(
@RequestBody user: User
): String {
return "User created: ${user.username}"
}
}

@SessionAttribute
використовується для доступу до атрибутів HTTP-сесії в методах контролера. Ця анотація дозволяє легко отримати значення атрибута сесії та використати його в методі контролера, не вдаючись до прямої взаємодії з об'єктом HttpSession.

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SessionAttribute
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.SessionAttributes
import org.springframework.web.bind.support.SessionStatus
import javax.servlet.http.HttpSession
@Controller
@RequestMapping("/session")
@SessionAttributes("user")
class SessionController {
@GetMapping("/set")
fun setSessionAttribute(
@RequestParam name: String,
session: HttpSession
): String {
session.setAttribute("user", name)
return "redirect:/session/show"
}
@GetMapping("/show")
fun showSessionAttribute(
@SessionAttribute("user") user: String
): String {
println("User in session: $user")
return "session"
}
@GetMapping("/invalidate")
fun invalidateSession(
session: HttpSession,
status: SessionStatus
): String {
status.setComplete()
session.invalidate()
return "redirect:/session/show"
}
}
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.SessionAttribute
@Controller
class UserController {
@GetMapping("/current-user")
fun currentUser(
@SessionAttribute("user") user: String
): String {
println("Current user: $user")
return "currentUser"
}
}

@SessionAttributes
використовується для вказання, які атрибути моделі мають бути збережені в HTTP-сесії та доступні протягом кількох запитів у межах однієї сесії користувача. Це корисно, коли ви хочете зберегти дані між запитами без необхідності вручну керувати об'єктом HttpSession.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SessionAttributes
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.SessionAttributes
import org.springframework.web.bind.support.SessionStatus
data class User(
var firstName: String = "",
var lastName: String = "",
var email: String = ""
)
@Controller
@RequestMapping("/session")
@SessionAttributes("user")
class SessionController {
@ModelAttribute("user")
fun user(): User {
return User()
}
@GetMapping("/form")
fun showForm(
model: Model
): String {
return "userForm"
}
@PostMapping("/save")
fun saveUser(
@ModelAttribute("user") user: User
): String {
// Збереження користувача в сесії
println("User saved: $user")
return "redirect:/session/details"
}
@GetMapping("/details")
fun userDetails(
@ModelAttribute("user") user: User
): String {
println("User details: $user")
return "userDetails"
}
@GetMapping("/clear")
fun clearSession(
sessionStatus: SessionStatus
): String {
sessionStatus.setComplete()
return "redirect:/session/form"
}
}

userForm.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>User Form</title>
</head>
<body>
<form action="#" th:action="@{/session/save}" th:object="${user}" method="post">
<label for="firstName">First Name:</label>
<input type="text" id="firstName" name="firstName" th:field="*{firstName}" /><br/>
<label for="lastName">Last Name:</label>
<input type="text" id="lastName" name="lastName" th:field="*{lastName}" /><br/>
<label for="email">Email:</label>
<input type="email" id="email" name="email" th:field="*{email}" /><br/>
<button type="submit">Save</button>
</form>
</body>
</html>

userDetails.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>User Details</title>
</head>
<body>
<h1>User Details</h1>
<p>First Name: <span th:text="${user.firstName}">First Name</span></p>
<p>Last Name: <span th:text="${user.lastName}">Last Name</span></p>
<p>Email: <span th:text="${user.email}">Email</span></p>
<a th:href="@{/session/clear}">Clear Session</a>
</body>
</html>

@ResponseStatus
використовується для вказання статусу HTTP-відповіді, який має бути повернений з анотованого методу або винятку. Це дозволяє вам керувати HTTP-статусами, що повертаються клієнту, без необхідності вручну встановлювати їх у коді контролера.

Основні параметри @ResponseStatus:

code (тип: HttpStatus):
Вказує код статусу HTTP, який має бути повернений. Це обов'язковий параметр.

reason (тип: String):
Вказує причину, яка буде повернена разом з кодом статусу. Цей параметр є необов'язковим.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseStatus
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api")
class MyController {
@GetMapping("/success")
@ResponseStatus(HttpStatus.OK)
fun success(): String {
return "Request was successful"
}
@GetMapping("/notfound")
@ResponseStatus(HttpStatus.NOT_FOUND)
fun notFound(): String {
return "Resource not found"
}
}

@RequestHeader
використовується для прив'язки значення заголовка HTTP-запиту до параметра методу в контролері. Це дозволяє вам отримувати значення заголовків HTTP-запиту безпосередньо в методах контролера, що спрощує роботу із заголовками запитів.

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestHeader
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api")
class HeaderController {
@GetMapping("/user-agent")
fun getUserAgent(
@RequestHeader("User-Agent") userAgent: String
): String {
return "User-Agent: $userAgent"
}
}
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api")
class HeaderController {
@GetMapping("/accept-language")
fun getAcceptLanguage(
@RequestHeader(
"Accept-Language",
defaultValue = "en-US"
) acceptLanguage: String
): String {
return "Accept-Language: $acceptLanguage"
}
}
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api")
class HeaderController {
@GetMapping("/required-header")
fun getRequiredHeader(
@RequestHeader(
"X-Custom-Header",
required = true
) customHeader: String
): String {
return "X-Custom-Header: $customHeader"
}
}

@RequestAttribute
використовується для прив'язки значення атрибута HTTP-запиту до параметра методу в контролері. Це дозволяє вам отримувати значення атрибутів запиту безпосередньо в методах контролера, що спрощує роботу з атрибутами, встановленими в запиті.

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestAttribute
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestAttribute
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api")
class MyController {
@GetMapping("/attribute")
fun getRequestAttribute(
@RequestAttribute("myAttribute") myAttribute: String
): String {
return "Request Attribute: $myAttribute"
}
}
import javax.servlet.Filter
import javax.servlet.FilterChain
import javax.servlet.FilterConfig
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import org.springframework.stereotype.Component
@Component
class MyFilter : Filter {
override fun init(filterConfig: FilterConfig?) {
}
override fun doFilter(
request: ServletRequest,
response: ServletResponse,
chain: FilterChain
) {
request.setAttribute("myAttribute", "Some Value")
chain.doFilter(request, response)
}
override fun destroy() {
}
}

@ExceptionHandler
використовується для визначення методу, який буде обробляти певні типи винятків

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Reflective({ExceptionHandlerReflectiveProcessor.class})
public @interface ExceptionHandler
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api")
class ExceptionController {
@GetMapping("/exception")
fun throwException(): String {
throw RuntimeException("Exception occurred!")
}
@ExceptionHandler(RuntimeException::class)
fun handleRuntimeException(
ex: RuntimeException
): ResponseEntity<String> {
return ResponseEntity(
"Handled exception: ${ex.message}",
HttpStatus.INTERNAL_SERVER_ERROR
)
}
}

@CrossOrigin
дозволяє кросдоменні запити до методу або класу.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api")
@CrossOrigin(origins = ["http://example.com"])
class CrossOriginController {
@GetMapping("/data")
fun getData(): String {
return "Cross-origin data"
}
}

@Qualifier
використовується у Spring для розв'язання неоднозначностей при впровадженні залежностей. Коли у Spring-контейнера є кілька бінів одного типу, @Qualifier дозволяє вказати, який саме бін має бути впроваджений.

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier
interface Animal {
fun speak()
}
@Component
@Qualifier("dog")
class Dog : Animal {
override fun speak() {
println("Woof!")
}
}
@Component
@Qualifier("cat")
class Cat : Animal {
override fun speak() {
println("Meow!")
}
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
@Component
class AnimalService @Autowired constructor(
@Qualifier("dog") private val animal: Animal
) {
fun makeAnimalSpeak() {
animal.speak()
}
}

@Primary
якщо у вас є бін, який ви хочете використовувати за замовчуванням, ви можете анотувати його за допомогою @Primary. Тоді @Qualifier буде використовуватися лише для уточнення. У цьому випадку, якщо @Qualifier не використовується, буде впроваджений бін DefaultAnimal

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Primary
@Component
@Primary
class DefaultAnimal : Animal {
override fun speak() {
println("Default sound!")
}
}

@ImportResource
використовується для завантаження XML-конфігурації в контекст застосунку. Це корисно, якщо у вас є наявні XML-файли конфігурації Spring, які ви хочете імпортувати в конфігурацію, засновану на анотаціях або Java-коді.

Приклад використання:
Припустімо, у вас є XML-конфігурація applicationContext.xml:

applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="exampleBean" class="com.example.ExampleBean">
<property name="property" value="value"/>
</bean>
</beans>

Тепер ви хочете імпортувати цю конфігурацію у ваш Spring Boot або інший Java-базований конфігураційний клас:

import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.ImportResource
@Configuration
@ImportResource("classpath:applicationContext.xml")
class AppConfig {
// Java-based configuration or bean definitions
}

@ComponentScan
використовується для автоматичного пошуку та реєстрації бінів (компонентів) у Spring-контейнері. Це включає класи, анотовані такими анотаціями, як @Component, @Service, @Repository, @Controller, та іншими, які вказують Spring, що ці класи є бінами, якими необхідно керувати.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.ComponentScan
@SpringBootApplication
@ComponentScan(basePackages = [
"com.example.demo.services",
"com.example.demo.controllers"
])
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}

@Entity
використовується в JPA (Java Persistence API) для вказання, що клас являє собою сутність (entity) і зіставляється з таблицею в реляційній базі даних. Це частина ORM (Object-Relational Mapping), яка дозволяє розробникам працювати з базою даних через об'єктно-орієнтовані моделі.

Основні аспекти анотації @Entity:

Клас як сутність:
Анотація @Entity визначає, що цей клас є сутністю і буде зіставлений з таблицею в базі даних.

Таблиця за замовчуванням:
Якщо ім'я таблиці не вказано, воно буде збігатися з ім'ям класу. Однак це можна перевизначити за допомогою анотації @Table.

Ідентифікаційні стовпці:
Кожна сутність повинна мати хоча б одне поле, анотоване як @Id, щоб вказати первинний ключ.

import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.Table
import javax.persistence.Column
@Entity
@Table(name = "users")
data class User(
@Id
@Column(name = "user_id")
val userId: String,
@Column(name = "email")
val email: String,
@Column(name = "username")
val username: String
)

@Entity
Вказує, що клас User є сутністю.

@Table
Визначає ім'я таблиці, з якою буде пов'язана сутність. У цьому випадку таблиця називається users.

@Id
Позначає первинний ключ сутності.

@Column
Вказує ім'я стовпця в таблиці, який буде зіставлений з полем userId.

@GeneratedValue
Використовується для вказання стратегії генерації значень для первинного ключа (наприклад, автоінкремент).

@OneToMany, @ManyToOne, @OneToOne, @ManyToMany
Визначають відносини між сутностями.

Приклад з відносинами:

import javax.persistence.*
@Entity
@Table(name = "users")
data class User(
@Id
@Column(name = "user_id")
val userId: String,
@Column(name = "email")
val email: String,
@Column(name = "username")
val username: String,
@OneToMany(mappedBy = "user")
val orders: List<Order>
)
@Entity
@Table(name = "orders")
data class Order(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "order_id")
val orderId: Long,
@ManyToOne
@JoinColumn(name = "user_id")
val user: User,
@Column(name = "product")
val product: String
)

@EntityScan
використовується для вказання Spring, де шукати JPA-сутності (класи, анотовані @Entity). Це необхідно, коли сутності розташовані поза стандартним шляхом сканування Spring Boot, або якщо ви хочете вказати інший пакет для сканування сутностей.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({EntityScanPackages.Registrar.class})
public @interface EntityScan
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.domain.EntityScan
@SpringBootApplication
@EntityScan(basePackages = ["com.example.demo.entities"])
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}

@Document у MongoDB
використовується у Spring Data MongoDB для вказання того, що клас являє собою документ у колекції MongoDB. Це еквівалент анотації @Entity у JPA для реляційних баз даних. Анотація @Document визначає, що цей клас буде відображатися на колекцію в базі даних MongoDB.

Основні атрибути анотації @Document:

collection:
Ім'я колекції в MongoDB, з якою пов'язаний документ. Якщо не вказано, ім'я колекції буде збігатися з ім'ям класу, але приведеним до нижнього регістру.

language:
Вказує мову для повнотекстового пошуку (опційно).

import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document
@Document(collection = "users")
data class User(
@Id
val id: String? = null,
val email: String,
val username: String,
)

@Document(collection = “users”):
Вказує, що клас User зіставляється з колекцією users у базі даних MongoDB.

@Id
Позначає ідентифікатор документа. У MongoDB це поле зазвичай називається _id.

Додаткові анотації та атрибути:

@Field
Використовується для вказання імені поля в документі MongoDB, якщо воно відрізняється від імені поля в класі.

@Indexed
Створює індекс на полі.

@CompoundIndex
Створює складений індекс на кілька полів.

@GeoSpatialIndexed
Створює геопросторовий індекс на полі.

import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.index.Indexed
import org.springframework.data.mongodb.core.mapping.Document
import org.springframework.data.mongodb.core.mapping.Field
@Document(collection = "users")
data class User(
@Id
val id: String? = null,
@Field("email")
@Indexed(unique = true)
val email: String,
@Field("username")
val username: String
)

@Field(“email”):
Вказує, що поле email у класі User відповідає полю email у документі MongoDB.

@Indexed(unique = true):
Створює унікальний індекс на полі email, що запобігає наявності дублюючих значень.

@Document в Elasticsearch
використовується у Spring Data Elasticsearch для вказання, що клас являє собою документ в індексі Elasticsearch. Ця анотація дозволяє налаштувати індекс і його властивості, такі як ім'я, налаштування шардування та реплікації, і деякі інші параметри.

Основні атрибути анотації @Document:

indexName:
Вказує ім'я індексу, в якому буде зберігатися документ.

shards:
Вказує кількість шардів (за замовчуванням 5).

replicas:
Вказує кількість реплік (за замовчуванням 1).

refreshInterval:
Вказує інтервал оновлення індексу.

indexStoreType:
Вказує тип сховища індексу (наприклад, “fs” або “memory”).

import org.springframework.data.annotation.Id
import org.springframework.data.elasticsearch.annotations.Document
@Document(
indexName = "users",
shards = 3,
replicas = 2
)
data class User(
@Id
val id: String? = null,
val email: String,
val username: String
)

indexName:
Ім'я індексу Elasticsearch, в якому буде зберігатися документ (users).

shards:
Кількість шардів (3).

replicas:
Кількість реплік (2).

@Table
використовується у Spring Data для позначення того, що клас являє собою сутність бази даних і зіставляється з таблицею в базі даних. У контексті Spring Data Cassandra ця анотація використовується для вказання, що клас буде відображатися на таблицю в базі даних Cassandra.

Основні атрибути анотації @Table:

value:
Ім'я таблиці в базі даних. Якщо не вказано, ім'я таблиці буде збігатися з ім'ям класу.

forceQuote:
Вказує, чи мають імена таблиць і стовпців братися в лапки.

import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn
import org.springframework.data.cassandra.core.mapping.Table
import org.springframework.data.cassandra.core.cql.PrimaryKeyType
import org.springframework.data.cassandra.core.cql.Ordering
@Table("users")
data class User(
@PrimaryKeyColumn(
name = "user_id",
type = PrimaryKeyType.PARTITIONED
)
val userId: String,
@PrimaryKeyColumn(
name = "region",
type = PrimaryKeyType.PARTITIONED
)
val region: String,
@PrimaryKeyColumn(
name = "email",
ordinal = 0,
type = PrimaryKeyType.CLUSTERED,
ordering = Ordering.ASCENDING
)
val email: String,
@PrimaryKeyColumn(
name = "username",
ordinal = 1,
type = PrimaryKeyType.CLUSTERED,
ordering = Ordering.ASCENDING
)
val username: String
)

@Table(“users”):
Вказує, що клас User зіставляється з таблицею users у базі даних Cassandra.

@PrimaryKeyColumn:
Вказує, які стовпці є частиною первинного ключа.

Повний опис:

Клас:
Клас User є сутністю, яка буде зіставлена з таблицею users у базі даних Cassandra.

Анотація @Table:
Вказує ім'я таблиці, з якою пов'язаний клас. Якщо ім'я таблиці не вказано, використовується ім'я класу.

Анотація @PrimaryKeyColumn:
Визначає стовпці первинного ключа в таблиці. У цьому випадку userId і region є складеними розділяючими ключами, а email і username є кластерними ключами з порядком сортування за зростанням.

@PrimaryKeyColumn
використовується у Spring Data Cassandra для визначення стовпців первинного ключа в таблицях Cassandra. Вона є частиною API, яке допомагає розробникам взаємодіяти з базою даних Cassandra зручніше та ефективніше. Ця анотація дозволяє явно вказати, які стовпці мають бути частиною первинного ключа і як вони мають бути оброблені.

Основні атрибути анотації @PrimaryKeyColumn:

name:
Ім'я стовпця в таблиці.

type:
Тип стовпця первинного ключа (partitioned, clustered).

ordinal:
Порядковий номер стовпця первинного ключа.

ordering:
Порядок сортування для кластерних стовпців (ASC або DESC).

import org.springframework.data.cassandra.core.cql.PrimaryKeyType
import org.springframework.data.cassandra.core.cql.Ordering
import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn
import org.springframework.data.cassandra.core.mapping.Table
@Table
data class User(
@PrimaryKeyColumn(
name = "user_id",
type = PrimaryKeyType.PARTITIONED
)
val userId: String,
@PrimaryKeyColumn(
name = "region",
type = PrimaryKeyType.PARTITIONED
)
val region: String,
@PrimaryKeyColumn(
name = "email",
ordinal = 0,
type = PrimaryKeyType.CLUSTERED,
ordering = Ordering.ASCENDING
)
val email: String,
@PrimaryKeyColumn(
name = "username",
ordinal = 1,
type = PrimaryKeyType.CLUSTERED,
ordering = Ordering.ASCENDING
)
val username: String
)

name: name = “user_id”
задає ім'я стовпця в таблиці, яке відповідає полю userId.

type: type = PrimaryKeyType.PARTITIONED
вказує, що стовпець userId є розділяючим ключем (partition key).

ordinal: ordinal = 0
задає порядок стовпця у складі кластерного ключа.

ordering: ordering = Ordering.ASCENDING
вказує порядок сортування для кластерного стовпця.

Типи стовпців первинного ключа:

PARTITIONED:
Розділяючий ключ (partition key) визначає, на якому вузлі буде зберігатися рядок. Розділяючий ключ може складатися з одного або кількох стовпців.

CLUSTERED:
Кластерний ключ (clustering key) визначає порядок рядків усередині одного розділу.

@Unwrapped
У контексті Spring Data MongoDB та інших реалізацій Spring Data анотація @Unwrapped використовується для вказання, що атрибути вкладеного об'єкта мають бути розгорнуті (або "розкриті") і збережені як окремі поля в батьківському документі замість того, щоб зберігати вкладений об'єкт як піддокумент.
Це може бути корисно, коли вам потрібно, щоб атрибути вкладеного об'єкта зберігалися на тому самому рівні, що й атрибути батьківського об'єкта, що дозволяє спростити структуру зберігання даних.

import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document
import org.springframework.data.mongodb.core.mapping.Unwrapped
@Document(collection = "users")
data class User(
@Id
val id: String? = null,
val name: String,
@Unwrapped(prefix = "address_")
val address: Address
)

User:
Основний документ, анотований як @Document для зберігання в колекції users.

Address:
Вкладений об'єкт.

@Unwrapped(prefix = “address_”)
Атрибути об'єкта Address будуть розгорнуті та збережені в документі User як окремі поля з префіксом address_.

@QueryIndexed
використовується у Spring Data для позначення поля як індексованого. Ця анотація застосовується здебільшого в контексті Spring Data для MongoDB і дозволяє вказувати, що певне поле має бути проіндексоване для покращення продуктивності пошуку.

Застосування анотації @QueryIndexed у Spring Data MongoDB:

Коли поле анотоване як @QueryIndexed, це вказує MongoDB створити індекс для цього поля. Індексація допомагає прискорити операції пошуку за цим полем, що особливо корисно для полів, які часто використовуються в запитах.

import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.index.QueryIndexed
import org.springframework.data.mongodb.core.mapping.Document
@Document(collection = "users")
data class User(
@Id
val id: String? = null,
@QueryIndexed
val email: String,
val username: String
)

@Document(collection = “users”):
Визначає, що клас User являє собою документ у колекції users MongoDB.

@Id:
Позначає поле id як ідентифікатор документа.

@QueryIndexed
Вказує, що поле email має бути проіндексоване для прискорення операцій пошуку за цим полем.

@PreAuthorize
у Spring Security використовується для обмеження доступу до методів на основі виразів SpEL (Spring Expression Language). Вона дозволяє задати умови, за яких доступ до методу буде дозволений або заборонений.

Основні можливості @PreAuthorize:

Перевірка ролей і привілеїв:
Перевіряє, чи має поточний користувач необхідні ролі або привілеї для виконання методу.

Перевірка атрибутів:
Дозволяє перевіряти різні атрибути поточного користувача, такі як ім'я користувача, email тощо.

Використання SpEL:
Дозволяє використовувати вирази SpEL для створення складних логічних умов.

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuthorize

Приклад використання @PreAuthorize:
Розгляньмо приклад використання @PreAuthorize для обмеження доступу до методів на основі ролей.
Створення сервісу з обмеженням доступу. У цьому прикладі метод adminMethod доступний лише користувачам з роллю ADMIN, метод userMethod доступний лише користувачам з роллю USER, а метод userOrAdminMethod доступний як користувачам з роллю USER, так і користувачам з роллю ADMIN.

import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Service
@Service
class UserService {
@PreAuthorize("hasRole('ADMIN')")
fun adminMethod() {
println("Admin method accessed")
}
@PreAuthorize("hasRole('USER')")
fun userMethod() {
println("User method accessed")
}
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
fun userOrAdminMethod() {
println("User or Admin method accessed")
}
}

Увімкнення анотацій безпеки:
Щоб @PreAuthorize працювала, необхідно увімкнути анотації методу безпеки в конфігурації безпеки.

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig : WebSecurityConfigurerAdapter() {}

@DataMongoTest
використовується для тестування MongoDB репозиторіїв. Вона фокусується на конфігурації компонентів Spring Data MongoDB і забезпечує швидкий тестовий контекст, виключаючи з нього всі зайві залежності, що не стосуються тестування роботи з даними.

@DynamicPropertySource
використовується для динамічного додавання властивостей до Environment контексту застосунку під час виконання тестів. Це особливо корисно в інтеграційних тестах, де необхідно налаштувати властивості, такі як URL бази даних, облікові дані або інші параметри, які можуть залежати від зовнішніх ресурсів, таких як Docker-контейнери, підняті за допомогою Testcontainers.

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
@ExtendWith(SpringExtension::class)
@SpringBootTest
@Testcontainers
class UserServiceTest {
@Autowired
private lateinit var userService: UserService
companion object {
@Container
val postgreSQLContainer = PostgreSQLContainer<Nothing>(
"postgres:latest"
).apply {
withDatabaseName("testdb")
withUsername("test")
withPassword("test")
}
@JvmStatic
@DynamicPropertySource
fun registerPgProperties(
registry: DynamicPropertyRegistry
) {
registry.add(
"spring.datasource.url",
postgreSQLContainer::getJdbcUrl
)
registry.add(
"spring.datasource.username",
postgreSQLContainer::getUsername
)
registry.add(
"spring.datasource.password",
postgreSQLContainer::getPassword
)
}
}
@Test
fun `test createUser`() {
val user = User(
email = "test@example.com",
username = "testuser"
)
val savedUser = userService.createUser(user)
assertNotNull(savedUser.id)
assertEquals(
"test@example.com",
savedUser.email
)
assertEquals(
"testuser",
savedUser.username
)
}
@Test
fun `test getAllUsers`() {
val user1 = User(
email = "test1@example.com",
username = "testuser1"
)
val user2 = User(
email = "test2@example.com",
username = "testuser2"
)
userService.createUser(user1)
userService.createUser(user2)
val users = userService.getAllUsers()
assertEquals(2, users.size)
}
}

@Testcontainers
Анотація, що позначає, що цей клас тесту використовує Testcontainers для керування життєвим циклом контейнерів.

@Container
Позначає контейнер, яким буде автоматично керувати Testcontainers.

@DynamicPropertySource
Статичний метод, анотований @DynamicPropertySource, використовується для реєстрації динамічних властивостей у контексті тесту.

У цьому прикладі метод registerPgProperties додає властивості, такі як URL бази даних, ім'я користувача та пароль, які витягуються з контейнера PostgreSQL.

@BeforeEach
анотація з бібліотеки JUnit 5, яка вказує, що анотований метод має виконуватися перед кожним тестовим методом у поточному класі тестів. Це корисно для налаштування умов або ініціалізації даних, необхідних для кожного тесту.

@Embedded і @Embeddable
у JPA (Java Persistence API) використовується для вказання того, що клас являє собою вбудовуваний компонент, який може бути включений в інші сутності. Вбудовувані компоненти дозволяють повторно використовувати поля та логіку в кількох сутностях без дублювання коду.

import javax.persistence.Embeddable
@Embeddable
data class Address(
val street: String,
val city: String,
val country: String
)
import javax.persistence.*
@Entity
data class User(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val name: String,
@Embedded
val address: Address
)

@Embeddable
вказує, що клас Address може бути вбудовуваним компонентом в інших сутностях.

@Embedded
використовується в сутності User для вказання, що об'єкт Address має бути включений як складова частина User.

@Secured
у Spring Security використовується для обмеження доступу до методів на основі ролей. На відміну від анотації @PreAuthorize, яка використовує вирази SpEL (Spring Expression Language) для перевірки умов, @Secured обмежується лише перевіркою ролей. Ця анотація є простішою та зручнішою, якщо потрібно просто перевіряти наявність певних ролей у користувача.

Основні можливості @Secured:

Перевірка ролей:
Дозволяє вказати, які ролі необхідні для доступу до методу.

Простота використання:
Використовується для простої перевірки наявності однієї або кількох ролей у користувача.

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Secured

Приклад використання @Secured:

Розгляньмо приклад використання @Secured для обмеження доступу до методів на основі ролей.
Увімкнення підтримки анотацій безпек
Щоб @Secured працювала, необхідно увімкнути підтримку анотацій методу безпеки в конфігурації безпеки.

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
class SecurityConfig : WebSecurityConfigurerAdapter() {}

Створення сервісу з обмеженням доступу:
У цьому прикладі метод adminMethod доступний лише користувачам з роллю ADMIN, метод userMethod доступний лише користувачам з роллю USER, а метод userOrAdminMethod доступний як користувачам з роллю USER, так і користувачам з роллю ADMIN.

import org.springframework.security.access.annotation.Secured
import org.springframework.stereotype.Service
@Service
class UserService {
@Secured("ROLE_ADMIN")
fun adminMethod() {
println("Admin method accessed")
}
@Secured("ROLE_USER")
fun userMethod() {
println("User method accessed")
}
@Secured("ROLE_USER", "ROLE_ADMIN")
fun userOrAdminMethod() {
println("User or Admin method accessed")
}
}

Відмінності між @PreAuthorize і @Secured:
Анотації @PreAuthorize і @Secured у Spring Security мають схожу мету — обмежувати доступ до методів на основі умов безпеки. Однак вони різняться за функціональністю, гнучкістю та способом використання.

@Secured
Простота використання: @Secured використовується для перевірки наявності однієї або кількох ролей у поточного користувача.
Підтримка ролей: Перевіряє лише ролі та не підтримує складніші умови.
Менше можливостей: Підтримує лише прості перевірки ролей, що робить її менш гнучкою.
Анотації: Працює з простими списками рядкових значень, що представляють ролі.

@PreAuthorize
Гнучкість: @PreAuthorize використовує вирази SpEL (Spring Expression Language) для перевірки умов безпеки, що робить її гнучкішою.
Підтримка складних умов: Може перевіряти не лише ролі, але й інші атрибути, такі як ім'я користувача, параметри методу тощо.
Широкі можливості: Підтримує складні логічні вирази та дозволяє перевіряти різні атрибути безпеки.
Здатність до налаштування: Може використовуватися для налаштування складніших умов доступу.

@Column у JPA
використовується в Java Persistence API (JPA) для вказання відображення поля сутності на стовпець таблиці в базі даних. Ця анотація надає гнучкість у конфігуруванні відповідності полів класу та стовпців бази даних, дозволяючи вказати ім'я стовпця, його тип, обмеження на унікальність та інші атрибути.

Основні атрибути анотації @Column:

name:
Ім'я стовпця в таблиці. Якщо не вказано, використовується ім'я поля.

nullable:
Вказує, чи може стовпець містити NULL значення. За замовчуванням true.

unique:
Вказує, чи має значення стовпця бути унікальним. За замовчуванням false.

length:
Довжина стовпця. Застосовно лише до рядкових типів даних. За замовчуванням 255.

precision:
Точність для числових стовпців (загальна кількість цифр). Застосовно до типів BigDecimal і BigInteger.

scale:
Масштаб для числових стовпців (кількість цифр після коми). Застосовно до типів BigDecimal і BigInteger.

insertable:
Вказує, чи має стовпець брати участь у SQL-операціях вставки. За замовчуванням true.

updatable:
Вказує, чи має стовпець брати участь у SQL-операціях оновлення. За замовчуванням true.

columnDefinition:
SQL вираз, який визначає тип даних стовпця.

table:
Ім'я таблиці, якщо воно відрізняється від таблиці за замовчуванням.

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
@Entity
data class Person(
@Id @GeneratedValue(strategy = GenerationType.AUTO)
val id: Long? = null,
@Column(name = "person_name", nullable = false, length = 100)
val name: String,
@Column(name = "person_age", nullable = false)
val age: Int,
@Column(name = "email", unique = true, length = 150)
val email: String?
)

Поле name відображається на стовпець person_name, який не може бути NULL і має максимальну довжину 100 символів.
Поле age відображається на стовпець person_age, який не може бути NULL.
Поле email відображається на стовпець email, який має бути унікальним і має максимальну довжину 150 символів.

@PostConstruct
це анотація, що надається пакетом javax.annotation, яка використовується для позначення методу, який має бути виконаний після завершення ініціалізації залежностей біна і до того, як бін буде доступний для використання.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface PostConstruct
import org.springframework.stereotype.Service
import javax.annotation.PostConstruct
@Service
class ExampleService {
private lateinit var someData: String
@PostConstruct
fun init() {
// Ініціалізація даних після створення біна
someData = "Ініціалізовані дані"
println("ExampleService ініціалізований з даними: $someData")
}
fun getData(): String {
return someData
}
}

@PreDestroy
анотація з Java Specification Request (JSR) 250, яка використовується в Java EE і Spring для вказання методу, який має бути виконаний перед знищенням біна. Ця анотація зазвичай застосовується до методу, який виконує операції очищення, такі як звільнення ресурсів, закриття з'єднань та інші завершальні дії.

Приклад використання @PreDestroy у Spring:

У Spring біни можуть керуватися контейнером Spring, і іноді необхідно виконати деякі дії перед знищенням біна. Це може бути корисно для завершення з'єднань з базою даних, закриття файлових потоків та інших операцій очищення.

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class AppConfig {
@Bean
fun exampleBean(): ExampleBean {
return ExampleBean()
}
}
import javax.annotation.PreDestroy
class ExampleBean {
fun start() {
println("Bean is starting")
}
@PreDestroy
fun cleanup() {
println("Bean is being destroyed")
// Тут можна звільнити ресурси
}
}
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.ConfigurableApplicationContext
@SpringBootApplication
class Application
fun main(args: Array<String>) {
val context: ConfigurableApplicationContext = SpringApplication.run(Application::class.java, *args)
val bean = context.getBean(ExampleBean::class.java)
bean.start()
// Застосунок закривається, і Spring викликає метод cleanup()
context.close()
}

Як це працює:

Ініціалізація:
Коли Spring контейнер створює бін ExampleBean, він викликає метод start, якщо це передбачено в коді.

Знищення:
Коли застосунок закривається (наприклад, при виклику context.close()), Spring контейнер викликає метод cleanup, позначений анотацією @PreDestroy.

Важливі зауваження:
Метод, позначений @PreDestroy, не повинен приймати аргументів і не повинен повертати значення.
Він має бути публічним або захищеним, щоб бути доступним контейнеру Spring.
Анотація @PreDestroy може використовуватися як з компонентами Spring, так і з бінами, визначеними в конфігураційних файлах.

@EnableJdbcRepositories
Використовується у Spring Data для ввімкнення підтримки репозиторіїв JDBC. Вона автоматично налаштовує інфраструктуру Spring Data JDBC і сканує пакет на наявність інтерфейсів репозиторіїв, які будуть перетворені на реалізації на основі JDBC.

Основні функції @EnableJdbcRepositories:

Автоматичне налаштування репозиторіїв JDBC:
Вмикає механізм репозиторіїв Spring Data JDBC. Автоматично налаштовує необхідні біни для роботи з JDBC.

Сканування пакетів:
Сканує вказані пакети на наявність інтерфейсів репозиторіїв і створює їх реалізації.

Налаштування бінів інфраструктури:
Налаштовує JdbcTemplate, DataSource та інші необхідні компоненти для роботи з базою даних.

@Transient
використовується в контексті Java і деяких фреймворків для вказання, що певне поле не має бути серіалізоване або збережене в базу даних. Вона може застосовуватися в різних контекстах, таких як JPA (Java Persistence API) і стандартна серіалізація Java. Розгляньмо докладніше використання цієї анотації в кожному з цих контекстів.

@Transient у JPA
У JPA анотація @Transient вказує, що поле не має бути персистентним, тобто не має зберігатися в базу даних. Це корисно для тимчасових або обчислюваних полів, які не потребують зберігання в базі даних.

@Transient у стандартній серіалізації Java
У контексті стандартної серіалізації Java анотація @Transient вказує, що поле не має бути серіалізоване, коли об'єкт серіалізується.

@MappedCollection
використовується у Spring Data JDBC для позначення колекції пов'язаних сутностей в основній сутності. Вона дозволяє вказувати, що поле в основній сутності має бути відображене на відповідні записи в пов'язаній таблиці. Це корисно для моделювання відносин “один-до-багатьох” і “багато-до-багатьох” у базах даних з використанням Spring Data JDBC.

Основні особливості @MappedCollection:

Колекції пов'язаних сутностей:
Вказує, що поле є колекцією пов'язаних сутностей.

Підтримка складних структур даних:
Дозволяє легко працювати з колекціями всередині основної сутності.

Зіставлення із зовнішніми ключами:
Автоматично керує зовнішніми ключами для пов'язаних сутностей.

import org.springframework.data.annotation.Id
import org.springframework.data.relational.core.mapping.MappedCollection
import org.springframework.data.relational.core.mapping.Table
@Table("users")
data class User(
@Id
val id: Long? = null,
val email: String,
val username: String,
@MappedCollection(idColumn = "user_id")
val addresses: List<Address> = listOf()
)
@Table("addresses")
data class Address(
val street: String,
val city: String,
val country: String
)

User:
Основна сутність, анотована @Table(“users”).

Address:
Пов'язана сутність, анотована @Table(“addresses”).

@MappedCollection
Вказує, що addresses є колекцією пов'язаних сутностей Address. Атрибут idColumn вказує на стовпець зовнішнього ключа в таблиці addresses

@PayloadRoot
використовується у Spring Web Services для визначення методу-обробника в класі, який буде обробляти вхідні SOAP-повідомлення з певним кореневим елементом у тілі (payload) повідомлення

@Endpoint
використовується для позначення класу як Spring Web Service Endpoint (кінцева точка вебсервісу)

@RequestPayload
вказує, що параметр методу (request) має бути прив'язаний до тіла вхідного SOAP-запиту

@ResponsePayload
Анотація вказує, що значення, яке повертає метод, має бути перетворене на тіло SOAP-відповіді

@XmlRootElement
використовується для вказання, що клас являє собою кореневий елемент XML-документа. Ця анотація застосовується в контексті Java Architecture for XML Binding (JAXB) і дозволяє об'єкту бути маршальованим (перетвореним на XML) і демаршальованим (перетвореним з XML) як кореневий елемент
У контексті Spring Web Services @XmlRootElement використовується для анотування класів, які будуть автоматично маршалюватися та демаршалюватися як XML повідомлення.

Основні аспекти @XmlRootElement:

Простір імен (namespace):
Простір імен XML (namespace) може бути вказаний за допомогою атрибута namespace.

Локальне ім'я (name):
Локальне ім'я кореневого елемента може бути вказане за допомогою атрибута name.

<HelloRequest xmlns="http://example.com/helloworld">
<name>John</name>
</HelloRequest>
import javax.xml.bind.annotation.XmlRootElement
@XmlRootElement(
namespace = "http://example.com/helloworld",
name = "HelloRequest"
)
data class HelloRequest(
var name: String = ""
)

@SpringBootApplication
є комбінацією трьох анотацій: @Configuration, @EnableAutoConfiguration і @ComponentScan. Вона зазвичай використовується для позначення основного класу конфігурації застосунку Spring Boot.
Рекомендується мати лише один клас з анотацією @SpringBootApplication, який буде головним класом конфігурації для всього застосунку. Якщо необхідно розділити конфігурацію на кілька частин, краще використовувати інші анотації, такі як @Configuration, для створення окремих конфігураційних класів

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)

Також- application.properties

my.property=Hello, Spring!
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update

Чим @Component відрізняється від @Service

У Spring Framework анотації @Component і @Service використовуються для позначення класів як бінів Spring. Хоча технічно вони функціонують однаково і мають однакову роль у процесі створення та керування бінами, їх використання пов'язане з різними семантичними значеннями, що допомагає краще організувати та задокументувати ваш код. Ось основні відмінності:

@Component:
Використовується для загального призначення, коли клас не підходить під інші спеціалізовані анотації (@Service, @Repository, @Controller).

@Service:
Використовується для сервісних класів, які містять бізнес-логіку.

Читання та підтримка коду:
Використання спеціалізованих анотацій, таких як @Service, допомагає краще організувати код і зробити його зрозумілішим для інших розробників. Вони одразу розуміють, що цей клас використовується для бізнес-логіки.

Застосування аспектів (AOP):
У деяких випадках спеціалізовані анотації, такі як @Service, можуть бути використані для застосування аспектів (AOP) специфічнішим чином.

Чим @Component відрізняється від @Bean

@Component і @Bean — це два різні способи визначення бінів (компонентів) у Spring, але вони використовуються в різних контекстах і мають свої особливості.

@Component:
анотація, яка використовується для маркування класу як компонента Spring.

Використовується в поєднанні з механізмом сканування класів (component scanning). Spring автоматично виявляє класи, анотовані як @Component, і реєструє їх як біни в контексті застосунку.

Також існують спеціалізовані версії @Component, такі як @Service, @Repository і @Controller, які роблять те саме, але додають семантичне значення для розробників та інструментарію.

import org.springframework.stereotype.Component
@Component
class MyComponent {
fun doSomething() {
println("Doing something")
}
}

Використання @Component і @Autowired:

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import org.springframework.stereotype.Service
@Component
class MyComponent {
fun doSomething() {
println("Doing something")
}
}
@Service
class MyService(@Autowired private val myComponent: MyComponent) {
fun serve() {
myComponent.doSomething()
}
}

@Bean:
анотація, яка використовується для визначення методу, що повертає об'єкт, який має бути зареєстрований як бін у контексті Spring.

Зазвичай використовується в конфігураційних класах, анотованих як @Configuration.

Дозволяє явним чином створити та налаштувати бін з певними параметрами або логікою ініціалізації.

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class MyConfig {
@Bean
fun myBean(): MyBean {
return MyBean()
}
}
class MyBean {
fun doSomething() {
println("Doing something")
}
}

Використання @Bean у конфігураційному класі:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
class MyBean {
fun doSomething() {
println("Doing something")
}
}
class MyService(
private val myBean: MyBean
) {
fun serve() {
myBean.doSomething()
}
}
@Configuration
class MyConfig {
@Bean
fun myBean(): MyBean {
return MyBean()
}
@Bean
fun myService(myBean: MyBean): MyService {
return MyService(myBean)
}
}

Порівняння @Component і @Bean:

Механізм:

@Component:
використовується в поєднанні з механізмом сканування класів (component scanning). Це означає, що Spring автоматично виявляє класи, анотовані @Component, і реєструє їх як біни в контексті застосунку.

@Bean:
використовується для визначення методу, що повертає об'єкт, який має бути зареєстрований як бін у контексті Spring. Зазвичай ця анотація застосовується в конфігураційних класах, анотованих @Configuration.

Використання:

@Component:
застосовується до класу, щоб автоматично виявити та зареєструвати його як бін.

@Bean:
використовується для створення та налаштування бінів через методи, що дозволяє явнішим чином контролювати процес створення бінів.

Контекст:

Клас, анотований @Component, автоматично стає біном у контексті Spring.

Метод у конфігураційному класі, анотований @Bean, повертає об'єкт, який стає біном у контексті Spring.

Додаткові анотації:

Для @Component існують спеціалізовані версії, такі як @Service, @Repository і @Controller. Ці анотації роблять те саме, що й @Component, але додають семантичне значення для розробників та інструментарію.

Для @Bean немає додаткових анотацій, але він використовується в контексті конфігураційних класів для явного визначення бінів.

Використовуйте @Component (або його спеціалізовані версії) для класів, які мають бути автоматично виявлені та зареєстровані як біни.

Використовуйте @Bean для явного визначення бінів у конфігураційних класах, особливо коли потрібне додаткове налаштування або логіка ініціалізації.

Copyright: Roman Kryvolapov