Spring创建对象的原理分析与简单模拟实现Spring是如何解耦对象之间的依赖关系

目录(暂时没有申请修改js权限,先把目录整理到文章开头):

一、Spring创建对象模拟实现: 

二、Spring的IOC:利用Spring容器创建对象

三、Spring的单实例和多实例,以及Spring中应用到的设计模式之单例模式: 

四、Spring的DI:set方法注入和构造方法注入

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

一、Spring创建对象模拟实现: 

Spring容器管理它内部所有对象的生命周期,将创建,初始化,销毁对象等所有原本由程序员自己管理的行为权利,转交给Spring容器管理,让程序员从大量的重复性劳动和记忆负担中解脱出来,也大大减轻了程序后期维护成本。

以下代码基于Eclipse开发工具完成,主要模拟Spring容器通过管理对象的实例化,将所有程序中用到的对象(简称bean),接口及其实现类的对应关系声明在配置文件中进行管理,再以面向接口编程的方式,

在程序中设置方法getBean(String interName),以注入方法参数的形式获取继承指定接口下的所有实例,其中interName为在配置文件中声明的接口名称,然后通过基于反射的动态代理技术,让容器自动获取该接口的实例对象,

进而管理这个对象,在程序发布,后期维护的过程中,如果想更换不同的实现方案,就只需通过修改配置文件,即可的更改程序中不同实例对象,实现了代码的非侵入性更改实现方案,即可以在无需重新编译程序,

由非专业人员修改配置文件,进而达到控制程序的目的。而不是依赖于程序员自己new对象的方式,必须对源代码进行相应的修改。这样就把配置在spring容器中的对象全权交给spring容器进行管理,也就是IoC控制反转。

代码模拟实现如下: 

1.对象管理工厂类:/spring/src/main/java/com/fhy/BeanFactory.java

 1 package com.fhy;
 2 /**
 3  * 模拟Spring 容器如何创建实例:
 4  * (1) 提供一个 config.properties配置文件,在这个文件中配置接口和实现类的对应关系,即程序中具体使用这个接口的哪一种实现方案 :
 5  * 比如EmpService=com.fhy.EmpServiceImpl
 6  * (2)在当前类中读取配置文件中的配置信息,将文件中的所有配置信息读取并存储到Properties对象中,方便在内存值直接使用这些配置信息。
 7  * (3)在BeanFactory中提供一个getBean方法 ,接收一个接口名(例如:EmpService)作为方法的入参,
 8  * 根据接口名获取在配置文件中指定的该接口所调用的具体的实现方案,即获取指定此接口在配置文件中对应的所选实现类的全限定类名
 9  * (例如:com.fhy.EmpServiceImpl),
10  * 最后再基于反射创建该类的实例作为方法的返回值直接返回即可。
11  * @author Administrator
12  *
13  */
14 
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.util.Properties;
18 
19 import org.junit.Test;
20 
21 public class BeanFactory {
22     private static Properties prop;//在静态代码 块中不能访问 非静态数据
23     static {
24         try {
25             //初始化 prop
26             prop = new Properties();
27             //获取指向config.properties文件的文件流
28             ClassLoader loader = BeanFactory.class.getClassLoader();//获取一个类加载器
29             InputStream in = loader.getResourceAsStream("config.properties");
30             //将config.properties文件中的所有内容加载到prop对象 中
31             prop.load(in);
32         } catch (IOException e) {
33             e.printStackTrace();
34         }
35     }
36     
37     /**
38      * 单元测试的方法 不能带有参数,底层就是个main方法 
39      */
40     @Test
41     public void testProp() {
42         
43     }
44     
45     /**
46      * 根据入参的接口名,从配置文件中获取该接口对应所有子类的全限定类名,再基于反射创建该子类的 实例对象。
47      * 静态方法的好处就是可以通过类名直接调用
48      * @interName 接口名(也是 配置文件中的key的名字)
49      * @return Object 根据入参接口名,返回该接口对应子类实例
50      */
51     public static Object getBean(String interName) {
52         try {
53             //根据接口名获取配置文件入参接口所对应的子类的全限定类名
54             String className = prop.getProperty(interName);
55             //通过 子类的全限定类名获取该类的 字节码对象
56             Class clz = Class.forName(className);
57             //再通过该类的字节码对象,获取该类的实例
58             Object obj = clz.newInstance();
59             return obj;
60         } catch (Exception e) {
61             e.printStackTrace();
62         }
63         return null;
64     }
65 }

