Spring IoC&DI

1.IoC&DI简介

1.1 Spring是什么

  • Spring 是一个包含了众多⼯具⽅法的 IoC 容器
  • Spring两大核心思想:IOC和AOP(AOP后续博文)
  • 容器是⽤来容纳某种物品的(基本)装置

比如说:

list/map 装数据的容器

tomcat 装web的容器

Spring容器,装的是对象,对象这个词,在Spring的范围内,称为bean


1.2 IoC

  • IoC思想:把对象交给Spring管理

在类上⾯添加 @RestController 和 @Controller 注解, 就是把这个对象交给Spring管理, Spring 框架启动时就会加载该类,这就是IoC思想

  • IoC: 控制反转, 也就是说 Spring 是⼀个"控制反转"的容器

什么是控制反转呢? 也就是控制权反转.

什么的控制权发⽣了反转? 获得依赖对象的过程被反转了

也就是说, 当需要某个对象时, 传统开发模式中需要⾃⼰通过 new 创建对象, 现在不需要再进⾏创建, 把创建对象的任务交给容器, 程序中只需要依赖注⼊就可以了.

这个容器称为:IoC容器. Spring是⼀个IoC容器, 所以有时Spring 也称为Spring 容器.

⽐如招聘, 企业的员⼯招聘,⼊职, 解雇等控制权, 由⽼板转交给给HR(⼈⼒资源)来处理

1.2.1 案例

造一辆车:

1.2.1.1 传统

public class Main {
    public static void main(String[] args) {
        Car car = new Car(19);
        car.run();
    }
}


public class Car {
    private Framework framework;

    public Car(int size) {
        framework = new Framework(size);
        System.out.println("car init...");
    }

    public void run(){
        System.out.println("car run...");
    }
}


public class Framework {
    private Bottom bottom;

    public Framework(int size) {
        bottom = new Bottom(size);
        System.out.println("framework init...");
    }
}


public class Bottom {
    private Tire tire;

    public Bottom(int size) {
        tire = new Tire(size);
        System.out.println("bottom init...");
    }
}



public class Tire {
    private int size;

    public Tire(int size) {
        this.size = size;
        System.out.println("tire init...size " + size);
    }
}


这样的设计看起来没问题,但是可维护性却很低.

当最底层代码改动之后,整个调⽤链上的所有代码都需要修改.

程序的耦合度⾮常⾼.

1.2.1.2 改进

public class Main {
    public static void main(String[] args) {
        Tire tire = new Tire(17,"red");
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);
        car.run();
    }
}


public class Car {
    private Framework framework;

    public Car(Framework framework) {
        this.framework = framework;
        System.out.println("car init");
    }

    public void run(){
        System.out.println("run init...");
    }
}


public class Framework {
    private Bottom bottom;

    public Framework(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("framework init...");
    }
}


public class Bottom {
    private Tire tire;

    public Bottom(Tire tire) {
        this.tire = tire;
        System.out.println("bottom init...");
    }
}


public class Tire {
    private int size;
    private String color;

    public Tire(int size,String color) {
        this.size = size;
        this.color = color;
        System.out.println("tire init...size "+ size +",color "+color);
    }
}


此时,我们只需要将原来由⾃⼰创建的下级类,改为传递的⽅式(也就是注⼊的⽅式),因为我们不需要在当前类中创建下级类了,所以下级类即使发⽣变化(创建或减少参数),当前类本⾝也⽆需修改任何代码,这样就完成了程序的解耦 


1.3 IoC &DI使⽤

既然Spring是⼀个IoC(控制反转)容器,作为容器,那么它就具备两个最基础的功能:

•存 

•取

@Autowired告诉Spring,从容器中取出这个对象,赋值给当前对象的属性

那么如何存呢?

@Component让Spring帮我们存

2. IOC详解

2.1 Bean的存储

类注解:@Controller、@Service、@Repository、@Component、@Configuration

⽅法注解:@Bean

2.1.1 @Controller(控制器存储)

@Controller
public class UserController {
    public void  doController(){
        System.out.println("do Controller...");
    }
}

public static void main(String[] args) {
        //Spring上下文
        //返回的就是Spring的运行环境
        ApplicationContext context = SpringApplication.run(Application.class, args);
        UserController bean = context.getBean(UserController.class);
        bean.doController();
    }

 


2.1.2 @Service(服务存储)

@Service
public class UserService {
    public void doService(){
        System.out.println("do Service...");
    }
}
public static void main(String[] args) {

        ApplicationContext context = SpringApplication.run(Application.class, args);

        //根据名称和类型
        UserService bean3 = context.getBean("userService",UserService.class);
        
        //根据名称,需要进行类型转换
        UserService bean2 = (UserService) context.getBean("userService");
        bean2.doService();
        
        //根据类名
        UserService bean1 = context.getBean(UserService.class);
        bean1.doService();

  
    }

2.1.3 @Repository(仓库存储)

@Repository
public class UserRepository {
    public void doRepository(){
        System.out.println("do doRepository...");
    }
}

2.1.4 @Component(组件存储)

