2022/05/23

Annotations in Spring Boot

Spring 在 2.5 以後,不用集中式的 xml 設定檔,改用 annotation 定義,傳統的 XML 設定冗長,改用 annotation 這個方法後有好有壞,這讓開發者專注在程式開發,但會有不知道為什麼這樣設定就能運作的錯覺。

以下是 Spring Application 能看到的 annotations

Core Spring Framework

@Autowired

可用在 fields, setter methods, constructors,能夠直接 inject object dependency

fields: 透過 property name 自動設定 field value

public class Customer {
    @Autowired                               
    private Person person;                   
    private int type;
}

setter: 告訴 Spring 要在 init bean 時,呼叫 setter method

public class Customer {                                                                                         
    private Person person;
    @Autowired                                                                                                      
    public void setPerson(Person person) {
       this.person=person;
    }
}

constructor: 產生物件時,做 injection,只能有一個 constructor 帶有 @Autowired annotation

@Component
public class Customer {
    private Person person;
    @Autowired
    public Customer (Person person) {
        this.person=person;
    }
}

@Qualifier

跟 @Autowired 一起使用,當該 @Bean 有多個 instance 時,可透過 @Qualifier 指定 instance

如果有兩個 BeanInterface 的 instances

@Component
public class BeanB1 implements BeanInterface {
    //
}
@Component
public class BeanB2 implements BeanInterface {
    //
}

透過 @Qualifier("beanB2") 指定 beanB2

@Component
public class BeanA {
    @Autowired
    @Qualifier("beanB2")
    private IBean dependency;
    ...
}

@Configuration

類似以往的 XML config file (applicationContext.xml),用來設定 Spring IoC beans

ex:

@Configuartion
public class DataConfig {
    @Bean
    public DataSource source() {
        DataSource source = new OracleDataSource();
        source.setURL();
        source.setUser();
        return source;
    }
    @Bean
    public PlatformTransactionManager manager() {
        PlatformTransactionManager manager = new BasicDataSourceTransactionManager();
        manager.setDataSource(source());
        return manager;
    }
}

@SpringBootApplication 本身就包含了 @Configuartion,可直接在裡面使用 @Bean

ex:

@SpringBootApplication
public class SpringBootBeanDemoApplication {

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

    @Bean
    public FooService fooService() {
        return new FooService();
    }

}

@ComponentScan

跟著 @Configuration 一起使用

告訴 Spring 去哪一個 package 掃描 annotated components

@ComponentScan("com.test.config")
@Configuration
public class AppConfig {
}

可以指定 basePackageClasses 或 basePackages,取代直接設定 package。這樣可避免 class package 異動時,還要修改 @ComponentScan 的 package name

@ComponentScan(basePackageClasses = ApplicationController.class)
@Configuration
public class AppConfig {
}

@Bean

只能用在 method,跟 @Configuration 一起使用,告訴 Spring 產生哪些 bean

@Configuration
public class AppConfig {
    @Bean
    public Person person() {
        return new Person(address());
    }
    @Bean
    public Address address() {
        return new Address();
    }
}

@Lazy

用在 component class

autowired dependencies 預設會在啟動時,自動 created & configured

加上 @Lazy,表示該 bean 會在第一次被使用時,才被 created

@Lazy 也可以用在 @Configuration classes,表示所有的 @Bean 都會被延遲建立

@Value

用在 field, constructor parameter, method parameter

表示該 field/parameter 的初始值

通常 Spring 會自動載入 application.properties 的設定,如果是自訂 properties file,要用 @PropertySource 指定

ex:

#application.properties
user.name=admin

#my.properties
user.password=pwd123
@PropertySource("classpath:my.properties")
public class ValueController {
    @Value("${user.name}")
    private String name;

    @Value("${user.password}")
    private String password;
}

也可以指定 List, Array

tools=car,train,airplane
@Value("${tools}")
private String[] toolArray;

@Value("${tools}")
private List<String> toolList;

Spring Framework Stereotype Annotations

@Component

用在 class,代表一個 Spring component

@Component 可讓 Java class 在 component scanning 時被放入 application context

@Controller

代表 Spring controller

用在 Spring MVC 或 Spring WebFlux 的 controller

@Service

用在 class,讓 java class 提供某個 service,ex: 執行 business logic、計算、呼叫外部 API,這是一種用在 service layer 特殊的 @Component

@Repository

用在 Java class 可直接存取 DB,也就是 Data Access Object 的角色

有 auto translation 功能,ex: 發生 exception 時,會有 handler 處理該 exception,不需要加上 try-catch block

Spring Boot Annotations

@EnableAutoConfiguration

用在 main application class

