Java EE进阶5:Spring IoC&DI

1.Spring、IoC、DI介绍
Spring是开源框架,是容器,是包含了众多工具方法的IoC容器。
容器是用来容纳某种物品的基本装置,例如水杯、冰箱等。

IoC(Inversion of Control,控制反转)是一种设计思想
它将获得依赖对象的控制权从应用程序自身转移到外部容器,从而改变传统开发中通过 `new` 主动创建对象的方式。
DI(Dependency Injection,依赖注入)则是 IoC 的具体实现
容器在运行期间动态地为应用程序提供所需的资源,即程序运行时需要某个资源,容器便为其注入该资源。
IoC 容器将资源交由第三方管理,带来资源集中配置、易管理以及降低耦合度两大好处。
2.IoC & DI 使用

Spring容器管理的对象,才称为Bean。
我们把这些对象交由Spring管理,由 Spring来负责对象的创建和销毁;我们程序只需要告诉Spring,哪些需要存,以及如何从Spring中取出对象。

(1)把BookDao交给Spring管理,由Spring来管理对象。

(2)把BookService交给Spring管理,由Spring来管理对象。

(3)删除创建BookDao的代码,从Spring中获取对象。

(4)删除创建BookService的代码,从Spring中获取对象。

3.IoC详解-Bean的存储
Spring容器管理的对象,才称为Bean。
在之前的案例中,要把某个对象交给IOC容器管理,需要在类上添加⼀个注解: @Component。
Spring框架为了更好的服务web应用程序,提供了更丰富的注解。
1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
2. 方法注解:@Bean. (bean的名称 = 方法名)
【Spring帮我们new了对象 所以我们可以直接调用】
3.1 类注解
(1)@Controller(控制器存储)
使用@Controller存储bean的代码如下所示:
@Controller // 将对象存储到 Spring 中
public class UserController {
public void sayHi(){
System.out.println("hi,UserController...");
}
}
如何观察这个对象已经存在Spring容器当中了呢? 接下来我们学习如何从Spring容器中获取对象
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
UserController userController = context.getBean(UserController.class);
//使⽤对象
userController.sayHi();
}
}


获取bean对象的其他方式
上述代码是根据类型来查找对象,如果Spring容器中,同⼀个类型存在多个bean的话,怎么来获取呢?
ApplicationContext也提供了其他获取bean的方式,ApplicationContext获取bean对象的功能,是父类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命名约定
我们看下官方文档的说明:https://docs.spring.io/spring-framework/reference/core/beans/definition.html#beans-beanname


@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//根据bean类型, 从Spring上下⽂中获取对象;有局限性,一个类型对应多个bean。
UserController userController1 = context.getBean(UserController.class);
//根据bean名称, 从Spring上下⽂中获取对象;一个bean可以有多个名称,但是名称不会重复。
UserController userController2 = (UserController)context.getBean("userController");
//根据bean类型+名称, 从Spring上下⽂中获取对象
UserController userController3 = context.getBean("userController",UserController.class);
System.out.println(userController1);
System.out.println(userController2);
System.out.println(userController3);
}
}


(2)@Service(服务存储)
使用@Service存储bean的代码如下所示:
@Service
public class UserService {
public void sayHi(String name) {
System.out.println("Hi," + name);
}
}
读取bean的代码:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring中获取UserService对象
UserService userService = context.getBean(UserService.class);
//使⽤对象
userService.sayHi();
}
}

(3)@Repository(仓库存储)
使用@Repository 存储bean的代码如下所示:
@Repository
public class UserRepository {
public void sayHi() {
System.out.println("Hi, UserRepository~");
}
}
读取bean的代码:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
UserRepository userRepository = context.getBean(UserRepository.class);
//使⽤对象
userRepository.sayHi();
}
}

(4)@Component(组件存储)
使用@Component存储bean的代码如下所示:
@Component
public class UserComponent {
public void sayHi() {
System.out.println("Hi, UserComponent~");
}
}
读取bean的代码:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
UserComponent userComponent = context.getBean(UserComponent.class);
//使⽤对象
userComponent.sayHi();
}
}

(5)@Configuration(配置存储)
使用@Configuration存储bean的代码如下所示:
@Configuration
public class UserConfiguration {
public void sayHi() {
System.out.println("Hi,UserConfiguration~");
}
}
读取bean的代码:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
UserConfiguration userConfiguration = context.getBean(UserConfiguration.class);
//使⽤对象
userConfiguration.sayHi();
}
}

3.2 为什么要这么多类注解?


类注解之间的关系


3.3 方法注解@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)方法注解要配合类注解使用
在Spring框架的设计中,方法注解 @Bean 要配合类注解才能将对象正常的存储到Spring容器中, 如下代码所示。
@Component
public class BeanConfig {
@Bean
public User user(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}
再次执行以上代码,运行结果如下:

(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的名称就是它的方法名
//因为一个类型(即对象0..0)可以对应多个bean(即多个名称,即它的方法名) ,所以根据bean类型, 从Spring上下文中获取对象就有局限性。 【一个类型可以对应多个bean的前提是一个bean可以有多个名称……………】
//因为一个bean可以有多个名称(bean的名称就是它的方法名!!),但是名称不会重复,所有可以根据根据bean名称, 从Spring上下文中获取对象。
(结合前面命名bean时的代码再思考一下)
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//根据bean类型, 从Spring上下⽂中获取对象;有局限性,一个类型对应多个bean。
UserController userController1 = context.getBean(UserController.class);
//根据bean名称, 从Spring上下⽂中获取对象;一个bean可以有多个名称,但是名称不会重复。
UserController userController2 = (UserController)context.getBean("userController");
//根据bean类型+名称, 从Spring上下⽂中获取对象
UserController userController3 = context.getBean("userController",UserController.class);
System.out.println(userController1);
System.out.println(userController2);
System.out.println(userController3);
}
}
接下来我们根据名称来获取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);
}
}
运行结果:

(3)重命名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(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
User u1 = (User) context.getBean("u1");
//使⽤对象
System.out.println(u1);
}
}

3.4 扫描路径

再运行代码:
@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({"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);
}
}



4.DI详解@Autowired

4.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("userController");
//使⽤对象
userController.sayHi();
}
}
最终结果如下:

4.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来明确指定到底使用哪个构造方法。(优先使用无参的)
4.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();
}
}
练习一下:尝试一下set方法如果不加@Autowired注解能注入成功吗? 不能
4.4 三种注入优缺点分析


更多DI相关内容参考:https://docs.spring.io/spring-framework/reference/core/beans/dependencies.html
4.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
(1)使用@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;
}
}
(2)使用@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);
}
}
(3)使用@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。
Autowired装配顺序

5.练习
通过上面的学习,我们把前面的图书管理系统代码进行调整。
Service层的注解,改成@Service
Dao层的注解,改成@Repository
重新运行代码,验证程序访问正常

6.总结
6.1 Spring、Spring Boot 和Spring MVC的关系以及区别



最后⼀句话总结:Spring MVC (火车站)和 Spring Boot(12306软件)都属于Spring(火车),Spring MVC 是基于Spring的一个 MVC 框架,而Spring Boot是基于Spring的一套快速开发整合包。

这三者专注的领域不同,解决的问题也不一样,总的来说,Spring就像一个大家族,有众多衍生产品,但他们的基础都是Spring,用一张图来表示他们三个的关系。

6.2 bean的命名
6.3 常见面试题

更多推荐


所有评论(0)