Spring IoC&DI

本文章内容
1. 了解Spring,Spring MVC, Spring Boot 之间的联系及区别
2. 掌握IoC&DI的概念以及写法

1.IoC & DI ⼊⻔

我们可以使用Spring Boot和Spring MVC的开发, 可以完成⼀些基本功能的开发了, 但是
什么是Spring呢? Spring, Spring Boot 和SpringMVC⼜有什么关系呢? 咱们还是带着问题去学习.
我们先看什么是Spring.

1.1 Spring 是什么?

通过前⾯的学习, 我们知道了Spring是⼀个开源框架, 他让我们的开发更加简单. 他⽀持⼴泛的应⽤场 景, 有着活跃⽽庞⼤的社区, 这也是Spring能够⻓久不衰的原因.
但是这个概念相对来说, 还是⽐较抽象.
我们⽤⼀句更具体的话来概括Spring, 那就是: Spring 是包含了众多⼯具⽅法的 IoC 容器
那问题来了,什么是容器?什么是 IoC 容器?接下来我们⼀起来看

1.1.1 什么是容器?

容器是⽤来容纳某种物品的(基本)装置。⸺来⾃:百度百科
⽣活中的⽔杯, 垃圾桶, 冰箱等等这些都是容器.
我们想想,之前课程我们接触的容器有哪些?
List/Map -> 数据存储容器
Tomcat -> Web 容器

1.1.2 什么是 IoC?

IoC 是Spring的核⼼思想, 也是常⻅的⾯试题, 那什么是IoC呢?
其实IoC我们在前⾯已经使⽤了, 我们在前⾯讲到, 在类上⾯添加 @RestController
@Controller 注解, 就是把这个对象交给Spring管理, Spring 框架启动时就会加载该类. 把对象交
给Spring管理, 就是IoC思想.
IoC: Inversion of Control (控制反转), 也就是说 Spring 是⼀个"控制反转"的容器.
什么是控制反转呢? 也就是控制权反转. 什么的控制权发⽣了反转? 获得依赖对象的过程被反转了 也就是说, 当需要某个对象时, 传统开发模式中需要⾃⼰通过 new 创建对象, 现在不需要再进⾏创建, 把创建对象的任务交给容器, 程序中只需要依赖注⼊ (Dependency Injection,DI)就可以了.
这个容器称为:IoC容器. Spring是⼀个IoC容器, 所以有时Spring 也称为Spring 容器.

控制反转是⼀种思想, 在⽣活中也是处处体现.

⽐如⾃动驾驶, 传统驾驶⽅式, ⻋辆的横向和纵向驾驶控制权由驾驶员来控制, 现在交给了驾驶⾃ 动化系统来控制, 这也是控制反转思想在⽣活中的实现.
⽐如招聘, 企业的员⼯招聘,⼊职, 解雇等控制权, 由⽼板转交给给HR(⼈⼒资源)来处理

 1.2 IoC 介绍

接下来我们通过案例来了解⼀下什么是IoC
需求: 造⼀辆⻋

1.2.1 传统程序开发

我们的实现思路是这样的:
先设计轮⼦(Tire),然后根据轮⼦的⼤⼩设计底盘(Bottom),接着根据底盘设计⻋⾝(Framework),最 后根据⻋⾝设计好整个汽⻋(Car)。这⾥就出现了⼀个"依赖"关系:汽⻋依赖⻋⾝,⻋⾝依赖底盘,底 盘依赖轮⼦.
最终程序的实现代码如下:
class Car{
    Framework framework;
    public Car(){
        framework = new Framework();
        System.out.println("Car init...");
    }
    public void rum(){
        System.out.println("Car run");
    }
}
class Framework{
Bottom bottom;
public Framework(){
    bottom = new Bottom();
    System.out.println("Framework run");
}
class Bottom {
Tire tire;
public Bottom(){
    tire = new Tire();
    System.out.println("Bottom run");}
}
}
class Tire{
    private int size;
    public Tire(){
        this.size = 17;
        System.out.println("轮胎尺⼨:" + size);
}
}

1.2.2 问题分析