會自動定義一個 base "search package",告訴 Spring Boot,根據 classpath 設定, perperties 設定,加入beans

會檢查所有 sub-packages & class

@Configuration
@EnableAutoConfiguration
public class EmployeeApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(EmployeeApplication.class, args);
        // ...
    }
}

排除 JdbcTemplateAutoConfiguration.class

@Configuration
@EnableAutoConfiguration(exclude={JdbcTemplateAutoConfiguration.class})
public class EmployeeApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(EmployeeApplication.class, args);
        // ...
    }
}

@SpringBootApplication

用在 application class,設定 Spring Boot project

會自動掃描 sub-packages,ex:

會掃描 com.example 的所有 sub-packages

package com.example;

@SpringBootApplication
public class EmployeeApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(EmployeeApplication.class, args);
        // ...
    }
}

使用 @SpringBootApplication 就等於使用了

  • @Configuration
  • @EnableAutoConfiguration
  • @ComponentScan

Spring MVC and REST Annotations

@Controller

會自動偵測 classpath 的 component classes,並自動註冊 bean definitions

如果要偵測 annotated controllers,必須加入 component scanning 到 configuration。

@Controller 可處理多個 request mappings

@Controller 用在 Spring MVC 及 Spring WebFlux

@RequestMapping

可用在 class 及 method

用來 map web request 到某個 class / handler methods

用在 class level 時,會產生該 controler 的 base URI,然後就能使用所有 handler methods

如果要限制 HTTP method,可在 handler method 上加上 @RequestMapping,ex:

只接受 GET method request 發送到 /welcome

@Controller
@RequestMapping("/welcome")
public class WelcomeController {
    @RequestMapping(method = RequestMethod.GET)
    public String welcomeAll() {
        return "welcome all";
    }
}

用在 Spring MVC 及 Spring WebFlux

@CookieValue

用在 method parameter level

用在 request mapping method 的參數,透過 cookie name 綁定

ex:

當有個 http request 夾帶 JSESSIONID 的 cookie,可透過 @CookieValue 取得 JSESSIONID 的值

JSESSIONID=418AB76CD83EF94U85YD34W
@ReuestMapping("/cookieValue")
    public void getCookieValue(@CookieValue "JSESSIONID" String cookie){
}

@CrossOrigin

用在 class 及 method level,可 enable cross-origin requests

如果可讓 js 從不同 host 取得資料,就要用 @CrossOrigin 啟用 cross-origin resource sharing

@CrossOrigin 預設允許所有 origins, headers,maxAge 為 30 min

ex:

getMessage 與 getNot 都會是 maxAge of 3600 seconds

getMessage 只允許來自 http://example.com 的 request

getNote 允許所有 hosts

@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin(origins = "http://example.com")
    @RequestMapping("/message")
    public Message getMessage() {
        // ...
    }

    @RequestMapping("/note")
    public Note getNote() {
        // ...
    }
}

Composed @RequestMapping Variants

用在 Spring MVC 及 WebFlux

@GetMapping

只接受 Get method request

跟以下 @RequestMapping 意義一樣

@RequestMapping(method = RequestMethod.GET)

@PostMapping

只接受 Postmethod request

跟以下 @RequestMapping 意義一樣

@RequestMapping(method = RequestMethod.POST)

@PutMapping

只接受 PUT method request

跟以下 @RequestMapping 意義一樣

@RequestMapping(method = RequestMethod.PUT)

@PatchMapping

只接受 Patch method request

跟以下 @RequestMapping 意義一樣

@RequestMapping(method = RequestMethod.PATCH)

@DeleteMapping

只接受 DELETE method request

跟以下 @RequestMapping 意義一樣

@RequestMapping(method = RequestMethod.DELETE)

@ExceptionHandler

處理 controller level 的 exception,定義該 class 要 catch 的 exception class

@ExceptionHandler 的 value 可以是 array of Expception types

@InitBinder

method-level

識別用來初始化 WebDataBinder 的 methods

WebDataBinder 是可將 request parameter 綁定 JavaBean objects 的 DataBinder

可在 controller 使用 @InitBinder annotated method

@InitBinder annotated methods 會在每一次 http request 被呼叫。用來驗證參數是否符合規則

ex: 如果有一個 request form 是 Student 資料

先產生 Student Java Bean

public class Student{
 private String Id;
 private String firstName;

 @NotNull(message="is required")
 @Size(min=1,message="is required")
 private String lastName; // validation done to check if lastname is NULL

 @Max(value=10,message="Value should between 0 and 10")
 @Min(value=0,message="Value should between 0 and 10")
 private String standard;
 private String Age;
}

用 initBinder 註冊 StringTrimmerEditor

@InitBinder
public void initBinder(WebDataBinder dataBinder) {
  StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
    dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
}

