手把手带你实现Spring IOC,看完还不懂来打我

前言

Spring ioc 相信很多人都知道这是Spring框架中一个非常核心的组件,IoC(控制反转),对于初学Spring的人来说,对其的设计思想理解可能非常表面,要理解好Ioc的关键是要明确 以下几点,谁控制谁,控制什么,为何是反转(那正转?),哪些方面反转了

谁控制谁,控制什么 还记得你们当时学习servlet的时候在每一层内部通过new关键字进行创建对象吗,要清楚这是程序主动去创建依赖对象; 而当你们接触了Spring之后IoC是有专门一个容器来创建这些对象,控制对象。

为何是反转(那正转?),这个简单理解当我们主动控制去获取依赖对象,这就是正转? 而反转则是由容器来帮忙创建及注入依赖对象; 为何是反转 ? 因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转; 哪些方面反转了?依赖对象的获取被反转了。

手撕最简易版IOC容器

可能你还不明白,觉得很抽象?那么跟着小编手把手带你来实现IOC!

ioc不是一个容器嘛,ok,我们先来定义一个容器,容器的特性肯定拥有存、取对象嘛~

Spring的对象都是一个个bean,但是我们不知道bean类型是什么,那么就泛型体现,还有bean什么时候存放进容器的啊?spring是在启动的时候会进行初始化扫描,我们就定义一个initAutoWired方法来模拟。

先定义一个容器接口JsContainer。

/**
 * @创建人 : 头条账号 "深夜敲代码"
 * @创建时间 2021/7/13
 */
public interface JsContainer {

    /**
     * 根据Class获取Bean
     * @param clazz
     * @return
     */
     T getBean(Class clazz);

    /**
     * 注册一个Class到容器中
     *
     * @param clazz
     */
    Object registerBean(Class<?> clazz);

    /**
     * 初始化装配
     */
    void initAutoWired();

我们再写一个自定义注解标识JsAutowired,来为了后面通过反射获取实例化bean,自定义注解可以指定要注入的类型,以及注入的bean名称。

package com.orangesongjava.ioc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @创建人 : 头条账号 "深夜敲代码"
 * @创建时间 2021/7/13
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JsAutowired {

    /**
     * @return 要注入的类类型
     */
    Class<?> value() default Class.class;

    /**
     * @return bean的名称
     */
    String name() default "";
}

容器接口和注解都有了,接下来我们思考来实现它!我们定义一个类 JsSampleContainer 来实现容器接口JsContainer 。

package com.orangesongjava.ioc;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @创建人 : 头条账号 "深夜敲代码"
 * @创建时间 2021/7/13
 */
@SuppressWarnings("unchecked")
public class JsSampleContainer implements JsContainer {
	/**
     * 保存所有bean对象,格式为 com.xxx.xxx.XxxClass : @56x2ya
     */
    private Map beanNameMap;
    
    /**
     * 存储bean和name的关系
     */
    private Map beanKeys;
    
    public JsSampleContainer() {
    	this.beanNameMap = new ConcurrentHashMap<>();
    	this.beanKeys = new ConcurrentHashMap<>();
    }
	
	@Override
	public  T getBean(Class clazz) {
		String name = clazz.getName();
		Object object = beanNameMap.get(name);
		if(null != object){
			return (T) object;
		}
		return null;
	}

	@Override
	public Object registerBean(Class<?> clazz) {
		String name = clazz.getName();
		beanKeys.put(name, name);
		Object bean = newInstance(clazz);
        beanNameMap.put(name, bean);
		return bean;
	}

