Spring IOC DI

转自
https://blog.csdn.net/jiangyu1013/article/details/72654373#commentBox
https://blog.csdn.net/yerenyuan_pku/article/details/69663685#t33

1 IOC、DI概念

IOC(DI):java程序中的每个业务逻辑至少需要两个或以上的对象来协作完成。通常,每个对象在使用他的合作对象时,自己均要使用像new object() 这样的语法来完成合作对象的申请工作。你会发现:对象间的耦合度高了。

IOC的思想是:Spring容器来实现这些相互依赖对象的创建、协调工作对象只需要关心业务逻辑本身就可以了。从这方面来说,对象如何得到他的协作对象的责任被反转了(IOC、DI)。

所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期对象间的关系

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。

DI通过反射(reflection)实现的,它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

2 Spring IOC运行原理

下面来让大家了解一下Spring到底是怎么运行的。

    public static void main(String[] args) {   
            ApplicationContext context = new FileSystemXmlApplicationContext(   
                    "applicationContext.xml");   
            Animal animal = (Animal) context.getBean("animal");   
            animal.say();   
        }  

这段代码你一定很熟悉吧,不过还是让我们分析一下它吧,首先是applicationContext.xml


        <bean id="animal" class="phz.springframework.test.Cat">   
            <property name="name" value="kitty" />   
        </bean>

他有一个类phz.springframework.test.Cat

 public class Cat implements Animal {   
        private String name;   
        public void say() {   
            System.out.println("I am " + name + "!");   
        }   
        public void setName(String name) {   
            this.name = name;   
        }   
    }  

实现了phz.springframework.test.Animal接口

    public interface Animal {   
        public void say();   
    } 

很明显上面的代码输出I am kitty!

2.1 自己写个Spring

那么到底Spring是如何做到的呢?
接下来就让我们自己写个Spring 来看看Spring 到底是怎么运行的吧!

首先,我们定义一个Bean类,这个类用来存放一个Bean拥有的属性

    /* Bean Id */  
        private String id;   
        /* Bean Class */  
        private String type;   
        /* Bean Property */  
        private Map<String, Object> properties = new HashMap<String, Object>();  

一个Bean包括id,type,和Properties。

接下来Spring 就开始加载我们的配置文件了,将我们配置的信息保存在一个HashMap中,HashMap的key就是Bean 的 Id ,HasMap 的value是这个Bean,只有这样我们才能通过context.getBean(“animal”)这个方法获得Animal这个类。我们都知道Spirng可以注入基本类型,而且可以注入像List,Map这样的类型,接下来就让我们以Map为例看看Spring是怎么保存的吧。

Map配置可以像下面的

    <bean id="test" class="Test">   
            <property name="testMap">   
                <map>   
                    <entry key="a">   
                        <value>1</value>   
                    </entry>   
                    <entry key="b">   
                        <value>2</value>   
                    </entry>   
                </map>   
            </property>   
        </bean>  

Spring是怎样保存上面的配置呢?,代码如下:

 if (beanProperty.element("map") != null) {   
                        Map<String, Object> propertiesMap = new HashMap<String, Object>();   
                        Element propertiesListMap = (Element) beanProperty   
                                .elements().get(0);   
                        Iterator<?> propertiesIterator = propertiesListMap   
                                .elements().iterator();   
                        while (propertiesIterator.hasNext()) {   
                            Element vet = (Element) propertiesIterator.next();   
                            if (vet.getName().equals("entry")) {   
                                String key = vet.attributeValue("key");   
                                Iterator<?> valuesIterator = vet.elements()   
                                        .iterator();   
                                while (valuesIterator.hasNext()) {   
                                    Element value = (Element) valuesIterator.next();   
                                    if (value.getName().equals("value")) {   
                                        propertiesMap.put(key, value.getText());   
                                    }   
                                    if (value.getName().equals("ref")) {   
                                        propertiesMap.put(key, new String[] { value   
                                                .attributeValue("bean") });   
                                    }   
                                }   
                            }   
                        }   
                        bean.getProperties().put(name, propertiesMap);   
                    } 

接下来就进入最核心部分了,让我们看看Spring 到底是怎么依赖注入的吧,其实依赖注入的思想也很简单,它是通过反射机制实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。让我们看看具体它是怎么做的吧。