@Mappings and @Mapping

用在 fields

@Mappings 是 web mapping annotation,可對應 source field 到 target field

@MaxtrixVariable

用在 request handler method arguments

可 inject relevant bits of a maxtri URI

maxtrix variables 會以 semicolon 區隔

如果 URL 包含 matrix variables,request mapping pattern 會以 URI template 表示

http://localhost:8080/spring/employees/John;beginContactNumber=22001

用以下 method

@RequestMapping(value = "/employees/{name}", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<List<Employee>> getEmployeeByNameAndBeginContactNumber(
  @PathVariable String name, @MatrixVariable String beginContactNumber) {
    List<Employee> employeesList = new ArrayList<Employee>();
    ...
    return new ResponseEntity<>(employeesList, HttpStatus.OK);
}

URI

http://localhost:8080/spring/employees/id=1;name=John;contactNumber=2200112334
@GetMapping("employees/{employee}")
@ResponseBody
public ResponseEntity<Map<String, String>> getEmployeeData(
  @MatrixVariable Map<String, String> matrixVars) {
    return new ResponseEntity<>(matrixVars, HttpStatus.OK);
}

@PathVariable

用在 method arguments

處理 dynamic changes in URI

可以用 regular expression

ex:

@GetMapping("/api/employees/{id}/{name}")
@ResponseBody
public String getEmployeesByIdAndName(@PathVariable String id, @PathVariable String name) {
    return "ID: " + id + ", name: " + name;
}

@RequestAttribute

綁定 request attribute 到 handler method paratmeter

ex: 計算 visit counter

Interceptor 增加 request attribute

public class MyCounterInterceptor extends HandlerInterceptorAdapter {
  private AtomicInteger counter = new AtomicInteger(0);

  @Override
  public boolean preHandle (HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

      request.setAttribute("visitorCounter", counter.incrementAndGet());
      return true;
  }
}

controller

@Controller
public class ExampleController {

  @RequestMapping("/")
  @ResponseBody
  public String handle (@RequestAttribute("visitorCounter") Integer counter) {
      return String.format("Visitor number: %d", counter);
  }
}

JavaConfig

@EnableWebMvc
@ComponentScan("com.example")
public class AppConfig extends WebMvcConfigurerAdapter {

  @Override
  public void addInterceptors (InterceptorRegistry registry) {
      registry.addInterceptor(new MyCounterInterceptor());
  }
}

ref: https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/request-attribute.html

@RequestBody

一般用於處理非 Content-Type: application/x-www-form-urlencoded 編碼格式的資料,例如:application/json、application/xml

@RequestMapping(path = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
    writer.write(body);
}

@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value="/user", consumes = MediaType.APPLICATION_JSON_VALUE)
public void process2(@RequestBody User user) {
  logger.info("User: {}", user);
}

@RequestHeader

將 request header 裡面的 name 對應到 handler method parameter

@RequestParam

在 controller 中,跟 @RequestMapping 一起使用,綁定 request parameter 到 method parameter

@RequestPart

取代 @RequestParam

可取得特定 multipart 的 content 到 method parameter

Content-type 必須要是 multipart

@ResponseBody

用在 handler methods,類似 @RequestBody

spring 會將物件透過 HttpMessageConverter 轉換為 JSON/XML 寫入response body

@ResponseStatus

用在 methods, exception class,指定回傳的 status code

@ControllerAdvice

class level

可定義@ExceptionHandler, @InitBinder, and @ModelAttribute methods ,就會套用在所有 @RequestMapping methods

如果在 @ControllerAdvice class 的某個 method 加上 @ExceptionHandler,會被套用在所有 controllers

@RestController

將 class 標記為 controller

使用 @RestController 就不需要在所有 RequestMapping methods 加上 @ResponseBody,這表示不會在 response 回傳 html

@RestControllerAdvice

等同 @ControllerAdvice 與 @ResponseBody,再加上 @ExceptionHandler 處理 exceptions

@SessionAttribute

method parameter level

綁定 method parameter to a session attribute

@SessionAttributes

type level

要跟 @ModelAttribute 一起使用

可以將 JavaBean object 增加到 session 中

@ModelAttribute("person")
public Person getPerson() {}
// within the same controller as above snippet
@Controller
@SeesionAttributes(value = "person", types = {
    Person.class
})
public class PersonController {}

Spring Cloud Annotations

@EnableConfigServer

class level

當 project 有多個 services,需要集中設定所有 services

centralized config server 的優點是不需要知道 components 在哪裏

@EnableEurekaServer

用在 class

使用 microservices 時,很難知道每一個 service 相關的 addresses,必須要有 service discovery 方法

