Loading

Spring IOC的使用以及原理

1. IOC中bean对象的使用以及模拟实例化过程

关于bean对象的实例化有以下三种方法:

  1. 构造器实例化(最常用)
  2. 静态工厂方法实例化
  3. 实例方法工厂实例化
    我们以第一种方法介绍实例化的过程:

在创建的maven项目的基础上,为了实现一个简单地spring框架的功能,需要在pom文件中引入SpringContext的依赖

  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.16</version>
    </dependency>

之后在对应的main文件夹下创建resources文件夹,在里面写.xml配置文件,配置文件名称、数量没有规定。之后,如果需要将某个对象实例化,那么就将该该对象以id和class的形式配置在配置文件中,之后再在测试程序中通过new ClassPathXmlApplicationContext("配置文件名.xml")形式获取到bean容器运行的上下文,之后通过getBean(“配置文件中bean的id”)获取到所需要的类的实例,再调用其中的方法。

package org.example.dao;

public class UserDao {
    public void test(){
        System.out.println("userDao!!!   test");
       
    }
}

<?xml version="1.0" encoding="UTF-8"?>


<beans>
<!--    设置javabean对应的bean标签-->
    <bean id="userDao" class="org.example.dao.UserDao" ></bean>


    <bean id="userService" class="org.example.service.UserService"></bean>


</beans>
public class App 
{
    public static void main( String[] args )
    {

        ApplicationContext ac = new ClassPathXmlApplicationContext("spring01.xml");

       UserDao userDao = (UserDao) ac.getBean("userDao");
      userDao.test();


    }
}

这是整个运行效果。

2. IOC注入

为了降低整个系统之间的耦合度,同时方便各个类之间的相互调用,我们使用IOC依赖注入来达成这一目标,将在一个bean中注入新的属性这一功能交给spring来完成。IOC注入分为自动注入和手动注入

2-1. 手动注入

  1. setter注入
  2. 构造器注入
  3. 静态工厂注入
  4. 实例工厂注入
  5. 基于注解的注入

最主要是前两种,因此对前两种加以说明。
setter方法注入

  1. 在被注入的bean对应的类文件中添加注入对象的变量形式以及构造方法
  2. 在需要被注入的bean标签里面加入标签,其中name是注入的属性名称,ref是被注入的bean对象的id,如果注入的对象是一个bean,那么这时候的name和ref是一致的。
package org.example.dao;

import org.example.service.UserService;

public class UserDao {
private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void test(){
        System.out.println("userDao!!!   test");
    }
}

<?xml version="1.0" encoding="UTF-8"?>


<beans  xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        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-4.0.xsd
             http://www.springframework.org/schema/context
             http://www.springframework.org/schema/context/spring-context-4.0.xsd
              "
      >
    <!--    设置javabean对应的bean标签-->
    <bean id="userDao" class="org.example.dao.UserDao" >
        <property name="userService" ref="userService"></property>
    </bean>
    <bean id="userService" class="org.example.service.UserService"></bean>
    
</beans>

构造器注入
和setter注入一样,也需要先写好变量名以及构造方法,但是这里的构造方法是被注入对象的构造方法

package org.example.dao;

import org.example.service.UserService;

public class UserDao {
private UserService userService;

    public UserDao(UserService userService) {
        this.userService = userService;
    }


//    public void setUserService(UserService userService) {
//        this.userService = userService;
//    }

    public void test(){
        System.out.println("userDao!!!   test");
        userService.test();
    }
}

之后在配置文件的bean标签中加入的标签

<beans  xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        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-4.0.xsd
             http://www.springframework.org/schema/context
             http://www.springframework.org/schema/context/spring-context-4.0.xsd
              "
      >
    <!--    设置javabean对应的bean标签-->
    <bean id="userDao" class="org.example.dao.UserDao" >
      <constructor-arg name="userService" ref="userService"></constructor-arg>


    </bean>
    <bean id="userService" class="org.example.service.UserService"></bean>