2.功能控制器:/spring/src/main/java/com/fhy/EmpController.java

 1 package com.fhy;
 2 
 3 import org.junit.Test;
 4 
 5 /**
 6  * Controller(表现层) --> Service(业务层) --> Dao(持久层)
 7  * @author Administrator
 8  *
 9  */
10 public class EmpController {
11     //获取EmpService接口的子类实例
12     /*
13      * EmpService service = new EmpServiceImpl();
14      * 这里采用new的形式获取Service层的对象,增加了对象之间的依赖关系 也就是增加了耦合度,
15      * 如果Service层的业务实例,这个类一旦被替换,通过new的形式创建对象的这个类名也需要更换,否则这段代码就会发生报错,
16      * 整个项目中可能会有很多类似的错误,就需要修改很多程序代码 ,导致程序需要重新编译,重新发布,提高了整个项目的维护成本,
17      * 造成了很多不必要的麻烦。
18      * 
19      * 
20      */
21     //EmpService service = new EmpServiceImpl();
22     EmpService service = (EmpService)BeanFactory.getBean("EmpService");
23     
24     
25     @Test
26     //JUnit工具
27     public void testAddEmp() {
28         service.addEmp();
29     }
30 }

3.业务功能接口: /spring/src/main/java/com/fhy/EmpService.java

 1 package com.fhy;
 2 /**
 3  * Service层的接口
 4  * @author Administrator
 5  *
 6  */
 7 public interface EmpService {
 8     /** 新增员工 */
 9     public void addEmp();
10     
11 }

4.功能接口实例对象1: /spring/src/main/java/com/fhy/EmpServiceImpl.java

 1 package com.fhy;
 2 /**
 3  * EmpService接口的实现类
 4  * @author Administrator
 5  *
 6  */
 7 public class EmpServiceImpl01 implements EmpService {
 8 
 9         @Override
10         public void addEmp() {
11                 System.out.println("EmpServiceImpl01: 成功插入了一条信息...");
12         }
13 }

5.功能接口实例对象2: /spring/src/main/java/com/fhy/EmpServiceImpl2.java

 1 package com.fhy;
 2 /**
 3  * EmpService接口的实现类
 4  * @author Administrator
 5  *
 6  */
 7 public class EmpServiceImpl02 implements EmpService {
 8 
 9         @Override
10         public void addEmp() {
11                 System.out.println("EmpServiceImpl02: 成功插入了一条信息...");
12         }
13 }

6.Properties配置文件:/spring/src/main/resources/config.properties

EmpService=com.fhy.EmpServiceImpl01

it is all,game over!!!!!!!!

二、Spring的IOC:利用Spring容器创建对象

1.jar包依赖:/spring/pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.fhy</groupId>
  <artifactId>spring</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.1.3.RELEASE</version>
    </dependency>
  </dependencies>  
</project>

2.业务功能接口: /spring/src/main/java/com/fhy/EmpService.java

package com.tedu;
/**
 * Service层的接口
 * @author Administrator
 *
 */
public interface EmpService {
    /** 新增员工 */
    public void addEmp();
}

3.功能接口实例对象1: /spring/src/main/java/com/fhy/EmpServiceImpl01.java

package com.fhy;
/**
 * EmpService接口的实现类
 * @author Administrator
 *
 */
public class EmpServiceImpl01 implements EmpService {

        @Override
        public void addEmp() {
                System.out.println("EmpServiceImpl01: 成功插入了一条信息...");
        }
}

4.功能接口实例对象2: /spring/src/main/java/com/fhy/EmpServiceImpl02.java

package com.fhy;
/**
 * EmpService接口的实现类
 * @author Administrator
 *
 */
public class EmpServiceImpl02 implements EmpService {

        @Override
        public void addEmp() {
                System.out.println("EmpServiceImpl02: 成功插入了一条信息...");
        }
}

5.Spring功能测试类: /spring/src/main/java/com/fhy/TestSpring.java

package com.fhy;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpring {
    /*
     * 1.测试spring的IOC(控制反转) 
     */    
    @Test
    public void TestIoC() {
        //1.测试spring的IOC(控制反转) 
        //获取spring的容器对象(加载spring核心配置文件applicationContext.xml)
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //通过spring容器获取EmpService接口的子类实例
        EmpService service = (EmpService) ac.getBean("empService");
        System.out.println("service:"+service);
        service.addEmp();
    }
}

