目录
-
-
-
- DI介绍
- [IoC & DI 使⽤](#IoC & DI 使⽤)
- IoC详解
-
- Bean的存储
-
- @Controller(控制器存储)
- @Service(服务存储)
- @Repository(仓库存储)
- @Component(组件存储)
- @Configuration(配置存储)
- 为什么要这么多类注解?
- [⽅法注解 @Bean](#⽅法注解 @Bean)
- [⽅法注解要配合类注解使⽤ & 定义多个对象](#⽅法注解要配合类注解使⽤ & 定义多个对象)
- [重命名 Bean](#重命名 Bean)
- 扫描路径
- DI详解
-
- [1. 属性注⼊](#1. 属性注⼊)
- [2. 构造⽅法注⼊](#2. 构造⽅法注⼊)
- [3. Setter 注⼊](#3. Setter 注⼊)
- 三种注⼊优缺点分析
- @Autowired存在问题
- 练习
- 总结
-
- [Spring, Spring Boot 和 Spring MVC 的关系以及区别](#Spring, Spring Boot 和 Spring MVC 的关系以及区别)
- [bean 的命名](#bean 的命名)
- 常⻅⾯试题
-
-
DI介绍
学习了IoC后, 什么是DI呢?
IoC是一种思想,DI是一种实现方式
DI: Dependency Injection(依赖注⼊)
容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。
程序运⾏时需要某个资源,此时容器就为其提供这个资源.
从这点来看, 依赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过引⼊ IoC 容器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦。
上述代码中, 是通过构造函数的⽅式, 把依赖对象注⼊到需要使⽤的对象中的
IoC 是⼀种思想,也是"⽬标", ⽽思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽ DI 就属于具体的实现。所以也可以说, DI 是IoC的⼀种实现.
⽐如说我今天⼼情⽐较好,吃⼀顿好的犒劳犒劳⾃⼰,那么"吃⼀顿好的"是思想和⽬标(是IoC),但最后我是吃海底捞还是杨国福?这就是具体的实现,就是 DI。
IoC & DI 使⽤
对IoC和DI有了初步的了解, 我们接下来具体学习Spring IoC和DI的代码实现.
依然是先使⽤, 再学习
既然 Spring 是⼀个 IoC(控制反转)容器,作为容器, 那么它就具备两个最基础的功能:
存
取
Spring 容器 管理的主要是对象, 这些对象, 我们称之为"Bean". 我们把这些对象交由Spring管理, 由Spring来负责对象的创建和销毁. 我们程序只需要告诉Spring, 哪些需要存, 以及如何从Spring中取出对象
⽬标: 把BookDao, BookService 交给Spring管理, 完成Controller层, Service层, Dao层的解耦
步骤:
- Service层及Dao层的实现类,交给Spring管理: 使⽤注解:
@Component
- 在Controller层和Service层注⼊运⾏时依赖的对象: 使⽤注解
@Autowired
实现:
- 把 BookDao 交给Spring管理, 由Spring来管理对象
java
@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来管理对象
java
//通过注解告诉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中获取对象
java
//通过注解告诉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中获取对象
java
@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
结果是一样的
在
@RestController
的源码
java@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Controller @ResponseBody public @interface RestController { @AliasFor( annotation = Controller.class ) String value() default ""; }
再看
@Controller
的源码
java@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Controller { @AliasFor( annotation = Component.class ) String value() default ""; }
可以看到有
@Component
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
。这五个注解只能加在类上,并且只能加在自己的代码上。如果引入第三方的jar包,也想交给Spring管理,是没有办法加这五个注解的。对于一个类,定义多个对象时,比如数据库操作,定义多个数据源,如果在这五个注解下取对象,取多少次都是同一个对象
-
⽅法注解 :
@Bean
接下来我们分别来看
@Controller(控制器存储)
控制层用这个
使⽤ @Controller
存储 bean 的代码如下所⽰:
java
@Controller//将对象存储到 Spring 中
public class UserController {
public void doController(){
System.out.println("doController...");
}
}
如何观察这个对象已经存在Spring容器当中了呢?
接下来我们学习如何从Spring容器中获取对象
java
@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();
}
}
ApplicationContext 翻译过来就是: Spring 上下⽂因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下⽂
关于上下⽂的概念
上学时, 阅读理解经常会这样问: 根据上下⽂, 说⼀下你对XX的理解
在计算机领域, 上下⽂这个概念, 在学习线程时了解到过, ⽐如我们应⽤进⾏线程切换的时候,切换前都会把线程的状态信息暂时储存起来,这⾥的上下⽂就包括了当前线程的信息,等下次该线程⼜得到CPU时间的时候, 从上下⽂中拿到线程上次运⾏的信息
这个上下⽂, 就是指当前的运⾏环境, 也可以看作是⼀个容器, 容器⾥存了很多内容, 这些内容是当前运⾏的环境
观察运⾏结果, 发现成功从Spring中获取到Controller对象, 并执⾏Controller的doController⽅法
如果把@Controller
删掉, 再观察运⾏结果
报错信息显⽰: 找不到类型是: com.example.demo.controller.UserController 的 bean
获取bean对象的其他⽅式
上述代码是根据类型来查找对象, 如果Spring容器中, 同⼀个类型存在多个bean的话, 怎么来获取呢?
ApplicationContext
也提供了其他获取bean的⽅式, ApplicationContext
获取bean对象的功能, 是⽗类 BeanFactory
提供的功能.
java
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的名称是什么呢?
Spring bean是Spring框架在运⾏时管理的对象, Spring会给管理的对象起⼀个名字.
⽐如学校管理学⽣, 会给每个学⽣分配⼀个学号, 根据学号, 就可以找到对应的学⽣.
Spring也是如此, 给每个对象起⼀个名字, 根据Bean的名称(
BeanId
)就可以获取到对应的对象.
Bean 命名约定
程序开发⼈员不需要为bean指定名称(BeanId), 如果没有显式的提供名称(BeanId),Spring容器将为该bean⽣成唯⼀的名称.
命名约定使⽤Java标准约定作为实例字段名. 也就是说,bean名称以⼩写字⺟开头,然后使⽤驼峰式⼤⼩写.
⽐如
类名: UserController, Bean的名称为: userController
类名: AccountManager, Bean的名称为: accountManager
类名: AccountService, Bean的名称为: accountService
也有⼀些特殊情况, 当有多个字符并且第⼀个和第⼆个字符都是⼤写时, 将保留原始的⼤⼩写. 这些规则与java.beans.Introspector.decapitalize (Spring在这⾥使⽤的)定义的规则相同.
⽐如
类名: UController, Bean的名称为: UController
类名: AManager, Bean的名称为: AManager
获取bean对象, 是⽗类BeanFactory
提供的功能
ApplicationContext
VSBeanFactory
(常⻅⾯试题)
继承关系和功能⽅⾯来说:Spring 容器有两个顶级的接⼝ :BeanFactory 和ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能⼒,⽽ApplicationContext 属于 BeanFactory 的⼦类,它除了继承了 BeanFactory 的所有功能之外,它还拥有独特的特性,还添加了对国际化⽀持 、资源访问⽀持 、以及事件传播等⽅⾯的⽀持.
从性能⽅⾯来说:ApplicationContext 是**⼀次性加载并初始化所有的 Bean 对象** ,⽽BeanFactory 是需要那个才去加载那个,因此更加轻量. (空间换时间)
@Service(服务存储)
业务逻辑层用这个
使⽤ @Service
存储 bean 的代码如下所⽰:
java
@Service
public class UserService {
public void doService(){
System.out.println("doService...");
}
}
读取 bean 的代码:
java
@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 的代码如下所⽰:
java
@Repository
public class UserRepository {
public void doRepository(){
System.out.println("doRepository...");
}
}
读取 bean 的代码:
java
@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 的代码如下所⽰:
java
@Component
public class UserComponent {
public void doComponent(){
System.out.println("doComponent...");
}
}
读取 bean 的代码:
java
@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 的代码如下所⽰:
java
@Configuration
public class UserConfig {
public void doConfig(){
System.out.println("doConfig...");
}
}
读取 bean 的代码:
java
@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
:配置层. 处理项⽬中的⼀些配置信息.
这和每个省/市都有⾃⼰的⻋牌号是⼀样的.
⻋牌号都是唯⼀的, 标识⼀个⻋辆的. 但是为什么还需要设置不同的⻋牌开头呢.
⽐如陕西的⻋牌号就是:陕X:XXXXXX,北京的⻋牌号:京X:XXXXXX,甚⾄⼀个省不同的县区也是不同的,⽐如西安就是,陕A:XXXXX,咸阳:陕B:XXXXXX,宝鸡,陕C:XXXXXX,⼀样.
这样做的好处除了可以节约号码之外,更重要的作⽤是可以直观的标识⼀辆⻋的归属地
程序的应⽤分层,调⽤流程如下:
类注解之间的关系
查看 @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
我们先来看看⽅法注解如何使⽤:
java
@Data
public class UserInfo {
private Integer id;
private String name;
private Integer age;
}
java
public class BeanConfig {
@Bean
public UserInfo userInfo(){
UserInfo userInfo=new UserInfo();
userInfo.setId(1);
userInfo.setName("zhangsan");
userInfo.setAge(12);
return userInfo;
}
}
然⽽,当我们写完以上代码,尝试获取 bean 对象中的 user 时却发现,根本获取不到:
java
@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 容器中,
对于同⼀个类, 如何定义多个对象呢?
⽐如多数据源的场景, 类是同⼀个, 但是配置不同, 指向不同的数据源.
如下代码所⽰:
java
@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;
}
}
定义了多个对象的话, 我们根据类型获取对象, 获取的是哪个对象呢?
java
@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对象
java
@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
可以针对同⼀个类, 定义多个对象。
javaUserInfo userInfo=context.getBean("userInfo",UserInfo.class); UserInfo userInfo2=context.getBean("userInfo2",UserInfo.class);
也可以通过这种方式获取对象,这样就可以不用
(UserInfo)
强转了
java@Configuration public class BeanConfig { @Bean public String name(){ return "zhangsan"; } @Bean public UserInfo userInfo(String name){ UserInfo userInfo=new UserInfo(); userInfo.setId(1); userInfo.setName(name); userInfo.setAge(12); return userInfo; } }
也可以通过这样传参,注意一定要给
name
加个@Bean
,因为它要求传入的参数是一个bean对象,所以这个name
也要加个@Bean
变为一个bean对象,对象名称就是name
。含义是:定义了一个名叫name
的String类型的bean对象
重命名 Bean
可以通过设置 name 属性给 Bean 对象进⾏重命名操作,如下代码所⽰:这两个都是它的别名
java
@Bean(name = {"u1","user1"})
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
此时我们使⽤ u1 就可以获取到 User 对象了,如下代码所⽰:
java
@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);
}
}
name={} 可以省略,如下代码所⽰:
java@Bean({"u1","user1"}) public User user1(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; }
只有⼀个名称时, {}也可以省略, 如:
java@Bean("u1") public User user1(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; }
扫描路径
Q: 使⽤前⾯学习的四个注解声明的bean,⼀定会⽣效吗?
A: 不⼀定(原因:bean想要⽣效,还需要被Spring扫描)
下⾯我们通过修改项⽬⼯程的⽬录结构,来测试bean对象是否⽣效:
再运⾏代码:
java
@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
来配置扫描路径.
java
@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启动类所在包及其⼦包
在配置类上添加
@ComponentScan
注解, 该注解默认会扫描该类所在的包下所有的配置类
推荐做法:
把启动类放在我们希望扫描的包的路径下, 这样我们定义的bean就都可以被扫描到
这样扫描路径就是
com.example.demo
,里面这几个包就都可以扫到了
DI详解
上⾯讲了控制反转IoC的细节,接下来我们学习依赖注⼊DI的细节。
依赖注⼊是⼀个过程,是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源,⽽资源指的就是对象.
在上⾯程序案例中,我们使⽤了 @Autowired
这个注解,完成了依赖注⼊的操作.
简单来说, 就是把对象取出来放到某个类的属性中.
在⼀些⽂章中, 依赖注⼊也被称之为 "对象注⼊", "属性装配", 具体含义需要结合⽂章的上下⽂来理解
关于依赖注⼊, Spring也给我们提供了三种⽅式:
- 属性注⼊(Field Injection)
- 构造⽅法注⼊(Constructor Injection)
- Setter 注⼊(Setter Injection)
接下来,我们分别来看。
下⾯我们按照实际开发中的模式,将 Service 类注⼊到 Controller 类中。
1. 属性注⼊
属性注入是以类型进行匹配,与注入的属性名称无关
但是一个类型如果存在多个对象时,优先名称匹配,如果名称都匹配不上,就报错了
而且@Autowired
无法注入一个final
修饰的属性
属性注⼊是使⽤ @Autowired
实现的,将 Service 类注⼊到 Controller 类中
Service 类的实现代码如下:
java
@Service
public class UserService {
public void doService(){
System.out.println("doService...");
}
}
Controller 类的实现代码如下:
java
@Controller//将对象存储到 Spring 中
public class UserController {
@Autowired
private UserService userService;
public void doController(){
userService.doService();
System.out.println("doController...");
}
}
获取 Controller 中的 doController⽅法:
java
@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. 构造⽅法注⼊
构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所⽰:
java
@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
注解 ,它不像是构造方法只有一个的话会默认帮你加上,如下代码所⽰:
java
@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
会存在问题
java
@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;
}
}
java
@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
注解,来确定默认的实现,标识默认的对象
java
@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
使⽤
java
@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的名称。
java
@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...");
}
}
常⻅⾯试题:
@Autowird
与@Resource
的区别
@Autowired
是 Spring框架提供的注解,⽽@Resource
是 JDK 提供的注解@Autowired
默认是按照类型注⼊,⽽@Resource
是按照名称注⼊ . 相⽐于@Autowired
来说,@Resource
⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean。@Autowired
默认,如果同个类型存在多个对象,按名称匹配,如果名称匹配不上,就会报错
练习
通过上⾯的学习, 我们把前⾯的图书管理系统代码进⾏调整
Service层的注解, 改成 @Service
Dao层的注解, 改成 @Repository
重新运⾏代码, 验证程序访问正常
一般我们默认是8080的端口号,但也可以修改,这里简单提一下
总结
Spring, Spring Boot 和 Spring MVC 的关系以及区别
Spring : 简单来说, Spring 是⼀个开发应⽤框架,什么样的框架呢,有这么⼏个标签:轻量级、⼀站式、模块化,其⽬的是⽤于简化企业级应⽤程序开发
Spring的主要功能:管理对象,以及对象之间的依赖关系, ⾯向切⾯编程, 数据库事务管理, 数据访问, web框架⽀持等.
但是Spring具备⾼度可开放性, 并不强制依赖Spring, 开发者可以⾃由选择Spring的部分或者全部, Spring可以⽆缝继承第三⽅框架, ⽐如数据访问框架(Hibernate 、JPA), web框架(如Struts、JSF)
Spring MVC : Spring MVC 是 Spring 的⼀个⼦框架, Spring诞⽣之后, ⼤家觉得很好⽤, 于是按照MVC模式设计了⼀个 MVC框架(⼀些⽤Spring 解耦的组件), 主要⽤于开发WEB应⽤和⽹络接⼝,所以,Spring MVC 是⼀个Web框架
Spring MVC 基于 Spring 进⾏开发的, 天⽣的与 Spring 框架集成. 可以让我们更简洁的进⾏Web层开发, ⽀持灵活的 URL 到⻚⾯控制器的映射, 提供了强⼤的约定⼤于配置的契约式编程⽀持, ⾮常容易与其他视图框架集成,如 Velocity、FreeMarker等
Spring Boot : Spring Boot 是对 Spring 的⼀个封装, 为了简化Spring应⽤的开发⽽出现的,中⼩型企业,没有成本研究⾃⼰的框架, 使⽤Spring Boot 可以更加快速的搭建框架, 降级开发成本, 让开发⼈员更加专注于Spring应⽤的开发,⽽⽆需过多关注XML的配置和⼀些底层的实现
Spring Boot 是个脚⼿架, 插拔式搭建项⽬, 可以快速的集成其他框架进来.
⽐如想使⽤SpringBoot开发Web项⽬, 只需要引⼊Spring MVC框架即可, Web开发的⼯作是SpringMVC完成的, ⽽不是SpringBoot, 想完成数据访问, 只需要引⼊Mybatis框架即可.
Spring Boot只是辅助简化项⽬开发的, 让开发变得更加简单, 甚⾄不需要额外的web服务器, 直接⽣成jar包执⾏即可.
最后⼀句话总结: Spring MVC 和 Spring Boot 都属于 Spring,Spring MVC 是基于 Spring 的⼀个MVC 框架,⽽Spring Boot 是基于 Spring 的⼀套快速开发整合包
⽐如我们的图书系统代码中
整体框架是通过 SpringBoot 搭建的
IoC, DI 功能是 Spring 的提供的,
web 相关功能是 Spring MVC 提供的
这三者专注的领域不同,解决的问题也不⼀样, 总的来说,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的理解