1. 程式人生 > >Spring註解學習筆記

Spring註解學習筆記

什麼是註解

傳統的Spring做法是使用.xml檔案來對bean進行注入或者是配置aop、事物,這麼做有兩個缺點:

  1. 如果所有的內容都配置在.xml檔案中,那麼.xml檔案將會十分龐大;如果按需求分開.xml檔案,那麼.xml檔案又會非常多。總之這將導致配置檔案的可讀性與可維護性變得很低
  2. 在開發中在.java檔案和.xml檔案之間不斷切換,是一件麻煩的事,同時這種思維上的不連貫也會降低開發的效率

為了解決這兩個問題,Spring引入了註解,通過"@XXX"的方式,讓註解與Java Bean緊密結合,既大大減少了配置檔案的體積,又增加了Java Bean的可讀性與內聚性。

註解組成

java annotation 的組成中,有3個非常重要的主幹類。它們分別是:

  1. Annotation.java
  2. ElementType.java
  3. RetentionPolicy.java

Annotation.java

public interface Annotation {
    boolean equals(Object obj);
    int hashCode();
    String toString();
    Class<? extends Annotation> annotationType();
}
  • Annotation 是個介面
  • 有四個函式

ElementType.java

public enum ElementType {
    TYPE,               // 類、介面(包括註釋型別)或列舉
    FIELD,              // 欄位(包括列舉常量)
    METHOD,             // 方法
    PARAMETER,          // 引數
    CONSTRUCTOR,        // 構造方法
    LOCAL_VARIABLE,     // 區域性變數
    ANNOTATION_TYPE,    // 註釋型別
    PACKAGE             // 包
}

ElementType 是Enum列舉型別,它用來指定Annotation的型別。

     “每1個Annotation” 都與 “n個ElementType”關聯。當Annotation與某個ElementType關聯時,就意味著:Annotation有了某種用途。
     例如,若一個Annotation物件是METHOD型別,則該Annotation只能用來修飾方法。

RetentionPolicy.java

public enum RetentionPolicy {
    SOURCE,    // 只保留在原始碼中,編譯器編譯時,直接丟棄這種註解,不記錄在.class檔案中
    CLASS,    // 編譯器把註解記錄在class檔案中。當執行Java程式時,JVM中不可獲取該註解資訊,這是預設值
    RUNTIME    // 編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊
}

RetentionPolicy 是Enum列舉型別,它用來指定Annotation的策略。

通俗點說,就是不同RetentionPolicy型別的Annotation的作用域不同

     “每1個Annotation” 都與 “1個RetentionPolicy”關聯。

  • SOURCE:只保留在原始碼中,編譯器編譯時,直接丟棄這種註解,不記錄在.class檔案中。
  • CLASS:編譯器把註解記錄在class檔案中。當執行Java程式時,JVM中不可獲取該註解資訊,這是預設值。
  • RUNTIME:編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。

準備工作

使用註解是在 Spring2.5(2.0以後陸續加入) 以後新加的功能,所以至少要保證版本是 2.5+,並且引入了註解的包
然後需要在 Spring 的配置檔案中告訴它你使用了註解,最簡單的一種方式就是加入下面的一句:
<context:component-scan base-package="com.xxx" />
它的意思就是開啟自動掃描,會自動掃描你設定的包路徑下的所有類,如果有註解就進行解析
這就是所謂的用註解來構造 IoC 容器;base-package 是可以指定多個包的,用逗號分割

註解分類

註解可以按照很多種方式分類,這裡我按照下面的來分類

1、宣告bean的註解

  1. @Componentn
  2. @Repository
  3. @Service
  4. @Controller

2、注入bean的註解

  1. @Autowired
  2. @Qualifier
  3. @Resource

3、Spring MVC常見註解

  1. @Controller
  2. @RequestMapping
  3. @RequestParam
  4. @PathVariable
  5. @RequestBody
  6. @RespopnseBody
  7. @ResController

其他。。。

宣告bean的註解

註解

作用範圍

含義

@Component

 註解在類上,可以作用在任何層次。

泛指元件,當元件不好歸類的時候,我們可以使用這個註解進行標註。

是一個泛化的概念,僅僅表示一個元件 (Bean) ,將一個實體類,放入bean中。

@Repository

註解在類上

用於標註資料訪問元件,即DAO元件。

@Service

註解在類上

用於標註業務層元件

@Controller

註解在類上

用於標註控制層元件(如struts中的action)

