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.

Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration
Kotlin
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.

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

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

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

@Service
специализирана анотация, която се използва за класове от сервизния слой. Тя също е разновидност на @Component.

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

@Repository
специализирана анотация за класове за достъп до данни (DAO). Тя също е разновидност на @Component.

Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository
Kotlin
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. Тази анотация се използва за обработка на уеб заявки.

Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller
Kotlin
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.

Java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController
Kotlin
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
се използва за глобална обработка на изключения и за предоставяне на съвети (advice) на контролерите, които работят с REST API. Това е разширение на анотацията @ControllerAdvice, което автоматично включва @ResponseBody, затова е подходяща за обработка на изключения в REST контролерите, като връща отговори във формат JSON или XML.

Java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice
Kotlin
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
)
}
}
Kotlin
// Контролер, който хвърля изключение
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) и тяло:

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

Ако възникне общо изключение, сървърът ще върне JSON отговор с HTTP статус 500 (Internal Server Error) и тяло:

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

@ResponseBody
прави автоматична сериализация в JSON на всеки върнат обект

Java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody
Kotlin
@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
показва, че методът-съвет (advice) трябва да се изпълни преди извикването на целевия метод. Използва се например за логване на влизането в метода или за проверка на правата за достъп.

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

@Around
позволява напълно да обвиете извикването на целевия метод: изпълнявате код преди него, извиквате самия метод чрез proceed(), а после изпълнявате код след него. Дава пълен контрол над изпълнението – може да промените аргументите, резултата или дори изобщо да не извиквате самия метод.

@Pointcut
описва към кои методи да се прилага аспектът (по сигнатури, анотации и т.н.)

Kotlin
@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
се използва за настройка на свързващите средства за данни (data binders) на ниво контролер. Те обработват данните, които идват от HTTP заявките, и ги преобразуват в обекти на модела. Анотацията @InitBinder позволява да регистрирате собствени редактори на свойства или валидатори, които да обработват данните, преди да бъдат свързани с обектите на модела.

@CookieValue
се използва за обвързване на стойност от HTTP cookie с параметър на метод в контролера. Така получавате стойностите на cookie директно в методите на контролера, което опростява работата с тях.

Kotlin
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 автоматично намира подходящия бийн и го инжектира в поле, конструктор или метод.

Java
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired
Kotlin
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 се основава на прокси и на аспектно-ориентираното програмиране за управление на транзакциите. Проксито прихваща извикванията на методите и взаимодейства с мениджъра на транзакциите, за да започне, потвърди или върне транзакция. Така транзакциите се обработват автоматично, което опростява разработката на надеждни и съгласувани приложения.

Java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Reflective
public @interface Transactional
Kotlin
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:
Задава реда на изпълнение на транзакционните аспекти.

Kotlin
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”.

Java
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestPart
Kotlin
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}"
}
}
Kotlin
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
се използва за инжектиране на стойности от файл със свойства в полета.

Java
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value
Kotlin
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
добавя пред url на метода това, което е подадено в нея

Java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
@Reflective({ControllerMappingReflectiveProcessor.class})
public @interface RequestMapping
Kotlin
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

Java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = {RequestMethod.GET})
public @interface GetMapping
Kotlin
@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

Java
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam
Kotlin
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

Java
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody
Kotlin
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.

Java
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SessionAttribute
Kotlin
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"
}
}
Kotlin
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.

Java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SessionAttributes
Kotlin
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

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

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):
Указва причината, която ще бъде върната заедно с кода на статуса. Този параметър е незадължителен.

Java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseStatus
Kotlin
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 заявката директно в методите на контролера, което опростява работата с хедърите на заявките.

Java
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestHeader
Kotlin
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"
}
}
Kotlin
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"
}
}
Kotlin
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 заявка с параметър на метод в контролера. Така получавате стойностите на атрибутите на заявката директно в методите на контролера, което опростява работата с атрибутите, зададени в заявката.

Java
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestAttribute
Kotlin
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"
}
}
Kotlin
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
се използва за определяне на метод, който ще обработва определени типове изключения

Java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Reflective({ExceptionHandlerReflectiveProcessor.class})
public @interface ExceptionHandler
Kotlin
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
разрешава междудомейнни (cross-origin) заявки към метод или клас.

Java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin
Kotlin
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 позволява да се укаже кой точно бийн трябва да бъде инжектиран.

Java
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier
Kotlin
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!")
}
}
Kotlin
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

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

@ImportResource
се използва за зареждане на XML конфигурация в контекста на приложението. Това е полезно, ако имате съществуващи XML файлове с конфигурация на Spring, които искате да импортирате в конфигурация, основана на анотации или на Java код.

Пример за използване:
Да предположим, че имате XML конфигурация applicationContext.xml:

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:

Kotlin
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, че тези класове са бийнове, които трябва да се управляват.

Java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan
Kotlin
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, за да се укаже първичният ключ.

Kotlin
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
Определят връзките между същностите.

Пример с връзки:

Kotlin
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 или когато искате да укажете друг пакет за сканиране на същности.

Java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({EntityScanPackages.Registrar.class})
public @interface EntityScan
Kotlin
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:
Указва езика за пълнотекстово търсене (по желание).

Kotlin
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
Създава геопространствен индекс върху полето.

Kotlin
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“).

