Spring 源码分析(二)—— 添加 Application Context 管理bean实例。

导读:本篇文章讲解 Spring 源码分析(二)—— 添加 Application Context 管理bean实例。,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

建议从下文开始阅读。
Spring源码 v1.0 —— 简陋版

从servlet 到 ApplicationContext

在上一个简陋版本中,了解到SpringMVC的入口是在DispatcherServlet。我们在init()方法中完成了IOC容器的初始化。

但是在我们日常使用Spring的过程中,见的最多的却是ApplicationContext,似乎Spring托管的所有实例Bean都可以通过调用getBean方法来获得。那么ApplicationContext又从何而来?从Spring源码中我们可以逐步分析,DispatacherServlet的类图如下:
在这里插入图片描述
​DispatcherServlet 继承了FrameworkServlet,FrameworkServlet继承了HttpServletBean,HttpServletBean继承了HttpServlet。在HttpServletBean的init()方法中调用了FrameworkServlet的initServletBean方法,在initServletBean()方法中初始化WebAppicationContext实例。在initServletBean()方法中调用了DispatcherServlet重写的onRefresh()方法。在DispatcherServlet的onRefresh()方法中调用了initStrategies()方法,初始化SpringMVC的九大组件。

其实,上面复杂的调用关系,我们可以简单的得出一个结论:就是在Servlet的init()方法中初始化了IOC容器和SpringMVC所依赖的九大组件。

三个重要的类

在这里插入图片描述

环境搭建

在初始环境上进行改进,将1.0版本代码复制到新工程即可。

该工程将1.0版本进行优化,通过applicationContext来进行IOC容器的初始化及DI依赖注入。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-weXo6tFg-1634525288395)(D:\cc\software\笔记\notes\源码分析\spring\手写Spring源码 V2.0 —— 从servlet 到 ApplicationContext.assets\image-20211018101046306.png)]

最顶层工厂接口

首先从最顶层开始实现,创建相关接口

public interface DemoBeanFactory {

    Object getBean(Class beanClass);

    Object getBean(String beanName);

}

beans(配置封装)模块

public class DemoBeanDefinition {

    /**
     * 是否懒加载
     * @return
     */
    public boolean isLazyInit() {
        return false;
    }

    private String factoryBeanName;//beanName

    private String beanClassName;//原生类的全类名

    public String getFactoryBeanName() {
        return factoryBeanName;
    }

    public void setFactoryBeanName(String factoryBeanName) {
        this.factoryBeanName = factoryBeanName;
    }

    public String getBeanClassName() {
        return beanClassName;
    }

    public void setBeanClassName(String beanClassName) {
        this.beanClassName = beanClassName;
    }
}
public class DemoBeanWrapper {

    // 存储 实例
    private Object wrapperInstance;

    // 存储 类
    private Class<?> wrappedClass;

    public DemoBeanWrapper(Object instance) {
        this.wrapperInstance = instance ;
        this.wrappedClass = instance.getClass();
    }

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

    public Class<?> getWrappedClass(){
        return this.wrappedClass;
    }
}

context(IOC容器)模块

该类为核心类,从构造方法开始查看代码。

是spring继BeanFactory之外的另一个核心接口或容器,允许容器通过应用程序上下文环境创建、获取、管理bean。

public class DemoApplicationContext implements DemoBeanFactory {

    private DemoDefaultListableBeanFactory registry = new DemoDefaultListableBeanFactory();

    //读取配置文件
    private DemoBeanDefinitionReader reader;

    // 三级缓存 (终结缓存) 通用的IOC容器
    private Map<String, DemoBeanWrapper> factoryBeanInstanceCache = new HashMap<>();

    //单例的IOC容器缓存
    private Map<String, Object> factoryObjectInstanceCache = new HashMap<>();

    //在servlet中实例化,传入配置信息
    public DemoApplicationContext(String... configLocations) throws Exception {
        //1.加载配置文件
        reader = new DemoBeanDefinitionReader(configLocations);

        //2.解析配置文件,将所有的配置信息封装为BeanDefinition对象
        List<DemoBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();

        //3.缓存所有配置信息
        this.registry.doRegisBeanDefinition(beanDefinitions);

        //4.加载非延时加载的所有bean
        doLoadInstance();
    }

    private void doLoadInstance() {
        //循环调用getBean方法
        for (Map.Entry<String, DemoBeanDefinition> entry : this.registry.beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            //只处理非延时加载的情况
            if (!entry.getValue().isLazyInit()) {
                getBean(beanName);
            }
        }
    }

    @Override
    public Object getBean(Class beanClass) {
        return getBean(beanClass.getName());
    }