所有聲明後的類,都用統一被Spring IoC容器管理。

@Component

原始碼

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
   String value() default "";
}

原始碼說明:

  1. @Target(ElementType.TYPE):可以用在類、介面(包括註釋型別)或列舉上
  2. @Retention(RetentionPolicy.RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。
  3. @Documented:該註解能出現在Javadoc中

當一個類加上@Component註解後,

  1. 宣告該類為通用的bean,並會被Spring IoC容器所管理
  2. 可以指定value,也就是bean的名字。不指定的話,預設為此類的首字母小寫
  3. @Component 是所有受 Spring 管理元件的通用形式
  4. @Component 不推薦使用:因為太通用所以不推薦,實在不好歸類的時候再用。

@Repository

原始碼

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
   @AliasFor(annotation = Component.class)
   String value() default "";
}

原始碼說明:

  1. @Target(ElementType.TYPE):可以用在類、介面(包括註釋型別)或列舉上
  2. @Retention(RetentionPolicy.RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。
  3. @Documented:該註解能出現在Javadoc中
  4. @Component:說明該註解擁有@Component註解的所有屬性,可理解為繼承自@Component

當一個類加上@Controller註解後,

  1. 宣告該類為資料訪問層的bean,並會被Spring IoC容器所管理
  2. 可以指定value,也就是bean的名字。不指定的話,預設為此類的首字母小寫

使用例子

@Repository
public class UserDaoImpl extends BaseDaoImpl<User> {
   //TODO
}

@Service

原始碼

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
   @AliasFor(annotation = Component.class)
   String value() default "";
}

原始碼說明

  1. @Target(ElementType.TYPE):可以用在類、介面(包括註釋型別)或列舉上
  2. @Retention(RetentionPolicy.RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。
  3. @Documented:該註解能出現在Javadoc中
  4. @Component:說明該註解擁有@Component註解的所有屬性,可理解為繼承自@Component

當一個類加上@Service註解後,

  1. 宣告該類為業務層的bean,並會被Spring IoC容器所管理
  2. 可以指定value,也就是bean的名字。不指定的話,預設為此類的首字母小寫

使用例子:

@Service
public class UserService{
    //TODO
}

@Controller

原始碼

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
   @AliasFor(annotation = Component.class)
   String value() default "";
}

原始碼說明

  1. @Target(ElementType.TYPE):可以用在類、介面(包括註釋型別)或列舉上
  2. @Retention(RetentionPolicy.RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。
  3. @Documented:該註解能出現在Javadoc中
  4. @Component:說明該註解擁有@Component註解的所有屬性,可理解為繼承自@Component

將一個類加上@Controller註解後,

  1. 宣告該類為控制層的bean,並會被Spring IoC容器所管理
  2. 可以指定value,也就是bean的名字。不指定的話,預設為此類的首字母小寫

使用例子

@Controller
public class CompanyController {
    //TODO
}

注入bean的註解

@Autowired

原始碼

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
   boolean required() default true;
}

原始碼說明

  1. @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}):可以作用在CONSTRUCTOR、METHOD、PARAMETER、FIELD、ANNOTATION
  2. @Retention(RetentionPolicy.RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。
  3. @Documented:該註解能出現在Javadoc中
  4. @Component:說明該註解擁有@Component註解的所有屬性,可理解為繼承自@Component

補充說明

  1. @Autowired註解可用於為類的屬性、構造器、方法進行注值。
  2. 預設情況下,其依賴的物件必須存在(bean可用),如果需要改變這種預設方式,可以設定其required屬性為false。
  3. @Autowired註解預設按照型別裝配,如果容器中包含多個同一型別的Bean,那麼啟動容器時會報找不到指定型別bean的異常,解決辦法是結合@Qualified註解進行限定,指定注入的bean名稱

@Qualifier

原始碼

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
   String value() default "";
}

