Spring註解學習筆記
什麼是註解
傳統的Spring做法是使用.xml檔案來對bean進行注入或者是配置aop、事物,這麼做有兩個缺點:
- 如果所有的內容都配置在.xml檔案中,那麼.xml檔案將會十分龐大;如果按需求分開.xml檔案,那麼.xml檔案又會非常多。總之這將導致配置檔案的可讀性與可維護性變得很低
- 在開發中在.java檔案和.xml檔案之間不斷切換,是一件麻煩的事,同時這種思維上的不連貫也會降低開發的效率
為了解決這兩個問題,Spring引入了註解,通過"@XXX"的方式,讓註解與Java Bean緊密結合,既大大減少了配置檔案的體積,又增加了Java Bean的可讀性與內聚性。
註解組成
java annotation 的組成中,有3個非常重要的主幹類。它們分別是:
- Annotation.java
- ElementType.java
- 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的註解
- @Componentn
- @Repository
- @Service
- @Controller
2、注入bean的註解
- @Autowired
- @Qualifier
- @Resource
3、Spring MVC常見註解
- @Controller
- @RequestMapping
- @RequestParam
- @PathVariable
- @RequestBody
- @RespopnseBody
- @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 "";
}
原始碼說明:
- @Target(ElementType.TYPE):可以用在類、介面(包括註釋型別)或列舉上
- @Retention(RetentionPolicy.RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。
- @Documented:該註解能出現在Javadoc中
當一個類加上@Component註解後,
- 宣告該類為通用的bean,並會被Spring IoC容器所管理
- 可以指定value,也就是bean的名字。不指定的話,預設為此類的首字母小寫
- @Component 是所有受 Spring 管理元件的通用形式
- @Component 不推薦使用:因為太通用所以不推薦,實在不好歸類的時候再用。
@Repository
原始碼
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(annotation = Component.class)
String value() default "";
}
原始碼說明:
- @Target(ElementType.TYPE):可以用在類、介面(包括註釋型別)或列舉上
- @Retention(RetentionPolicy.RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。
- @Documented:該註解能出現在Javadoc中
- @Component:說明該註解擁有@Component註解的所有屬性,可理解為繼承自@Component
當一個類加上@Controller註解後,
- 宣告該類為資料訪問層的bean,並會被Spring IoC容器所管理
- 可以指定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 "";
}
原始碼說明
- @Target(ElementType.TYPE):可以用在類、介面(包括註釋型別)或列舉上
- @Retention(RetentionPolicy.RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。
- @Documented:該註解能出現在Javadoc中
- @Component:說明該註解擁有@Component註解的所有屬性,可理解為繼承自@Component
當一個類加上@Service註解後,
- 宣告該類為業務層的bean,並會被Spring IoC容器所管理
- 可以指定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 "";
}
原始碼說明
- @Target(ElementType.TYPE):可以用在類、介面(包括註釋型別)或列舉上
- @Retention(RetentionPolicy.RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。
- @Documented:該註解能出現在Javadoc中
- @Component:說明該註解擁有@Component註解的所有屬性,可理解為繼承自@Component
將一個類加上@Controller註解後,
- 宣告該類為控制層的bean,並會被Spring IoC容器所管理
- 可以指定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;
}
原始碼說明
- @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}):可以作用在CONSTRUCTOR、METHOD、PARAMETER、FIELD、ANNOTATION上
- @Retention(RetentionPolicy.RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。
- @Documented:該註解能出現在Javadoc中
- @Component:說明該註解擁有@Component註解的所有屬性,可理解為繼承自@Component
補充說明
- @Autowired註解可用於為類的屬性、構造器、方法進行注值。
- 預設情況下,其依賴的物件必須存在(bean可用),如果需要改變這種預設方式,可以設定其required屬性為false。
- @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 "";
}
原始碼說明
- @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}):可以作用在CONSTRUCTOR、METHOD、PARAMETER、FIELD、ANNOTATION上
- @Retention(RetentionPolicy.RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。
- @Inherited:@Inherited指定註解具有繼承性。如果某個類使用了@xxx註解(定義該註解時使用了@Inherited修飾)修飾,則其子類將自動被@xxx修飾。
- @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並不知道應當引用哪個實現類。這種情況通常有兩個解決辦法:
- 刪除其中一個實現類,Spring會自動去base-package下尋找Car介面的實現類,發現Car介面只有一個實現類,便會直接引用這個實現類
- 實現類就是有多個該怎麼辦?此時可以使用@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 "";
}
原始碼說明:
- @Target({TYPE, FIELD, METHOD}):可以作用在TYPE、FIELD、METHOD上
- @Retention(RUNTIME):編譯器把註解記錄在class檔案中。當執行Java程式時,JVM可獲取該註解資訊。程式可以通過反射獲取該註解的資訊。
補充說明
- 對於@Resource註解,它並不屬於spring的註解,而是來自於JSR-250。
- @Resource預設為名稱注入,但也可以指定 name或type 進行注入。
使用例子
public class Zoo {
// 通過名稱進行注入
@Resource(name = "tiger")
private Tiger tiger;
// 通過型別進行注入
@Resource(type = Monkey.class)
private Monkey monkey;
}
@Resource與@Autowired區別
- @Autowired 預設按照 byType 方式進行 bean 匹配,@Resource 預設按照 byName 方式進行 bean 匹配
- @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 {};
}
原始碼說明:
- @Target({ElementType.METHOD, ElementType.TYPE}):可以作用在方法和類上
- @Retention(RetentionPolicy.RUNTIME):作用在執行時
- 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;
}
原始碼說明:
- @Target(ElementType.PARAMETER):只能作用在引數上
- @Retention(RetentionPolicy.RUNTIME):執行時
- boolean required() default true;:required預設為true,也就是說引數必填
- 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;
}
原始碼說明:
- @Target(ElementType.PARAMETER):作用在引數上
- @Retention(RetentionPolicy.RUNTIME):執行時
補充說明:
- @PathVariable : 該註解用於方法修飾方法引數,會將修飾的方法引數變為可供使用的uri變數(可用於動態繫結)。
- @PathVariable中的引數可以是任意的簡單型別,如int, long, Date等等。Spring會自動將其轉換成合適的型別或者丟擲 TypeMismatchException異常。當然,我們也可以註冊支援額外的資料型別。
- @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;
}
原始碼說明:
- @Target(ElementType.PARAMETER):只能作用在引數上
- @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 {
}
原始碼說明:
- @Target({ElementType.TYPE, ElementType.METHOD}):可以作用在類和方法上
- @Retention(RetentionPolicy.RUNTIME):執行時
補充說明:
- 新增 @ResponseBody 後 Spring 會根據請求中的 Accept 頭資訊來選擇合適的轉換器, Java 物件轉化為客戶端可接受的表述形式,比如Accept頭部資訊包含“application/json”, 就是Model -> JSON
- @ResponseBody在輸出JSON格式的資料時,會經常用到。
使用例子
@RequestMapping("/getJson")
@ResponseBody
public User jsonTest() {
User user = new User ();
user.setName("張三");
user.setAge(20);
return user;
}
結果
@RestController
- @[email protected][email protected]
- 如果一個類有這個註解,那麼就會在這個類下的每個方法都預設加上@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
https://www.bilibili.com/video/av21450362