Netflix’s Eureka 是 discovery server 的實作

@EnableDiscoveryClient

用在 java class

通知哪一個 application 要註冊到 Eureka,會將 host, port 註冊到 Eureka

@EnableCircuitBreaker

用在 java class

circuit breaker pattern 讓 microservice 在相關 service fails 還能持續運作,避免 error 擴散

讓 service 有 recover 的時間

@HystrixCommand

method level

Netflix’s Hystrix library 是 Circuit Breaker pattern 的實作

當 method 套用 circuit breaker,Hystrix 會監控 failure of the method。當錯誤發生,Hystrix 會打開 circuit,後續的呼叫會 fail,Hystrix 會 redirect calls 到這個 method

ex:

@Service
public class BookService {
    private final RestTemplate restTemplate;
    public BookService(RestTemplate rest) {
        this.restTemplate = rest;
    }
    @HystrixCommand(fallbackMethod = "newList") public String bookList() {
        URI uri = URI.create("http://localhost:8081/recommended");
        return this.restTemplate.getForObject(uri, String.class);
    }
    public String newList() {
        return "Cloud native Java";
    }
}

Spring Framework DataAccess Annotations

@Transactional

放在 interface 定義, interface 的 method, class 定義, class 的 public method 前面

@Transactional 只是一個 metadata,讓 beans 設定 tranactional behavior

Cache-Based Annotations

@Cacheable

用在 methods

可將 return 的資料以 addresses 儲存到 cache

@Cacheable("addresses")
public String getAddress(Book book){...}

每次該 method 被呼叫時,會先檢查 cache

@CachePut

當要 update cache 時,可用 @CachePut

@CachePut("addresses")
public String getAddress(Book book){...}

不建議將 @CachePut, @Cacheable 合併使用

@CacheEvict

用在 method

可將 cache 清空,allEntries 就是清除所有 values

@CacheEvict(value="addresses", allEntries="true")
public String getAddress(Book book){...}

@CacheConfig

class level

可儲存 cache configuration

Task Execution and Scheduling Annotations

@Scheduled

method level

該 method 必須回傳 void,且不能有任何參數

// 等 5s 後開始執行,會等前一次結束後再跑第二次
@Scheduled(fixedDelay=5000)
public void doSomething() {
    // something that should execute periodically   
}

// 每 5s 執行一次,不管前一次有沒有跑完
@Scheduled(fixedRate=5000)
public void doSomething2() { 
    // something that should execute periodically 
}

@Scheduled(initialDelay=1000,fixedRate=5000)
public void doSomething3() { 
   // something that should execute periodically after an initial delay  
}

@Async

method level

每個 method 都在不同 thread 執行

@Async 如果有 return value,必須要是 Future-typed

Spring Framework Testing Annotations

@BootstrapWith

class level

設定 Spring TestContext Framework 如何啟動

@ContextConfiguration

class level

宣告要載入 context 的 class,及設定的 xml

@ContextConfiguration(locations={"example/test-context.xml", loader = Custom ContextLoader.class})

@WebAppConfiguration

class level

ApplicationContext 要載入的必須要是 WebApplicationContext

@WebAppConfiguration 可產生 web version of the application context

default root of web app 為 src/main/webapp

@Timed

method

該method 必須在限定時間內執行結束。超時就代表測試失敗

@Timed(millis=10000)
public void testLongRunningProcess() {  ... }

@Repeat

用在 test method

重複執行

@Repeat(10)
@Test
public void testProcessRepeatedly() {  ... }

@Commit

class level or method level

執行後,transaction of the transactional test method 會被 commit

@RollBack

class level or method level

@Rollback(true), the transaction is rolled back

@DirtiesContext

class, method level

表示 ApplicationContext 已被 modified / corrupted

@BeforeTransaction

在 @Transactional 前執行

@AfterTransaction

在 @Transactional 後

@Sql

run SQL scripts against a database

@SqlConfig

決定 parse and execute SQL scripts 的方法

@SqlGroup

method level

a container annotation that can hold several @Sql annotations

@SpringBootTest

start the Spring context for integration tests

@DataJpaTest

test Spring Data JPA using an in-memory database such as H2

@DataMongoTest

@WebMVCTest

a mock servlet context for testing the MVC layer

@AutoConfigureMockMVC

@MockBean

產生並 inject Mockito Mock

@JsonTest

限制測試處理 JSON 的 auto-configuration of Spring Boot 的 components

@TestPropertySource

class level

property sources for the test class

References

A Guide to Spring Framework Annotations

太厲害了!終於有人把Spring Boot常用註釋講明白了!

Top 10 Spring Framework Annotations for Java Programmers

Spring Annotation Note

沒有留言:

張貼留言