一:Spring IoC&DI
1.1 方法注解 @Bean
类注解是添加到某个类上的, 但是存在两个问题:
- 使用外部包里的类, 没办法添加类注解
- ⼀个类, 需要多个对象, ⽐如多个数据源
这种场景, 我们就需要使用方法注解 @Bean
我们先来看方法注解如何使用:
public class BeanConfig {
@Bean
public User user(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}
然而,当我们写完以上代码,尝试获取 bean 对象中的 user 时却发现,根本获取不到:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
User user = context.getBean(User.class);
//使⽤对象
System.out.println(user);
}
}
以上程序的执行结果如下:
这是为什么呢?
1.1.1 方法注解要配合类注解使用
在 Spring 框架的设计中,方法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中,如下代码所示:
@Component
public class BeanConfig {
@Bean
public User user(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}
再次执行以上代码,运行结果如下:
1.1.2 定义多个对象
对于同⼀个类, 如何定义多个对象呢,比如多数据源的场景, 类是同⼀个, 但是配置不同, 指向不同的数据源.
我们看下 @Bean 的使用
@Component
public class BeanConfig {
@Bean
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2(){
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
定义了多个对象的话, 我们根据类型获取对象, 获取的是哪个对象呢?
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
User user = context.getBean(User.class);
//使⽤对象
System.out.println(user);
}
}
运行结果:
报错信息显示: 期望只有⼀个匹配, 结果发现了两个, user1, user2,从报错信息中, 可以看出来, @Bean 注解的 bean, bean 的名称就是它方法名
接下来我们根据名称来获取bean对象
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//根据bean名称, 从Spring上下⽂中获取对象
User user1 = (User) context.getBean("user1");
User user2 = (User) context.getBean("user2");
System.out.println(user1);
System.out.println(user2);
}
}
运行结果:
可以看到, @Bean 可以针对同⼀个类, 定义多个对象.
1.1.3 重命名 Bean
可以通过设置 name 属性给 Bean 对象进行重命名操作,如下代码所示:
@Bean(name = {"u1","user1"})
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean(name = {“u1”,“user1”}) 这一行是 @Bean 注解的一种变体,它不仅定义了一个Bean,还给这个 Bean 起了两个不同的名字,分别是 u1 和 user1。
此时我们使用 u1 就可以获取到 User 对象了,如下代码示:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
User u1 = (User) context.getBean("u1");
//使⽤对象
System.out.println(u1);
}
}
1.2 扫描路径
使用前面学习的四个注解声明的 bean,⼀定会生效吗?答案是否定的,因为 bean 想要生效,还需要被 Spring 扫描
下⾯我们通过修改项目工程的目录结构,来测试 bean 对象是否生效:
再运行代码:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从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(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
User u1 = (User) context.getBean("u1");
//使⽤对象
System.out.println(u1);
}
}
那为什么前⾯没有配置 @ComponentScan 注解也可以呢?
@ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication 中了,默认扫描的范围是 SpringBoot 启动类所在包及其子包,在配置类上添加 @ComponentScan 注解, 该注解默认会扫描该类所在的包下所有的配置类
推荐做法:把启动类放在我们希望扫描的包的路径下, 这样我们定义的 bean 就都可以被扫描到
1.3 DI 详解
依赖注入是⼀个过程,是指 IoC 容器在创建 Bean 时, 去提供运行时所依赖的资源,而资源指的就是对象.
在上面程序案例中,我们使用了 @Autowired 这个注解,完成了依赖注入的操作,简单来说, 就是把对象取出来放到某个类的属性中.
关于依赖注入, Spring 也给我们提供了三种方式:
- 属性注入
- 构造方法注入
- Setter 注入
1.3.1 属性注入
属性注入是使⽤ @Autowired 实现的,将 Service 类注入到 Controller 类中.
- Service 类的实现代码如下:
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void sayHi() {
System.out.println("Hi,UserService");
}
}
- Controller 类的实现代码如下:
@Controller
public class UserController {
//注⼊⽅法1: 属性注⼊
@Autowired
private UserService userService;
public void sayHi() {
System.out.println("hi,UserController...");
userService.sayHi();
}
}
- 获取 Controller 中的 sayHi⽅法:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
UserController userController = (UserController) context.getBean("Controller");
//使⽤对象
userController.sayHi();
}
}
最终结果如下:
去掉 @Autowired , 再运行一下程序看看结果
1.3.2 构造方法注入
构造方法注入是在类的构造方法中实现注入,如下代码所示:
@Controller
public class UserController2 {
//注⼊⽅法2: 构造⽅法
private UserService userService;
@Autowired
public UserController2(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hi,UserController2...");
userService.sayHi();
}
}
注意事项:如果类只有⼀个构造方法,那么 @Autowired 注解可以省略;如果类中有多个构造方法,那么需要添加上 @Autowired 来明确指定到底使用哪个构造方法。
1.3.3 Setter 注入
Setter 注入和属性的 Setter 方法实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注解 ,如下代码所示:
@Controller
public class UserController3 {
//注⼊⽅法3: Setter⽅法注⼊
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hi,UserController3...");
userService.sayHi();
}
}
1.3.4 三种注入优缺点分析
第一种:属性注入
优点:
- 简洁,使用方便
缺点:
- 只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)
- 不能注入一个 Final 修饰的属性
第二种:构造函数注入 (Spring 4.X 推荐)
优点:
- 可以注入 final 修饰的属性
- 注入的对象不会被修改
- 依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法。
- 通用性好,构造方法是 JDK 支持的,所以更换任何框架都适用
缺点:
- 注入多个对象时,代码会比较繁琐
第三种:Setter 注入 (Spring 3.X 推荐)
优点
- 方便在类实例之后,重新对该对象进行配置或者注入
缺点:
- 不能注入一个 Final 修饰的属性
- 注入对象可能会被改变,因为 setter 方法可能会被多次调用,就有被修改的风险
1.3.5 @Autowired 存在问题
当同⼀类型存在多个 bean 时, 使⽤ @Autowired 会存在问题
@Component
public class BeanConfig {
@Bean("u1")
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2() {
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
@Controller
public class UserController {
@Autowired
private UserService userService;
//注⼊user
@Autowired
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
userService.sayHi();
System.out.println(user);
}
}
运行结果:
报错的原因是,非唯⼀的 Bean 对象,如何解决上述问题呢?Spring 提供了以下几种解决方案:
- @Primary
- @Qualifier
- @Resource
下面 一 一 进行讲解
- 使用 @Primary 注解:当存在多个相同类型的 Bean 注入时,加上 @Primary 注解,来确定默认的实现.
@Component
public class BeanConfig {
@Primary //指定该bean为默认bean的实现
@Bean("u1")
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2() {
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
- 使用 @Qualifier 注解:指定当前要注入的 bean 对象。 在 @Qualifier 的 value 属性中,指定注入的 bean 的名称。
注意:@Qualifier 注解不能单独使用,必须配合 @Autowired 使用
@Controller
public class UserController {
@Qualifier("user2") //指定bean名称
@Autowired
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
- 使用 @Resource 注解:是按照 bean 的名称进行注入。通过 name 属性指定要注入的 bean 的名称。
@Controller
public class UserController {
@Resource(name = "user2")
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
@Autowird 与 @Resource 的区别
-
@Autowired 是 spring 框架提供的注解,而 @Resource 是 JDK 提供的注解
-
@Autowired 默认是按照类型注入,而 @Resource 是按照名称注入.
相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean。
1.4 总结
Spring, Spring Boot 和 Spring MVC 的关系以及区别:
- Spring:
简单来说, Spring 是⼀个开发应用框架,目的是用于简化企业级应用程序开发.
- Spring MVC:
Spring MVC 是 Spring 的⼀个子框架, Spring 诞生之后, 大家觉得很好用, 于是按照MVC模式设计了⼀个 MVC 框架,用于开发 web 应用和网络接口。
- Spring Boot:
Spring Boot 是对 Spring 的⼀个封装, 为了简化 Spring 用的开发而出现的,中小型企业,没有成本研究自己的框架, 使用 Spring Boot 可以更加快速的搭建框架, 降级开发成本, 让开发人员更加专注于 Spring 应用的开发,而无需过多关注 XML 的配置和⼀些底层的实现.
Spring, Spring Boot 和 Spring MVC 的功能:
- Spring
spring 主要用于管理对象和对象之间的依赖关系
- Spring MVC
基于 Spring 进行开发的, 天生的与 Spring 框架集成. 可以让我们更简洁的进行 Web 层开发, 支持灵活的 URL 到页面控制器的映射
- Spring Boot
Spring Boot 是个脚手架, 插拔式搭建项⽬, 可以快速的集成其他框架进来,比如想使用SpringBoot 开发 Web 项目, 只需要引入 Spring MVC 框架即可
- ⼀句话总结
Spring MVC 和 Spring Boot 都属于 Spring,Spring MVC 是基于 Spring的⼀个MVC 框架,而 Spring Boot 是基于 Spring 的⼀套快速开发整合包.
比如我们的图书系统代码中:
- 整体框架是通过 SpringBoot 搭建的
- IoC, DI 功能是 Spring 的提供的,
- web 相关功能是 Spring MVC 提供的
这三者专注的领域不同,解决的问题也不⼀样, 总的来说,Spring 就像⼀个大家族,有众多衍生产品, 但他们的基础都是 Spring