这样的设计看起来没问题,但是可维护性却很低.
接下来需求有了变更: 随着对的⻋的需求量越来越⼤, 个性化需求也会越来越多,我们需要加⼯多种尺⼨的轮胎.
那这个时候就要对上⾯的程序进⾏修改了,修改后的代码如下所⽰:
class Car{
    Framework framework;
    public Car(int size){
        framework = new Framework(size);
        System.out.println("Car init...");
    }
    public void rum(){
        System.out.println("Car run");
    }
}
class Framework{
Bottom bottom;
public Framework(int size){
    bottom = new Bottom(size);
    System.out.println("Framework run");
}
class Bottom {
Tire tire;
public Bottom(int size){
    tire = new Tire(size);
    System.out.println("Bottom run");}
}
}
class Tire{
    private int size;
    public Tire(int size){
        this.size = 17;
        System.out.println("轮胎尺⼨:" + size);
}
}
从以上代码可以看出,以上程序的问题是:当最底层代码改动之后,整个调⽤链上的所有代码都需要 修改.
程序的耦合度⾮常⾼(修改⼀处代码, 影响其他处的代码修改)

1.2.3 解决方案

在上⾯的程序中, 我们是根据轮⼦的尺⼨设计的底盘,轮⼦的尺⼨⼀改,底盘的设计就得修改. 同样因为我们是根据底盘设计的⻋⾝,那么⻋⾝也得改,同理汽⻋设计也得改, 也就是整个设计⼏乎都得改.(导致一个代码并不能得到复用)
我们尝试换⼀种思路, 我们先设计汽⻋的⼤概样⼦,然后根据汽⻋的样⼦来设计⻋⾝,根据⻋⾝来设计 底盘,最后根据底盘来设计轮⼦. 这时候,依赖关系就倒置过来了:轮⼦依赖底盘, 底盘依赖⻋⾝,⻋⾝依赖汽⻋.
这就类似我们打造⼀辆完整的汽⻋, 如果所有的配件都是⾃⼰造,那么当客⼾需求发⽣改变的时候,⽐如轮胎的尺⼨不再是原来的尺⼨了,那我们要⾃⼰动⼿来改了,但如果我们是把轮胎外包出去,那么即使是轮胎的尺⼨发⽣变变了,我们只需要向代理⼯⼚下订单就⾏了,我们⾃⾝是不需要出⼒的.
如何来实现呢:
我们可以尝试不在每个类中⾃⼰创建下级类,如果⾃⼰创建下级类就会出现当下级类发⽣改变操作,⾃⼰也要跟着修改.
此时,我们只需要将原来由⾃⼰创建的下级类,改为传递的⽅式(也就是注⼊的⽅式),因为我们不需要在当前类中创建下级类了,所以下级类即使发⽣变化(创建或减少参数),当前类本⾝也⽆需修改任何代码,这样就完成了程序的解耦.

1.2.4 IoC程序开发

基于以上思路,我们把调⽤汽⻋的程序⽰例改造⼀下,把创建⼦类的⽅式,改为注⼊传递的⽅式.
具体实现代码如下:
class Car{
Framework framework;
public Car(Framework framework){
    framework = framework;
    System.out.println("Car init");
}
}
class Framework{
Bottom bottom;
public Framework(Bottom bottom){
    bottom = bottom;
    System.out.println("Framework init");
}
}
class Bottom{
Tire tire;
public Bottom(Tire tire){
    tire = tire;
    System.out.println("Bottom init");
}
}
class Tire{
    int size;
    public Tire(int size){
        size = size;
        System.out.println("轮胎尺寸是+"+size);
    }
}
public class IOc {
    public static void main(String[] args) {
Tire tire = new Tire(20);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
    }
}

1.2.5 IoC 优势

