深入剖析Spring(五):IOC核心思想(代码篇)

11 篇文章 0 订阅

在上一篇文章中,我们粗略的对Spring源码IOC这块过了一遍,那么这篇文章来简单写一个IOC的过程。由于理论性的东西都在上一篇解释过了,这篇咱就直接在代码中理解。

一、准备工作
1.1本文所用到的依赖包:
		<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
1.2 web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <display-name>MVC Web Application</display-name>

    <servlet>
        <servlet-name>CcMvc</servlet-name>
        <!-- 指定我们自定义的DispatcherServlet的路径-->
        <servlet-class>com.ccc.spring2.webmvc.XHDispatcherServlet</servlet-class>
        <init-param>
            <!--指定spring的xml的配置文件,我在这里用properties文件代替。-->
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:application.properties</param-value>
        </init-param>
        <!-- 让tomcat启动时即加载时便初始化servlet -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <!-- url-->
        <servlet-name>CcMvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>
1.3 建立项目结构

我们模仿spring的项目结构

1.3.1 创建BeanFactory接口

在包beans下创建:

/**
 * @author chenxh
 * @date 2020/3/23 20:28
 * @Description: 单例工厂顶层
 * @modify:
 * @modifyDate:
 * @Description:
 */
public interface XHBeanFactory {
    /**
     * 根据beanName从IOC容器中获取一个实例Bean
     * spring中使用单例有利于管理和维护
     *
     */
    Object getBean(String beanName) throws Exception;

    Object getBean(Class<?> beanClass) throws Exception;

}
1.3.2 创建BeanDefinition

在之前说道过在Spring容器启动的过程中,会将Bean解析成BeanDefinition结构。因此我们也创建一个,模仿spring的包名创建一个config包:

@Data
public class XHBeanDefinition {

    private String beanClassName; //全包名
    private boolean isLazyInit = false; //是否懒加载
    private String factoryBeanName; //类名首字母小写
    private boolean isSingleton = true; //是否单例
}
1.3.3 BeanDefinitionReader

这个方法主要作用是解析配置文件并将其封装成BeanDefinition对象供IOC操作,此方法在上几篇文章MVC中写过,这里就不在解释了。

package com.ccc.spring2.beans.support;

import com.ccc.spring2.beans.config.XHBeanDefinition;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * 加载配置文件的类
 */
public class XHBeanDefinitionReader {

    private Properties config = new Properties();
    //定义配置文件,暂时写死
    private final String SCAN_PACKAGE = "scanPackage";
    //存储所有的类名
    private List<String> registyBeanClasses = new ArrayList<String>();  //全包名

    public XHBeanDefinitionReader(String... locations) {
        //通过URL定位找到对应的文件,转为文件流读取
        try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(locations[0].replace("classpath:", ""))) {
            config.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //进行扫描
        doScanner(config.getProperty(SCAN_PACKAGE));
    }

