首先我们要知道 Ioc是个啥?
? IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找
可是我们为什么要有这个思维,它的优点是什么? 我看到网上的一种解释特别好
要了解控制反转( Inversion of Control ), 我觉得有必要先了解软件设计的一个重要思想:依赖倒置原则(Dependency Inversion Principle )。什么是依赖倒置原则?假设我们设计一辆汽车:先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。这里就出现了一个“依赖”关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。这样的设计看起来没问题,但是可维护性却很低。假设设计完工之后,上司却突然说根据市场需求的变动,要我们把车子的轮子设计都改大一码。这下我们就蛋疼了:因为我们是根据轮子的尺寸设计的底盘,轮子的尺寸一改,底盘的设计就得修改;同样因为我们是根据底盘设计的车身,那么车身也得改,同理汽车设计也得改——整个设计几乎都得改!我们现在换一种思路。我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘, 底盘依赖车身, 车身依赖汽车。这时候,上司再说要改动轮子的设计,我们就只需要改动轮子的设计,而不需要动底盘,车身,汽车的设计了。这就是依赖倒置原则——把原本的高层建筑依赖底层建筑“倒置”过来,变成底层建筑依赖高层建筑。高层建筑决定需要什么,底层去实现这样的需求,但是高层并不用管底层是怎么实现的。这样就不会出现前面的“牵一发动全身”的情况。
我结合自己的实际学习过程,会发现在我们学习javase基础知识时我们一般都会想的是需要什么就建立什么对象,假如在我们建立三个接口分别是dao接口和service接口还有test接口每个接口都有对应的实现类分别是daoImp和serviceImp还有testImp,我们在dao接口里面定义了一个 String show();方法,daoImp实现了这个方法public string show(){System.out.println(“我是dao的方法”)}
IoC容器
日常的Java项目开发都是由两个或多个类的彼此合作实现业务逻辑的,这使得每个对象都需要与其合作的对象的引用(称为所依赖的对象),如果合作的对象的引用或依赖关系由具体的对象实现,这对复杂的面向对象系统的设计与开发是非常不利的,由此,如果能把这些依赖关系和对象的注人交给框架实现,让具体对象交出手中对于依赖对象的控制,就能很大程度上解耦代码,这显然是极有价值的,而这就是“依赖反转”,即反转对依赖的控制,把控制权从具体的对象中转交到平台或者框架。 依赖反转的实现有很多种,在Spring中,IoC容器就是实现这个模式的载体,它可以在对象生成或初始化的过程中,直接将数据或者依赖对象的引用注人对象的数据域中,从而实现方法调用的依赖。而这种依赖注人是递归的,依赖对象会被逐层注人,从而建立起一套有序的对象 依赖关系,简化了对象依赖关系的管理,把面向对象过程中需要执行的如对象的创建和对象引用赋值等操作交由容器统- 管理,极大程度上降低了面向对象编程的复杂性。 在Spring中Spring IoC提供了一个基本JavaBen容器,通过loC模式管理依赖关系,并通过依赖注人和AOP切面对类似POIO这样的对象提供了事务管理生命周期管理等功能,在应用开发中,设计组件往需要引人和调用其他组件的服务时,这种你关系如果固化在组件设计中,就会导致组件之间的耦合和维维护难度增大,这时如果使用IoC容器,把资源获取的方式反转,让IoC容器主动管理这些依赖关系,将依赖关系注人到组件中,那么这些依赖关系的适配和管理就会更加灵活。,能通过可视化的 文本完成应用管理依赖关系时,如果在IoC实现依赖反转的过程中那么肯定能能提高依赖关系配置,并且通过工具对这些配置信息进行可视化的管理和浏览,那代码,这符合在面向的管理水平,而且如果耦合关系变动,并不需要重新修改和编译Java对象过程中的开闭原则。Spring倡导的开发方式就是如此,所有的类都会在Spring容器中登记,告诉Spring你是一个什么组件,需要哪些组件,然后Spring会在系统运行到适当的时候,把你要的组件或对象主动给你,同时也把你交给其他需要你的组件或对象。所有对象的创建、销毁都由Spring控制,也就是说,控制对象生存周期的不再是引用它的对象,而是Spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被Spring控制,所以称控制反转。IoC的一个重点是在系统运行中,动态地向某个对象提供它所需要的其他对象。这- .点是通过DI( Dependency Injection, 依赖注人)实现的。例如,对象A需要操作数据库,以前我们总是要在A中自己编写代码获得一个Connection对象,有了Spring, 就只需要告诉Spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。系统运行时,Spring会在适当的时候制造一个Connection,然后像打针一样注射到A中,这样就完成了对各个对象之间关系的控制。A需要依赖Connection才能正常运行,而这个Connection是由Spring注人A中的,依赖注人的名字就是这么来的。
Spring的依赖注入
依赖注ADpeneney leioeiD与IoC的含义相同,只不过这两个称呼是从两个角度描述同一个概念。对于一个Spring初学者来说,这两个概念很难理解。下面对这两个概念进行简单的介绍。 当某个Java对角(调用对角)需要调用另一个Java对象(被调用者,即被依赖对象)时,在传统模式下,调用者通常会采用new运算符创建一个对象,这种方式会导致调用者与被调用者之间的耦合性增加,不利于后期项目的升级维护 在使用了spring框架之后对象的实例不再由调用者创建,而是用spring容器创建,spring容器负责控制对象之间的关系取代了由调用者控制对象之间的关系。这样,对象之间的依赖由程序转移到IoC容器,控制权发生了反转,这就是spring控制反转。 从Spring容器的角度看,Spring容器负责将被依赖对象赋值给调用者的成员变量,这相当于调用者注人了它依赖的实例,这就是Spring的依赖注入了
这样表述不是很清楚我们来看下代码
public interface Dao {
void show();
}
package dao;
public class daoImp implements Dao{
public void show(){
System.out.println("我是dao的方法");
}
}
package service;
public interface service {
void show();
}
package service;
import dao.Dao;
import dao.daoImp;
public class serviceImp implements service{
Dao dao=new daoImp();
public void show(){
dao.show();
}
}
import service.service;
import service.serviceImp;
public class test {
public static void main(String[] args) {
service s=new serviceImp();
s.show();
}
}
结果是
我是dao的方法
通过这种方式我们可以直观的看出来我们的test和dao没有接触过,假如我们先要再添加一个实现类MysqlImp去实现dao接口会怎么样
package dao;
public class MysqlImp implements Dao {
public void show(){
System.out.println("我是mysql的实现类");
}
}
我们通过test去访问这个实现类的方法时就需要去修改service的方法 修改后为
package service;
import dao.Dao;
import dao.MysqlImp;
public class serviceImp implements service{
Dao dao=new MysqlImp();
public void show(){
dao.show();
}
}
结果为
我是mysql的实现类
通过这次测试我们可以看出来,每次增加一种需求时都会修改原有的代码,这种方式代码耦合性比较高,目前代码量比较少,可能没有直观的感受,但是如果代码量比较大是修改起来耗费就有些昂贵了,我们其实可以修改为多态类型这样我们每次增加实现类都可以直接添加 修改后的serviceImp为
package service;
import dao.Dao;
import dao.MysqlImp;
public class serviceImp implements service{
private Dao dao1;
public void setDao1(Dao dao1){
this.dao1=dao1;
}
Dao dao=new MysqlImp();
public void show(){
dao.show();
}
}
修改后的test为
import dao.MysqlImp;
import service.service;
import service.serviceImp;
public class test {
public static void main(String[] args) {
service s = new serviceImp();
((serviceImp) s).setDao1(new MysqlImp());
s.show();
}
}
通过这种形式我们假如有个servletImp的类也同样实现了Dao接口那么我们去实现它时不用和之前一样去修改,serviceImp的代码了,我们只需要在test下改变为
import dao.MysqlImp;
import service.service;
import service.serviceImp;
public class test {
public static void main(String[] args) {
service s = new serviceImp();
((serviceImp) s).setDao1(new servltImp());
s.show();
}
}
这样程序的耦合性就降低了,不会牵一发而动全身
Ioc创建对象的几种形式
- 首先我建立了maven项目里面加入了spring的依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.10</version>
</dependency>
</dependencies>
- 我在src 的main下面建立了三级目录com/gql/test
然后在tesst里面建立了一个Java类Hello
package com.gql.test;
public class Hello {
private String src;
private String name;
public String getSrc() {
return src;
}
public void setSrc(String src) {
this.src = src;
}
@Override
public String toString() {
return "Hello{" +
"src='" + src + '\'' +
'}';
}
public void show(){
System.out.println("name="+src);
}
public Hello(){
System.out.println("我是Spring");
}
public Hello(String src){
this.src=src;
}
}
3.然后在resource加载资源目录下建立了一个xml配置文件,里面放入了从spring文档里面找到基于 XML 的配置元数据的基本结构: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-constructor-injection
?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
4.然后在test文件的Java目录中建立测试类·Mytest
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.gql.test.Hello;
public class Mytest {
public static void main(String[] args) {
ApplicationContext context= new ClassPathXmlApplicationContext("beans.xml");
Hello user = (Hello)context.getBean("user");
user.show();
}
}
完成以上步骤然后我们先在xml里面加入几种创建对象的方式
<bean id="user" class="com.gql.test.Hello">
<property name="src" value="gql" ></property>
</bean>
这里的name就是值我们定义的变量,src是我们在Hello里面定义的是私有变量,这里的value就是给src赋值,程序运行的结果就是
我是Spring
name=gql
值得注意的是:之所以产生”我是spring“这句话是因为在加载的过程中其实在时已经调用了我们的无参构造代替了我们自己去new一个类对象;
- 第二种方式是:
可以使用属性明确指定构造参数的索引index
2.可以使用属性明确指定构造参数的索引,如下示例所示:index
<bean id="user" class="com.gql.test.Hello">
<constructor-arg index="0" value="张浩楠"/>
这是给有产构造第一个参数赋值
</bean>
name=张浩楠
同样值得注意的是,这里的constructor从字面来看就是值构造的意思,这里的index应该是第一个有参构造的第一个参数的下标,value同样是给第一个参数进行赋值
3.第三种方式: 使用属性明确指定构造器参数的类型,则容器可以使用与简单类型匹配的类型,如下示例所示:type
<bean id="user" class="com.gql.test.Hello">
<constructor-arg type="java.lang.String" value="彭一名"></constructor-arg>
</bean>
name=彭一名
这个方法不建议使用当两个都是string类型就容易产生冲突了;如果是引用类型需要输入完整的string引用 但是基本类型可以直接用 ,同样的这个方法也有constructor的形式所以这里的type指的也是构造函数的参数类型,这个java,.lang.String就是有参的第一个类型,所以这就是为什么两个参数都是String类型时容易产生问题。
- 第四种方式:
<!-- 4.直接通过参数名来设置-->
<!-- <bean id="user" class="com.gql.test.Hello">-->
<!-- <constructor-arg name="src" value="李佳怡">-->
<!-- </constructor-arg>-->
<!-- </bean>-->
name=李佳怡
这个方式也是我们用的最多的形式,只需要通过参数名来进行设置,如果仔细看和第一种形式比较相似**在这些方法中我们并没有通过new关键字创建对象,而是通过spring容器获取实现类对象,这就是spring Ioc容器的工作机制
|