@Component
public class UserComponent {
    public void doComponent(){
        System.out.println("do Component...");
    }
}

2.1.5 @Configuration(配置存储)

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

2.1.6 修改BeanName

 

2.1.7 ApplicationContext 与 BeanFactory(常⻅⾯试题) 

  • 继承关系和功能⽅⾯来说:

Spring容器有两个顶级的接⼝:BeanFactory和 ApplicationContext

其中BeanFactory提供了基础的访问容器的能⼒,⽽ ApplicationContext 属于 BeanFactory 的⼦类,它除了继承了BeanFactory的所有功能之外, 它还拥有独特的特性,还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持.

  • 从性能⽅⾯来说:

ApplicationContext是⼀次性加载并初始化所有的Bean对象,⽽BeanFactory 是需要那个才去加载那个(懒加载),因此更加轻量.(空间换时间)

2.1.8 为什么要有这么多类注解

这个也是和应⽤分层是呼应的.让程序员看到类注解之后,就能直接了解当前类的⽤途

这和每个省/市都有⾃⼰的⻋牌号是⼀样的. ⻋牌号都是唯⼀的,标识⼀个⻋辆的.但是为什么还需要设置不同的⻋牌开头呢. 这样做的好处除了可以节约号码之外,更重要的作⽤是可以直观的标识⼀辆⻋的归属地

2.1.9 注解间关系

这些注解⾥⾯都有⼀个注解 @Component ,说明它们本⾝就是属于 @Component 的"⼦类"

既然这样,那可以使用@Component 代替其它其它注解嘛?

下面来测试以下@Controller与其它注解:

使用@Controller:

使用@Service或@Component:

这表明了程序的入口,只能使用@Controller,不能使用其它.

虽然@Component可以代替其它注解,但是并不推荐代替. 

注意:

这五个注解只能加在类上,并且只能加在自己的代码上

2.2. 方法注解@Bean

2.2.1 问题引入

下面先看一段代码: 

由此可以看出,使用五大注解不管从容器中取多少次对象,取的都是同一个 

①那如果我们需要一个场景:对于一个类,定义多个对象时,比如数据库操作,定义多个数据源,该怎么办?

②并且使⽤外部包⾥的类, 没办法添加类注解 

上述两种情况通过@Bean解决

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

下面假设为第三方包

然后来获取

表明@Bean必须搭配五大注解使用 

2.2.3 定义多个对象

此时加上注解@Configuration再来运行:

先通过类名获取:

此时都是这个类型,不知道获取哪个

那就使用名称或者类名+名称获取:

获取成功

2.2.4 传递参数

 

此时报错

修改代码定义一个叫name的String类型对象

再看下面代码:

这说明:

如果需要的@Bean的类型,对应的对象只有一个,直接赋值

如果有多个,通过名称去匹配 

2.2.5 重命名Bean

也可以

一个Bean可以有多个名称,但是一个名称只能对应一个Bean

2.3 扫描路径

SpringBoot特点:约定大于配置,扫描路径就是其中一个体现

默认扫描路径为:启动类所在的目录及其子孙目录

@ComponentScan可以制定扫描路径,l例如:

如果没有指定,默认就是该注解所在类的目录及其子孙目录

3. DI详解

汽车模型代码中, 是通过构造函数的⽅式, 把依赖对象注⼊到需要使⽤的对象中的 

容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊ 

3.1 属性注入

 

注意:

①属性注入以类型进行匹配,与注入的属性名称无关

②如果一个类型存在多个对象时,优先匹配名称,匹配不上就报错

此时注入两个:

结果为:


既然说与注入的属性名称无关,那就修改:

此时找不到了

③无法注入一个Final修饰的属性

1.定义时就进行赋值

2.构造方法中进行赋值

3.2 构造方法注入

如果只有一个构造方法,@Autowired可以省略

如果存在多个构造函数时,需要加@Autowired,注明使用哪个构造函数 

下面来解释:

①有无参的时候,优先使用无参的构造函数,没有进行赋值,空指针异常 

 

②没有无参的时候,识别不了用哪个

 

 ③所以要告诉Spring用哪个

3.3 Setter注入 

 

此时报错

Setter注入需要加@Autowired

3.4 三种注入方式的优缺点

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

3.5 @Autowired存在的问题

当程序中同一个类型有多个对象时,使用@AutoWired会报错(一些情况下)

3.5.1 解决1

属性名和需要使用的对象名保持一致

3.5.2 解决2@Primary(不推荐)

使用@Primary注解标识默认的对象 

3.5.3 解决3@Qualifier

使用@Qualifier

3.5.4 解决4@Resource

使用@Resource

3.6 @Autowired和@Resource区别

  • @Autowired是spring框架提供的注解,⽽@Resource是JDK提供的注解 
  • @Autowired默认是按照类型注⼊,如果一个类型存在多个对象时,优先匹配名称,匹配不上就报错
  • @Resource是按照名称注⼊.相⽐于@Autowired 来说, @Resource ⽀持更多的参数设置,例如name设置,根据名称获取Bean

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值