在传统的代码中对象创建顺序是:Car -> Framework -> Bottom -> Tire
改进之后解耦的代码的对象创建顺序是:Tire -> Bottom -> Framework -> Car
我们发现了⼀个规律,通⽤程序的实现代码,类的创建顺序是反的,传统代码是 Car 控制并创建了 Framework,Framework 创建并创建了 Bottom,依次往下,⽽改进之后的控制权发⽣的反转,不再是使⽤⽅对象创建并控制依赖对象了,⽽是把依赖对象注⼊将当前对象中,依赖对象的控制权不再由当前类控制了.
这样的话, 即使依赖类发⽣任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是 IoC 的实现思想。
学到这⾥, 我们⼤概就知道了什么是控制反转了, 那什么是控制反转容器呢, 也就是IoC容器.
这部分代码, 就是IoC容器做的⼯作.
从上⾯也可以看出来, IoC容器具备以下优点:
资源不由使⽤资源的双⽅管理,⽽由不使⽤资源的第三⽅管理,这可以带来很多好处。第⼀,资源集 中管理,实现资源的可配置和易管理。第⼆,降低了使⽤资源双⽅的依赖程度,也就是我们说的耦合度。
1. 资源集中管理: IoC容器会帮我们管理⼀些资源(对象等), 我们需要使⽤时, 只需要从IoC容器中去取就可以了
2. 我们在创建实例的时候不需要了解其中的细节, 降低了使⽤资源双⽅的依赖程度, 也就是耦合度.
Spring 就是⼀种IoC容器, 帮助我们来做了这些资源管理.

1.3 DI介绍

DI: Dependency Injection(依赖注⼊)
容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。
程序运⾏时需要某个资源,此时容器就为其提供这个资源.
从这点来看, 依赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过引⼊ IoC 容器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦。

上述代码中, 是通过构造函数的⽅式, 把依赖对象注⼊到需要使⽤的对象中的.

 

IoC 是⼀种思想,也是"⽬标", ⽽思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽ DI 就属于 具体的实现。所以也可以说, DI 是IoC的⼀种实现.
⽐如说我今天⼼情⽐较好,吃⼀顿好的犒劳犒劳⾃⼰,那么"吃⼀顿好的"是思想和⽬标(是
IoC),但最后我是吃海底捞还是杨国福?这就是具体的实现,就是 DI。

 2. IoC & DI 使⽤

对IoC和DI有了初步的了解, 我们接下来具体学习Spring IoC和DI的代码实现.
既然 Spring 是⼀个 IoC(控制反转)容器,作为容器, 那么它就具备两个最基础的功能:
Spring 容器 管理的主要是对象, 这些对象, 我们称之为"Bean". 我们把这些对象交由Spring管理, 由 Spring来负责对象的创建和销毁. 我们程序只需要告诉Spring, 哪些需要存, 以及如何从Spring中取出对象.

⽬标: 把BookDao, BookService 交给Spring管理, 完成Controller层, Service层, Dao层的解耦.

步骤:
1. Service层及Dao层的实现类,交给Spring管理: 使⽤注解: @Component
2. 在Controller层 和Service层 注⼊运⾏时依赖的对象: 使⽤注解 @Autowired
实现:
1. 把BookDao 交给Spring管理, 由Spring来管理对象.

@Component
public class BookDao {
    public List<BookInfo> mockData() {
        List<BookInfo> books = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            BookInfo book = new BookInfo();
            book.setId(i);
            book.setBookName("书籍" + i);
            book.setAuthor("作者" + i);
            book.setCount(i * 5 + 3);
            book.setPrice(new BigDecimal(new Random().nextInt(100)));
            book.setPublish("出版社" + i);
            book.setStatus(1);
            books.add(book);
        }
        return books;
    }
}
2. 把BookService 交给Spring管理, 由Spring来管理对象
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
@Component
public class BookService {
    @Autowired
    BookDao bookDao;
    public List<BookInfo> getBookList() {
        List<BookInfo> books = bookDao.mockData();
        for (BookInfo book : books) {
            if (book.getStatus() == 1) {
                book.setStatusCN("可借阅");
            } else {
                book.setStatusCN("不可借阅");
            }
        }
        return books;
    }
}
3. 删除创建BookDao的代码, 从Spring中获取对象
@Component
public class BookService {
 @Autowired
 private BookDao bookDao;
 public List<BookInfo> getBookList() {
 List<BookInfo> books = bookDao.mockData();
 for (BookInfo book : books) {
 if (book.getStatus() == 1) {
 book.setStatusCN("可借阅");
 } else {
 book.setStatusCN("不可借阅");
 }
 }
 return books;
 }
}
4. 删除创建BookService的代码, 从Spring中获取对象
@RequestMapping("/book")
@RestController
public class BookController {
 @Autowired
 private BookService bookService;
 @RequestMapping("/getList")
 public List<BookInfo> getList(){
 //获取数据
 List<BookInfo> books = bookService.getBookList();
 return books;
 }
}

