目录
DI介绍
学习了IoC后, 什么是DI呢?
IoC是一种思想,DI是一种实现方式
DI: Dependency Injection(依赖注⼊)
容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。
上述代码中, 是通过构造函数的⽅式, 把依赖对象注⼊到需要使⽤的对象中的
IoC 是⼀种思想,也是"⽬标", ⽽思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽ DI 就属于具体的实现。所以也可以说, DI 是IoC的⼀种实现.
IoC & DI 使⽤
对IoC和DI有了初步的了解, 我们接下来具体学习Spring IoC和DI的代码实现.
依然是先使⽤, 再学习
⽬标: 把BookDao, BookService 交给Spring管理, 完成Controller层, Service层, Dao层的解耦
步骤:
- Service层及Dao层的实现类,交给Spring管理: 使⽤注解:
@Component
- 在Controller层和Service层注⼊运⾏时依赖的对象: 使⽤注解
@Autowired
实现:
- 把 BookDao 交给Spring管理, 由Spring来管理对象
@Component
public class BookDao {
//mock - 虚拟数据,假数据
public List<BookInfo> mockData() {
//对于已知的数据量或者大概知道这个集合的数量时,创建List时建议指定初始化容量
List<BookInfo> bookInfos=new ArrayList<>(15);
for(int i=0;i<15;i++){
BookInfo bookInfo=new BookInfo();
bookInfo.setId(i);
bookInfo.setBookName("图书"+i);
bookInfo.setAuthor("作者"+i);
bookInfo.setCount(new Random().nextInt(200));
bookInfo.setPrice(new BigDecimal(new Random().nextInt(100)));
bookInfo.setPublish("出版社"+i);
bookInfo.setStatus(i%5==0?2:1);
bookInfos.add(bookInfo);
}
return bookInfos;
}
}
- 把 BookService 交给Spring管理, 由Spring来管理对象
//通过注解告诉Spring帮我们把BookService存入容器中
@Component
public class BookService {
private BookDao bookDao = new BookDao();
public List<BookInfo> getBookList(){
//1.获取图书数据
List<BookInfo> bookInfos=bookDao.mockData();
//2.对图书数据进行修改处理
for(BookInfo bookInfo:bookInfos){
if(bookInfo.getStatus()==1){
bookInfo.setStatusCN("可借阅");
}else{
bookInfo.setStatusCN("不可借阅");
}
}
return bookInfos;
}
}
- 删除创建BookDao的代码, 从Spring中获取对象
//通过注解告诉Spring帮我们把BookService存入容器中
@Component
public class BookService {
@Autowired
private BookDao bookDao;
public List<BookInfo> getBookList(){
//1.获取图书数据
List<BookInfo> bookInfos=bookDao.mockData();
//2.对图书数据进行修改处理
for(BookInfo bookInfo:bookInfos){
if(bookInfo.getStatus()==1){
bookInfo.setStatusCN("可借阅");
}else{
bookInfo.setStatusCN("不可借阅");
}
}
return bookInfos;
}
}
- 删除创建BookService的代码, 从Spring中获取对象
@RestController
@RequestMapping("/book")
public class BookController {
@Autowired//告诉Spring,从容器中取出这个对象,赋值给当前对象的属性
private BookService bookService;
//等价于
//private BookService bookService
//public BookController() {
// this.bookService = new BookService();
//}
@RequestMapping("/getBookList")
public List<BookInfo> getBookList(){
List<BookInfo> bookInfos = bookService.getBookList();
//3.返回数据
return bookInfos;
}
}
- 重新运⾏程序,
http://127.0.0.1:8080/book_list.html
结果是一样的
IoC详解
通过上⾯的案例, 我们已经知道了Spring IoC 和DI的基本操作, 接下来我们来系统的学习Spring IoC和DI的操作.
前⾯我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。
也就是bean的存储.
Spring是一个容器,存的是对象,对象这个词,在Spring的范围内,称之为bean
Bean的存储
在之前的⼊⻔案例中,要把某个对象交给IOC容器管理,需要在类上添加⼀个注解: @Component
⽽Spring框架为了更好的服务web应⽤程序, 提供了更丰富的注解.
共有两类注解类型可以实现:
-
类注解:
@Controller、@Service、@Repository、@Component、@Configuration
。 -
⽅法注解:
@Bean
接下来我们分别来看
@Controller(控制器存储)
控制层用这个
使⽤ @Controller
存储 bean 的代码如下所⽰:
@Controller//将对象存储到 Spring 中
public class UserController {
public void doController(){
System.out.println("doController...");
}
}
如何观察这个对象已经存在Spring容器当中了呢?
接下来我们学习如何从Spring容器中获取对象
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//Spring上下文,返回的就是Spring的运行环境
ApplicationContext context=SpringApplication.run(DemoApplication.class, args);
//从Spring上下⽂中获取对象
UserController bean = context.getBean(UserController.class);
//使用对象
bean.doController();
}
}
观察运⾏结果, 发现成功从Spring中获取到Controller对象, 并执⾏Controller的doController⽅法
如果把@Controller
删掉, 再观察运⾏结果
报错信息显⽰: 找不到类型是: com.example.demo.controller.UserController 的 bean
获取bean对象的其他⽅式
上述代码是根据类型来查找对象, 如果Spring容器中, 同⼀个类型存在多个bean的话, 怎么来获取呢?
ApplicationContext
也提供了其他获取bean的⽅式, ApplicationContext
获取bean对象的功能, 是⽗类 BeanFactory
提供的功能.
public interface BeanFactory {
//以上省略...
// 1. 根据bean名称获取bean
Object getBean(String var1) throws BeansException;
// 2. 根据bean名称和类型获取bean
<T> T getBean(String var1, Class<T> var2) throws BeansException;
// 3. 按bean名称和构造函数参数动态创建bean,只适⽤于具有原型(prototype)作⽤域的bean
Object getBean(String var1, Object... var2) throws BeansException;
// 4. 根据类型获取bean
<T> T getBean(Class<T> var1) throws BeansException;
// 5. 按bean类型和构造函数参数动态创建bean, 只适⽤于具有原型(prototype)作⽤域的bean
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
//以下省略...
}
常⽤的是上述1,2,4种, 这三种⽅式,获取到的bean是⼀样的
其中1,2种都涉及到根据名称来获取对象. bean的名称是什么呢?
Bean 命名约定
程序开发⼈员不需要为bean指定名称(BeanId), 如果没有显式的提供名称(BeanId),Spring容器将为该bean⽣成唯⼀的名称.
命名约定使⽤Java标准约定作为实例字段名. 也就是说,bean名称以⼩写字⺟开头,然后使⽤驼峰式⼤⼩写.
也有⼀些特殊情况, 当有多个字符并且第⼀个和第⼆个字符都是⼤写时, 将保留原始的⼤⼩写. 这些规则与java.beans.Introspector.decapitalize (Spring在这⾥使⽤的)定义的规则相同.
@Service(服务存储)
业务逻辑层用这个
使⽤ @Service
存储 bean 的代码如下所⽰:
@Service
public class UserService {
public void doService(){
System.out.println("doService...");
}
}
读取 bean 的代码:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//Spring上下文,返回的就是Spring的运行环境
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下⽂中获取对象
UserController bean = context.getBean(UserController.class);
//使用对象
bean.doController();
UserService userService = context.getBean(UserService.class);
userService.doService();
}
}
观察运⾏结果, 发现成功从Spring中获取到UserService对象, 并执⾏UserService的doService⽅法
同样的, 把注解@Service
删掉, 再观察运⾏结果,也是会有一样的异常找不到bean
@Repository(仓库存储)
数据访问层用这个
使⽤ @Repository
存储 bean 的代码如下所⽰:
@Repository
public class UserRepository {
public void doRepository(){
System.out.println("doRepository...");
}
}
读取 bean 的代码:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//Spring上下文,返回的就是Spring的运行环境
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下⽂中获取对象
UserController bean = context.getBean(UserController.class);
//使用对象
bean.doController();
UserService userService = context.getBean(UserService.class);
userService.doService();
//根据名称获取bean
UserService userService2 = (UserService)context.getBean("userService");
userService2.doService();
//根据名称和类型获取bean
UserService userService3 = context.getBean("userService", UserService.class);
userService3.doService();
UserRepository userRepository = context.getBean(UserRepository.class);
userRepository.doRepository();
}
}
观察运⾏结果, 发现成功从Spring中获取到UserRepository 对象, 并执⾏UserRepository 的doRepository⽅法
同样的, 把注解@Repository
删掉, 再观察运⾏结果,也是会有一样的异常找不到bean
@Component(组件存储)
用于除了控制层、业务逻辑层、数据访问层的代码
使⽤ @Component
存储 bean 的代码如下所⽰:
@Component
public class UserComponent {
public void doComponent(){
System.out.println("doComponent...");
}
}
读取 bean 的代码:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//Spring上下文,返回的就是Spring的运行环境
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下⽂中获取对象
UserController bean = context.getBean(UserController.class);
//使用对象
bean.doController();
UserService userService = context.getBean(UserService.class);
userService.doService();
//根据名称获取bean
UserService userService2 = (UserService)context.getBean("userService");
userService2.doService();
//根据名称和类型获取bean
UserService userService3 = context.getBean("userService", UserService.class);
userService3.doService();
UserRepository userRepository = context.getBean(UserRepository.class);
userRepository.doRepository();
UserComponent userComponent=context.getBean(UserComponent.class);
userComponent.doComponent();
}
}
观察运⾏结果, 发现成功从Spring中获取到UserComponent 对象, 并执⾏UserComponent 的doComponent⽅法
同样的, 把注解@Component
删掉, 再观察运⾏结果,也是会有一样的异常找不到bean
@Configuration(配置存储)
用于大型项目一些Spring以外的配置
使⽤ @Configuration
存储 bean 的代码如下所⽰:
@Configuration
public class UserConfig {
public void doConfig(){
System.out.println("doConfig...");
}
}
读取 bean 的代码:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//Spring上下文,返回的就是Spring的运行环境
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下⽂中获取对象
UserController bean = context.getBean(UserController.class);
//使用对象
bean.doController();
UserService userService = context.getBean(UserService.class);
userService.doService();
//根据名称获取bean
UserService userService2 = (UserService)context.getBean("userService");
userService2.doService();
//根据名称和类型获取bean
UserService userService3 = context.getBean("userService", UserService.class);
userService3.doService();
UserRepository userRepository = context.getBean(UserRepository.class);
userRepository.doRepository();
UserComponent userComponent=context.getBean(UserComponent.class);
userComponent.doComponent();
UserConfig userConfig=context.getBean(UserConfig.class);
userConfig.doConfig();
}
}
观察运⾏结果, 发现成功从Spring中获取到UserConfiguration 对象, 并执⾏UserConfiguration 的doConfig⽅法
同样的, 把注解@Configuration
删掉, 再观察运⾏结果,也是会有一样的异常找不到bean
为什么要这么多类注解?
这个也是和咱们前⾯讲的应⽤分层是呼应的. 让程序员看到类注解之后,就能直接了解当前类的⽤途.
@Controller
:控制层, 接收请求, 对请求进⾏处理, 并进⾏响应.@ServiCE
:业务逻辑层, 处理具体的业务逻辑.@Repository
:数据访问层,也称为持久层. 负责数据访问操作@Configuration
:配置层. 处理项⽬中的⼀些配置信息.
程序的应⽤分层,调⽤流程如下:
类注解之间的关系
查看 @Controller
/ @Service
/ @Repository
/ @Configuration
等注解的源码发现:
其实这些注解⾥⾯都有⼀个注解@Component
,说明它们本⾝就是属于 @Component
的"⼦类".
@Component
是⼀个元注解,也就是说可以注解其他类注解,如 @Controller
, @Service
,@Repository
等. 这些注解被称为 @Component
的衍⽣注解.
Controller
是被赋予其他功能的,想被外界访问到,只能用这个Controller
,想要接受请求就只能用这个Controller
,也就是说这个Controller
必须作为程序的第一关
基本大家程序入口都是Controller
@Controller
, @Service
和 @Repository
⽤于更具体的⽤例(分别在控制层, 业务逻辑层, 持久化层), 在开发过程中, 如果你要在业务逻辑层使⽤ @Component
或@Service
,显然@Service
是更好的选择
RequestMapping()
是SpringMVC的一个注解
⽅法注解 @Bean
类注解是添加到某个类上的,但是存在两个问题:
- 使⽤外部包⾥的类, 没办法添加类注解
- ⼀个类, 需要多个对象, ⽐如多个数据源
这种场景, 我们就需要使⽤⽅法注解 @Bean
我们先来看看⽅法注解如何使⽤:
@Data
public class UserInfo {
private Integer id;
private String name;
private Integer age;
}
public class BeanConfig {
@Bean
public UserInfo userInfo(){
UserInfo userInfo=new UserInfo();
userInfo.setId(1);
userInfo.setName("zhangsan");
userInfo.setAge(12);
return userInfo;
}
}
然⽽,当我们写完以上代码,尝试获取 bean 对象中的 user 时却发现,根本获取不到:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//Spring上下文,返回的就是Spring的运行环境
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//@Bean演示
UserInfo userInfo=(UserInfo) context.getBean(UserInfo.class);
System.out.println(userInfo);
}
}
以上程序的执⾏结果如下:
这是为什么呢?
⽅法注解要配合类注解使⽤ & 定义多个对象
在 Spring 框架的设计中,⽅法注解 @Bean
要配合类注解才能将对象正常的存储到 Spring 容器中,
对于同⼀个类, 如何定义多个对象呢?
如下代码所⽰:
@Configuration
public class BeanConfig {
@Bean
public UserInfo userInfo(){
UserInfo userInfo=new UserInfo();
userInfo.setId(1);
userInfo.setName("zhangsan");
userInfo.setAge(12);
return userInfo;
}
@Bean
public UserInfo userInfo2(){
UserInfo userInfo=new UserInfo();
userInfo.setId(2);
userInfo.setName("lisi");
userInfo.setAge(13);
return userInfo;
}
}
定义了多个对象的话, 我们根据类型获取对象, 获取的是哪个对象呢?
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//Spring上下文,返回的就是Spring的运行环境
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//@Bean演示
UserInfo userInfo=(UserInfo) context.getBean(UserInfo.class);
System.out.println(userInfo);
}
}
运⾏结果:
报错信息显⽰: 期望只有⼀个匹配, 结果发现了两个, userInfo,userInfo2
从报错信息中, 可以看出来, @Bean
注解的bean, bean的名称就是它的⽅法名
一个类型,存在多个bean时,我们就不能使用类型(.class
)来获取对象了
接下来我们根据名称(也就是方法名)来获取bean对象
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//Spring上下文,返回的就是Spring的运行环境
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//@Bean演示
UserInfo userInfo=(UserInfo) context.getBean("userInfo");
System.out.println(userInfo);
UserInfo userInfo2=(UserInfo) context.getBean("userInfo2");
System.out.println(userInfo2);
}
}
运⾏结果:
可以看到, @Bean
可以针对同⼀个类, 定义多个对象。
重命名 Bean
可以通过设置 name 属性给 Bean 对象进⾏重命名操作,如下代码所⽰:这两个都是它的别名
@Bean(name = {"u1","user1"})
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
此时我们使⽤ u1 就可以获取到 User 对象了,如下代码所⽰:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio
//从Spring上下⽂中获取对象
User u1 = (User) context.getBean("u1");
//使⽤对象
System.out.println(u1);
}
}
扫描路径
Q: 使⽤前⾯学习的四个注解声明的bean,⼀定会⽣效吗?
A: 不⼀定(原因:bean想要⽣效,还需要被Spring扫描)
下⾯我们通过修改项⽬⼯程的⽬录结构,来测试bean对象是否⽣效:
再运⾏代码:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio
//从Spring上下⽂中获取对象
User u1 = (User) context.getBean("u1");
//使⽤对象
System.out.println(u1);
}
}
运⾏结果:
解释: 没有bean的名称为u1
为什么没有找到bean对象呢?
使⽤五⼤注解声明的bean,要想⽣效, 还需要配置扫描路径, 让Spring扫描到这些注解
也就是通过 @ComponentScan
来配置扫描路径.
@ComponentScan({"com.example.demo"})
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio
//从Spring上下⽂中获取对象
User u1 = (User) context.getBean("u1");
//使⽤对象
System.out.println(u1);
}
}
那为什么前⾯没有配置 @ComponentScan
注解也可以呢?
@ComponentScan
注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication
中了
SpringBoot特点:约定大于配置
其中之一体现:扫描路径
默认的扫描路径:启动类所在的目录及其子孙目录(这句话和下面这句是一样的)
默认扫描的范围是SpringBoot启动类所在包及其⼦包
推荐做法:
把启动类放在我们希望扫描的包的路径下, 这样我们定义的bean就都可以被扫描到
DI详解
上⾯讲了控制反转IoC的细节,接下来我们学习依赖注⼊DI的细节。
依赖注⼊是⼀个过程,是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源,⽽资源指的就是对象.
在上⾯程序案例中,我们使⽤了 @Autowired
这个注解,完成了依赖注⼊的操作.
简单来说, 就是把对象取出来放到某个类的属性中.
关于依赖注⼊, Spring也给我们提供了三种⽅式:
- 属性注⼊(Field Injection)
- 构造⽅法注⼊(Constructor Injection)
- Setter 注⼊(Setter Injection)
接下来,我们分别来看。
下⾯我们按照实际开发中的模式,将 Service 类注⼊到 Controller 类中。
1. 属性注⼊
属性注入是以类型进行匹配,与注入的属性名称无关
但是一个类型如果存在多个对象时,优先名称匹配,如果名称都匹配不上,就报错了
而且@Autowired
无法注入一个final
修饰的属性
属性注⼊是使⽤ @Autowired
实现的,将 Service 类注⼊到 Controller 类中
Service 类的实现代码如下:
@Service
public class UserService {
public void doService(){
System.out.println("doService...");
}
}
Controller 类的实现代码如下:
@Controller//将对象存储到 Spring 中
public class UserController {
@Autowired
private UserService userService;
public void doController(){
userService.doService();
System.out.println("doController...");
}
}
获取 Controller 中的 doController⽅法:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//Spring上下文,返回的就是Spring的运行环境
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下⽂中获取对象
UserController bean = context.getBean(UserController.class);
//使用对象
bean.doController();
}
}
最终结果如下:
去掉@Autowired
, 再运⾏⼀下程序看看结果
2. 构造⽅法注⼊
构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所⽰:
@Controller//将对象存储到 Spring 中
public class UserController {
//属性注入
//@Autowired
//private UserService userService;
//构造方法注入
private UserService userService;
private UserInfo userInfo;
//Spring会默认优先使用无参构造函数,这样就没有userService对象,下面doController()方法就空指针异常了
//public UserController() {
//}
//注释了无参构造,下面两个构造函数Spring就不知道用哪个了,因为Spring是默认优先使用无参的
//因此我们要显式告诉Spring用哪个,这里就用到@Autowired
@Autowired
public UserController(UserService userService) {
this.userService=userService;
}
public UserController(UserService userService, UserInfo userInfo) {
this.userService = userService;
this.userInfo = userInfo;
}
public void doController(){
userService.doService();
System.out.println("doController...");
}
}
Spring会默认优先使用无参构造函数,这样就没有userService对象,下面doController()方法中的userService.doService()
就空指针异常了
注意事项:如果类只有⼀个构造⽅法,那么 @Autowired
注解可以省略;如果类中有多个构造⽅法,那么需要添加上 @Autowired
来明确指定到底使⽤哪个构造⽅法,不然Spring不知道用哪个构造方法。
3. Setter 注⼊
Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 Setter ⽅法的时候需要加上 @Autowired
注解 ,它不像是构造方法只有一个的话会默认帮你加上,如下代码所⽰:
@Controller//将对象存储到 Spring 中
public class UserController {
//属性注入
//@Autowired
//private UserService userService;
//构造方法注入
//private UserService userService;
//private UserInfo userInfo;
//Spring会默认优先使用无参构造函数,这样就没有userService对象,下面doController()方法就空指针异常了
//public UserController() {
//}
//注释了无参构造,下面两个构造函数Spring就不知道用哪个了,因为Spring是默认优先使用无参的
//因此我们要显式告诉Spring用哪个,这里就用到@Autowired
//@Autowired
//public UserController(UserService userService) {
// this.userService=userService;
//}
//public UserController(UserService userService, UserInfo userInfo) {
// this.userService = userService;
// this.userInfo = userInfo;
//}
//Setter方法注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void doController(){
userService.doService();
System.out.println("doController...");
}
}
尝试⼀下 Setter ⽅法如果不加 @Autowired 注解能注⼊成功吗?
肯定不行
三种注⼊优缺点分析
-
属性注⼊
-
优点: 简洁,使⽤⽅便;
-
缺点:
-
只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指针异常)
-
不能注⼊⼀个Final修饰的属性
-
-
-
构造函数注⼊(Spring 4.X推荐)
-
优点:
-
可以注⼊final修饰的属性
-
注⼊的对象不会被修改
-
依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法是在类加载阶段就会执⾏的⽅法.
-
通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的
-
-
缺点:
- 注⼊多个对象时, 代码会⽐较繁琐
-
-
Setter注⼊(Spring 3.X推荐)
-
优点: ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊
-
缺点:
-
不能注⼊⼀个Final修饰的属性
-
注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险.
-
更多DI相关内容参考:https://docs.spring.io/spring-framework/reference/core/beans/dependencies.html
@Autowired存在问题
当同⼀类型存在多个bean时, 使⽤@Autowired
会存在问题
@Configuration
public class BeanConfig {
@Bean
public String name(){
return "zhangsan";
}
@Bean
public UserInfo userInfo1(String name){
UserInfo userInfo=new UserInfo();
userInfo.setId(1);
userInfo.setName(name);
userInfo.setAge(12);
return userInfo;
}
@Bean
public UserInfo userInfo2(){
UserInfo userInfo=new UserInfo();
userInfo.setId(2);
userInfo.setName("lisi");
userInfo.setAge(13);
return userInfo;
}
}
@Controller//将对象存储到 Spring 中
public class UserController {
//属性注入
@Autowired
private UserService userService;
@Autowired
private UserInfo userInfo;
public void doController(){
userService.doService();
System.out.println(userInfo);
System.out.println("doController...");
}
}
报错的原因是,⾮唯⼀的 Bean 对象。
如何解决上述问题呢?Spring提供了以下⼏种解决⽅案:(解决方法思想:给bean重命名或者指定名称)
-
属性名和你需要使用的对象名保持一致(不这样做就以下三种方法)
-
@Primary
-
@Qualifier
-
@Resource
**(不推荐)**使⽤@Primary
注解:当存在多个相同类型的Bean注⼊时,加上@Primary
注解,来确定默认的实现,标识默认的对象
@Configuration
public class BeanConfig {
@Bean
public String name(){
return "zhangsan";
}
@Primary
@Bean
public UserInfo userInfo1(){
UserInfo userInfo=new UserInfo();
userInfo.setId(1);
userInfo.setName("zhangsan");
userInfo.setAge(12);
return userInfo;
}
@Bean
public UserInfo userInfo2(){
UserInfo userInfo=new UserInfo();
userInfo.setId(2);
userInfo.setName("lisi");
userInfo.setAge(13);
return userInfo;
}
}
**(较常用)**使⽤@Qualifier
注解:指定当前要注⼊的bean对象。 在@Qualifier
的value属性中,指定注⼊的bean的名称。
@Qualifier
注解不能单独使⽤,必须配合@Autowired
使⽤
@Controller//将对象存储到 Spring 中
public class UserController {
//属性注入
@Autowired
private UserService userService;
@Qualifier("userInfo2")
@Autowired
private UserInfo userInfo;
public void doController(){
userService.doService();
System.out.println(userInfo);
System.out.println("doController...");
}
}
(较常用)使⽤@Resource
注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称。
@Controller//将对象存储到 Spring 中
public class UserController {
//属性注入
@Autowired
private UserService userService;
@Resource(name="userInfo2")
private UserInfo userInfo;
public void doController(){
userService.doService();
System.out.println(userInfo);
System.out.println("doController...");
}
}
练习
通过上⾯的学习, 我们把前⾯的图书管理系统代码进⾏调整
Service层的注解, 改成 @Service
Dao层的注解, 改成 @Repository
重新运⾏代码, 验证程序访问正常
总结
Spring, Spring Boot 和 Spring MVC 的关系以及区别
Spring: 简单来说, Spring 是⼀个开发应⽤框架,什么样的框架呢,有这么⼏个标签:轻量级、⼀站式、模块化,其⽬的是⽤于简化企业级应⽤程序开发
Spring MVC: Spring MVC 是 Spring 的⼀个⼦框架, Spring诞⽣之后, ⼤家觉得很好⽤, 于是按照MVC模式设计了⼀个 MVC框架(⼀些⽤Spring 解耦的组件), 主要⽤于开发WEB应⽤和⽹络接⼝,所以,Spring MVC 是⼀个Web框架
Spring Boot: Spring Boot 是对 Spring 的⼀个封装, 为了简化Spring应⽤的开发⽽出现的,中⼩型企业,没有成本研究⾃⼰的框架, 使⽤Spring Boot 可以更加快速的搭建框架, 降级开发成本, 让开发⼈员更加专注于Spring应⽤的开发,⽽⽆需过多关注XML的配置和⼀些底层的实现
最后⼀句话总结: Spring MVC 和 Spring Boot 都属于 Spring,Spring MVC 是基于 Spring 的⼀个MVC 框架,⽽Spring Boot 是基于 Spring 的⼀套快速开发整合包
这三者专注的领域不同,解决的问题也不⼀样, 总的来说,Spring 就像⼀个⼤家族,有众多衍⽣产品, 但他们的基础都是Spring, ⽤⼀张图来表⽰他们三个的关系:
bean的存是五大注解和@Bean,使用这六个的时候,Spring会给一个默认的名称,五大注解的名称是类名的小驼峰表示法,特殊情况,前两位字母都为大写,名称就是类名;@Bean的名称就是方法名
bean 的命名
添加之后之前的名称就不再有了,Spring会使用程序员定义的bean名称
- 五⼤注解存储的bean
① 前两位字⺟均为⼤写, bean名称为类名
② 其他的为类名⾸字⺟⼩写
③ 通过 value属性设置,比如 @Controller(value = "user")
- @Bean 注解存储的bean
① bean名称为⽅法名
②通过name属性设置 @Bean(name = {"u1","user1"})
③一个Bean可以有多个名称,但一个名称只能对应一个Bean
常⻅⾯试题
- 三种注⼊⽅式的优缺点
- 常⻅注解有哪些? 分别是什么作⽤?
web url映射: @RequestMapping
参数接收和接⼝响应: @RequestParam, @RequestBody, @ResponseBody
bean的存储: @Controller, @Service, @Repository, @Component, @Configuration, @Bean
bean的获取: 属性注入、构造方法注入、Setter方法注入
@Autowired
和@Resource
区别- 说下你对Spring, SpringMVC, Springboot的理解