    //从IOC容器中获得一个Bean对象 核心
    @Override
    public Object getBean(String beanName) {
        //1.先拿到BeanDefinition配置信息
        DemoBeanDefinition beanDefinition = registry.beanDefinitionMap.get(beanName);

        //2.反射实例化对象
        Object instance = instantiateBean(beanName, beanDefinition);

        //3.将返回的bean对象 封装成 BeanWrapper
        DemoBeanWrapper beanWrapper = new DemoBeanWrapper(instance);

        //4.执行依赖注入
        populateBean(beanName, beanDefinition, beanWrapper);

        //5.保存到IOC容器中
        this.factoryBeanInstanceCache.put(beanName, beanWrapper);

        return beanWrapper.getWrappedInstance();
    }

    // 依赖注入
    private void populateBean(String beanName, DemoBeanDefinition beanDefinition, DemoBeanWrapper beanWrapper) {

        Object instance = beanWrapper.getWrappedInstance();
        Class<?> clazz = beanWrapper.getWrappedClass();

        if (!(clazz.isAnnotationPresent(DemoController.class) || clazz.isAnnotationPresent(DemoService.class))) {
            return;
        }


        //忽略字段的修饰符
        for (Field field : clazz.getDeclaredFields()) {
            if (!field.isAnnotationPresent(DemoAutowired.class)) {
                continue;
            }

            DemoAutowired autowired = field.getAnnotation(DemoAutowired.class);
            String autowiredBeanName = autowired.value().trim();
            if ("".equals(autowiredBeanName)) {
                autowiredBeanName = field.getType().getName();
            }

            //强制访问
            field.setAccessible(true);
            try {
                if (this.factoryBeanInstanceCache.get(autowiredBeanName) == null) {
                    continue;
                }
                // 相当于 demoAction.demoService = ioc.get("com.demo.service.IDemoService");
                field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    //反射实例化对象
    private Object instantiateBean(String beanName, DemoBeanDefinition beanDefinition) {
        String className = beanDefinition.getBeanClassName();
        Object instance = null;
        try {
            Class<?> clazz = Class.forName(className);
            instance = clazz.newInstance();

            //如果是代理对象,则触发AOP的逻辑

            this.factoryObjectInstanceCache.put(beanName, instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }

    public int getBeanDefinitionCount() {
        return this.registry.beanDefinitionMap.size();
    }

    public String[] getBeanDefinitionNames() {
        return this.registry.beanDefinitionMap.keySet().toArray(new String[0]);
    }
}

该类为BeanFactory下的另一实现类,用来实现Bean的list集合操作,在本工程中用来缓存配置信息。

public class DemoDefaultListableBeanFactory implements DemoBeanFactory {

    public Map<String, DemoBeanDefinition> beanDefinitionMap = new HashMap<>();

    @Override
    public Object getBean(Class beanClass) {
        return null;
    }

    @Override
    public Object getBean(String beanName) {
        return null;
    }

    //缓存所有配置信息
    public void doRegisBeanDefinition(List<DemoBeanDefinition> beanDefinitions) throws Exception {
        for (DemoBeanDefinition beanDefinition : beanDefinitions) {
            if (this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new Exception("The" + beanDefinition.getFactoryBeanName() + "is Exists!!!");
            }
            this.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
        }
    }
}

读取配置文件

该类的作用是读取 Spring 配置文件中的内容,将其转换为 IoC 容器内部的数据结构:BeanDefinition。

public class DemoBeanDefinitionReader {

    //保存用户篇配置好的配置文件
    private Properties contextConfig = new Properties();

    //缓存从包路径下扫描的全类名,需要被注册的BeanClass
    private List<String> registryBeanClasses = new ArrayList<String>();

    public DemoBeanDefinitionReader(String... locations) {
        //1.加载配置文件
        doLoadConfig(locations[0]);

        //2.扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));

    }

    //对配置信息进行封装
    public List<DemoBeanDefinition> loadBeanDefinitions() {
        List<DemoBeanDefinition> result = new ArrayList<>();
        try {
            for (String className : registryBeanClasses) {
                Class<?> beanClass = Class.forName(className);

                //beanClass本身是接口的话不做处理
                if (beanClass.isInterface()) {
                    continue;
                }
                //1.默认类名首字母小写的情况
                result.add(doCreateBeanDefinition(toLowerFirstCase(beanClass.getSimpleName()), beanClass.getName()));

                //2.如果是接口,就用实现类
                for (Class<?> i : beanClass.getInterfaces()) {
                    result.add(doCreateBeanDefinition(i.getName(), beanClass.getName()));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;
    }

    private DemoBeanDefinition doCreateBeanDefinition(String factoryBeanName, String factoryClassName) {
        DemoBeanDefinition beanDefinition = new DemoBeanDefinition();
        beanDefinition.setBeanClassName(factoryClassName);
        beanDefinition.setFactoryBeanName(factoryBeanName);
        return beanDefinition;
    }


    //根据contextConfigLocation的路径去classpath下找到的对应的文件
    private void doLoadConfig(String contextConfigLocation) {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);

        try {
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    // 扫描 classpath下符合包路劲规则所有的class文件
    private void doScanner(String scanPackage) {
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classPath = new File(url.getFile());

        for (File file : classPath.listFiles()) {
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                // 不是class文件则跳过
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                // 包名.类型  比如: com.demo.DemoAction
                String className = (scanPackage + "." + file.getName().replace(".class", ""));

                // 实例化 需要用到class.forName(className);
                registryBeanClasses.add(className);
            }
        }
    }

    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;  //利用ascll码,大小写字母之间相差32
        return String.valueOf(chars);
    }
}

servlet修改

和V1版本相似,只不过在init()方法中调用applicationContext上下文类,将读取配置文件,ioc容器,依赖注入等功能交给上下文处理。

public class DemoDispatchServlet extends HttpServlet {

    //保存controller 中 URL和Method的对应关系
    private Map<String, Method> handlerMapping = new HashMap<String, Method>();

    // IOC 容器的访问上下文
    private DemoApplicationContext applicationContext = null;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //6.根据url委派给具体的调用方法
        try {
            doDispatcher(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Exception,Detail: " + Arrays.toString(e.getStackTrace()));
        }
    }

    private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");

        if (!this.handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 Not Found!");
            return;
        }

        Method method = this.handlerMapping.get(url);

        // 1.先把形参的位置和参数名字建立映射关系,缓存下来
        Map<String, Integer> paramIndexMapping = new HashMap<String, Integer>();
        // 多个参数, 每个参数后面有多个值
        Annotation[][] pa = method.getParameterAnnotations();
        for (int i = 0; i < pa.length; i++) {
            for (Annotation annotation : pa[i]) {
                if (annotation instanceof DemoRequestParam) {
                    String paramName = ((DemoRequestParam) annotation).value();
                    if (!"".equals(paramName.trim())) {
                        paramIndexMapping.put(paramName, i);
                    }
                }
            }
        }

        Class<?>[] paramTypes = method.getParameterTypes();
        for (int i = 0; i < paramTypes.length; i++) {
            Class<?> type = paramTypes[i];
            if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
                paramIndexMapping.put(type.getName(), i);
            }
        }

        // 2.根据参数位置匹配参数名字,从url中取到参数名字对应的值
        Object[] paramValues = new Object[paramTypes.length];

        //http://localhost/demo/query?name=Tom&name=Tomcat&name=Mic
        Map<String, String[]> params = req.getParameterMap();
        for (Map.Entry<String, String[]> param : params.entrySet()) {
            String value = Arrays.toString(param.getValue())
                    .replaceAll("\\[|\\]", "")
                    .replaceAll("\\s", "");

            if (!paramIndexMapping.containsKey(param.getKey())) {
                continue;
            }

            int index = paramIndexMapping.get(param.getKey());

            //设计到类型强制转换
            paramValues[index] = value;
        }

        if (paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
            int index = paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[index] = req;
        }

        if (paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
            int index = paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[index] = resp;
        }


        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        // 3.组成动态参数列表 ,传给反射调用。
        method.invoke(applicationContext.getBean(beanName), paramValues);
    }

    @Override
    public void init(ServletConfig config) throws ServletException {

        try {
            applicationContext = new DemoApplicationContext(config.getInitParameter("contextConfigLocation"));
        } catch (Exception e) {
            e.printStackTrace();
        }

        //============MVC功能===============
        //5.初始化HandleMapping
        doInitHandleMapping();

        System.out.println("Spring framework is init.");

    }

    private void doInitHandleMapping() {
        if (applicationContext.getBeanDefinitionCount() == 0) {
            return;
        }

        for (String beanName : this.applicationContext.getBeanDefinitionNames()) {
            Object instance = applicationContext.getBean(beanName);
            Class<?> clazz = instance.getClass();

            if (!clazz.isAnnotationPresent(DemoController.class)) {
                continue;
            }

            //处理controller类添加地址注解
            String baseUrl = "";
            if (clazz.isAnnotationPresent(DemoRequestMapping.class)) {
                DemoRequestMapping requestMapping = clazz.getAnnotation(DemoRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            //只迭代public方法
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(DemoRequestMapping.class)) {
                    continue;
                }

                DemoRequestMapping requestMapping = method.getAnnotation(DemoRequestMapping.class);
                // /demo/query 使用正则表达处理 多个斜杠问题
                String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
                handlerMapping.put(url, method);
                System.out.println("Mapped : " + url + "--->" + method);

            }
        }
    }

    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;  //利用ascll码,大小写字母之间相差32
        return String.valueOf(chars);
    }

}

测试

按照V1.0版本测试即可。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/16856.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!