什么是SpringIOC以及和DI的关系

前言

什么是IOC:IOC的英文全称是Inversion of Control,翻译一下就是控制反转,是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的实现方式叫做依赖注入(Dependency Injection,简称DI),其它实现方式有“依赖查找”(Dependency Lookup)和依赖拖拽(Dependency Pull)。更多Spring内容进入【Spring解读系列目录】

IOC的实现

在日常面试中有一个经典问题,IOC和DI有什么区别。其实在上面的解释中,其实很容易能够看出,能够问出这种问题,本身就是对IOC概念一知半解。这个问题就类似于:最高时速超过200km的车和兰博基尼跑车有什么区别。前者是对一个概念的描述,而后则是这个概念的一个实现,这两个东西怎么能放在一起比较呢?兰博基尼出了多少钱,我保时捷能给两倍对不对,那布加迪的脸往哪放呢?一个道理。IOC就是我们在编码过程中的一个目标,而DI就是实现IOC的一种技术手段。这种手段也可以换成DL,或者DP。

依赖注入 Dependency Injection

我们这次就重点讲解DI,毕竟Spring框架DI用的最多,也最方便。什么是依赖?比如Class A中有一个Class B的属性,那么我们可以理解为A依赖了B。当然这种依赖关系也可以通过构造参数传递,方法很多随便举两个例子。

Class A{
    
    
	Private B b; //A依赖了B
}
Class B{
    
       
	Private C c; //这样B就又依赖了C
	Public B(Class c){
    
    
		this.c=c;
	}
}

那么为什么我们会有依赖呢?因为这个就是面向抽象编程的思想,这样做可以灵活的控制我们的类和接口。比如我们有个类IOCDemo依赖了IOCTest。IOCDemo有一个方法在里面又调用了IOCTest里面的方法。如果我们直接把IOCTest这个类new出来使用,如下:

public class IOCDemo {
    
    
     IOCTest test = new IOCTest();
     public void doDemo(){
    
    
         test.dotest();
     }
}

加入未来某一天我们想要对IOCTest做个代理,或者我们想重写这个类,那就得去IOCDemo里面修改new代码。这就要耗费很大的精力去做这个事情,而且改完还不能保证对别的地方完全没有影响。但是如果不写死,就是声明一下。将来有一天要修改,也只是需要把新的实现类注入进来就可以了。注入就好操作多了,可以选择setter方法,也可以选择构造方法。无论外面要做一个什么修改,只需要注入进来,就可以改变具体的实现。这样就做到面向抽象编程。那么什么是注入也就好理解了,所谓的注入其实就是传递new好的实例进入目标方法。

public class IOCDemo {
    
    
    IOCTest test;
    public void setTest(IOCTest test) {
    
      //注入进来新的实现类
        this.test = test;
    }
    public IOCDemo(IOCTest test) {
    
      //注入进来新的实现类
        this.test = test;
    }
    public void doDemo(){
    
    
         test.dotest();
     }
}

简单来说就是依赖写死对于以后的修改比较麻烦,而编程抽象化则是简化这个过程,但是抽象化以后就必然会产生依赖。为什么大家都用Spring框架呢?因为Spring框架本质上就是一个十分强大的依赖管理容器。类的产生过程交给了容器,那么自己写的代码则可以不需要去关心这些对象的产生和依赖,转而关注业务的实现,大大减轻了开发的成本。

Spring框架实现IOC的思路是提供一些配置信息用来描述类之间的依赖关系,然后由容器去解析这些配置信息,继而维护好对象之间的依赖关系。当然这些描述和真实代码中的依赖关系,必须一一对应不能有错。

Spring编程的风格

Spring框架给我们提供了三种不同但是互不干扰的编程风格。就是说这三种编程风格你可以混着用,没有冲突,也没有矛盾。

第一种:schemal-based

其实也就是我们说的xml格式的配置,大概就是下面的这种。以下摘自官方范例,具体的可以参考【官方文档:aop-schema】里面的内容。

<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>
<bean id="aBean" class="...">
    ...
</bean>

第二种:annotation-based

简单来说就是用@Required或者@Autowired这样的风格编程,都是老熟人了。以下摘自官方范例,具体参考【官方文档:beans-annotation-config】

public class SimpleMovieLister {
    
    
    private MovieFinder movieFinder;
    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
    
    
        this.movieFinder = movieFinder;
    }
    // ...
}
public class MovieRecommender {
    
    
    private final CustomerPreferenceDao customerPreferenceDao;
    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
    
    
        this.customerPreferenceDao = customerPreferenceDao;
    }
    // ...
}

第三种:java-based

Java Configuration就是基于@Bean 和 @Configuration 这两个注解来的,相信这些代码,大家一定也是很熟悉的。以下摘自官方范例,具体参考【官方文档:beans-beans-java】

@Configuration
public class AppConfig {
    
    
    @Bean
    public MyService myService() {
    
    
        return new MyServiceImpl();
    }
}
<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

这里三种编码风格没有本质的区别,因为可以混用所以每个人都有自己的理解,怎么习惯怎么来,但是我相信大部分的项目都是基于annotation-based和java-based来的,或者混用。因为这两个最符合大多数人的编程习惯。