首先实例化一个类,像这样

    public static Object newInstance(String className) {   
            Class<?> cls = null;   
            Object obj = null;   
            try {   
                cls = Class.forName(className);   
                obj = cls.newInstance();   
            } catch (ClassNotFoundException e) {   
                throw new RuntimeException(e);   
            } catch (InstantiationException e) {   
                throw new RuntimeException(e);   
            } catch (IllegalAccessException e) {   
                throw new RuntimeException(e);   
            }   
            return obj;   
        }  

接着它将这个类的依赖注入进去,像这样

 public static void setProperty(Object obj, String name, String value) {   
            Class<? extends Object> clazz = obj.getClass();   
            try {   
                String methodName = returnSetMthodName(name);   
                Method[] ms = clazz.getMethods();   
                for (Method m : ms) {   
                    if (m.getName().equals(methodName)) {   
                        if (m.getParameterTypes().length == 1) {   
                            Class<?> clazzParameterType = m.getParameterTypes()[0];   
                            setFieldValue(clazzParameterType.getName(), value, m,   
                                    obj);   
                            break;   
                        }   
                    }   
                }   
            } catch (SecurityException e) {   
                throw new RuntimeException(e);   
            } catch (IllegalArgumentException e) {   
                throw new RuntimeException(e);   
            } catch (IllegalAccessException e) {   
                throw new RuntimeException(e);   
            } catch (InvocationTargetException e) {   
                throw new RuntimeException(e);   
            }   
    }  

最后它将这个类的实例返回给我们,我们就可以用了。我们还是以Map为例看看它是怎么做的,我写的代码里面是创建一个HashMap并把该HashMap注入到需要注入的类中,像这样,

    if (value instanceof Map) {   
                    Iterator<?> entryIterator = ((Map<?, ?>) value).entrySet()   
                            .iterator();   
                    Map<String, Object> map = new HashMap<String, Object>();   
                    while (entryIterator.hasNext()) {   
                        Entry<?, ?> entryMap = (Entry<?, ?>) entryIterator.next();   
                        if (entryMap.getValue() instanceof String[]) {   
                            map.put((String) entryMap.getKey(),   
                                    getBean(((String[]) entryMap.getValue())[0]));   
                        }   
                    }   
                    BeanProcesser.setProperty(obj, property, map);   
                }  

好了,这样我们就可以用Spring 给我们创建的类了,当然Spring能做到的远不止这些,这个示例程序仅仅提供了Spring最核心的依赖注入功能中的一部分。

3 IOC的底层实现原理

IOC:Inversion of Control,控制反转。指的是对象的创建权反转(交给)给Spring,其作用是实现了程序的解耦合。也可这样解释:获取对象的方式变了。对象创建的控制权不是“使用者”,而是“框架”或者“容器”

用更通俗的话来说,IOC就是指对象的创建,并不是在代码中用new操作new出来的,而是通过Spring进行配置创建的。其底层实现原理是XML配置文件+SAX解析+工厂设计模式。

就拿持久层(也即dao(data access object,数据访问对象)层)的开发来说,官方推荐做法是先创建一个接口,然后再创建接口对应的实现类。
先创建一个Userdao接口

public interface UserDao {
    public void add();
}

再创建Userdao接口的UserDaoImpl实现类

public class UserDaoImpl implements UserDao {
    public void add() {
        balabala......
    }
}

接着我们在service层调用dao层,核心代码如下:

// 接口 实例变量 = new 实现类
UserDao dao = new UserDaoImpl();
dao.add();

可发现缺点:service层和dao层耦合度太高了。解决方法是使用工厂模式进行解耦合操作。
创建一个工厂类,在工厂类中提供一个方法,返回实现类的对象。

public class Factory {
    // 提供返回实现类对象的方法
    public static UserDao getUserDaoImpl() {
        return new UserDaoImpl();
    }
}

然后在service层调用dao层的核心代码就变为:

UserDao dao = Factory.getUserDaoImpl();
dao.add();

