1. 程式人生 > 其它 >@autowired 報錯如何解決_Spring IOC的@Autowired、@Resource、@Inject的區別

@autowired 報錯如何解決_Spring IOC的@Autowired、@Resource、@Inject的區別

技術標籤:@autowired 報錯如何解決

使用過Spring的同學相信對這幾個註解都已經很熟悉了,這幾個都是用來做依賴注入的,我們挨個的來看一下。

@Autowired

@Autowired可以加在建構函式、方法、方法引數、成員變數、註解上,從Spring4.3開始,如果bean只有一個建構函式,或者只有一個primary/default的建構函式,那麼建構函式上的@Autowired可以不加,否則還是要在某個建構函式上明確的加上@Autowired。

原始碼如下:

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

舉個例子:

public class MovieRecommender {
    private final CustomerPreferenceDao customerPreferenceDao;
    //成員變數注入
    @Autowired
    private MovieCatalog movieCatalog;
    //建構函式注入,此處可以省略
    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
      this.customerPreferenceDao = customerPreferenceDao;
    }
}

還可以注入到集合裡面:

public class MovieRecommender {
  //所有的MovieCatalog型別的bean
  @Autowired
  private MovieCatalog[] movieCatalogs;
  }
}

當然也可以注入到map:

public class MovieRecommender {
    //map的key是bean的名字,value是bean:
    private Map<String, MovieCatalog> movieCatalogs;
    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
      this.movieCatalogs = movieCatalogs;
    }
}

預設@Autowired是不允許為空的,如果允許為空可以設定@Autowired(required = false):

public class SimpleMovieLister {
    private MovieFinder movieFinder;
    //此時,MovieFinder 這個bean可以不存在
    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
      this.movieFinder = movieFinder;
    }
}

此外還可使用java8裡面提供的java.util.Optional:

public class SimpleMovieLister {
    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
    }
}

在Spring5.0中還可以使用JSR305的javax.annotation.Nullable:

public class SimpleMovieLister {
    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
    }
}

此外,可以用@Autowired直接注入BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher, and MessageSource,還有他們的擴充套件介面:ConfigurableApplicationContext、ResourcePatternResolver。

需要注意的是:@Autowired, @Inject, @Value, 和 @Resource 都是由Spring的BeanPostProcessor來處理的(其中@Autowired和@Value是由AutowiredAnnotationBeanPostProcessor來處理的),因此,在你自己定義的BeanPostProcessor或者BeanFactoryPostProcessor裡面是不可以使用這些註解的,要麼使用xml要麼使用@Bean。

那麼建構函式注入與setter方法注入該如何選擇呢?

一般來說,如果不能為空必須要注入的,使用建構函式注入,允許為空可以不注入的則可以選擇setter方法注入。

@Primary

當多個候選bean都滿足注入條件的時候,@Primary標記的bean的優先順序更高,比如:

@Configuration
public class DemoApp {
  @Bean
  public IService service1(){
    return new Service1();
  }
  @Bean
  public IService service2(){
    return new Service2();
  }
  @Bean
  public Service3 service3(IService iService){
    return new Service3(iService);
  }
}

Service3在注入IService的時候就會報錯,因為容器中有2個IService:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'com.github.xjs.iocdemo.injectanno.IService' available:
expected single matching bean but found 2: service1,service2

可以在service1()或者service2()任意一個上新增@Primary得以解決,如下:

@Bean
@Primary
public IService service2(){
  return new Service2();
}

@Qualifier

可以用一個識別符號在多個候選的bean中選擇注入的bean,預設的識別符號是bean的名字。

因此前一個例子也可以這樣來解決:

public Service3 service3(@Qualifier("service2") IService iService){
    return new Service3(iService);
}

需要說明的是,@Qualifier識別符號不需要全域性唯一,因為Autowired主要還是按照型別來進行注入的,如果多個Qualifier都滿足,還是可以繼續使用@Primary之類的註解的,並且這多個Qualifier還可以一塊注入到集合中:

@Configuration
@ComponentScan
public class DemoApp {
  @Bean
  @Qualifier("s1")
  public IService service1(){
    return new Service1();
  }
  @Bean
  @Qualifier("s1")
  @Primary
  public IService service2(){
    return new Service2();
  }
  @Bean
  public Service3 service3(@Qualifier("s1") IService iService){
    return new Service3(iService);
  }
}
@Service
public class Service4 {
  @Autowired
  @Qualifier("s1")
  private Set<IService> serviceSet;
  }
}

還可以把泛型型別當成是一種限定符:

public interface IService<T> {
}
public class IntegerService implements IService<Integer> {
}
public class StringService implements IService<String> {
}
@Component
public class Service6 {
  /**這裡注入的是StringService*/
  @Autowired
  private IService<String> s1;
  /**這裡注入的是IntegerService*/
  @Autowired
  private IService<Integer> s2;
  public IService<String> getS1(){
    return s1;
  }
  public IService<Integer> getS2(){
    return s2;
  }
}

@Resource

JSR250註解,它是優先按照bean的名字進行注入,由CommonAnnotationBeanPostProcessor進行處理。

@Service
public class Service5 {
  /**這是注入容器中名字叫service1的bean*/
  @Resource
  private IService service1;
  /**這是注入容器中名字叫service2的bean*/
  @Resource
  private IService service2;
  /**這是注入容器中名字叫service3的bean,顯然service3的型別並不是IService,因此注入失敗*/
  //@Resource
  //private IService service3;
  /**這是注入容器中名字叫service33的bean,因為容器中根本就沒有service33這個bean,因此退化成按型別注入,注入成功*/
  @Resource
  private IService service33;
  public IService getIService1(){
    return service1;
  }
  public IService getIService2(){
    return service2;
  }
}

上面的例子中,service3的注入會失敗,因為名字叫service3的bean它的型別並不是IService,為啥service33的注入會成功呢?因為容器中根本就沒有名字叫service33的bean,因此退化成按型別去注入,因此得以注入成功。

@Inject

JSR 330定義的註解,基本跟@Autowired是一樣的。

@Component
public class SimpleMovieLister {
  private Provider<MovieFinder> movieFinder;
  //允許為空,也可以用Optional<MovieFinder> 和 @Nullable
  @Inject
  public void setMovieFinder(Provider<MovieFinder> movieFinder) {
    this.movieFinder = movieFinder;
  }
  public void listMovies() {
  this.movieFinder.get().findMovies(...);
    // ...
  }
}

此外,還可以用@Required做依賴注入,一般是用來標記set方法,注入的依賴必須不能為空,但是在Spring5.1已經被標記為過時,因為更推薦使用建構函式來注入。

總結一下

1. @Autowired是先按照型別去挑選候選的bean,然後根據qualifier在這些候選的bean中去挑選要注入的bean。

2. @Resource是先按照名稱去挑選要注入bean,如果找不到則退化成按照型別去注入。

3. @Inject和@Autowired功能基本差不多,是由JSR330定義的。

測試程式碼下載:https://github.com/xjs1919/enumdemo下面的ioc-demo。

掃碼關注公眾號檢視更多文章:

a26dbe6dfa677379d846291f53d1551a.png