6.Spring配置文件:/spring/src/main/resources/applicationContext.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" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    
    <!-- 
        将EmpService接口的子类作为bean,声明到spring容器中(即由spring容器创建该类的实例)。其中,
        (1)id属性的值就是一个独一无二的编号,没有什么实际的意义,可以任意定义其名称,只要编号唯一即可,
                但最好还是能和程序中的接口或类的名称相对应,逻辑清晰,方便记忆。通过这个id,可以用于获取当前id所指定的类的实例。
        (2)class属性的值是一个类的全限定类名,可以让框架在底层通过反射创建该类的实例.
        (3)bean的id属性和class属性的关系就类似于key-value对的形式。
     -->
    <bean id="empService" class="com.fhy.EmpServiceImpl02"/>
</beans>

三、Spring的单实例和多实例,以及Spring中应用到的设计模式之单例模式: 

1.数据表实例对象: /spring/src/main/java/com/fhy/pojo/User.java

package com.fhy.pojo;

public class User {
    private String name;
    private Integer age;
    private UserInfo info;
    //提供无参构造函数
    public User() {    }
    //提供有参的构造函数
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    public User(String name, Integer age, UserInfo info) {
        this.name = name;
        this.age = age;
        this.info = info;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        System.out.println("setName()方法执行了..."+name);
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        System.out.println("setAge()方法执行了..."+age);
        this.age = age;
    }
    public UserInfo getInfo() {
        return info;
    }
    public void setInfo(UserInfo info) {
        this.info = info;
    }
    @Override
    public String toString() {
        return "User [name=" + name + ", age=" + age + ", info=" + info + "]";
    }
}

2.数据表实例对象的属性对象: /spring/src/main/java/com/fhy/pojo/UserInfo.java

package com.fhy.pojo;

public class UserInfo {

}

3.Spring功能测试类: /spring/src/main/java/com/fhy/TestSpring.java

package com.fhy;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.fhy.pojo.User;

public class TestSpring {
    /*
     * 1.测试spring的IOC(控制反转) 
     * 2.测试spring的单实例和多实例
     *     单实例:通过spring容器多次获取同一个类的实例,spring容器从始至终只会为该类创建一个实例
     *         优势:可以节省内存空间,减少资源浪费
     *         缺点:由于每次获取的都是同一个对象,访问的也是同一个对象的成员,可能会引发线程安全问题。
     *     多实例:通过spring容器多次获取同一个类的实例,spring容器每次都会为该类创建新的实例
     *         优点:不会出现线程安全问题
     *         缺点:耗费内存空间
     *     总结:在保证不会出现线程安全问题的前提下,推荐使用单实例,否则使用多实例。
     */    
    @Test
    public void TestIoC() {
        //1、测试spring的IoC(控制反转)
        //获取spring的容器对象(加载spring核心配置文件applicationContext.xml)
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //通过spring容器获取EmpService接口的子类实例
        EmpService service = (EmpService) ac.getBean("empService");
        System.out.println("service:"+service);
        service.addEmp();
        
        //2.测试spring的单实例和多实例
        //通过Spring容器获取User类的实例
        User user1 = (User) ac.getBean("user");
        System.out.println("user1:"+user1);
        User user2 = (User) ac.getBean("user");
        System.out.println("user2:"+user2);
        /*
         * (1)Bean对象的单实例:在Spring配置对象的bean时,需要声明属性scope="singleton",如果没有声明scope属性,则默认就是单例的,即单例模式。
         *     如果user1==user2,则说明user1和user2指向的是同一个对象,spring容器为User类从始至终只创建了一个实例。
         * (2)Bean对象的多实例:在Spring配置对象的bean时,需要声明属性scope="prototype",就是多实例的,即多例模式。
         *    如果user1!=user2,则说明user1和user2指向的是不同的对象,spring容器为User类每次都会创建不同的实例。
         * (3)单例模式(Singleton Pattern):确保某一个类只有一个实例,且自行实例化,并向整个系统提供这个实例。单例模式是一种对象创建模型模式。
         *     单例模式有两种不同的实现方式,饿汉式单例模式(Eager Singleton)和懒汉式单例模式(Lazy Singleton)。
         * (4)Spring的配置Bean对象的属性scope="singleton"单实例就是饿汉式单例模式,懒汉式单例模式则需要修改Spring配置文件,
         *     在beans标签内部声明属性default-lazy-init="true",即可实现懒汉式单例模式。
         * (5)饿汉模式:启动容器时(即实例化容器时),为所有spring配置文件中定义的bean都生成一个实例,每次getBean请求获取的都是此实例。
         * (6)懒汉模式:在第一个请求时才生成一个实例(即第一调用getBean请求时才生成一个实例),以后的请求都调用这个实例。
         * (7)多例模式:任何一个实例都是新的实例,调用getBean时,就new一个新实例。应该注意的是,多例模式中,实例只会随着getBean而创建,
         *     不会随着容器初始化而创建!也就是说,多例模式只有懒汉。
         */
        //此处演示多实例模式,运行结果为false。具体配置见spring配置文件applicationContext.xml
        System.out.println(user1 == user2);
    }
}