如若这样做,会发现又产生了一个缺点:service层和工厂类又耦合了。所以使用工厂模式进行解耦合也只是一种权宜之计。下面我就来简单讲讲Spring IOC的底层实现原理:

配置文件中可能会有如下配置信息:

<bean id="userDaoImpl" class="cn.itcast.dao.impl.UserDaoImpl" />

也是要创建一个工厂类,在工厂类中提供一个返回实现类对象的方法,但并不是直接new实现类,而是使用SAX解析配置文件,根据标签bean中的id属性值得到对应的class属性值,使用反射创建实现类对象

  public class Factory {
        public static UserDao getUserDaoImpl() {
            // 1.使用SAX解析得到配置文件内容
            // 直接根据id值userDaoImpl得到class属性值
            String classvalue = "class属性值";
            // 2.使用反射得到对象
            Class clazz = Class.forName(classvalue);
            UserDaoImpl userDaoImpl = (UserDaoImpl)lazz.newInstance();
            return userDaoImpl;
        }
    }

3.1 面向对象设计的七大原则

这里我稍微讲一下面向对象设计的七大原则,不必强记,重在理解。

  • 单一职责原则(Single Responsibility Principle):每一个类应该专注于做一件事情
  • 里氏替换原则(Liskov Substitution Principle):超类存在的地方,子类是可以替换的
  • 依赖倒置原则(Dependence Inversion Principle):实现尽量依赖抽象,不依赖具体实现
  • 接口隔离原则(Interface Segregation Principle):应当为客户端提供尽可能小的单独的接口,而不是提供大的总的接口
  • 迪米特法则(Law Of Demeter):又叫最少知识原则,一个软件实体应当尽可能少的与其他实体发生相互作用
  • 开闭原则(Open Close Principle):对扩展开放,对修改关闭
  • 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP):尽量使用组合/聚合达到复用,尽量少用继承。原则: 一个类中有另一个类的对象。

3.2 Spring的bean管理

通俗一点说,Spring的bean管理即指创建对象时不需要new操作代码实现,而是交给Spring进行配置完成。

Spring进行bean管理有两种方式:

  • 使用配置文件方式实现
  • 使用注解方式实现

本文将重点放在第一种方式上,后一种方式后面会讲。

3.3 Spring实例化bean的三种方式

  • 使用无参构造(重点)
    创建对象时候,调用类里面的无参数的构造方法实现。那么Spring配置文件中又该怎样写呢?基本类似于如下写法:
<!-- 1.配置user对象的创建 --> 
 < bean id="user" class="cn.itcast.ioc.User">< /bean>
  • 使用静态工厂(了解)
    创建一个工厂类,在工厂类中提供一个静态的方法,这个方法返回类的对象;调用工厂类的方法时候,直接使用类名.方法名称即可以调用。下面举例来说明。
    在src目录下创建一个cn.itcast.bean包,并在该包下创建一个Bean1类。
public class Bean1 {

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

}

然后在该包下创建一个Bean1Factory工厂类。

public class Bean1Factory {

    // 静态方法
    public static Bean1 getBean1() {
        return new Bean1();
    }

}

接着Spring配置文件中应向下面这样配置:

<!-- 2.使用静态工厂创建对象 -->
<bean id="bean1" class="cn.itcast.bean.Bean1Factory" factory-method="getBean1"></bean>

最后在该包下创建一个TestIOC单元测试类。

public class TestIOC {

    @Test
    public void demo1() {
        // 1.加载Spring配置文件,把配置文件中的对象进行创建
        ApplicationContext context = 
                new ClassPathXmlApplicationContext("bean1.xml");
        // 2.根据配置文件的id得到user对象
        Bean1 bean1 = (Bean1) context.getBean("bean1");
        System.out.println(bean1);
    }
}
  • 使用实例工厂(了解)
    创建一个工厂类,在工厂类里面提供一个普通的方法,这个方法返回类对象;调用工厂类的方法时候,创建工厂类对象,使用对象调用方法即可。下面也举例来说明。
    在src目录下的cn.itcast.bean包下创建一个Bean2类。
public class Bean2 {

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

}

然后在该包下创建一个Bean2Factory工厂类。

public class Bean2Factory {

    public Bean2 getBean2() {
        return new Bean2();
    }

}