3.IoC 详解

通过上⾯的案例, 我们已经知道了Spring IoC 和DI的基本操作, 接下来我们来系统的学习Spring IoC和DI的操作.
前⾯我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对
象。
也就是bean的存储.

3.1 Bean的存储

在之前的⼊⻔案例中,要把某个对象交给IOC容器管理,需要在类上添加⼀个注解: @Component ⽽Spring框架为了更好的服务web应⽤程序, 提供了更丰富的注解.
共有两类注解类型可以实现:
1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
2. ⽅法注解:@Bean.
接下来我们分别来看

3.1.1 @Controller(控制器存储)

使⽤ @Controller 存储 bean 的代码如下所⽰:

import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    public void sayHi(){
        System.out.println("hi,UserController");
    }
}
如何观察这个对象已经存在Spring容器当中了呢?
接下来我们学习如何从Spring容器中获取对象
我们要在启动类中操作.
@SpringBootApplication
@RestController
public class SpringMvcApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringMvcApplication.class, args);
        UserController userController = context.getBean(UserController.class);
        //使⽤对象
        userController.sayHi();

ApplicationContext 翻译过来就是: Spring 上下⽂
因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下⽂关于上下⽂的概念
上学时, 阅读理解经常会这样问: 根据上下⽂, 说⼀下你对XX的理解
在计算机领域, 上下⽂这个概念, 咱们最早是在学习线程时了解到过, ⽐如我们应⽤进⾏线程切换的时候,切换前都会把线程的状态信息暂时储存起来,这⾥的上下⽂就包括了当前线程的信息,等下次该线程⼜得到CPU时间的时候, 从上下⽂中拿到线程上次运⾏的信息
这个上下⽂, 就是指当前的运⾏环境, 也可以看作是⼀个容器, 容器⾥存了很多内容, 这些内容是当前运⾏的环境.

如果把@Controller删掉, 再观察运⾏结果.

 

获取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的名称是什么呢?
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 VS BeanFactory(常⻅⾯试题)
继承关系和功能⽅⾯来说:Spring 容器有两个顶级的接⼝:BeanFactory 和
ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能⼒,⽽
ApplicationContext 属于 BeanFactory 的⼦类,它除了继承了 BeanFactory 的所有功能之外,
它还拥有独特的特性,还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持.
从性能⽅⾯来说:ApplicationContext 是⼀次性加载并初始化所有的 Bean 对象,⽽
BeanFactory 是需要那个才去加载那个,因此更加轻量. (空间换时间)

3.1.2 @Service(服务存储)

使⽤ @Service 存储 bean 的代码如下所⽰:

package com.example.demo;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void sayhi(){
        System.out.println("hi UserService");
    }
}
读取 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.1.3 @Repository(仓库存储)

使⽤ @Repository 存储 bean 的代码如下所⽰:
package com.example.demo;

import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {
    public void sayhi(){
        System.out.println("hi Repository");
    }
}
读取 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();
 }
}
同样的, 把注解@Repository删掉, 再观察运⾏结果.

3.1.4 @Component(组件存储)

使⽤ @Component 存储 bean 的代码如下所⽰:
package com.example.demo;

import org.springframework.stereotype.Component;

@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();
 }
}
同样的, 把注解@Component删掉, 再观察运⾏结果

3.1.5 @Configuration(配置存储)

使⽤ @Configuration 存储 bean 的代码如下所⽰:

package com.example.demo;

import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfiguration {
    public void sayhi(){
        System.out.println("hi UserConfiguration");
    }
}

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