原始碼說明

  1. @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}):可以作用在CONSTRUCTOR、METHOD、PARAMETER、FIELD、ANNOTATION
  2. @Retention(RetentionPolicy.RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。
  3. @Inherited:@Inherited指定註解具有繼承性。如果某個類使用了@xxx註解(定義該註解時使用了@Inherited修飾)修飾,則其子類將自動被@xxx修飾。
  4. @Documented:該註解能出現在Javadoc中

補充說明

這個註解需要配合@Autowired使用,是用來指定注入 Bean 的名稱 ;因為@Autowired是按照型別來匹配的,如果一個型別有兩個實現,直接使用@Autowired就會報錯。

也就是說如果容器中有一個以上匹配的 Bean,則可以通過 @Qualifier 註解限定 Bean 的名稱,否則呼叫的時候會拋異常
比如:某個 bean 中引用了一個介面,實現這個介面的 bean 有多個,Spring 在注入的時候就不知道注入那一個了,這樣要麼刪除其他的 bean 要麼就是使用 @Qualifier 註解。

我們來舉個例子

Car.java

public interface Car{
    public String carName();
}

兩個實現類BMW和Benz:

BMW.java

@Service
public class BMW implements Car{
    public String carName(){
        return "BMW car";
    }
}

Benz.java

@Service
public class Benz implements Car{
    public String carName(){
        return "Benz car";
    }
}

寫一個CarFactory,引用Car:

CarFactory.java

@Service
public class CarFactory{
    // 報錯!!!
    @Autowired
    private Car car;
    public String toString(){
        return car.carName();
    }
}

不用說,一定是報錯的,Car介面有兩個實現類,Spring並不知道應當引用哪個實現類。這種情況通常有兩個解決辦法:

  1. 刪除其中一個實現類,Spring會自動去base-package下尋找Car介面的實現類,發現Car介面只有一個實現類,便會直接引用這個實現類
  2. 實現類就是有多個該怎麼辦?此時可以使用@Qualifier註解

使用Qualifier()後的CarFactory.java

@Service
public class CarFactory{
    @Autowired
    @Qualifier("BMW")
    private Car car;
    public String toString(){
        return car.carName();
    }
}

@Resource

原始碼

// 並不是來自Spring,而是來自於Java
package javax.annotation;

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
    String name() default "";
    String lookup() default "";
    Class<?> type() default java.lang.Object.class;
    enum AuthenticationType {
            CONTAINER,
            APPLICATION
    }
    AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
    boolean shareable() default true;
    String mappedName() default "";
    String description() default "";
}

原始碼說明:

  1. @Target({TYPE, FIELD, METHOD}):可以作用在TYPE、FIELD、METHOD
  2. @Retention(RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。

補充說明

  1. 對於@Resource註解,它並不屬於spring的註解,而是來自於JSR-250。
  2. @Resource預設為名稱注入,但也可以指定 name或type 進行注入。

使用例子

public class Zoo {
    // 通過名稱進行注入
    @Resource(name = "tiger")
    private Tiger tiger;

    // 通過型別進行注入
    @Resource(type = Monkey.class)
    private Monkey monkey;
}

@Resource與@Autowired區別

  1. @Autowired 預設按照 byType 方式進行 bean 匹配,@Resource 預設按照 byName 方式進行 bean 匹配
  2. @Autowired 是 Spring 的註解,@Resource 是 J2EE 的註解

Spring 屬於第三方的,J2EE 是 Java 自己的東西

Spring MVC常見註解

@Controller

同上

@RequestMapping

原始碼

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

// 有以下屬性
String name() default "";
   @AliasFor("path")
   String[] value() default {};
   @AliasFor("value")
   String[] path() default {};
   RequestMethod[] method() default {};
   String[] params() default {};
   String[] headers() default {};
   String[] consumes() default {};
   String[] produces() default {};
}

原始碼說明:

  1. @Target({ElementType.METHOD, ElementType.TYPE}):可以作用在方法和類上
  2. @Retention(RetentionPolicy.RUNTIME):作用在執行時
  3. RequestMethod[]:以指定訪問方式,如果不指定,預設既可以通過GET也可通過POST方式來訪問

這個註解的屬性有很多,這裡只介紹常用的幾種

使用例子

@Controller
// 可以作用在類上
@RequestMapping(value = "/aaa")
public class HappyController {
    // 可以作用在方法上
    // value可省略不寫
    @RequestMapping("/bbb")
    public void sayHello() {
        //TODO
    }
    // 可指定訪問方式,GET還是POST
    @RequestMapping(value = "/ccc", method = RequestMethod.GET)
    public void sayHaHa() {
        //TODO
    }
}

@RequestParam

原始碼

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
   @AliasFor("name")
   String value() default "";
   @AliasFor("value")
   String name() default "";
   boolean required() default true;
   String defaultValue() default ValueConstants.DEFAULT_NONE;
}