接着Spring配置文件中应向下面这样配置:

<!-- 3.使用实例工厂创建对象 -->
<!-- 3.1先创建工厂对象 -->
< bean id="bean2Factory" class="cn.itcast.bean.Bean2Factory">< /bean>
<!-- 3.2再使用工厂对象创建bean2对象 -->
< bean id="bean2" factory-bean="bean2Factory" factory-method="getBean2">< /bean>

最后将TestIOC单元测试类的代码修改为:

public class TestIOC {

    @Test
    public void demo1() {
        // 1.加载Spring配置文件,把配置文件中的对象进行创建
        ApplicationContext context = 
                new ClassPathXmlApplicationContext("bean1.xml");
        // 2.根据配置文件的id得到user对象
        Bean2 bean2 = (Bean2) context.getBean("bean2");
        System.out.println(bean2);
    }
}

3.4 Spring配置文件中bean标签常用的属性

Spring配置文件中bean标签常用的属性有以下四种:

  • id属性:根据id属性值得到配置对象。
    在Spring配置文件中会有多个bean标签,但它们的id属性值是不能相同的。Bean起名字时,在约束中采用的是ID约束——唯一,而且名字必须以字母开始,可以使用字母、数字、连字符、下划线、句号、冒号等,但id属性值不能有特殊符号。
  • class属性:要创建对象的类的全路径。
  • scope属性:bean的作用范围。
    scope属性共有以下5个属性:
  1. singleton:创建的对象是单例的,也是scope属性的默认值。
    下面我来举例说明它。将TestIOC单元测试类的代码修改为:
public class TestIOC {

            // 得到配置的user对象
            @Test
            public void demo1() {
                // 1.加载Spring配置文件,把配置文件中的对象进行创建
                ApplicationContext context = 
                        new ClassPathXmlApplicationContext("bean1.xml");
                // 2.根据配置文件的id得到user对象
                User user1 = (User) context.getBean("user");
                User user2 = (User) context.getBean("user");
                System.out.println(user1);
                System.out.println(user2);
            }
        }

单元测试以上方法,一切就尽在不言中。其实,此时Spring配置文件中有关如下bean的配置:

<bean id="user" class="cn.itcast.ioc.User"></bean>

就相当于:

<bean id="user" class="cn.itcast.ioc.User" scope="singleton"></bean>
  1. prototype:创建的对象是多实例的。
    也可举例来说明它。将Spring配置文件中有关如下bean的配置:
< bean id="user" class="cn.itcast.ioc.User" scope="prototype">< /bean>
  1. globalSession:用在单点登录(即SSO,single sign on)上。

name属性:name属性的功能和id属性是一样的。name属性和id属性区别是:在id属性值里面不能有特殊符号,在name属性值里面可以添加特殊符号。

3.5 bean的生命周期的配置

通过配置< bean>标签上的init-method作为bean的初始化的时候执行的方法,配置destroy-method作为bean的销毁的时候执行的方法。销毁方法想要执行,需要是单例创建的Bean而且在工厂关闭的时候,Bean才会被销毁

3.6 Spring中Bean的属性注入

实际上,有关Bean的属性注入共有三种方式,下面我分别加以简单的说明:

  1. set方法注入
    用代码可表示如下:
    public class Book {

        private String bookname;

        public void setBookname(String bookname) {
            this.bookname = bookname;
        }

    }

    Book book = new Book();
    book.setBookName("Java编程思想");
  1. 有参数构造注入
    用代码可表示如下:
    public class Book {

        private String bookname;

        public Book(String bookname) {
            this.bookname = bookname;
        }

    }

    Book book = new Book("代码大全");
  1. 接口注入
    先编写一个接口:
  public interface Dao {
        public void add(String name);
    }

再编写这个接口的实现类:

    public class DaoImpl implements Dao {
        private String name;

        public void add(String name) {
            this.name = name;
        }
    }

但在Spring框架里面,只支持前两种方式,即set方法注入和有参数构造注入。下面我来举例分别演示。

构造方法的方式注入属性

在src目录下创建一个cn.itcast.property包,并在该包下编写一个Book实体类。

public class Book {

    private String bookname;

    public Book(String bookname) {
        this.bookname = bookname;
    }

