SpringMvc手写简单实现篇 - IOC容器、DI依赖注入篇

写Java的人Spring肯定用过了吧,但是有多少人知道原理呢?
由于版本的迭代,spring体系的越来越完整,代码分析起来也就越复杂,我们先从简单手写的案例来驱动分析源码吧。

spring手写简单版IOC容器和DI依赖注入

通过这些代码了解一个大概,然后再去实践分析源码!

1.预先准备

新建一个空的maven项目,代码结构如下
代码结构

  • action 和 service,接口就不贴了
@TController
public class HelloAction {

    @TAutowired
    HelloService helloService;

    public String hello(String name) {
        return helloService.sayHello(name);
    }
}

@TService
public class HelloServiceImpl implements HelloService {
    
    public String sayHello(String name) {
        return "my name is " + name;
    }
}

  • 由于没有引入任何jar包,annotation下面的这几个注解就直接复制改个名字,内容就不贴了
  • 配置文件application.properties

#需要扫描的包路径
packscanner=com.xxx.demo

实现内容都在TestApplicationContext,也就是上下文中

2.TestApplicationContext上下文

2.1 上下文 成员变量和init()

public class TestApplicationContext {

    //保存配置
    public Properties contextConfig = new Properties();
    //保存需要加载的clasName
    public List<String> classNames = new ArrayList<String>();
    //ioc容器
    public ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<String, Object>();
	//构造方法 main方法启动时调用
    public TestApplicationContext(String... classpath) {
        init(classpath);
    }
    public void init(String... classpath) {
        //初始化配置 这里就一个
        doLoadConfig(classpath[0]);
        //扫描对应的类
        doScanner(contextConfig.getProperty("packscanner"));
        //实例化对象 并加到IOC容器 加了注解的类
        doInstance();
        //依赖注入
        doAutowired();
        System.out.println("application is init");
    }
}

2.2 初始化配置doLoadConfig

private void doLoadConfig(String config) {
	//读流转配置
    InputStream inputStream = this.getClass().getClassLoader()
            .getResourceAsStream(config.replace("classpath:", ""));
    try {
        contextConfig.load(inputStream);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2.3 扫描对应的class类 doScanner

//保存className
private void doScanner(String packscanner) { //com.xxx.demo
	//替换成目录形式
    URL url = this.getClass().getResource("/" + packscanner.replaceAll("\\.", "/"));
    File file = new File(url.getPath());
    for (File f : file.listFiles()) {
        if (f.isDirectory()) {
        	//com.xxx.demo.action
            doScanner(packscanner + "." + f.getName());
        } else {
            if (!f.getName().endsWith(".class")) continue;
            //com.xxx.demo.action.HelloAction
            this.classNames.add(packscanner + "." + f.getName().replaceAll("\\.class", ""));
        }
    }
}

2.4 对加了注解的类初始化,并添加到IOC容器 doInstance

private void doInstance() {
    for (String className : this.classNames) {
        try {
            Class<?> clazz = Class.forName(className);

            //只有加了注解的类才初始化
            if (!clazz.isAnnotationPresent(TController.class) && !clazz.isAnnotationPresent(TService.class)) {
                continue;
            }
            //实例化对象 源码中并不是这个流程 这里方便理解 忽略细节 
            Object instance = clazz.newInstance();

            //保存到IOC容器 beanName -> instance 生成bean name 首字母小写
            String beanName = toFristLowerCase(clazz.getSimpleName());
            //如果是service 看是否有自定义名称
            if(clazz.isAnnotationPresent(TService.class)){
                TService service = clazz.getAnnotation(TService.class);
                if(!"".equals(service.value())){
                    beanName = service.value();
                }
            }

            //比如beanName 相同的暂不考虑 主要是体现思想
            this.ioc.put(beanName,instance);

            //如果是接口实现的 需要把接口全路径对应到service
            Class<?>[] interfaces = clazz.getInterfaces();
            for (Class<?> i : interfaces) {
                this.ioc.put(i.getName(),instance);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//首字母小写
private String toFristLowerCase(String simpleName) {
    char[] chars = simpleName.toCharArray();
    chars[0] += 32;
    return new String(chars);
}

2.5 @Autowired依赖注入 doAutowired

//简化版的实现 先了解这个思路
private void doAutowired() {
	//ioc里面的都是已经初始化的类
    for (Map.Entry<String, Object> entry : this.ioc.entrySet()) {
        //判断声明的方法里是否有 @TestAutowired注解的
        Object instance = entry.getValue();
        Class<?> clazz = instance.getClass();
        //所有声明的方法
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if(!field.isAnnotationPresent(TAutowired.class)) continue;
            //默认beanName是全路径
            String name = field.getType().getName();
            //通过注解value上的value来确定beanName 等同于@Qualifier(value="xxx") 这里就简化了
            TAutowired annotation = field.getAnnotation(TAutowired.class);
            if(!"".equals(annotation.value())){
                name = annotation.value();
            }
            
            Object autowiredService = this.ioc.get(name);
            if(autowiredService == null) continue;
			//授权 反射赋值
            field.setAccessible(true);
            try {
                field.set(instance,autowiredService);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

3.Test测试

public static void main(String[] args) {
    TestApplicationContext context = new TestApplicationContext("classpath:application.properties");
    HelloAction action = (HelloAction) context.getBean(HelloAction.class);
    String hello = action.hello("张三");
    System.out.println(hello);
}

//输出结果
application is init
my name is 张三    

理解这个思想以后,我们是不是可以猜想一下AOP是怎么实现的了? 下篇文章一起探索吧

本文仅供参考,不允许转载(因为最开始直接看源码,确实难以入门,这篇文章仅供了解思想)
以上就是本章的全部内容了。源码地址在手写结束篇提供

上一篇:最详细Java中动态代理分析-- Proxy
下一篇:SpringMvc手写简单实现篇 - AOP切面编程篇

若要功夫深,铁杵磨成针

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值