Spring注解驱动开发 第十二节 @Autowired 实验
首先先上代码。
@Service
public class BookService {
public BookDao getBookDao() {
return bookDao;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
@Autowired
private BookDao bookDao;
@Override
public String toString() {
return "BookService{" +
"bookDao=" + bookDao +
'}';
}
}
@Configuration
@ComponentScan({"com.meng.service","com.meng.dao","com.meng.controller"})
public class MainConfig6 {
}
配置类,扫描了三个包上标注注解的类全部注入到spring IOC容器。
上面的代码是BookService代码。
@Repository
public class BookDao {
}
上面的代码是BookDao代码,而在BookService中需要用到BookDao的代码,所以需要在BookService中声明BookDao的属性,然后在属性上标注@Autowired注解,最后BookDao就从spring容器中取出,然后赋值给这个属性,这就是spring的DI依赖注入。我们写一个注释类,测试一下在BookService用@Autowired获取的对象与getBean获取的对象是否是同一个对象。
@Test
public void test01(){
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig6.class);
BookService bookService = context.getBean(BookService.class);
BookDao bookDao = context.getBean(BookDao.class);
System.out.println(bookService);
System.out.println(bookDao);
}
查看打印结果:
四月 24, 2019 11:47:42 上午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2f410acf: startup date [Wed Apr 24 11:47:42 GMT+08:00 2019]; root of context hierarchy
BookService{bookDao=com.meng.dao.BookDao@17c1bced}
com.meng.dao.BookDao@17c1bced
Process finished with exit code 0
看的出来,它们获取的是同一个对象。
@Autowirte的作用是这样的,首先它先在spring容器中按类型匹配对应的类,匹配到了,就赋值给属性,它类似于applicationContext.getBean(Class)按类型获取容器中组件的方式
如果在容器中找到多个同类型的组件,就按照属性的名称作为组件的id进行查找。类似于applicationContext.getBean(String) 根据id名称获取容器中的组件。
下面我们做一个小实验验证它。
@Configuration
@ComponentScan({"com.meng.service","com.meng.dao","com.meng.controller"})
public class MainConfig6 {
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLabel("2");
return bookDao;
}
}
我们首先在配置类中不仅扫描一个BookDao,而且在配置类中也注入了一个,这样spring容器就有两个同类型组件。当然我还做了一个标注,如果label属性是2就是bookDao2,如果是1就是bookDao。
@Repository
public class BookDao {
private String label = "1";
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
@Override
public String toString() {
return "BookDao{" +
"label='" + label + '\'' +
'}';
}
}
上面是BookDao类
@Service
public class BookService {
public BookDao getBookDao() {
return bookDao;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
@Autowired
private BookDao bookDao;
@Override
public String toString() {
return "BookService{" +
"bookDao=" + bookDao +
'}';
}
}
上面是BookService类
@Test
public void test01(){
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig6.class);
BookService bookService = context.getBean(BookService.class);
System.out.println(bookService);
//BookDao bookDao = context.getBean(BookDao.class);
//System.out.println(bookDao);
((AnnotationConfigApplicationContext) context).close();
}
主方法,然后运行,查看打印结果:
四月 24, 2019 2:11:06 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2f410acf: startup date [Wed Apr 24 14:11:06 GMT+08:00 2019]; root of context hierarchy
四月 24, 2019 2:11:06 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
BookService{bookDao=BookDao{label='1'}}
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@2f410acf: startup date [Wed Apr 24 14:11:06 GMT+08:00 2019]; root of context hierarchy
Process finished with exit code 0
结果打印的label属性是1,让我们来分析一下为什么是1,首先可以确定,现在的容器中有两个同类型的组件,一个label属性是1,一个是2,因为@Autowired默认首先按照类型查找,但是容器里又不止一个,所以就要开始按照名称查找,而BookService属性的名称正好是bookDao,所以属性label为1的被获取出来,下面我们试着修改BookService属性的名称,看看bookDao2是否可以被获取到。
@Service
public class BookService {
public BookDao getBookDao() {
return bookDao2;
}
public void setBookDao(BookDao bookDao2) {
this.bookDao2 = bookDao2;
}
@Autowired
private BookDao bookDao2;
@Override
public String toString() {
return "BookService{" +
"bookDao=" + bookDao2 +
'}';
}
}
查看打印结果:
四月 24, 2019 2:17:05 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2f410acf: startup date [Wed Apr 24 14:17:05 GMT+08:00 2019]; root of context hierarchy
BookService{bookDao=BookDao{label='2'}}
四月 24, 2019 2:17:06 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@2f410acf: startup date [Wed Apr 24 14:17:05 GMT+08:00 2019]; root of context hierarchy
Process finished with exit code 0
看到label属性已经是2。
总结:@Autowired属性首先按照类型从spring容器中获取组件,如果spring容器中有多个组件,他会自动按照属性名称当作组件的id去spring容器查找相关的组件。
我们还可以专门指定属性被spring容器的那个组件赋值,不受属性名称的控制。
@Service
public class BookService {
public BookDao getBookDao() {
return bookDao2;
}
public void setBookDao(BookDao bookDao2) {
this.bookDao2 = bookDao2;
}
@Autowired
@Qualifier("bookDao")
private BookDao bookDao2;
@Override
public String toString() {
return "BookService{" +
"bookDao=" + bookDao2 +
'}';
}
}
使用@Qualifier(“指定组件的id”),使用这个注解就可以不受属性名称的控制,直接指定spring容器中的组件id,并装配到属性上。查看打印结果:
四月 24, 2019 2:24:17 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2f410acf: startup date [Wed Apr 24 14:24:17 GMT+08:00 2019]; root of context hierarchy
四月 24, 2019 2:24:17 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@2f410acf: startup date [Wed Apr 24 14:24:17 GMT+08:00 2019]; root of context hierarchy
BookService{bookDao=BookDao{label='1'}}
Process finished with exit code 0
可以看出,我们的属性名虽然是bookDao2,但是装配的却是bookDao。
如果容器中没有bookDao组件,会出现什么问题。
//@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLabel("2");
return bookDao;
}
//@Repository
public class BookDao {
private String label = "1";
public String getLabel() {
return label;
}
我们注掉了部分注解,现在spring容器应该是没有对应的组件的,现在我们来试一下,看看会不会报错。
四月 24, 2019 3:10:03 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2f410acf: startup date [Wed Apr 24 15:10:03 GMT+08:00 2019]; root of context hierarchy
四月 24, 2019 3:10:03 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bookService': Unsatisfied dependency expressed through field 'bookDao2'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.meng.dao.BookDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=bookDao)}
果然抛异常了,是一个NoSuchBeanDefinitionException异常,表示我们spring容器没有这个组件,所以装配不上,要怎样才能在spring没有对应组件的情况下装配,而且还不报错。
@Autowired(required=false)
@Qualifier("bookDao")
private BookDao bookDao2;
@Override
public String toString() {
return "BookService{" +
"bookDao=" + bookDao2 +
'}';
}
@Autowired注解有一个属性是required,表示是否必须,如果spring容器有对应组件就装配,没有就返回null,不报错,我们运行一下,查看一下打印结果:
四月 24, 2019 3:16:17 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2f410acf: startup date [Wed Apr 24 15:16:17 GMT+08:00 2019]; root of context hierarchy
BookService{bookDao=null}
四月 24, 2019 3:16:18 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@2f410acf: startup date [Wed Apr 24 15:16:17 GMT+08:00 2019]; root of context hierarchy
Process finished with exit code 0
可以看出不报错了,只是没有进行装配。
我们还有一种装配方式,注解@Primary,表示首选自动状态,但是有一点,只要@Primary指定后@Qualifier就不能用了,他表示明确指定。
@Configuration
@ComponentScan({"com.meng.service","com.meng.dao","com.meng.controller"})
public class MainConfig6 {
@Primary
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLabel("2");
return bookDao;
}
}
上面的代码就表示我们注入的bookDao在spring容器中有两个这样的类型,@Primary表示首选装配这个注解下的组件,我们查看一下打印结果:
四月 24, 2019 3:28:15 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2f410acf: startup date [Wed Apr 24 15:28:15 GMT+08:00 2019]; root of context hierarchy
四月 24, 2019 3:28:15 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@2f410acf: startup date [Wed Apr 24 15:28:15 GMT+08:00 2019]; root of context hierarchy
BookService{bookDao=BookDao{label='2'}}
Process finished with exit code 0
可以看出,我们配置的首选装配起效果了。