javax.inject中@Inject、@Named、@Qualifier和@Provider用法
javax.inject
包 javax.inject 指定了獲取對象的一種方法,該方法與構造器、工廠以及服務定位器(例如 JNDI))這些傳統方法相比可以獲得更好的可重用性、可測試性以及可維護性。此方法的處理過程就是大家熟知的依賴注入,它對于大多數應用是非常有價值的。
javax.inject包里的幾個類:
在我們的程序中,很多類型依賴于其他類型。例如,一個 Stopwatch 可能依賴于一個 TimeSource。一些類型被另一個類型依賴,我們就把這些類型叫做這個類型的依賴(物)。在運行時查找一個依賴實例的過程叫做解析依賴。如果找不到依賴的實例,那我們稱該依賴為不能滿足的,并導致應用運行失敗。
在不使用依賴注入時,對象的依賴解析有幾種方式。最常見的就是通過編寫直接調用構造器的代碼完成:
class Stopwatch {
final TimeSource timeSource;
Stopwatch () {
timeSource = new TimeSource(…);
}
void start() { … }
long stop() { … }
}
如果需要更有彈性一點,那么我們可以通過工廠或服務定位器實現:
class Stopwatch {
final TimeSource timeSource;
Stopwatch () {
timeSource = DefaultTimeSource.getInstance();
}
void start() { … }
long stop() { … }
}
在使用這些傳統方式進行依賴解析時,程序員必須做出適當權衡。構造器非常簡潔,但卻有一些限制(對象生存期,對象復用)。工廠確實解耦了客戶與實現,但卻需要樣本式的代碼。服務定位器更進一步地解耦了客戶與實現,但卻降低了編譯時的類型安全。并且,這三個方式都不適合進行單元測試。例如,當程序員使用工廠時,該工廠的每一個產品都必須模擬出來,測試完后還要得記得清理:
void testStopwatch() {
TimeSource original = DefaultTimeSource.getInstance();
DefaultTimeSource.setInstance(new MockTimeSource());
try {
// Now, we can actually test Stopwatch.
Stopwatch sw = new Stopwatch();
//…
} finally {
DefaultTimeSource.setInstance(original);
}
}
現實中,要模擬工廠將導致更多的樣本式代碼。測試模擬出的產品并清理它們在依賴多的情況下很快就控制不了了。更糟的是,程序員必須精確地預測未來到底需要多少這樣的彈性,并為他做的“彈性選擇”負責。如果程序員開始時選擇了構造器方式,但后來需要一個更有彈性的方式,那他就不得不替換所有調用構造器的代碼。如果程序員一開始過于謹慎地選擇了工廠方式,結果可能導致要編寫很多額外的樣本式代碼,引入了不必要的復雜度,潛在的問題比比皆是。
依賴注入就是為了解決這些問題。代替程序員調用構造器或工廠,一個稱作依賴注入器的工具將把依賴傳遞給對象:
class Stopwatch {
final TimeSource timeSource;
@Inject Stopwatch(TimeSource timeSource) {
this.TimeSource = timeSource;
}
void start() { … }
long stop() { … }
}
注入器將更進一步地傳遞依賴給其他的依賴,直到它構造出整個對象圖。例如,假設一個程序員需要注入器創建一個StopwatchWidget 實例:
/** GUI for a Stopwatch */
class StopwatchWidget {
@Inject StopwatchWidget(Stopwatch sw) { … }
…
}
注入器可能會:
查找一個 TimeSource 實例
使用找到的 TimeSource 實例構造一個 Stopwatch
使用構造的 Stopwatch 實例構造一個 StopwatchWidget
這使得代碼保持干凈,使得程序員感到使用依賴(物)的基礎設施非常容易。
現在,在單元測試中,程序員可以直接構造對象(不使用注入器)并將該對象以模擬依賴的方式直接傳入待測對象的構造中。程序員再也不需要為每一次測試都配置并清理工廠或服務定位器。這大大簡化了我們的單元測試:
void testStopwatch() {
Stopwatch sw = new Stopwatch(new MockTimeSource());
//…
}
完全降低了單元測試的復雜度,降低的復雜程度與待測對象的數目及其依賴成正比。
包 javax.inject 為使用這樣的輕便類提供了依賴注入注解,但沒有引入依賴配置方式。依賴配置方式取決于注入器的實現。程序員只需要標注了構造器、方法或字段來說明它們的可注入性(上面的例子就是構造器注入)。依賴注入器通過這些注解來識別一個類的依賴,并在運行時注入這些依賴。此外,注入器要能夠在構建時驗證所有的依賴是否滿足。相比之下,服務定位器在構建時是不能檢測到依賴不滿足情況的,直到運行時才能發現。
注入器實現有很多形式。一個注入器可以通過 XML、注解、DSL(領域規約語言),或是普通 Java代碼來進行配置。注入器實現可以使用反射或代碼生成。使用編譯時代碼生成的注入器甚至可能沒有它自己的運行時描述。而其他注入器實現無論在編譯時還是運行時可能都不使用代碼生成。一個“容器”,其實可以把它定義為一個注入器,不過包 javax.inject不涉及非常大概念,旨在最小化注入器實現的限制。
@Inject支持構造函數、方法和字段注解,也可能使用于靜態實例成員。可注解成員可以是任意修飾符(private,package-private,protected,public)。注入順序:構造函數、字段,然后是方法。父類的字段和方法注入優先于子類的字段和方法,同一類中的字段和方法是沒有順序的。
@Inject注解的構造函數可以是無參或多個參數的構造函數。@Inject每個類中最多注解一個構造函數。
在字段注解:
用@Inject注解
字段不能是final的
擁有一個合法的名稱
在方法上注解:
用@Inject注解
不能是抽象方法
不能聲明自身參數類型
可以有返回結果
擁有一個合法的名稱
可以有0個或多個參數
@Inject MethodModirers ResultType Identifier(FormalParameterList ) Throws MethodBody
[上述翻譯:inject的doc文檔,翻譯不好敬請諒解]
構造函數注解:
@Inject
public House(Person owner) {
System.out.println("---這是房屋構造函數---");
this.owner = owner;
}
字段注解:
@Inject private Person owner;
方法注解:
@Inject
public void setOwner(Person owner) {
this.owner = owner;
}
@Inject注解和Spring的@Autoware注解都是根據類型對其進行自動裝配。
SpringUtil類:
public class SpringUtil {
private static ApplicationContext context = null;
public static ApplicationContext getApplicationContext() {
if (context == null) {
context = new ClassPathXmlApplicationContext("spring.xml");
}
return context;
}
public static ApplicationContext getApplicationContext(String path) {
return new ClassPathXmlApplicationContext(path);
}
public static ApplicationContext getAnnotationConfigApplicationContext(String basePackages) {
return new AnnotationConfigApplicationContext(basePackages);
}
}
Person類:
import javax.inject.Named;
@Named
public class Person {
private String name;
public Person() {
System.out.println("---這是人的構造函數---");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
House類:
@Named
public class House {
@Inject private Person owner;
public House() {
System.out.println("---這是房屋構造函數---");
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
}
測試類:
public class Test {
public static void main(String[] args) {
ApplicationContext context = SpringUtil.getApplicationContext(
"test/spring/inject/bean-inject.xml");
House house = (House)context.getBean("house");
Person p = house.getOwner();
p.setName("張三");
System.out.println(house.getOwner().getName());
}
}
輸出結果:
---這是房屋構造函數---
---這是人的構造函數---
張三
上述例子在Spring3.1下測試成功,在Spring3.1下,每個構造函數只初始化一次及默認的單例形式,個人感覺如果脫離Spring環境應該每次用都會實例化新的對象,當然根據實現的jar包不同而不同,要不javax.inject下的@Singleton注解就沒有什么用途了。
@Named
@Named和Spring的@Component功能相同。@Named可以有值,如果沒有值生成的Bean名稱默認和類名相同。
例如:
@Named public class Person
該bean的名稱就是person。
@Named("p") public class Person
如果指定名稱,那么就是指定的名稱嘍。
@Qualifier
任何人都可以定義一個新的修飾語,一個qualifier注解應該滿足如下條件:
定義的注解類有@Qualifier,@Retention(RUNTIME)和@Documented。
可以有屬性
可以是公共API的一部分
可以用@Target注解限定使用范圍
下面是Qualifier的例子:
Genre注解類:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
@Target(value = {ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
public @interface Genre {
User user() default User.STUDENT;
public enum User {STUDENT, TEACHER}
}
用戶接口:(對個數進行統計)
public interface IUserDAO {
int count();
}
StudentDAO:
@Named
@Genre(user = User.STUDENT)
public class StudentDAO implements IUserDAO{
@Override
public int count() {
System.out.println("----StudentDAO----");
return 0;
}
}
TeacherDAO:
@Named
@Genre(user = User.TEACHER)
public class TeacherDAO implements IUserDAO {
@Override
public int count() {
System.out.println("--TeacherDAO--");
return 0;
}
}
UserDAOProcessor:
@Named
public class UserDAOProcessor {
/*對TeacherDAO類的注入,如果對StudentDAO類注入應該是:@Genre(user = User.STUDENT)或@Genre,因為@Genre默認的是STUDENT*/
@Inject
private @Genre(user = User.TEACHER) IUserDAO userDAO;
public int count() {
return userDAO.count();
}
public IUserDAO getUserDAO() {
return userDAO;
}
public void setUserDAO(IUserDAO userDAO) {
this.userDAO = userDAO;
}
}
測試類:
public class Test {
public static void main(String[] args) {
ApplicationContext context = SpringUtil.getApplicationContext(
"test/spring/inject/bean-inject.xml");
UserDAOProcessor processor = (UserDAOProcessor)context.getBean("userDAOProcessor");
System.out.println(processor.count());
}
}
輸出結果:
--TeacherDAO--
0
個人對@Qualifier的理解:
和Spring的@Qualifier大致相同
單獨用@Inject無法滿足對接口的注入,無法找到哪個具體類,所以用@Qualifier來確定注入的具體類
用到@Qualifier的注解中可以有值、無值和用枚舉類型
@Singleton
使用該注解標記該類只創建一次,不能被繼承。一般在類上用該注解。
總結
以上是生活随笔為你收集整理的javax.inject中@Inject、@Named、@Qualifier和@Provider用法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SCI、EI、核心期刊的分区
- 下一篇: 气象台再发高温预警:多地气温破纪录 今年