Spring IoC&DI

目录

一、IoC介绍

二、DI介绍

三、IoC&DI的使用

Bean的存储

获取Bean对象的方式

方法注解@Bean

重命名Bean

四、依赖注入

属性注入

构造方法注入

Setter注入

@Autowired存在的问题

总结


Spring是一个开源框架,让我们的开发更加简单,我们用一句更具体的话来概括Spring就是:Spring是包含了众多工具方法的IoC容器这里的容器类似于我们在数据结构阶段学习的List/Map等数据存储容器一样用来存储IoC

一、IoC介绍

IoC是Spring的核心思想,在类上面添加注解@RestController和@Controller意思是将这个对象交给Spring管理,Spring框架就会在启动时加载该类;将对象交给Spring管理,就是IoC思想。

@RestController
@RequestMapping("/Message")
public class MessageController {

}

IoC:全程 Inversion of Control(控制反转),也就是说Spring是一个控制反转的容器

控制反转也就是控制权反转,当需要某个对象时,传统的开发模式中需要通过自己new创建对象,现在只需要将创建对象的任务交给容器(Spring),程序中只要进行依赖注入(Dependency Injection,DI)就可以了。

通过案例来了解一下IoC

当我们想设计一辆车时,传统的实现思路是这样的:先设计轮子(Tire)然后根据轮子的大小设计底盘(Bottom),再根据地盘来设计车身(Framework),最后根据车身设计好整个汽车(car),此时就会出现耦合非常高依赖关系

根据思路可以写出这样的代码

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

    static class Car{
        private Framework framework;
        public Car(){
            framework = new Framework();
            System.out.println("car init");
        }
        public void run(){
            System.out.println("car run");
        }

    }

    static class Framework{
        private Bottom bottom;
        public Framework(){
            bottom = new Bottom();
            System.out.println("Framework init");
        }
    }
    static class Bottom{
        private Tire tire;
        public Bottom(){
            this.tire = new Tire();
            System.out.println("Bottom init");
        }
    }
    static class Tire{
        private int size;
        public Tire(){
            this.size = 10;
            System.out.println("轮胎大小: "+ size);
        }
    }
}

此时我们想创建一个car对象先调用了车身framework,车身对象的创建需要地盘bottom,以此类推,所以我们的打印顺序为:

因为不同的厂商对于轮胎的尺寸有不同的要求,所以我们不能把代码写死,就要在Tire的构造方法上添加参数int size,修改后因为其他的调用程序都用调用了该方法,所以其他程序会报错,我们得继续修改,可谓是牵一发而动全身,当底层代码改动后整个调用链所有代码都需要修改。

解决方案

在上面的程序中我们根据轮子的尺寸设计底盘,改变轮子需要改动一系列程序,而我们选择换一种思路,先设计汽车的样子,根据汽车的样子来设计车身,根据车身设计地盘,这时候依赖关系就导致过来了:轮子依赖地盘,地盘依赖车身,类似于我们打造一辆完整的汽车,汽车的配件都向代理工厂下单,需求变化时只需要改变向代理工厂下的单就可以了。

根据这个思路可以将代码改成这样:

public class demo2 {
    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);
        car.run();
    }

    static class Car{
        private Framework framework;
        public Car(Framework framework){
            this.framework = framework;
            System.out.println("car init");
        }
        public void run(){
            System.out.println("car run");
        }

    }

    static class Framework{
        private Bottom bottom;
        public Framework(Bottom bottom){
            this.bottom = bottom;
            System.out.println("Framework init");
        }
    }
    static class Bottom{
        private Tire tire;
        public Bottom(Tire tire){
            this.tire = tire;
            System.out.println("Bottom init");
        }
    }
    static class Tire{
        private int size;
        public Tire(int size){
            this.size = size;
            System.out.println("轮胎大小: "+ size);
        }
    }
}

通过这样的调整,无论底层发生什么样的变化,整个调用链是不用做任何改变的,这样就完成了代码的解耦,这就是IoC思想开发

这两种方法的实现代码类的创建顺序是反的,改进后的控制权发生了反转,不再是使用方创建并控制依赖对象,而是把依赖对象注入当前对象中,依赖对象的控制权不再是当前类控制。

到这就大概可以知道什么是控制反转了,控制反转容器就是IoC容器,

这部分代码就是IoC容器做的工作

IoC容器的优点:

1、资源集中管理:IoC容器会帮我们管理一些资源,我们需要使用时只需要从容器中去取就行

2、我们在创建实例的时候不需要了解其中的细节,降低了使用资源双方的依赖程度,也就是耦合降低了