4.Spring配置文件:/spring/src/main/resources/applicationContext.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" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    
    <!-- 
        将EmpService接口的子类作为bean,声明到spring容器中(即由spring容器创建该类的实例)。其中,
        (1)id属性的值就是一个独一无二的编号,没有什么实际的意义,可以任意定义其名称,只要编号唯一即可,
                但最好还是能和程序中的接口或类的名称相对应,逻辑清晰,方便记忆。通过这个id,可以用于获取当前id所指定的类的实例。
        (2)class属性的值是一个类的全限定类名,可以让框架在底层通过反射创建该类的实例.
        (3)bean的id属性和class属性的关系就类似于key-value对的形式。
     -->
    <bean id="empService" class="com.fhy.EmpServiceImpl02"/>
    
    <!-- 将User作为bean声明到Spring容器中 -->
    <bean id="user" class="com.fhy.pojo.User" scope="prototype"/>    
</beans>

四、Spring的DI:set方式注入和构造器方式注入

1.set方式注入:/spring/src/main/resources/applicationContext.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" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    
    <!-- 
        将EmpService接口的子类作为bean,声明到spring容器中(即由spring容器创建该类的实例)。其中,
        (1)id属性的值就是一个独一无二的编号,没有什么实际的意义,可以任意定义其名称,只要编号唯一即可,
        但最好还是能和程序中的接口或类的名称相对应,逻辑清晰,方便记忆。通过这个id,可以用于获取当前id所指定的类的实例。
        (2)class属性的值是一个类的全限定类名,可以让框架在底层通过反射创建该类的实例.
        (3)bean的id属性和class属性的关系就类似于key-value对的形式。
     -->
    <bean id="empService" class="com.fhy.EmpServiceImpl02"/>
    
    <!-- 将User作为bean声明到Spring容器中 -->
    <bean id="user" class="com.fhy.pojo.User" scope="prototype">
        <!-- 
            通过配置Spring配置文件中的,bean标签下的子标签property,来实现set方式为对象的属性赋值,其中,
            (1)property标签属性name指代对应实体类setXxx方法的方法名Xxx(即set后面的那部分名称),
            即name属性的值,必须要和User类中所注入属性 【对应的set方法名去掉set后、首字母变为小写的名字】 相同。
            (2)property标签属性value或ref指代对应set方法的入参名称.即value或ref的值就是为name所指代的那个方法传入的参数。
            (3)property标签属性value或ref的区别是:普通属性直接通过value注入即可;对象属性通过ref属性注入。
            (4)当注入的属性为对象属性时,必须先有这个对象属性的实例存在,即在spring的配置文件中要事先配置好这个属性为对象的类的bean的实例,
            否则会报如下异常:org.springframework.beans.factory.BeanCreationException: 
             Error creating bean with name 'user' defined in class path resource [applicationContext.xml]: 
             Cannot resolve reference to bean 'userInfo' while setting bean property 'info'; 
             nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: 
             No bean named 'userInfo' is defined...
         -->
        <!-- 通过set方式为普通属性赋值 -->    
        <property name="name" value="韩少云"/>
        <property name="age" value="18"/>
        <!-- 
            通过set方法为User对象中的对象属性赋值,底层实际调用了User对象中的setUser方法.
            property标签属性ref指代对象属性对应set方法的入参,其具体内容指向了那个属性对象的bean的id值所代表的对象的内容.
            例如,此处是将UserInfo对象作为值赋给另一个对象的属性,因此ref属性的值,为UserInfo对象bean标签的id值。
         -->
        <!-- 通过set方式为对象属性赋值 -->
        <property name="info" ref="userInfo"/>        
    </bean>
    
    <!-- 将UserInfo作为bean声明到spring容器中 -->
    <bean id="userInfo" class="com.fhy.pojo.UserInfo"/>
</beans>

以set方式注入对象属性关系图:

2.构造方法注入:/spring/src/main/resources/applicationContext.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" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    
    <!-- 
        将EmpService接口的子类作为bean,声明到spring容器中(即由spring容器创建该类的实例)。其中,
        (1)id属性的值就是一个独一无二的编号,没有什么实际的意义,可以任意定义其名称,只要编号唯一即可,
        但最好还是能和程序中的接口或类的名称相对应,逻辑清晰,方便记忆。通过这个id,可以用于获取当前id所指定的类的实例。
        (2)class属性的值是一个类的全限定类名,可以让框架在底层通过反射创建该类的实例.
        (3)bean的id属性和class属性的关系就类似于key-value对的形式。
     -->
    <bean id="empService" class="com.fhy.EmpServiceImpl02"/>
    
    <!-- 将User作为bean声明到Spring容器中 -->
    <bean id="user" class="com.fhy.pojo.User" scope="prototype">
        <!-- set方式注入对象属性值 -->
        <!-- 
            通过配置Spring配置文件中的,bean标签下的子标签property,来实现set方式为对象的属性赋值,其中,
            (1)property标签属性name指代对应实体类setXxx方法的方法名Xxx(即set后面的那部分名称),
            即name属性的值,必须要和User类中所注入属性 【对应的set方法名去掉set后、首字母变为小写的名字】 相同。
            (2)property标签属性value或ref指代对应set方法的入参名称.即value或ref的值就是为name所指代的那个方法传入的参数。
            (3)property标签属性value或ref的区别是:普通属性直接通过value注入即可;对象属性通过ref属性注入。
            (4)当注入的属性为对象属性时,必须先有这个对象属性的实例存在,即在spring的配置文件中要事先配置好这个属性为对象的类的bean的实例,
            否则会报如下异常:org.springframework.beans.factory.BeanCreationException: 
            Error creating bean with name 'user' defined in class path resource [applicationContext.xml]: 
            Cannot resolve reference to bean 'userInfo' while setting bean property 'info'; 
            nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: 
            No bean named 'userInfo' is defined...
         -->
        <!-- 通过set方式为普通属性赋值 --> 
<!--            
        <property name="name" value="韩少云"/>
        <property name="age" value="18"/>
 -->        
        <!-- 
            通过set方法为User对象中的对象属性赋值,底层实际调用了User对象中的setUser方法.
            property标签属性ref指代对象属性对应set方法的入参,其具体内容指向了那个属性对象的bean的id值所代表的对象的内容.
            例如,此处是将UserInfo对象作为值赋给另一个对象的属性,因此ref属性的值,为UserInfo对象bean标签的id值。
         -->
        <!-- 通过set方式为对象属性赋值 -->
<!--         <property name="info" ref="userInfo"/>         -->

        <!-- 构造器方式注入对象属性值 -->
        <!--
            注:不能用set方式和构造器方式同时为同一个属性进行注入,否则会报如下错误:所以将set方式注入先注释掉。            org.springframework.beans.factory.BeanCreationException: 
            Error creating bean with name 'user' defined in class path resource [applicationContext.xml]: 
            Could not resolve matching constructor (hint: specify index/type/name arguments 
            for simple parameters to avoid type ambiguities) 
        -->
        <!-- 
            通过配置Spring配置文件中的,bean标签下的子标签constructor-arg,来实现构造器方式为对象的属性赋值,其中,
            constructor-arg标签name属性的值必须和构造函数中参数的名字相同,即name属性指定的值要和构造函数中形参的名字保持一致.
            同样的,普通属性直接通过value注入即可;对象属性通过ref属性注入.
            通过构造器中参数为属性赋值,底层实际根据在配置文件配置的参数个数调用了对象中与其参数个数相对应的构造方法. 
        -->
        <!-- 通过构造器方式为对象属性赋值 -->
        <constructor-arg name="name" value="小明"></constructor-arg>
        <constructor-arg name="age" value="28"></constructor-arg>
        <!-- 通过构造器方式为对象属性赋值 -->
        <constructor-arg name="info" ref="userInfo"></constructor-arg>  
    </bean>
    
    <!-- 将UserInfo作为bean声明到spring容器中 -->
    <bean id="userInfo" class="com.fhy.pojo.UserInfo"/>
</beans>        

以构造方式注入对象属性关系图:

 

3.Spring功能测试类: /spring/src/main/java/com/fhy/TestSpring.java

package com.fhy;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.fhy.pojo.User;