原始碼說明:

  1. @Target(ElementType.PARAMETER):只能作用在引數上
  2. @Retention(RetentionPolicy.RUNTIME):執行時
  3. boolean required() default true;:required預設為true,也就是說引數必填
  4. defaultValue:預設引數,也就是不填引數時,預設是什麼

補充說明:

@RequestParam :將請求的引數繫結到方法中的引數上,有required引數,預設為true,也就是改引數必須要傳。如果改引數可以傳可不傳,可以配置false。

使用例子

@RequestMapping("/happy")
public String sayHappy(
// 作用在引數上
// 引數名為name,必填
@RequestParam(value = "name", required = true) String name,
// age不是必填項,預設為20
@RequestParam(value = "age", required = false, defaultValue = "20") String age){
        //TODO
        }

@PathVariable

原始碼

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
   @AliasFor("name")
   String value() default "";
   @AliasFor("value")
   String name() default "";
   boolean required() default true;
}

原始碼說明:

  1. @Target(ElementType.PARAMETER):作用在引數上
  2. @Retention(RetentionPolicy.RUNTIME):執行時

補充說明:

  1. @PathVariable : 該註解用於方法修飾方法引數,會將修飾的方法引數變為可供使用的uri變數(可用於動態繫結)。
  2. @PathVariable中的引數可以是任意的簡單型別,如int, long, Date等等。Spring會自動將其轉換成合適的型別或者丟擲 TypeMismatchException異常。當然,我們也可以註冊支援額外的資料型別。
  3. @PathVariable支援使用正則表示式,這就決定了它的超強大屬性,它能在路徑模板中使用佔位符,可以設定特定的字首匹配,字尾匹配等自定義格式。

使用例子:

@RequestMapping(value="/happy/{dayid}",method=RequestMethod.GET)
// 繫結 {dayid} 到String dayid
public String findPet(@PathVariable String dayid, Model mode) {
    //TODO
}

@RequestBody

原始碼

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
   boolean required() default true;
}

原始碼說明:

  1. @Target(ElementType.PARAMETER):只能作用在引數上
  2. @Retention(RetentionPolicy.RUNTIME):執行時

補充說明

新增 @RequestBody 後 Spring 會根據請求中的 Content-Type 頭資訊來選擇合適的轉換器, 將請求資料轉為 Java 物件

比如Content-Type是application/json, 那麼就是 JSON -> Model

使用例子:

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

@ResponseBody

原始碼

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

}

原始碼說明:

  1. @Target({ElementType.TYPE, ElementType.METHOD}):可以作用在類和方法上
  2. @Retention(RetentionPolicy.RUNTIME):執行時

補充說明:

  1. 新增 @ResponseBody 後 Spring 會根據請求中的 Accept 頭資訊來選擇合適的轉換器, Java 物件轉化為客戶端可接受的表述形式,比如Accept頭部資訊包含“application/json”, 就是Model -> JSON
  2. @ResponseBody在輸出JSON格式的資料時,會經常用到。

使用例子

@RequestMapping("/getJson")
@ResponseBody
public User jsonTest() {
    User user = new User ();
    user.setName("張三");
    user.setAge(20);
    return user;
}

結果

@RestController

  1. @[email protected][email protected]
  2. 如果一個類有這個註解,那麼就會在這個類下的每個方法都預設加上@ResponseBody

Java配置

除了xml和註解配置,Spring還提供了Java配置,什麼叫java配置,即建立一個類來進行資訊的注入,它和註解配置相似,不同的是它不是在bean的實現類中進行註解,而是新建立一個類進行配置: 這裡涉及到了兩個註解:

  • @Configuration
  • @Bean

User.java

public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //...
}

JavaConfig.java

//用來宣告這個是由Java來配置
@Configuration
public class JavaConfig {
    @Bean
    public publisher createUser() {
        return new user("張三",20);
    }
}

JavaConfigTest.java

@Test
public void javaConfigTest() {
    ApplicationContext context =
            new AnnotationConfigApplicationContext(JavaConfig.class);
    User user = context.getBean("user", User.class);
    System.out.println(user.getName());
}

執行結果

參考:

http://www.voidcn.com/article/p-vnuuxhnq-bcq.html

http://blog.leanote.com/post/[email protected]/spring%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3%E6%B1%87%E6%80%BB

https://www.bilibili.com/video/av21450362

https://bfchengnuo.com/2017/09/04/Spring%E4%B8%AD%E7%9A%84%E6%B3%A8%E8%A7%A3%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93/