IoC 1|学习笔记

简介: 快速学习IoC 1

开发者学堂课程【高校精品课-上海交通大学 -互联网应用开发技术IoC 1】学习笔记,与课程紧密联系,让用户快速学习知识。

课程地址:https://developer.aliyun.com/learning/course/76/detail/15769


IoC 1


内容介绍

一、复习回顾

二、WEB 后端——依赖注入


一、 复习回顾

上节课讲解了 Spring 中的一些 anotation,全部来自于 component,包括 service、repository、controler,它们都是 component 的子类,在 Spring 启动后会扫描工程包并找到这些 anotation 的类,接管这些对象的生命周期,何时创建,何时销毁以及像 controler 坚定哪一个 url,前台的请求发送到后台之后被 Spring 拦截,便知道如何去调用。凡是被 component 注解的都会被 spring 托管。service、repository、controler 这三个本质上无差异,整个分层架构在不同的层面上去用不同的 anotation

标注它。

 

二、 WEB 后端——依赖注入

依赖注入(DI)也被称作控制反转(IOC),即 Inversion of Control。

例如这是 Spring 官网上给出的代码片段,

class MovieLister...

public Movie[] moviesDirectedBy(String arg){

List allMovies = finder.findAll();

for (lterator it = allMovies.iterator(); it.hasNext();) {

Movie movie = (Movie) it.next();

if (!movie.getDirector().equals(arg)) it.remove();

return(Movie[])allMovies.toArray(newMovie[allMovies.size()]);

想搜索一些电影,并按照导演的名字来搜索,希望能返回一些 movies 的数组,会有类似于 repository 的对象,或者是自己设计的 DAO 对象,叫 finder,finder 上会暴露出来一些方法,比如 findAll,之后会返回一个 list,list 上通过迭代器不断迭代,然后 movie 的对象被依次拿出来,跟传递参数做对比,看看是否等于这个参数,如果是则认为这是我们需要找的电影之一,如果不是便把它从集合里移出。

因此 list 里就只包含我们想查找的这些电影了。创建了与剩下元素尺寸相同的 movie 的数组,然后把前面的 movie 的内容转成数组返回。因为这是个 list,所以要转成数组返回。

代码有个问题是,list 的对象如何获取 finder?比如对数据库所有的电影设计了一个叫 movie 的实体类,创建一个 repository,里面的方法已经定义了一些比如 find by id,若此方法不行,再自定义一些方法,在 Query 里写一个 Spring 对象的搜索语言查询条件,创建一个方法,比如 find by dic。那么是如何拿到 repository 的呢?如何拿到 finder 对象的呢?

public interface MovieFinder{List findAll();}

class MovieLister.

private MovieFinder finder;

public MovieLister(){

finder = new ColonDelimitedMovieFinder("movies1.txt");

finder 有一个接口,即 repository 接口,针对它会有一个实践类,实际上是想创建一个实践类的对象,finder 的类型即为接口的类型,所以在 lister 类里,有一个 finder 类型,这个 finder 是上面的 MovieFinder 接口定义的。这里面就有我们调用的 FindAll 方法,这只是一个接口没有任何实践,所以需要再写个实践 ColonDelimitedMovieFinder,作用是读取 txt 文件,不是读数据库,txt 文件是用冒号隔开的,即为电影的名字、年代、导演等。实际上接口的对象无法创建,只能创建接口某个实践类的对象,把它当做接口去使用,下面肯定不是创建 MovieFinder 的对象,必须要指定是哪一个类型。

于是代码里就会出现,在 MovieLister 的构造器里直接创建个对象复制给 finder,创建的是一个用冒号分隔的,它可以处理 movies1.txt 文件,将来 FinderAll 会对这个文本文件进行操作,去找里面用冒号隔开的信息导演,或者把数据返回组装成一个 movie。发现代码有一个问题即代码里直接出现了具体的实践类。假如又定义了一个叫 DBMovieFinder 的类型,它是到 mysql 数据库里找,如果要求 finder 不要再 movies1.txt 里找,替换成这个到数据库里找,会怎样呢?则会修改代码把这段代码ColonDelimitedMovieFinder("movies1.txt");

改写掉。改写掉意味着 MovieLister 的类和具体的实践类是偶合在一起的。

只要换一个实践类,它就需要改。改代码比较麻烦,因为首先要满足一个条件,有源码 sourcecode,其次改完后要有编译的环境 compile,要再进行一次回归测试 test,检查修改之后的代码是否受影响,还要重新部署 redeploy,工作量大,但必须这么做。正常代码里所谓的控制为需要用这个对象,需要创建它,这是正常的控制流,使用正序控制流,需用 new 关键字来创建。写完后发现如果想替换一个实现便很困难。必须满足有源代码的条件,然后进行一系列步骤。

图片1.png

是否有其他的方式?现在的本质是 MovieLister 依赖于 MovieFinder 这个接口,接口很稳定,它不包含任何实现,问题是同时依赖于 implementation 这个实现,所以代码不是很好,只要替换实现就需要改。想用哪个对象就需创建。

Problem

Implemented class of MovieFinder needn't connect to program during compiling the program.

Hope to plug-in concrete implemented class during run-time.

How to make MovieLister class to cooperate with other instances while they don't know the details of the implemented class?

Solution

Inversion of Control

代码出现了很大的问题,本来实现 MovieFinder 这个类,不必一定要跟代码连接在一起,所以希望具体的实现类能够在运行中确定是什么东西。插入到 MovieLister 里,这样 MovieLister 就只依赖于 MovieFinder 这个接口,不依赖于具体的实践类。在运行时刻,动态地创建一个实现类,插入到 MovieLister 里,这样 MovieLister 在哪儿都不用改。Inversion of Control 叫控制反转,控制如何反转呢?

What aspect of control are they inverting?

In naive example the lister looked up the finder implementation by directly instantiating it

This stops the finder from being a plugin.

The inversion is about how they lookup a plugin implementation.

Any user of a plugin follows some convention that allows a separate assembler module to inject the implementation into the lister.

Dependency Injection

lister 这个对象里会直接用 new 关键字去创建刚刚的实现类,所以我们需要让外界有一个第三方的工具。比如有一个单独分开来的组装模块,这就是 spring core 提供的一部分功能,可以认为 spring 就是这个角色,它来帮助形成一个具体的实现类。然后把它附给在 lister 里定义的 finder 类型的变量。因此代码依赖于 finder 对象,但是代码 finder 不是自己做的,而是 spring 做完后把对象附给了 finder,所以是注入进来了,依赖注入即为控制反转的途径。

如何进行依赖注入?

图片2.png

代码里始终有一个 MovieFinder,然后进行编程,外部有一个第三方类似于 spring,它会创建一个实现 MovieFinder 类的接口的实例,它把对象的引用附给了持有的 finder 对象。因此控制流反过来了。finder 不是用 new 关键字创建的,是第三方创建的。创建完后把对象的引用拿回来,给了这个 MovieFinder 类里的对象,这便是控制反转。控制反转中控制流反过来了。spring 如此智能吗?它怎么知道要做这些事?

显然不可能,没这么智能,需要告诉他。如何告诉?需要在代码里写一堆的 anotation。anotation 不是给 Java 编译器用的,Java 本身要 anotation 没有任何用,anotation 是第三方的框架。在一个代码里出现了 anotation 后,比如 service,这是个 Java 的文件,Java 编译器拿到它之后是忽略掉这些的。它只针对写的 Java 语句编译出来的 .class 文件,但是可以把 anotation 放进 .class 文件里,这些 anotation 对 Java 编译出来的没有任何影响,方法编译出来有一些字节码,这些字节码有无 anotation 编译出来都相同,所以留在里面比如 spring 读到 class 里的 anotation 会做一些事情。

每一种工具都有自己的 anotation,所以假如某一天写了一个工具,写了一个 anotation 叫 lunch,写好地点等其他属性,写到一个方法上,比如 order,写了一个工具比如饿了吗,则饿了吗这个框架拿到编译好的 class 文件后就去读里面有 lunch 的 anotation,其他的比如 service anotation 不用管。

饿了吗读有 kunch 的 anotation,便知道再给定的时间把方法发送到餐厅,所以 anotation 是框架,在 anotation 里写清楚,编译完后会在做一次处理,所以要想告诉 spring 如何帮忙做,便需要在变量前面的 anotation 里写,这样编译完的 class 文件被 spring 的框架拿到之后,便知道如何做了。

逻辑是按名字查找,发现这两个名字前一半是接口,也可以人为地写这个类是什么。这个机制比较复杂,需要同学们自己看手册。接口和实现的差异为在实现后加一个 impl,三个字母或四个字母都可以,只要 spring 能解析出来就可以。

通过这种方式出来之后,Your Business Objects(POJOs) 这个对象被扔进来后会根据配置信息,去帮助做依赖注入,把需要注入的对象信息注入到对象里,出来的就是一个可以直接使用的类了。再也不是只声明了一个 finder,没有给出定义的对象了。

图片3.png

举例,

hello/MessageService.java

package hello;

public interface MessageService{

String getMessage();

@Component

public class MessagePrinter{

@ Autowired

private MessageService service;

public void printMessage(){

System.out.println(this.service.getMessage());

MessageService 是一个接口,有一个方法叫 getmessage,所有实现在这个接口的类,将来都可以调用 getmessage 的方法得到一个消息,这个方法的实现是各自不同方法的实现类,他们自己去实现。只要写了 component,在 spring 启动扫描后整个代码就知道这个类是需要去托管,有一个 MessageService 的对象。Autowired 的含义是通过 spring 依赖注入的方式创建一个对象附给它,看这个代码,只声明了一个对象是 service,没有去给它定义,此时如果调用 getMessage,一定会报错,因为这是个空指针,没有对象。这里为什么可以调用 getmessage 呢?原因为spring 创建的对象附的这个是有一个对象,这确实是个 MessageService,是个接口类型,并没有说必须要跟某个接口的实践类绑定。

hello/ Application.java

@Configuration

@ComponentScan

public class Application{

@Bean

public MessageService mockMessageService() {

return new MockMessageService();

return new AnotherMessageService();

return new MessageService() [

public String getMessage() i

return "Hello World!";

有一个叫 MockMessageService 的方法,

return new MockMessageService();

return new AnotherMessageService();

return new MessageService() [

这三句话需要注释两句,只留一句。这个方法的返回值是message service类型,这个对象要么是Mock message service,要么是another message service,要么是底下定义的一个匿名类 getMessage。

这个匿名类说 getmessage 返回 Hello Word。Mockmessage service返回的是"hello world Mock message service!"Another message service返回的是"hello world another message service!"他们三个是三种不同的实现。

hello/MockMessageService

package hello;

public class MockMessageService implements MessageService {public String getMessage() {

return "Hello World! Mock Message Service!";

}

}

它实现了接口。

hello/ AnotherMessageService

package hello;

public class AnotherMessageService implements MessageService lpublic String getMessage() {

return "Hello World! Another Message Service!";

}

}

不管怎么样会返回一个结果,这个方法如果想被 spring 托管,需要在前面加个 Bean。当 spring 托管需要获取一个message 类型对象时,可以通过这个方法来获取。

整个application没有写外部应用,只是写了个简单应用,所以 spring 如果启动之后扫描整个系统配置,需要在底下这个类里扫。Java的图标是咖啡杯,他就是咖啡豆 coffe bean。所有组成单位最小的是 bean。所以 bean 的意思是可以进行的对象参数。加载类型的时候,一定要把当前的这个包所在的包里的所有的类都去扫描一遍,扫描一下,看看哪些有 component。component 配置信息在当前 Application 里有。当前配置信息在哪儿呢?就是 bean 里描述的东西。当 skan 里面有需要获取对象的时候,bean 注解的方法便会得到。这三个方法返回的都是 MessageService 类的实现类对象。三个实现各不相同。第一和第二是自己定义的,第三个是匿名的实现类。

hello/Application.java

public static void main(String[] args) {

ApplicationContext context =

new AnnotationConfigApplicationContext(Application.class);MessagePrinter printer=context.getBean(MessagePrinter.class);

printer.printMessage();

}

}

要跑这个应用,目的是创建一个 MessagePrinter 的对象,调 PrinterMessage 的方法。希望得到 MessagePrinter Printer 对象,加载 MessagePrinter 这个类,联系上下文对象,整个应用跑起来,有个 ApplicationContext,它注解配置应用上下文,配置哪里去了呢?就是刚才的这个类。所以 spring在执行

AnnotationConfigApplicationContext(Application.class);

这一行代码时就知道配置从 application 里来,拿的逻辑是首先要配置文件,其次扫描整个包,整个包里凡是需要注入 MessageService 的地方,都通过 MessageService 这个类去获取。在 printer 上面调 PrinterMessage,结果就出来了。逻辑是知道了配置信息,然后获取一个 MessagePrinter 对象,对象通过上下文,通过 bean 获取的,bean 是最小的可执行单位,所以到整个包里扫描,找到了 message printer。在里面创建一个对象,看到是 component,所以来帮忙托管,并创建 MessagePrinter,创建完后发现 Service 对象需要 spring 依赖注入,怎么依赖注入呢?

找配置文件。在这段代码里,配置文件是 application。整个代码的好处是,

return new MockMessageService();

return new AnotherMessageService();

return new MessageService() [

这三个每次执行时时只需打开一个,其他的注释掉,三个里任意打开一个,首先这段代码即 MessagePrinter 是不发生任何改变的,不要动它,因为在代码里从来没有说 service 是哪一个类,可以是接口类,给这三个任意一个都可以。都有 getMessage 方法,所以做切换时,不影响这边的任何代码。代码里不需有 MovieFinder 实践类,这个对象是哪来的?是根据后面的配置信息得到的,无论怎么改,改哪个前面的代码不会做任何修改。凡是使用 MessageService 实践类,这便是依赖注入。

跑一个例子感受一下。

图片4.png这个是刚才的代码,有一个代码,有一个类,写了几个实现类,第一个是Mock message service,它实现自message service,Get message返回了一个信息,然后another service message,他的 get message 返回的是另外的一句话,最后是 application,首先配置信息在里面,其次 component 扫描整个包,发现里面哪些东西需要托管。先注释掉两个,默认的message service 会返回 hello word,他在这里执行application的context,配置信息在application 里,通过它去创建 Message printer,在这里注入了 service 对象,用 autowired 标注 service 对象,通过刚才调用 service 的方法,创建一个 message service 对象。之后在 printer 上调,如果把它执行一下,可以看到,返回的是那就hello word。把它注释掉,开一个 another message,现在往 message 里注入的就是 another message。再跑一遍,看到显示的是Hello word another message service。所以这个例子的重点在于,无论返回哪一个,因为在这里是用 spring 获取的 message printer,组装的依据是通过 bean 来描述,所以在这里来回切换不管用哪一个,始终没有改 message printer的代码。所以未来 Message service 在很多地方应用,替换实现只需要在这里改动一下,其他代码都不动。

为什么这是依赖的反转呢?因为在正常情况下,message service 等于 another message service,正常情况下用 service ,就需要创建,现在相反,不创建了,是 spring 根据逻辑创建一个,并引用对象,附到 service 上,所以叫控制反转。依赖于这个对象,这个对象不是自己创建的,把对象注入进来。

A Spring loC container manages one or more beans.

These beans are created with the configuration metadata that you supply to thecontainer,

for example, in the form of XML <bean/> definitions.

Within the container itself, these bean definitions are represented as

BeanDefinition objects, which contain (among other information) the following metadata:

A package-qualified class name: typically the actual implementation class of the bean beingdefined.

Bean behavioral configuration elements, which state how the bean should behave in thecontainer[scope, lifecycle callbacks, and so forth).

References to other beans that are needed for the bean to do its work; these references arealso called collaborators or dependencies.

Other configuration settings to se in the newly created object,

for example, the number of connections to use in a bean tha manages a connection pool, or thesize limit of the pool.

This metadata translates to a set of properties that make up each bean definition.

所谓的依赖反转是 spring 需控制很多 bean,创建时用所给的配置信息,怎么创建一个 message service?是自己给的配置信息来创建的。然后把它注入进去,这些配置信息可以写到 anotation,container 拿到 bean 后回去找实际的实现类,找到后把 bean 创建出来,设置状况,复制给需要的声明的对象。如果 bean 还需要一些其他的类,所以把需要有依赖关系的对象都录入进去创建。按照所给的其他的设置去设置一下,最后会把对象复制。

举例

Constructor-based dependency injectionpublic class ExampleBean

//No. of years to the calculate the Ultimate Answerprivate int years;

//The Answer to Life, the Universe, and Everythingprivate String ultimateAnswer;

public ExampleBean (int years, String ultimateAnswer)lthis.years = years;

this.ultimateAnswer = ultimateAnswer;

public String answer()(

return ultimateAnswer+"" + years;

]

有一个 exambean,依赖注入如何注入?比如有一个examplebean,有两个量 year 和 ultimateanswer,这两个值不想在代码里写死,一旦传进来,比如传入("40","1000"),能不能让 spring 帮忙创建examplbean?有一个问题,那么这两个值是多少?需要写一个配置文件,这里有一个构造器,有两个参数,会读 xml 文件,要去加载这个文件,用 context 获取 bean,让 spring 帮忙创建一个 examplebean,永远都不会写 new examplebean 这样的代码,只是说创建这样的代码,没说参数是什么,Spring 拿到代码之后进行创建,加载出后去配置文件中找,发现要创建的是这个对象,里面有很多参数,拿到参数的值,复制到构造器里,client 代码没有创建参数代码,是配置过去的。

hello/ExampleBeanClient.java

package hello;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicatioContext

public class ExampleBeanClientl

public static void main (String[] args) l

ApplicationContext context =

new ClassPathXmlApplicationContext(new Stringl] ("ExampleContext.xmI");ExampleBean bean = context.getBean(ExampleBean.class);

System.out.println(bean.answer());

看一下效果。配置文件有两种写法。配置文件里面可以通过构造器参数的方式。没告诉参数是什么,可以直接调用。输出的结果是42。可以看到没有改动任何代码,只是把配置文件改了。在构造器层面上,配置文件有三种写法。按照构造器顺序,按照类型,代码,client,构造器都没有变。

这个例子说明现在发生了三次变化,因为给的配置文件不一样,但是代码和 client 没有变,bean 也没有变。本身并不需要自己设置它的值是什么。

bean 读取这两个字之后,把他们复制到构造器里,这样的好处是,比如这是打折率变量,数字会根据节日的变化而发生变化,代码不能写死,否则很麻烦。有一种不改源代码的方式,XML ,XML 不存在编译的问题,文本编辑器打开改一下,只要对源代码没有任何的修改,就不需要进行重新的编译和测试,打开一个编译器随便改一下,如果遇见不同的时间改一下配置文件,改一下 discount 值就可以。

再比如说,电子书编写的好,需要给A B C三个同学用,不用写三份代码,只写一份就可以,因为代码没写死,只要按照要求设置对了就可以。

相关文章
|
6月前
|
XML Java 数据格式
从六个方面读懂IoC(控制反转)和DI(依赖注入)
在一开始学习 Spring 的时候,我们就接触 IoC 了,作为 Spring 第一个最核心的概念,我们在解读它源码之前一定需要对其有深入的认识,对于初学Spring的人来说,总觉得IOC是模糊不清的,是很难理解的,今天和大家分享网上的一些技术大牛们对Spring框架的IOC的理解以及谈谈我对Spring IOC的理解。
200 2
|
XML Java 数据格式
03Spring - 控制反转IOC入门案例
03Spring - 控制反转IOC入门案例
46 0
|
6月前
|
XML Java 数据格式
深入理解 Spring IoC 和 DI:掌握控制反转和依赖注入的精髓
在本文中,我们将介绍 IoC(控制反转)和 DI(依赖注入)的概念,以及如何在 Spring 框架中实现它们。
369 0
|
6月前
|
XML Java 数据格式
Spring框架入门:IoC与DI
【5月更文挑战第15天】本文介绍了Spring框架的核心特性——IoC(控制反转)和DI(依赖注入)。IoC通过将对象的创建和依赖关系管理交给容器,实现解耦。DI作为IoC的实现方式,允许外部注入依赖对象。文章讨论了过度依赖容器、配置复杂度等常见问题,并提出通过合理划分配置、使用注解简化管理等解决策略。同时,提醒开发者注意过度依赖注入和循环依赖,建议适度使用构造器注入和避免循环引用。通过代码示例展示了注解实现DI和配置类的使用。掌握IoC和DI能提升应用的灵活性和可维护性,实践中的反思和优化至关重要。
363 4
|
6月前
|
Java 测试技术 开发者
[Spring 基础] IOC控制反转
[Spring 基础] IOC控制反转
|
Java 容器 Spring
[javaweb]——spring框架之控制反转(IOC)与依赖注入(DI)
[javaweb]——spring框架之控制反转(IOC)与依赖注入(DI)
|
XML 开发框架 Java
Spring框架IoC控制反转
Spring是与2003年兴起的一个轻量级的Java开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring的核心是控制反转(IOC)和面向切面编程(AOP)。Spring是可以在Java SE/EE中使用的轻量级开源框架。 Spring的主要作用就是为代码"解耦",降低代码间的耦合度。就是让对象和对象(模板和模板)之间关系不是使用代码关联,而是通过配置来说明。即在Spring中说明对象(模块)的关系。 Spring根据代码的功能特点,使用IOC降低业务对象之间耦合度。IOC使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了,而是由Spring容器统一
81 2
|
安全 前端开发 Java
IoC 2‍|学习笔记
快速学习IoC 2‍
107 0
IoC 2‍|学习笔记
|
运维 Java vr&ar
控制反转|学习笔记
快速学习控制反转
控制反转|学习笔记
|
Java API C++
IOC理解
成功就是简单道理的深刻理解与灵活运用 前不久,阿里大牛虾总在群里抛出一个问题:“从深层次讲解一下如何理解IOC,IOC和DI是一回事吗?” 这个问题真是让人平静而又不平静 平静源于此问题就像问中国人怎么使用筷子,天天使用筷子,难道还不会使用筷子? 但又不平静,你能写出一份详细的说明书,让一个不会使用筷子的人按此说明成功地使用上筷子吗?
351 0
IOC理解