    private void doScanner(String scanPackage) {
        //转换为文件路径,实际上就是把.替换为/就OK了
        URL url = this.getClass().getResource("/" + scanPackage.replaceAll("\\.","/"));
        File classPath = new File(url.getFile());
        for (File file : classPath.listFiles()) {
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                registyBeanClasses.add(className);
            }
        }

    }

    public Properties getConfig() {
        return this.config;
    }

    //扫描配置信息并内部封装成XHBeanDefinition对象,便于IOC操作
    public List<XHBeanDefinition> loadBeanDefinitions(String... locations) {
        List<XHBeanDefinition> result = new ArrayList<XHBeanDefinition>();
        try {
            System.out.println("registyBeanClasses:"+registyBeanClasses);
            for (String className : registyBeanClasses) {
                Class<?> beanClass = Class.forName(className);
                //如果是一个接口,是不能实例化的
                //用它实现类来实例化
                if(beanClass.isInterface()) { continue; }

                //beanName有三种情况:
                //1、默认是类名首字母小写
                //2、自定义名字
                //3、接口注入
                result.add(doCreateBeanDefinition(toLowerFirstCase(beanClass.getSimpleName()),beanClass.getName()));
                result.add(doCreateBeanDefinition(beanClass.getName(),beanClass.getName()));
                Class<?> [] interfaces = beanClass.getInterfaces();
                for (Class<?> i : interfaces) {
                    //如果是多个实现类,只能覆盖
                    //为什么?因为Spring没那么智能,就是这么傻
                    //这个时候,可以自定义名字
                    result.add(doCreateBeanDefinition(i.getName(),beanClass.getName()));
                }

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

    //把配置信息解析成BeanDefinition
    //把每一个配信息解析成一个BeanDefinition
    private XHBeanDefinition doCreateBeanDefinition(String factoryBeanName,String beanClassName){
        XHBeanDefinition beanDefinition = new XHBeanDefinition();
        beanDefinition.setBeanClassName(beanClassName);
        beanDefinition.setFactoryBeanName(factoryBeanName);
        return beanDefinition;
    }

    /**
     * 首字母小写
     */
    private String toLowerFirstCase(String simpleName) {
        char [] chars = simpleName.toCharArray();
        chars[0] += (1<<5);
        return String.valueOf(chars);
    }
}

1.3.4 BeanWrapper

BeanWrapper是对Bean的包装,其接口中所定义的功能很简单包括设置获取被包装的对象,获取被包装bean的属性描述器.在包beans下:

public class XHBeanWrapper {

    private Object wrappedInstance; //类名,首字母小写
    private Class<?> wrappedClass; //对象

    public XHBeanWrapper(Object wrappedInstance){
        this.wrappedInstance = wrappedInstance;
    }

    public Object getWrappedInstance(){
        return this.wrappedInstance;
    }

    // 返回代理以后的Class
    // 可能会是这个 $Proxy0
    public Class<?> getWrappedClass(){
        return this.wrappedInstance.getClass();
    }
}
1.3.5 IOC容器AbstractApplicationContext

创建包名context在子包support下创建(support包名的意思在java中大多数是扩展的意思。)
在上篇文章中提到过IOC初始化真正的逻辑是在refresh() 中,而该又是实际上又是一个模板方法,只是规定了IOC启动的流程,具体的逻辑还是由其子类来实现的。

public abstract class XHAbstractApplicationContext {
    //规范方法,提供重写
    public void refresh() throws Exception {};
}
1.3.6 DefaultListableBeanFactory

是否还记得这个类,在这稍微解释一下spring中BeanFactory定义了一系列规范都由子类去实现,例如表示可序列化的Bean,有继承关系的bean等等,这些Bean在spring中都是分开管理的而DefaultListableBeanFactory这个类就是这些子类最终的实现类。也就是这些类最终都由它来实现了。
在这里我们简单的定义一下。理解一下它的思想:在beans包下创建support

//我们简单就实现一个IOC父类
public class XHDefaultListableBeanFactory extends XHAbstractApplicationContext {

     //存储注册信息的BeanDefinition,伪IOC容器 //key是类名小写,value是beanDefinition对象
     protected final Map<String, XHBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, XHBeanDefinition>();
}
1.3.7 ApplicationContext

这个应该不陌生了。

public class XHApplicationContext extends XHDefaultListableBeanFactory implements XHBeanFactory {
	//配置文件
    private String[] configLocations;
    //加载配置文件的类
    private XHBeanDefinitionReader reader;
    //单例IOC容器
    private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    //通用IOC容器
    private Map<String, XHBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>();

    public XHApplicationContext(String... configLocations) throws Exception {
        this.configLocations = configLocations;
        refresh();
    }
    /**
     * 重写AbstractApplicationContext的refresh方法
     * 此方法是一个模板方法,规定了IOC容器的运行流程,
     * 调用的是父类AbstractApplicationContext 的方法.
     * 真正的载入也是从这个时候开始的。
     */
     
    @Override
    public void refresh() throws Exception {
        //IOC
        //1.定位,找到配置文件
        reader = new XHBeanDefinitionReader(this.configLocations);

        //2.加载配置文件,扫描相关的类,把它们封装成BeanDefinition
        List<XHBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();

        //3.注册,把配置信息放到容器里(伪IOC容器)
        doRegisterBeanDefinition(beanDefinitions);

        //4.把非懒加载的类提前初始化
        doAutowired();
    }
}

手动创建以上方法。到此准备工作结束了。

2. 填坑

既然我们主路拉通了,那么现在就开始来填坑。
关注refresh()这个方法是IOC容器初始化的入口。其实在之前的文章中就讲过这个过程。在这里1、2两步就不说啦,代码也贴出来了也注释啦,有兴趣的朋友可以去看看小编这个专栏下的MVC篇。里面提到了这些。

2.1 doRegisterBeanDefinition(beanDefinitions)

此方法将BeanDefinitionReader对象解析出来对象进行迭代并存放到Map中。

private void doRegisterBeanDefinition(List<XHBeanDefinition> beanDefinitions) {
        //IOC加载
        for (XHBeanDefinition beanDefinition : beanDefinitions) {
            //父类XHDefaultListableBeanFactory中定义的map。这个Map不是真正意义上的IOC容器
            super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
        }
    }
2.2 doAutowired()

遍历上一步中存入Map中的集合,并判断对象中isLazyInit属性是否为懒加载,如果不是懒加载的就进行getBean()DI注入操作。

//处理非延时加载
    private void doAutowired() throws Exception {
        //遍历定位
        for (Map.Entry<String, XHBeanDefinition> beanDefinitionEntry : super.beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            if (!beanDefinitionEntry.getValue().isLazyInit()) {
                getBean(beanName);
            }
        }
    }

IOC的简单步骤到此就介绍的差不多了,我们发现不管是之前MVC中介绍的,还是本文章介绍的内容,我们都能发现Spring在处理过程中,都是一步步去解析的,例如之前在解析配置文件的时候,会先加载->在找到所有class类->在判断是否@Service、@Controller这些类注解->在判断是否@Autowried等注解。

对于IOC 整理就以上这些就介绍这么多,如果发现有补充的地方,我会继续添加,同时希望各位看过的伙伴们如果发现了问题能够及时批评指正,在此感谢。下一篇我会记录DI的过程。

上一篇:深入剖析Spring(四):IOC核心思想(源码分析篇)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring中的IOC(Inversion of Control)是一种设计模式,它将对象的创建和管理交给容器来完成,而不是由程序员手动创建和管理对象。IOC核心思想是“控制反转”,即将原本由程序员控制的对象创建和管理转交给容器来控制。 在Spring中,IOC容器是一个核心的概念,它负责管理所有的Bean对象。程序员只需要在配置文件或者注解中定义好所需要的Bean,容器会负责创建、初始化、依赖注入和销毁等工作。 下面是一个简单的Spring IOC示例: 1.定义一个接口: ```java public interface MessageService { String getMessage(); } ``` 2.实现接口: ```java public class MessageServiceImpl implements MessageService { @Override public String getMessage() { return "Hello, World!"; } } ``` 3.配置文件: ```xml <beans> <bean id="messageService" class="com.example.MessageServiceImpl"/> </beans> ``` 4.使用IOC容器: ```java public class App { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); MessageService messageService = (MessageService) context.getBean("messageService"); System.out.println(messageService.getMessage()); } } ``` 在上面的示例中,首先定义了一个接口MessageService和它的实现类MessageServiceImpl。然后在配置文件中定义了一个Bean,并指定了它的类名。最后在应用程序中使用ApplicationContext来获取Bean并调用它的方法。这个过程中,容器会自动进行依赖注入,即将MessageServiceImpl对象注入到MessageService接口中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈橙橙丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值