这个也是和咱们前⾯讲的应⽤分层是呼应的. 让程序员看到类注解之后,就能直接了解当前类的⽤途.
@Controller:控制层, 接收请求, 对请求进⾏处理, 并进⾏响应.
@Servie:业务逻辑层, 处理具体的业务逻辑.
@Repository:数据访问层,也称为持久层. 负责数据访问操作
@Configuration:配置层. 处理项⽬中的⼀些配置信息.
这和每个省/市都有⾃⼰的⻋牌号是⼀样的.
⻋牌号都是唯⼀的, 标识⼀个⻋辆的. 但是为什么还需要设置不同的⻋牌开头呢.
⽐如陕西的⻋牌号就是:陕X:XXXXXX,北京的⻋牌号:京X:XXXXXX,甚⾄⼀个省不同的县区也
是不同的,⽐如西安就是,陕A:XXXXX,咸阳:陕B:XXXXXX,宝鸡,陕C:XXXXXX,⼀样.
这样做的好处除了可以节约号码之外,更重要的作⽤是可以直观的标识⼀辆⻋的归属地.
类注解之间的关系
查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发
现:
其实这些注解⾥⾯都有⼀个注解 @Component ,说明它们本⾝就是属于 @Component 的"⼦类".
@Component 是⼀个元注解,也就是说可以注解其他类注解,如 @Controller , @Service ,
@Repository 等. 这些注解被称为 @Component 的衍⽣注解.
@Controller , @Service @Repository ⽤于更具体的⽤例(分别在控制层, 业务逻辑层, 持
久化层), 在开发过程中, 如果你要在业务逻辑层使⽤ @Component 或@Service,显然@Service是更好的选择.
⽐如杯⼦有喝⽔杯, 刷⽛杯等, 但是我们更倾向于在⽇常喝⽔时使⽤⽔杯, 洗漱时使⽤刷⽛杯.

3.3  方法注解

类注解是添加到某个类上的, 但是存在两个问题:
1. 使⽤外部包⾥的类, 没办法添加类注解
2. ⼀个类, 需要多个对象, ⽐如多个数据源
这种场景, 我们就需要使⽤⽅法注解 @Bean
我们先来看看⽅法注解如何使⽤:
package com.example.demo;

import org.springframework.context.annotation.Bean;

public class BeanText {
    @Bean
    public User user(){
        User user = new User();
        user.setName("hahaha");
        user.setAge(12);
        return user;
    }
}
然⽽,当我们写完以上代码,尝试获取 bean 对象中的 user 时却发现,根本获取不到:
这是为什么呢?

3.3.1 ⽅法注解要配合类注解使⽤

在 Spring 框架的设计中,⽅法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中,如下代码所⽰:
package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class BeanText {
    @Bean
    public User user(){
        User user = new User();
        user.setName("hahaha");
        user.setAge(12);
        return user;
    }
}
再次执⾏以上代码,运⾏结果如下:

3.3.2 定义多个对象

对于同⼀个类, 如何定义多个对象呢?
⽐如多数据源的场景, 类是同⼀个, 但是配置不同, 指向不同的数据源.

 我们看下@Bean的使⽤

package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class BeanText {
    @Bean
    public User user1(){
        User user = new User();
        user.setName("hahaha");
        user.setAge(12);
        return user;
    }
    @Bean
    public User user2(){
        User user = new User();
        user.setName("ziiziz");
        user.setAge(12);
        return user;
    }
}
定义了多个对象的话, 我们根据类型获取对象, 获取的是哪个对象呢?
报错信息显⽰: 期望只有⼀个匹配, 结果发现了两个, user1, user2
从报错信息中, 可以看出来, @Bean 注解的bean, bean的名称就是它的⽅法名
接下来我们根据名称来获取bean对象
User user1=(User) context.getBean("user1");
       User user2=(User) context.getBean("user2");
        System.out.println(user1);
        System.out.println(user2);

可以看到, @Bean 可以针对同⼀个类, 定义多个对象.

3.3.3 重命名 Bean

可以通过设置 name 属性给 Bean 对象进⾏重命名操作,如下代码所⽰:
@Component
public class BeanText {
    @Bean(name = {"u1","user1"})
    public User user1(){
        User user = new User();
        user.setName("hahaha");
        user.setAge(12);
        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);
 }
}

name={} 可以省略,如下代码所⽰:
@Bean({"u1","user1"})
public User user1(){
 User user = new User();
 user.setName("zhangsan");
 user.setAge(18);
 return user;
}
只有⼀个名称时, {}也可以省略, 如:
@Bean("u1")
public User user1(){
 User user = new User();
 user.setName("zhangsan");
 user.setAge(18);
 return user;
}

3.4 扫描路径