Spring Dependency Injection

注入的两种方法:setter和Constructor两种,老版本的Spring还有基于接口interface的,但是现在已经不用了,在Spring4时代就已经被抛弃了。记清楚,只有两种了。以下摘自Spring官方文档:DI exists in two major variants: Constructor-based dependency injection and Setter-based dependency injection.。以下代码摘自官方范例,小标题直达官方网站,具体参考【官方文档:beans-factory-collaborators】

Constructor-based Dependency Injection

public class SimpleMovieLister {
    
    
    // the SimpleMovieLister has a dependency on a MovieFinder
    private MovieFinder movieFinder;
    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
    
    
        this.movieFinder = movieFinder;
    }
    // business logic that actually uses the injected MovieFinder is omitted...
}

Setter-based Dependency Injection

public class SimpleMovieLister {
    
    
    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;
    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
    
    
        this.movieFinder = movieFinder;
    }
    // business logic that actually uses the injected MovieFinder is omitted...
}

实现注入

那么我们怎么实现这个注入呢?举个简单的例子:我们现在有一个接口DemoDao,一个实现类DemoDaoImpl,一个业务类DemoService。这里声明下,这篇博客的所有例子都是在SpringBoot框架下的Web项目,不知道怎么搭建的,请参考【Idea创建一个JSP web项目】
DemoDao

public interface DemoDao {
    
    
    void test();
}

DemoDaoImpl

public class DemoDaoImpl implements DemoDao{
    
    
    @Override
    public void test() {
    
    
        System.out.println("test");
    }
}

DemoService

public class DemoService {
    
    
    private DemoDao dao;
    public void myDaoService(){
    
    
        dao.test();
    }
}

可以看出DemoService依赖了DemoDao,我们在代码里提供了一个依赖关系。那么下一步就是需要把这几个类交给Spring框架容器管理。怎么交出去呢?Spring给出的描述文件是一个.xml文件。名字无所谓,resource文件夹下创建出来,这里我们命名为spring.xml。

实现基于Setter的注入

首先我们演示下setter方法是怎么注入的。在DemoService创建Setter方法:

public class DemoService {
    
    
    private DemoDao dao;
    public void setDao(DemoDao dao) {
    
    
        this.dao = dao;
    }
    public void myDaoService(){
    
    
        dao.test();
    }
}

那么我们开始编写spring.xml,首先把DemoDao和DemoService都告诉容器。然后在DemoService中维护这个方法。因为DemoService依赖于DemoDao。

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--先把我们dao类告诉spring容器-->
    <bean id="dao" class="com.example.demo.DemoDaoImpl"></bean>
    <!--把我们service类告诉spring容器-->
    <bean id="service" class="com.example.demo.DemoService">
        <!--配置依赖关系,第一个dao是DemoService中的setter,第二个dao就是上面的dao-->
        <property name="dao" ref="dao"></property> 
    </bean>
</beans>

除此之外,还要有一个test类用来测试:

public class DemoTest {
    
    
    public static void main(String[] args) {
    
    
        //加载配置
        ClassPathXmlApplicationContext cpth
                = new ClassPathXmlApplicationContext("classpath:spring.xml");
        //拿到service
        DemoService service= (DemoService) cpth.getBean("service");
        service.myDaoService();
    }
}

如果说是通过一个classpath下面的一个xml配置文件来维持spring框架的初始化的,那么就需要用ClassPathXmlApplicationContext这个类,就看名字也非常的直:在ClassPath下的Xml来运行我们应用Application的环境内容Context。运行这个测试类拿到打印的test。

[main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7c53a9eb
[main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 2 bean definitions from class path resource [spring.xml]
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dao'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'service'
test        //拿到打印的test

test被打印出来,说明我们的依赖就维护好了。这个就是setter的注入例子。

实现基于Constructor的注入

我们再演示下Constructor方法是怎么注入的。在DemoService创建Constructor方法:

public class DemoService {
    
    
    private DemoDao dao;
    public DemoService(DemoDao dao) {
    
      //构造方法
        this.dao = dao;
    }
    public void myDaoService(){
    
    
        dao.test();
    }
}

spring.xml也要修改,改成构造方法的依赖:

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--先把我们dao类告诉spring容器-->
    <bean id="dao" class="com.example.demo.DemoDaoImpl"></bean>
    <!--把我们service类告诉spring容器-->
    <bean id="service" class="com.example.demo.DemoService">
         <constructor-arg ref="dao"></constructor-arg>
    </bean>
</beans>

跑一遍,同样拿到拿到打印的test。

[main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7c53a9eb
[main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 2 bean definitions from class path resource [spring.xml]
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dao'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'service'
test        //拿到打印的test

这里就完成了Constructor的注入。

其他

当然你也可以注入很多其他的东西,比如一个String类型的静态值等等。

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="masterkaoli"/>
</bean>

或者再一个property中给多个值等等。

<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>

例子很多甚至可以直接定义一个list,一个map。但是这就太变态了,谁会用一个写死的集合呢,如果要写死还不如直接用枚举。具体可以参考:【官方文档:beans-value-element】这里就不多说了。

猜你喜欢

转载自blog.csdn.net/Smallc0de/article/details/107887686