	@Override
	public void initAutoWired() {
        beanNameMap.forEach((k,v) -> injection(v));
	}
	/**
	 * 注入对象
	 * @param object
	 */
	public void injection(Object object) {
		// 所有字段
	    try {
			Field[] fields = object.getClass().getDeclaredFields();
			for (Field field : fields) {
				// 需要注入的字段
                JsAutowired jsAutowired = field.getAnnotation(JsAutowired.class);
			    if (null != jsAutowired) {
			    	// 要注入的字段
			        Object autoAutoWiredField = null;
			        String name = jsAutowired.name();
			        // 如果说这里JsAutowired自定义注解没指定name属性 则默认值是""
	        		if(!name.equals("")){
	        		    // 指定了特定的name
	        			String className = beanKeys.get(name);
	        			if(null != className && !className.equals("")){
                                            autoAutoWiredField = beanNameMap.get(className);
	        			}
	        			if (null == autoAutoWiredField) {
				            throw new RuntimeException("Unable to load " + name);
				        }
	        		} else {
                                        // JsAutowired注解没有name属性
                                        // 判断注入的类型 是否是类Class类型 默认值也是Class
	        			if(jsAutowired.value() == Class.class){
                                               autoAutoWiredField = register(field.getType());
				        } else {
				            // 指定装配的类
                                            autoAutoWiredField = this.getBean(jsAutowired.value());
				            if (null == autoAutoWiredField) {
                                                autoAutoWiredField = register(jsAutowired.value());
				            }
					}
			        }
			        if (null == autoAutoWiredField) {
			            throw new RuntimeException("Unable to load " + field.getType().getCanonicalName());
			        }
			        boolean accessible = field.isAccessible();
			        field.setAccessible(true);
			        field.set(object, autoAutoWiredField);
			        field.setAccessible(accessible);
			    }
			}
		} catch (SecurityException e) {
        	e.printStackTrace();
        } catch (IllegalArgumentException e) {
        	e.printStackTrace();
        } catch (IllegalAccessException e) {
        	e.printStackTrace();
        }
    }
    
    private Object register(Class<?> clazz){
        if(null != clazz){
            return this.registerBean(clazz);
        }
        return null;
    }

    /**
     * 创建一个实例对象
     * @param clazz class对象
     * @return
     */
    public static Object newInstance(Class<?> clazz) {
        try {
            return clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
}

测试一下

我们现在来测试一下,我们先定义一个类OrangeSongJavaService,方便待会测试被调用。注意哦,这个类上面没有任何注解。也就是他并没有被Spring管理起来。

/**
 * @创建人 : 头条账号 "深夜敲代码"
 * @创建时间 2021/7/13
 */
package com.orangesongjava.ioc;

public class OrangeSongJavaService {
    public void writeArticleOnJs(String name){
        System.out.println(name + "在头条上写文章!");
    }
}

在定义本地调用的模拟调用OrangeSongJavaClient。注意这个类上面也是没有注解,另外引用的OrangeSongJavaService 被我们的自定义注解JsAutowired标识起来了。

/**
 * @创建人 : 头条账号 "深夜敲代码"
 * @创建时间 2021/7/13
 */
package com.orangesongjava.ioc;

public class OrangeSongJavaClient {

    @JsAutowired
    private OrangeSongJavaService orangeSongJavaService;

    public void work() {
        orangeSongJavaService.writeArticleOnJs("深夜敲代码");
    }
    public OrangeSongJavaService getOrangeSongJavaService() {
        return orangeSongJavaService;
    }
}

现在我们写个测试main方法测试一下。

/**
 * @创建人 : 头条账号 "深夜敲代码"
 * @创建时间 2021/7/13
 */
package com.orangesongjava.ioc;

public class IocTest {

    private static JsSampleContainer jsContainer = new JsSampleContainer();

    public static void main(String[] args) {
        // 将类注入容器
        jsContainer.registerBean(OrangeSongJavaClient.class);
        // 初始化注入-扫描引用
        jsContainer.initAutoWired();
        // 容器获取bean
        OrangeSongJavaClient client jsContainer.getBean(OrangeSongJavaClient.class);
        // 执行
        client.work();
    }
}
/**
 * @创建人 : 头条账号 "深夜敲代码"
 * @创建时间 2021/7/13
 */
package com.orangesongjava.ioc;

public class IocTest {

    private static JsSampleContainer jsContainer = new JsSampleContainer();

    public static void main(String[] args) {
        // 将类注入容器
        jsContainer.registerBean(OrangeSongJavaClient.class);
        // 初始化注入-扫描引用
        jsContainer.initAutoWired();
        // 容器获取bean
        OrangeSongJavaClient client jsContainer.getBean(OrangeSongJavaClient.class);
        // 执行
        client.work();
    }
}

如果能输出执行逻辑,则表示这段程序没问题。看到这里,你对ioc理解是不是有点清晰了,有任何疑问,或者不懂的地方可以下方留言。

展开阅读全文

页面更新:2024-06-02

标签:创建人   手把手   注解   字段   初始化   容器   账号   标识   实例   深夜   接口   定义   对象   类型   代码   时间   科技

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top