</beans>

正常情况下使用构造器注入是没有问题的,但是如果互相注入会导致循环依赖问题。构造器在实例化的时候会先去找构造器的参数,然后再去实例化对象,如果出现循环依赖,可以通过改为set注入解决,那为什么set注入不会循环依赖呢?因为set注入是先实例化对象,然后再找被注入的属性set进来。

2-2. 基于注解的自动注入

需要在配置文件中开启自动注入

<context:annotation-config>

关于@resources有如下说明

1 注解默认通过属性名称查找对应的bean对象(属性字段名称与bean标签的id属性一致)

2 如果属性字段名称不一样,则会通过类型(class)查找

3 属性字段可以提供set方法,也可以不提供

4 注解可以声明在属性字段上,或者set方法级别

5 可以设置注解的name属性,name属性值要与bean标签的id属性值一致(如果设置了name属性,则会使用name属性查询bean对象)

6 当注入接口时,如果接口只有一个实现类,则正常实例化;如果接口有多个实现类,则需要使用name属性指定需要被实例化的bean对象

@Autowired要注意的点:

1 该注解默认通过类型(class)查找bean对象,与属性字段无关,这个@resources不同

2 属性可以提供set方法,也可以不提供set方法

3 注解可以声明在属性级别或set级别

4 可以添加@Qualifier结合使用,通过value属性值查找bean对象(value属性必须设置,且值要与bean比钱的id属性值对应)

@resource注解和@Autowired注解是有很大区别的,比如@Resource首先是通过名字去寻找被注入的bean,而@Autowired则是通过类型(class)去查找bean对象

3. SpringIOC扫描器

bean太多了总不可能每个bean都配置,所以使用扫描器来得到bean对象,扫描的话剧需要给类标注注解,让扫描器知道这个类要被扫描。扫描器的特点如下:

1 设置自动化扫描的范围
如果bean对象未在指定的保范围,即使声明了注解,也可能无法实例化
2 使用指定的注解(声明在类级别) bean对象的id属性默认是类的首字母小写

Dao层:
@Repository
Service层
@Service
Controller层
@Controller
任意类
@Component

4. IOC仿写

在IOC的过程中,最重要的就是ClassPathXmlApplicationContext这个方法找到实例化的bean对象的容器上下文,因此尝试手动实现这个类

4-1 手动实现ClassPathXmlApplicationContext

package org.example.spring;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;

import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 模拟Spring的实现
 * 1 通过构造器得到对应的配置文件
 * 2 通过dom4j解析配置文件(本程序中的spring.xml)得到list集合(list存放xml文件中的bean标签的id和class属性值)
 * <p>
 * 3 通过反射得到对应的实例化对象,放置在map中,键为id,值为class对应的实例(遍历list集合 通过获取对应的class属性,获取class属性的方法是利用class.forName(XX).newInstance)
 * <p>
 * 4 通过id属性获取指定的实例化对象
 */
public class MyClassPathXmlApplicationContext implements MyFactory {
    /**
     * 存放从配置文件中获取到的bean标签得到的id和class文件
     */
    List<MyBean> beanList = new ArrayList<>();


    /**
     * map存放实例化对象,key存放id value存放实例化对象
     */

    private Map<String, Object> map = new HashMap<>();


    /**
     * 1 因为需要通过带参构造器来获得对应的配置文件,因此这里需要一个构造方法
     *
     * @param filename
     */
    public MyClassPathXmlApplicationContext(String filename) {

        /**
         * 2 通过dom4j解析配置文件 得到list集合
         *
         *
         *
         */

        this.parsXml(filename);

        /**
         * 3 通过反射得到实例化对象,放置在map中
         */


        this.instanceBean();


    }