二、DI介绍

DI:全称Dependency Injection(依赖注入)容器在运行期间,动态的为应用程序提供运行时所依赖的资源,称为依赖注入。

刚才的代码中就是通过构造方法的方式将依赖对象注入到需要使用的对象中

IoC是一种思想,也是目标,思想是一种指导原则,最终还是要有可行的落地方案,而DI就是具体的实现,所以可以说DI是IoC的一种实现。

三、IoC&DI的使用

既然Spring是一个IoC容器,作为容器就具备两个最基础的功能:存/取;

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

Bean的存储

Spring框架为了更好的服务web应用程序,提供了更丰富的注解,共有两类注解类型可以实现:

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

2、方法注解:@Bean

只要在想要交给Spring管理的类上面加上五大类注解即可完成“存”操作,那么为什么要分成五大注解呢?下面将进行讲解,先来了解获取Bean对象的方式

获取Bean对象的方式

获取Spring上下文对象,从上下文中获取对象

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
    ApplicationContext context = SpringApplication.run(DemoApplication.class);
	//从Spring中获取上下文context
	
	
	UserController userController = context.getBean(UserController.class);
	//从上下文中获取bean对象
}

上述方法是通过类型来查找对象通过创建好的spring上下文使用UserController创建对象来接收提取的类对象,但此方法只适用于一个类型存在一个bean对象

如果同一个类型存在多个bean该怎么来获取呢?

ApplicationContext也提供了其他获取bean的方式

ApplicationContext获取对象的功能是父类BeanFactory提供的功能

//1、根据bean名称获取bean    
 Object getBean(String name) throws BeansException;

//2、根据bean名称和类型获取bean
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

//3、根据bean名称和构造函数参数动态创建bean,只适用于具有原型作用域的bean
    Object getBean(String name, Object... args) throws BeansException;

//4、根据类型获取bean
    <T> T getBean(Class<T> requiredType) throws BeansException;

//5、根据bean类型和构造函数参数动态创建bean
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

常用的是上述124,这三种方式获取到的bean是一样的,12中都涉及到根据名称来获取名称

bean的名称

Spring bean是Spring框架在运行时管理的对象,Spring会给管理的对象起一个名字,Bean的命名约定可以参照官方文档Bean命名约定

程序员不需要为bean指定名称,如果没有显示的名称,Spring容器将为bean生成唯一的名称,命名约定使用Java标准约定作为实例字段,也就是说bean名称以小写字母开头,使用驼峰式大小写,例如类名UserController,bean的名称就为userController;对于特殊情况:当有多个字符并且第一个第二个字符都是大写时将保留原始的大小写,例如类名UController,bean的名称为UController。

根据这个命名规则我们来获取bean


@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//SpringApplication.run(DemoApplication.class, args);
		ApplicationContext context = SpringApplication.run(DemoApplication.class);
        
		//根据类型获取
		ControllerDemo controllerDemo = context.getBean(ControllerDemo.class);
		//根据名称获取
		ControllerDemo controllerDemo1 = (ControllerDemo) context.getBean("controllerDemo");
		//根据类型和名称获取
		ControllerDemo controllerDemo2 =  context.getBean("controllerDemo",ControllerDemo.class);

		System.out.println(controllerDemo);
		System.out.println(controllerDemo1);
		System.out.println(controllerDemo2);
	}

通过日志打印的结果可以看出这三种方法取到的是同一个对象

ApplicationContext 与 BeanFactory的关系

继承关系和性能方面:Spring容器有两个顶级接口:BeanFactory和ApplicationContext,其中BeanFactory提供了基础的访问容器能力,而ApplicationContext属于BeanFactory的子类,它除了继承父类的所有功能外它还拥有独特的特性,添加了堆国际化的支持,资源访问支持,以及事件传播等方面的功能。

从性能方面:ApplicationContext是一次性加载并初始化所有Bean对象,而BeanFactory是需要哪个才去加载哪个,因此更加轻量。

了解了Bean的获取,我们来了解为什么要这么多类注解:

为了应用分层,让程序员看到类注解之后就能直接了解当前类的作用

@Controller:控制层,接受请求,对请求进行处理并进行响应.

@Service:业务逻辑层,处理具体的业务逻辑

@Repository:数据访问层,也称为持久层,负责数据访问操作

@Configuration:配置层,处理项目中的配置信息

像车牌的根据不同的车牌开头可以直观的标识一辆车的归属地

程序的应用分层调用顺序如下:

看这个四个注解的源码可以发现,它们本身属于@Compoent的子类,实际开发中@Controller@Service@Repository用于更具体的用例。

方法注解@Bean

类注解是添加到某个类上的,但是存在两个问题:1、使用外部包里的列没办法添加类注解。2、一个类需要多个对象。

这两种情景我们就需要使用方法注解@Bean

需要注意使用方法注解要搭配类注解使用

@Component
@Data
public class User {
    public String name;

    @Bean
    public User demo1() {
        User user = new User();
        user.setName("zhangsan");
        return user;
    }
    @Bean
    public User demo2() {
        User user = new User();
        user.setName("lisi");
        return user;
    }
}

当我们直接使用类型名来获取bean时可以发现报错信息提示我们User类中有三个匹配的对象,接下来我们通过名称来获取bean对象

此时我们就成功获取到了想要的bean对象

重命名Bean

可以通过设置name属性给Bean对象进行重命名操作,如下此时可以通过user1来访问demo1了

当只有一个对象时也可以不使用大括号

四、依赖注入

依赖注入是一个过程,是指IoC容器在创建Bean时去提供运行时所依赖的资源(对象),向IoC容器中存储完对象,就要有一个取的动作,我们使用@Autowired这个注解来取出对象放到类中使用,完成依赖注入的操作。

简单来说,就是把对象取出来放到某个类的属性中。

关于依赖注入,Spring也给我们提供了三种方式:

1、属性注入。2、构造方法注入。3、Setter注入

属性注入

属性注入使用@Aurowired实现,将Service类注入到Controller类中

Service类代码:

@Service
public class ServiceDemo {
    public void sayHello() {
        System.out.println("Hello World...Service");
    }
}

Controler代码:

@Controller
public class ControlerDemo {
    @Autowired
    private ServiceDemo serviceDemo;
    public void sayHello() {
        System.out.println("Hello World...Controller");
        serviceDemo.sayHello();
    }

}

获取Controller中的sayHello方法

通过日志我们可以发现获取Service对象成功了

构造方法注入

是在类的构造方法中实现

@Controller
public class ControlerDemo {
    private ServiceDemo serviceDemo;
    @Autowired
    public ControlerDemo(ServiceDemo serviceDemo) {
        this.serviceDemo = serviceDemo;
    }

    public void sayHello() {
        System.out.println("Hello World...Controller");
        serviceDemo.sayHello();
    }

}

Setter注入

在设置set方法的时候加上@Autowired

@Controller
public class ControlerDemo {
    private ServiceDemo serviceDemo;

    @Autowired
    public void setServiceDemo(ServiceDemo serviceDemo) {
        this.serviceDemo = serviceDemo;
    }

    public void sayHello() {
        System.out.println("Hello World...Controller");
        serviceDemo.sayHello();
    }

}

@Autowired存在的问题

当同一类型存在多个bean时,使用@Autowired会存在问题,Spring提供了几种解决方案:

1、@Primary,当存在多个相同类型的Bean注入时,加上这个注解来确认默认的实现

2、@Qualifier,指定当前要注入的bean对象,在注解的value属性中指定对象的名称

@Controller
public class ControlerDemo {
    
    @Autowired
    @Qualifier("serviceDemo")
    private ServiceDemo serviceDemo;
    public void sayHello() {
        System.out.println("Hello World...Controller");
        serviceDemo.sayHello();
    }

}

3、@Resource,按照bean的名称的注入,通过name属性指定要注入的bean名称

@Controller
public class ControlerDemo {
    @Resource(name = "serviceDemo")
    private ServiceDemo serviceDemo;
    
    public void sayHello() {
        System.out.println("Hello World...Controller");
        serviceDemo.sayHello();
    }

}

@Autowired和@Resource的区别

@Aurowired是Spring框架提供的注解,而Resource是JDK提供的注解

@Autowired是默认按照类型注入,而Resouce按照名称注入,相比于@Autowired来说Resource支持更多的参数设置。

总结

Spring/Spring Boot/Spring MVC的关系和区别

Spring:Spring是一个开发应用框架,有这么几个标签来描述:轻量级/一站式/模块化,其目的是用于简化企业级应用程序开发;Spring的主要功能是管理对象以及对象间的依赖关系

Spring MVC:是基于Spring开发的一个子框架,主要用于开发WEB应用和网络接口

Spring Boot:对于Spring的一个封装,为了简化Spring应用开发而出现的,可以更加快速的搭建框架降低开发成本,让开发人员更加专注于Spring应用的开发而无需过多关注XML的配置和一些底层的实现

感谢观看

道阻且长,行则将至

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值