Q: 使⽤前⾯学习的四个注解声明的bean,⼀定会⽣效吗?
A: 不⼀定(原因: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就都可以被扫描到.

4.DI 详解

上⾯我们讲解了控制反转IoC的细节,接下来呢,我们学习依赖注⼊DI的细节。

依赖注⼊是⼀个过程,是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源,⽽资源指的就是对象.
在上⾯程序案例中,我们使⽤了 @Autowired 这个注解,完成了依赖注⼊的操作.
简单来说, 就是把对象取出来放到某个类的属性中.
在⼀些⽂章中, 依赖注⼊也被称之为 "对象注⼊", "属性装配", 具体含义需要结合⽂章的上下⽂来理解.
关于依赖注⼊, Spring也给我们提供了三种⽅式:
1. 属性注⼊(Field Injection)
2. 构造⽅法注⼊(Constructor Injection)
3. Setter 注⼊(Setter Injection)
接下来,我们分别来看。
下⾯我们按照实际开发中的模式,将 Service 类注⼊到 Controller 类中.

4.1 属性注⼊

属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中.
Service 类的实现代码如下:
package com.example.demo;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void sayhi(){
        System.out.println("hi UserService");
    }
}
Controller 类的实现代码如下:
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    @Autowired
    UserService userService;
    public void sayHi(){
        System.out.println("hi,UserController");
        userService.sayhi();
    }
}
获取 Controller 中的 sayHi⽅法:
去掉@Autowired , 再运⾏⼀下程序看看结果

4.2 构造⽅法注⼊

构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所⽰:
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;

public class UserController2 {
    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 注
解 ,如下代码所⽰:
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;

public class UserController2 {
    private UserService userService;
    @Autowired
    public UserController2(UserService userService) {
        this.userService = userService;
    }
@Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }//set方法

    public void sayHi(){
        System.out.println("hi,UserController2...");
        userService.sayhi();
    }
}

4.4 三种注⼊优缺点分析

属性注⼊
   ◦ 优点: 简洁,使⽤⽅便;
   ◦ 缺点:
      ▪ 只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指
针异常)
      ▪ 不能注⼊⼀个Final修饰的属性
构造函数注⼊(Spring 4.X推荐)
   ◦ 优点:
     ▪ 可以注⼊final修饰的属性
     ▪ 注⼊的对象不会被修改
     ▪ 依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法
是在类加载阶段就会执⾏的⽅法.
     ▪ 通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的
   ◦ 缺点:
     ▪ 注⼊多个对象时, 代码会⽐较繁琐
Setter注⼊(Spring 3.X推荐)
   ◦ 优点: ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊
   ◦ 缺点:
     ▪ 不能注⼊⼀个Final修饰的属性
     ▪ 注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险.

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
使⽤@Primary注解:当存在多个相同类型的Bean注⼊时,加上@Primary注解,来确定默认的实现.
使⽤@Qualifier注解:指定当前要注⼊的bean对象。 在@Qualifier的value属性中,指定注⼊的bean
的名称。
@Qualifier注解不能单独使⽤,必须配合@Autowired使⽤
使⽤@Resource注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称。
常⻅⾯试题:
@Autowird 与 @Resource的区别
@Autowired 是spring框架提供的注解,⽽@Resource是JDK提供的注解
@Autowired 默认是按照类型注⼊,⽽@Resource是按照名称注⼊. 相⽐于 @Autowired 来说, @Resource ⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean。

6. 总结

6.1 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, ⽤⼀张图来表⽰他们三个的关系:

6.2 bean 的命名

1) 五⼤注解存储的bean
① 前两位字⺟均为⼤写, bean名称为类名
② 其他的为类名⾸字⺟⼩写
③ 通过 value属性设置 @Controller(value = "user")
2) @Bean 注解存储的bean
① bean名称为⽅法名
②通过name属性设置 @Bean(name = {"u1","user1"})

6.3 常⻅⾯试题

1) 三种注⼊⽅式的优缺点
2) 常⻅注解有哪些? 分别是什么作⽤?
web url映射: @RequestMapping
参数接收和接⼝响应: @RequestParam, @RequestBody, @ResponseBody
bean的存储: @Controller, @Service, @Repository, @Component, @Configuration,
@Bean
bean的获取: @Autowired, @Qualifier, @Resource
3) @Autowired 和@Resource 区别
4) 说下你对Spring, SpringMVC, Springboot的理解
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值