Kotlin
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:
Указва дали имената на таблиците и колоните трябва да се поставят в кавички.

Kotlin
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 са съставни ключове за разделяне (partition key), а email и username са клъстерни ключове с наредба във възходящ ред.

@PrimaryKeyColumn
се използва в Spring Data Cassandra за определяне на колоните на първичния ключ в таблиците на Cassandra. Тя е част от API, което помага на разработчиците да взаимодействат с базата данни Cassandra по-удобно и по-ефективно. Тази анотация позволява изрично да се укаже кои колони трябва да са част от първичния ключ и как трябва да се обработват.

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

name:
Името на колоната в таблицата.

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

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

ordering:
Редът на сортиране за клъстерните колони (ASC или DESC).

Kotlin
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 се използва, за да се укаже, че атрибутите на вложения обект трябва да бъдат разгърнати (или „разкрити“) и запазени като отделни полета в родителския документ, вместо вложеният обект да се запазва като поддокумент.
Това може да е полезно, когато искате атрибутите на вложения обект да се съхраняват на същото ниво като атрибутите на родителския обект, което позволява да се опрости структурата на съхранение на данните.

Kotlin
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 да създаде индекс за това поле. Индексирането помага да се ускорят операциите за търсене по това поле, което е особено полезно за полета, които често се използват в заявките.

Kotlin
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 за създаване на сложни логически условия.

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

Пример за използване на @PreAuthorize:
Нека разгледаме пример за използване на @PreAuthorize за ограничаване на достъпа до методи въз основа на роли.
Създаване на услуга с ограничен достъп. В този пример методът adminMethod е достъпен само за потребители с роля ADMIN, методът userMethod е достъпен само за потребители с роля USER, а методът userOrAdminMethod е достъпен както за потребители с роля USER, така и за потребители с роля ADMIN.

Kotlin
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, е необходимо да се включат анотациите за сигурност на методите в конфигурацията за сигурност.

Kotlin
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.

Kotlin
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 = "[email protected]",
username = "testuser"
)
val savedUser = userService.createUser(user)
assertNotNull(savedUser.id)
assertEquals(
savedUser.email
)
assertEquals(
"testuser",
savedUser.username
)
}
@Test
fun `test getAllUsers`() {
val user1 = User(
email = "[email protected]",
username = "testuser1"
)
val user2 = User(
email = "[email protected]",
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) се използват, за да се укаже, че класът представлява вграден компонент, който може да бъде включен в други същности. Вградените компоненти позволяват повторно използване на полета и логика в няколко същности без дублиране на код.

Kotlin
import javax.persistence.Embeddable
@Embeddable
data class Address(
val street: String,
val city: String,
val country: String
)
Kotlin
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:

Проверка на роли:
Позволява да се укаже кои роли са необходими за достъп до метода.

Простота на използване:
Използва се за проста проверка дали потребителят има една или няколко роли.

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

Пример за използване на @Secured:

Нека разгледаме пример за използване на @Secured за ограничаване на достъпа до методи въз основа на роли.
Включване на поддръжката на анотации за сигурност
За да работи @Secured, е необходимо да се включи поддръжката на анотации за сигурност на методите в конфигурацията за сигурност.

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

Създаване на услуга с ограничен достъп:
В този пример методът adminMethod е достъпен само за потребители с роля ADMIN, методът userMethod е достъпен само за потребители с роля USER, а методът userOrAdminMethod е достъпен както за потребители с роля USER, така и за потребители с роля ADMIN.

Kotlin
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:
Името на таблицата, ако се различава от таблицата по подразбиране.

Java
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column
Kotlin
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, която се използва за означаване на метод, който трябва да се изпълни след приключването на инициализацията на зависимостите на бийна и преди бийнът да стане достъпен за използване.

Java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface PostConstruct
Kotlin
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 и понякога е необходимо да се изпълнят някои действия преди унищожаването на бийна. Това може да е полезно за приключване на връзките с базата данни, затваряне на файловите потоци и други операции по почистване.

Kotlin
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class AppConfig {
@Bean
fun exampleBean(): ExampleBean {
return ExampleBean()
}
}
Kotlin
import javax.annotation.PreDestroy
class ExampleBean {
fun start() {
println("Bean is starting")
}
@PreDestroy
fun cleanup() {
println("Bean is being destroyed")
// Тук може да се освободят ресурси
}
}
Kotlin
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, не трябва да приема аргументи и не трябва да връща стойност.
Той трябва да е публичен или защитен (protected), за да е достъпен за контейнера на 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:

Колекции от свързани същности:
Указва, че полето е колекция от свързани същности.

Поддръжка на сложни структури от данни:
Позволява лесна работа с колекции вътре в основната същност.

Съпоставяне с външни ключове:
Автоматично управлява външните ключове за свързаните същности.

Kotlin
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.

XML
<HelloRequest xmlns="http://example.com/helloworld">
<name>John</name>
</HelloRequest>
Kotlin
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, за създаване на отделни конфигурационни класове

Java
@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
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}

Също така – application.properties

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, които правят същото, но добавят семантично значение за разработчиците и инструментите.

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

Използване на @Component и @Autowired:

Kotlin
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.

Позволява изрично да се създаде и настрои бийн с определени параметри или логика за инициализация.

Kotlin
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 в конфигурационен клас:

Kotlin
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