    public void testBook() {
        System.out.println("book.............." + bookname);
    }

}

接着在Spring配置文件中对以上JavaBean添加如下配置:

<!-- 4.使用有参数的构造注入属性 -->
<bean id="book" class="cn.itcast.property.Book">
    <!-- 使用标签,name:为属性的名字;value:为属性的值 -->
    <constructor-arg name="bookname" value="beautifulMan_美美侠"></constructor-arg>
</bean>

最后在该包下编写一个TestIOC单元测试类:

public class TestIOC {

    @Test
    public void demo1() {
        // 1.加载Spring配置文件,把配置文件中的对象进行创建
        ApplicationContext context = 
                new ClassPathXmlApplicationContext("bean1.xml");

        Book book = (Book) context.getBean("book");
        book.testBook();
    }
}

自己自行测试去吧!

set方法的方式注入属性

我们同样在cn.itcast.property包下编写一个Person实体类,在类中定义属性,并生成set方法。

public class Person {

    // 1.定义一个属性
    private String username;

    // 2.生成这个属性的set方法
    public void setUsername(String username) {
        this.username = username;
    }

    public void testperson() {
        System.out.println("person.............." + username);
    }

}

然后在Spring配置文件中,使用bean标签创建对象,在bean标签里面使用property标签注入属性。即在Spring配置文件中对以上JavaBean添加如下配置,

<!-- 5.使用set方法进行注入属性 -->
<bean id="person" class="cn.itcast.property.Person">
    <!--
        使用property标签注入属性值
        name:类属性名称
        value属性:往属性中注入的值
    -->
    <property name="username" value="李阿昀"></property>
</bean>

最后将TestIOC单元测试类的代码修改为:

public class TestIOC {

    @Test
    public void demo1() {
        // 1.加载Spring配置文件,把配置文件中的对象进行创建
        ApplicationContext context = 
                new ClassPathXmlApplicationContext("bean1.xml");

        Person person = (Person)context.getBean("person");
        person.testperson();
    }
}

4 IoC和DI的区别

  • IoC:控制反转,即把对象的创建交给Spring进行管理。所以Spring IoC容器是用来创建对象,管理依赖关系的。
  • DI(Dependency Injection):依赖注入,即在创建对象的过程中,向类里面的属性中设置值。
  • IoC和DI的关系:依赖注入不能单独存在,须在控制反转基础之上完成,用更通俗点的话来说,就是注入类里面的属性值,不能直接注入,须创建类的对象再完成注入

5 Spring中的工厂

  1. ApplicationContext
    ApplicationContext接口有两个实现类:
    ★ ClassPathXmlApplicationContext:加载的是类路径下的Spring配置文件
    ★ FileSystemXmlApplicationContext:加载的是本地磁盘下的Spring配置文件

  2. BeanFactory

  3. ApplicationContext和BeanFactory的区别
    虽然使用这两个对象都可以加载Spring的配置文件,并创建配置文件中的对象。但他俩还是有区别的,最主要的区别是
    使用applicationContext操作时,可把Spring里面的配置文件中的对象都进行创建
    ★ 使用BeanFactory对象操作时,在调用getBean方法的时候进行对象的创建

6 Spring整合Web项目的底层原理

在实际的开发中,我们一般会使用SSH(即Struts2、Spring、Hibernate)进行开发,然后创建Spring的配置文件,使用applicationContext对象加载配置文件,继而创建对象。在真正的开发中,我们一般不会直接写applicationContext代码加载配置文件。

Spring整合Web项目的思想
在服务器启动的时候,加载Spring配置文件,并创建对象

Spring整合Web项目用到以下两个技术
★ 使用ServletContext对象(在服务器启动时创建)
★ 使用监听器

服务器启动的时候,每个项目都会创建一个ServletContext对象,而且每个项目只有一个ServletContext对象。在ServletContext对象创建的时候,使用监听器可以监听到其创建。当监听到ServletContext对象创建时,Spring就会帮我们做一件事情——加载Spring配置文件并把配置文件中的对象进行创建,对象创建之后,放到ServletContext域里面去,最终我们要使用创建的对象,可从ServletContext域里面获取出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值