假設場景
假設我們有這樣一個場景,為了保證執行緒安全的使用一個類的方法,我們需要在每個執行緒中都需要創建該類(這里記作B類)的實體,這個時候我們又是在單例bean(這里記作A類)通過@Autowired注解注入的,這個時候每次獲取A類的實體并不是每次都是不同的,而是相同的,這就違背了我們的意愿了,那么在spring中有哪些解決方案呢?
Spring解決方案
這里我給出了3個方案
方案1:使用注解@Lookup
方案2:由于注解@Lookup不能與@Bean協作,使用注解@Autowired+ObjectProvider<T>類(或者ObjectFactory<T>)+@Bean
方案3:使用ApplicationContext
簡單原理:
上面3個方案其實都是直接或者間接的通過BeanFactory的getBean方法回傳原型bean實體來保證每次獲取的bean實體都是不一樣的,這也是拋開spring提供的解決方案的最終統一實作,
解決方案分析
1.使用注解@Lookup
代碼演示
單例User:
package com.duxd.prototype;
@Component
public class User {
@Lookup
public Car getCar() {
//這里完全可以輸出null,這里回傳不為null完全為了測驗
return new Car("保時捷");
}
}
普通類Car:
重寫了toString方法輸出name和實體的hashCode
package com.duxd.prototype;
public class Car {
private String name;
public Car(String name) {
this.name = name;
}
//輸出物件的name和hashcode
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
"hashCode='" + this.hashCode() + '\'' +
'}';
}
}
配置類:
輸出原型實體的hashcode
@Configuration(proxyBeanMethods = false)
@ComponentScan("com.duxd.prototype")
public class LookupTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(LookupTest.class);
context.refresh();
System.out.println("user物件class型別:" + context.getBean(User.class));
System.out.println("car物件:" + context.getBean(User.class).getCar().toString());
System.out.println("car物件:" + context.getBean(User.class).getCar().toString());
}
//向容器注入car,并且scope是原型
@Bean("car")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Car car(){
return new Car("法拉利");
}
}
輸出結果:
user物件class型別:com.duxd.prototype.User$$EnhancerBySpringCGLIB$$f761903c@57c758ac
car物件:Car{name='法拉利'hashCode='178049969'}
car物件:Car{name='法拉利'hashCode='333683827'}
根據結果輸出的hashCode不一樣驗證,使用注解@Lookup達到了效果,而且輸出的name不是“保時捷”,所以我們的getCar方法邏輯并沒有執行,所以我們在getCar方法中直接回傳null值是可以的,
提醒:使用注解@Lookup注解來獲取實體標注的方法簽名必須滿足一下條件:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
因為實作原理是通過CGLIB代理創建User實體的,所以這里的getCar方法必須滿足被重寫的條件,必須不能是private的,不能是final的,其實不管你這里回傳什么,這個方法已經被CGLIB代理重寫了,而且不會呼叫super.getCar方法,所以這里回傳什么都沒有任何作用,
根據上面代碼來看User類感覺是個工具類,而且并沒有使用@Autowired注解注入Car實體,其實加上去也不會用任何作用,因為我們是通過getCar方法獲取的,spring會將帶有lookup注解的類使用CGLIB代理創建實體的,所以我們沒有必要在User類里提供Car的屬性了,
注意:@Lookup使用陷阱:
根據官方檔案描述:
Recommendations for typical Spring configuration scenarios: When a concrete class may be needed in certain scenarios, consider providing stub implementations of your lookup methods. And please remember that lookup methods won't work on beans returned from @Bean methods in configuration classes; you'll have to resort to @Inject Provider<TargetBean> or the like instead.
檔案中提示了,@Lookup注解下的當前類不能通過@Bean方式注入,這樣@Lookup不起作用,必須通過@Component注解標注類然后通過掃描的方式注入,
最后一句話還給我們提示了替代方案,下面有代碼會分析到,
錯誤代碼示例:將user通過@Bean的方式注入
User類將@Component注解去掉,并且提供一個靜態物件來驗證getCar里面的邏輯被執行了,也就是User類沒有被代理,
public class User {
static final Car car = new Car("保時捷");
@Lookup
public Car getCar() {
//這里回傳一個靜態物件驗證這個方法的邏輯被執行了
return User.car;
}
}
配置類添加@Bean注入User實體
@Configuration(proxyBeanMethods = false)
@ComponentScan("com.duxd.prototype")
public class LookupTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(LookupTest.class);
context.refresh();
System.out.println("user物件class型別:" + context.getBean(User.class));
System.out.println("car物件:" + context.getBean(User.class).getCar().toString());
System.out.println("car物件:" + context.getBean(User.class).getCar().toString());
}
//向容器注入car,并且scope是原型
@Bean("car")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Car car(){
return new Car("法拉利");
}
@Bean
public User user(){
return new User();
}
}
輸出結果:
user物件class型別:com.duxd.prototype.User@258e2e41
car物件:Car{name='保時捷'hashCode='64133603'}
car物件:Car{name='保時捷'hashCode='64133603'}
輸出結果可以看出,User類并沒有被代理,所以getCar的邏輯會被執行,回傳的是我們在User類中的靜態實體,通過IOC容器創建的,
上面提到過根據官方檔案最后還有一句話:
you'll have to resort to @Inject Provider<TargetBean> or the like instead.
也就是通過依賴注入一個Provider物件(這里的Provider在spring里是ObjecrProvider或者是ObectFactory),那么我們來試試,我們只需要修改User類和配置類即可
public class User {
@Autowired
private ObjectProvider<Car> car;
public Car getCar(){
return car.getIfAvailable();
}
}
配置類:
@Configuration(proxyBeanMethods = false)
@ComponentScan("com.duxd.prototype")
public class LookupTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(LookupTest.class);
context.refresh();
System.out.println("user物件class型別:" + context.getBean(User.class));
System.out.println("car物件:" + context.getBean(User.class).getCar().toString());
System.out.println("car物件:" + context.getBean(User.class).getCar().toString());
}
//向容器注入car,并且scope是原型
@Bean("car")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Car car(){
return new Car("法拉利");
}
@Bean
public User user(){
return new User();
}
}
輸出結果:
user物件class型別:com.duxd.prototype.User@5906ebcb
car物件:Car{name='法拉利'hashCode='1436901839'}
car物件:Car{name='法拉利'hashCode='1866161430'}
結論:
1.使用ObjectProvider的方式注入原型Bean不是通過動態代理實作的
2.根據hashcode不一樣判斷確實是回傳了兩個物件,根據name是“法拉利”而不是“保時捷”判斷,我們的getCar方法邏輯沒有執行,而是通過IOC注入的,
3.方案1和方案2輸出的結果都是我們想要的,
原始碼分析
這一塊我單獨一篇文章講解,分析@Lookup注解有效和無效的實作原理,也會分析ObjectProvider有效的原因,
思考問題
1:既然可以使用ObjectProvider,那么可以使用jdk的Optional嗎?
這里先給出答案:不可以,這塊分析也在下篇文章一起原始碼分析,
Optional測驗示例:
@Component
public class User {
static final Car DEFAULT_CAR = new Car("保時捷");
@Autowired
private Optional<Car> car;
public Car getCar(){
return car.orElse(User.DEFAULT_CAR);
}
}
@Configuration(proxyBeanMethods = false)
@ComponentScan("com.duxd.prototype")
public class LookupTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(LookupTest.class);
context.refresh();
System.out.println("user物件class型別:" + context.getBean(User.class));
System.out.println("car物件:" + context.getBean(User.class).getCar().toString());
System.out.println("car物件:" + context.getBean(User.class).getCar().toString());
}
//向容器注入car,并且scope是原型
@Bean("car")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Car car(){
return new Car("法拉利");
}
}
輸出結果:
user物件class型別:com.duxd.prototype.User@7920ba90
car物件:Car{name='法拉利'hashCode='112466394'}
car物件:Car{name='法拉利'hashCode='112466394'}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/181358.html
標籤:其他
上一篇:如何給自己的公司做一個網站