public class TestSpring {
    /*
     * 1.测试spring的IOC(控制反转) 
     * 2.测试spring的单实例和多实例
     *     单实例:通过spring容器多次获取同一个类的实例,spring容器从始至终只会为该类创建一个实例
     *         优势:可以节省内存空间,减少资源浪费
     *         缺点:由于每次获取的都是同一个对象,访问的也是同一个对象的成员,可能会引发线程安全问题。
     *     多实例:通过spring容器多次获取同一个类的实例,spring容器每次都会为该类创建新的实例
     *         优点:不会出现线程安全问题
     *         缺点:耗费内存空间
     *     总结:在保证不会出现线程安全问题的前提下,推荐使用单实例,否则使用多实例。
     */    
    @Test
    public void TestIoC() {
        //1.测试spring的IoC(控制反转)
        //获取spring的容器对象(加载spring核心配置文件applicationContext.xml)
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //通过spring容器获取EmpService接口的子类实例
        EmpService service = (EmpService) ac.getBean("empService");
        System.out.println("service:"+service);
        service.addEmp();
        
        //2.测试spring的单实例和多实例
        //通过Spring容器获取User类的实例
        User user1 = (User) ac.getBean("user");
        System.out.println("user1:"+user1);
        User user2 = (User) ac.getBean("user");
        System.out.println("user2:"+user2);
        /*
         * (1)Bean对象的单实例:在Spring配置对象的bean时,需要声明属性scope="singleton",如果没有声明scope属性,则默认就是单例的,即单例模式。
         *     如果user1==user2,则说明user1和user2指向的是同一个对象,spring容器为User类从始至终只创建了一个实例。
         * (2)Bean对象的多实例:在Spring配置对象的bean时,需要声明属性scope="prototype",就是多实例的,即多例模式。
         *    如果user1!=user2,则说明user1和user2指向的是不同的对象,spring容器为User类每次都会创建不同的实例。
         * (3)单例模式(Singleton Pattern):确保某一个类只有一个实例,且自行实例化,并向整个系统提供这个实例。单例模式是一种对象创建模型模式。
         *     单例模式有两种不同的实现方式,饿汉式单例模式(Eager Singleton)和懒汉式单例模式(Lazy Singleton)。
         * (4)Spring的配置Bean对象的属性scope="singleton"单实例就是饿汉式单例模式,懒汉式单例模式则需要修改Spring配置文件,
         *     在beans标签内部声明属性default-lazy-init="true",即可实现懒汉式单例模式。
         * (5)饿汉模式:启动容器时(即实例化容器时),为所有spring配置文件中定义的bean都生成一个实例,每次getBean请求获取的都是此实例。
         * (6)懒汉模式:在第一个请求时才生成一个实例(即第一调用getBean请求时才生成一个实例),以后的请求都调用这个实例。
         * (7)多例模式:任何一个实例都是新的实例,调用getBean时,就new一个新实例。应该注意的是,多例模式中,实例只会随着getBean而创建,
         *     不会随着容器初始化而创建!也就是说,多例模式只有懒汉。
         */
        //此处演示多实例模式,运行结果为false。具体配置见spring配置文件applicationContext.xml
        System.out.println(user1 == user2);
    }
    
    /*
     * 一、由程序员手动new的方式创建对象的两种方式:
     * (1)通过set方法为属性赋值:
     * User user = new User();
     * user.setName("韩少云");//通过setName方法为name属性赋值
     * user.setAge(18);//通过setAge方法为age属性赋值
     * (2)构造器的方式为属性赋值:
     * User user = new User("韩少云",18);//在创建(new)对象的同时,通过有参构造函数为对象的属性赋值
     * 二、测试spring的DI(依赖注入):由Spring通过配置文件的方式给属性赋值,具体操作见Spring的配置文件
     * DI:在通过spring容器创建对象的同时或者创建 对象之后,由spring框架为对象的属性赋值。
     * (1)set方法注入:调用对象的setXxx方法 为xxx属性赋值,例如:调用user.setName()为name属性赋值。
     * (2)构造方法注入:在spring容器创建对象的同时,通过有参构造函数为对象的属性赋值,
     *     例如:User user = new User("韩少云",18); 为user对象的name属性和age属性赋值。
     */
    @Test
    public void testDI() {
        //获取Spring的容器对象(加载spring核心配置文件applicationContext.xml)
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //通过spring容器获取User类的实例
        User user = (User)ac.getBean( "user" );
        //输出User实例
        System.out.println( user );
    }
}

创建于20200721;修改于20200722; 

原文地址:https://www.cnblogs.com/HarryVan/p/13353165.html