    /**
     * 2.1 获取解析器
     * 2.2 获取配置文件的URL
     * 2.3 通过解析器配置文件
     * 2.4 通过xpath语法解析 获取beans标签下的所有bean标签
     * 2.5 指定解析语法解析文档对象 返回元素集合
     * 2.6 判断元素集合是否为空
     * 2.7 如果元素集合不为空 便遍历集合
     * 2.8 获取bean标签元素的属性(id和class属性)
     * 2.9 获取MyBean对象,将id和class属性值设置到对象中,再将对象设置到MyBean集合中
     */
    private void parsXml(String filename) {
        /**
         *  2.1 获取解析器
         */

        SAXReader saxReader = new SAXReader();
        /**
         *  2.2 获取配置文件的URL
         */

        /**
         * 再说明一下
         * Object类的getClass()方法返回的是该对象的运行时类,一个对象的运行时类是该对象通过new创建时指定的类
         */
        URL url = this.getClass().getClassLoader().getResource(filename);


        /**
         * 2.3 通过解析器解析配置文件
         */
        try{
            Document document = saxReader.read(url);


            /**
             * 2.4 通过xpath语法解析 获取beans标签下的所有bean标签
             */

            XPath xPath = document.createXPath("beans/bean");
            /**
             * 2.5 指定解析语法解析文档对象 返回元素集合
             */
            List<Element> elementList = xPath.selectNodes(document);

            /**
             * 2.6 判断元素集合是否为空
             */
            if(elementList!=null&&elementList.size()>0){
                /**
                 * 2.7 如果元素集合不为空 便遍历集合
                 */
                for (Element el : elementList) {
                    /**
                     * 2.8 获取bean标签元素的属性(id和class属性)
                     */
                   String id = el.attributeValue("id");
                   String clazz = el.attributeValue("class");

                    /**
                     * 2.9 获取MyBean对象,将id和class属性值设置到对象中,再将对象设置到MyBean集合中
                     */
                   MyBean myBean = new MyBean(id,clazz);
                   beanList.add(myBean);

                }

            }

        }catch (Exception e){
            e.printStackTrace();
        }

    }

    /**
     * 3.1 判断集合是否为空,如果不为空,则遍历集合,获取对象的id和class属性
     * 3.2 通过类的全路径名反射得到实例化对象class.forName(class).newInstance()
     * 3.3 将对应的id和实例化好的bean对象设置到map中
     */

    private void instanceBean() {
        /**
         * 3.1 判断集合是否为空,如果不为空,则遍历集合,获取对象的id和class属性
         */
        if(beanList!=null&&beanList.size()>0){
            for (MyBean bean : beanList) {
                String id = bean.getId();
                String clazz = bean.getClazz();
               try{
                   /**
                    *  3.2 通过类的全路径名反射得到实例化对象class.forName(class).newInstance()
                    */
                   Object object = Class.forName(clazz).newInstance();
                   /**
                    * 3.3 将对应的id和实例化好的bean对象设置到map中
                    */
                   map.put(id,object);

               }catch (Exception e){
                   e.printStackTrace();
               }




            }
        }

    }


    /**
     * 4 通过id属性从map中获取指定的实例化对象
     *
     * @param id
     * @return
     */
    @Override
    public Object getBean(String id) {
        Object object = map.get(id);

        return object;
    }
}

在这个类的实现过程中还需要bean工厂以及Mybean对象

package org.example.spring;

import lombok.*;

/**
 * bean属性对象
 *
 * 用来存放配置文件中bean标签对应的id和class属性
 */
@Data
@AllArgsConstructor
@NoArgsConstructor

public class MyBean {

    //bean标签的id属性值
    private String id;
    //bean标签的class属性值
    private String clazz;


}

package org.example.spring;

/**
 * 创建一个工厂作为bean对象工厂接口的定义
 */
public interface MyFactory {
        //通过id属性值获取对象
    public Object getBean(String id);

    //工厂的实现类



}

posted @ 2022-03-19 03:09  kevin_kay  阅读(45)  评论(0编辑  收藏  举报