使用ChatGPT从0到1实现一个简单版的IOC容器

246 阅读7分钟

在生成式人工智能的时代,徒手撕一个简单的框架已经不再是难事。甚至可以在半小时内完成一个简单的IOC容器。不仅如此,从它给出的代码中,还可以学到框架最基本的原理。ChatGPT不仅是编程好帮手,也是一个好老师。

仿照Spring框架实现一个IOC容器,对于熟练的人来说,可能需要半天到一天,就可以实现最基本的功能。对于没那么熟练的人,则可能需要好几天。但是在ChatGPT的帮助下,只要懂得基本的Java语法,就可以在半小时内完成,并且可运行。下面来看看到底如何实现。

生成IOC容器代码

首先很简单粗暴地,给它一个一句话需求:

用Java实现一个简单的IOC容器

紧接着ChatGPT会输出如下的回答:


IOC(Inversion of Control)容器是一种用于简化Java应用程序开发的技术。它实现了对象的创建、管理、依赖注入等功能,使得开发者可以更加关注业务逻辑而不需要太多的关注底层细节。本篇文章将会介绍如何使用Java实现一个简单的IOC容器。

  1. 定义注解

我们需要定义一个注解来标注需要被IOC容器管理的类。这个注解可以命名为@Component。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String value() default "";
}

2. 实现IOC容器

我们可以定义一个@Controller注解,用于标注控制器类。通过IOC容器管理这些控制器类,可以实现请求路由。同时,我们还需要定义一个存储组件的容器Map。

public class IocContainer {

    private Map<String, Object> componentMap = new ConcurrentHashMap<>();

    public void register(Class<?>... componentClasses) throws Exception {
        for (Class<?> componentClass : componentClasses) {
            if (componentClass.isAnnotationPresent(Component.class)) {
                Component component = componentClass.getAnnotation(Component.class);
                String name = component.value();
                if (name.equals("")) {
                    name = componentClass.getSimpleName().toLowerCase();
                }
                componentMap.put(name, componentClass.newInstance());
            }
        }
    }

    public Object getComponent(String name) {
        return componentMap.get(name);
    }

    public void route() {
        for (Object object : componentMap.values()) {
            if (object.getClass().isAnnotationPresent(Controller.class)) {
                Method[] methods = object.getClass().getDeclaredMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                        String path = requestMapping.value();
                        HttpHandler httpHandler = new HttpHandler(object, method);
                        HttpServer.getInstance().addHandler(path, httpHandler);
                    }
                }
            }
        }
    }
}

在register方法中,我们通过遍历所有被@Component注解的类,将它们的实例化对象存储到一个ConcurrentHashMap中,以类名为key,实例对象为value。

在route方法中,我们遍历所有被IOC容器管理的对象,检查是否有@Controller注解,如果有,则获取它所有的方法,并检查是否有@RequestMapping注解,如果有,则将该方法添加到HttpServer中。

  1. 使用IOC容器

现在我们可以使用我们的IOC容器来管理组件了。我们可以在需要管理的类上添加@Component注解,然后在主函数中进行注册,最后进行路由。

@Component
public class HelloWorld {

    public String sayHello() {
        return "Hello world!";
    }
}

@Controller
public class HelloController {

    @Autowired
    private HelloWorld helloWorld;

    @RequestMapping("/hello")
    public void hello(Request request, Response response) throws Exception {
        response.setBody(helloWorld.sayHello());
    }
}

public class Main {

    public static void main(String[] args) throws Exception {
        IocContainer iocContainer = new IocContainer();
        iocContainer.register(HelloWorld.class, HelloController.class);
        iocContainer.route();

        HttpServer.getInstance().start();
    }
}

在HelloController中,我们通过@Autowire注解来实现依赖注入。当执行到hello方法时,我们可以直接调用helloWorld的sayHello方法。

最后,在主函数中,我们首先创建一个IocContainer,并向其中注册我们要管理的组件。然后,我们调用route方法,将控制器类的方法添加到HttpServer中。最后,我们启动HttpServer来处理请求。

到此为止,我们已经实现了一个简单的IOC容器。


基本上已经把最基本的IOC框架生成出来了。把代码复制到IDE中,发现缺少了很多关键的类,比如@Autowired的定义,RequestMapping和HttpHandler的定义等等都没有,于是只能看到有什么缺失的就问它补上什么。比如

缺少了Controller的注解定义
RequestMapping和HttpHandler的定义也没有
RequestResponse也没有定义

等等,问完一堆之后,补上相应的类,编译没有报错了。

基础框架有了,如何测试?

此时问题又来了,如何测试呢?同理,依然可以让ChatGPT帮忙生成测试代码

请给出这个IOC容器的使用样例

很快啊,ChatGPT就输出了下面代码:


好的,下面是这个IOC容器的使用样例代码:

@Component
public class HelloWorld {

    public String sayHello() {
        return "Hello world!";
    }
}

@Controller
public class HelloController {

    @Autowired
    private HelloWorld helloWorld;

    @RequestMapping("/hello")
    public void hello(Request request, Response response) throws Exception {
        response.setBody(helloWorld.sayHello());
    }
}

public class Main {

    public static void main(String[] args) throws Exception {
        IocContainer iocContainer = new IocContainer();
        iocContainer.register(HelloWorld.class, HelloController.class);
        iocContainer.route();

        HttpServer.getInstance().start();
    }
}

在这个样例中,我们先定义了一个HelloWorld类,并在该类的sayHello方法中返回一句话。然后,我们定义了一个HelloController控制器类,用于处理请求,并将HelloWorld类注入到HelloController中。在HelloController的hello方法中,我们调用HelloWorld的sayHello方法,并将结果设置到响应中返回。

在主方法中,我们首先创建了一个IocContainer实例,并向其中注册了HelloWorld和HelloController两个组件。然后,我们调用IocContainer的route方法,将路由表更新到HttpServer中。最后,我们启动HttpServer来监听和处理请求。

当我们在浏览器中访问http://localhost:8080/hello时,可以看到浏览器中输出了一句话:"Hello world!",这就验证了我们的IOC容器和控制器类的功能。


把这些代码拿过来一运行,是能正常跑起来的,说明ChatGPT生成的代码还是挺靠谱的。

Pasted image 20230418003526.png

还有另外的问题

但是访问 http://localhost:8080/hello的时候却报了Not Found。竟然匹配不到这个路径,经过简单的debug,发现是注册bean的时候,没有把@Controller注解的类注册上去。于是,我又让ChatGPT解决这个问题:

@Controller没有被注册上

很快就给出了方案:


如果控制器类在IocContainer中没有被注册,就会导致控制器方法无法与请求路径对应,从而无法正确处理请求。

为了实现将控制器类注册到IOC容器中,我们需要对IocContainer类进行一些修改。

在IocContainer类中添加registerController方法,用于将控制器类注册到组件Map中:

public void registerController(Class<?> controllerClass) throws Exception {
    if (controllerClass.isAnnotationPresent(Controller.class)) {
        Object controllerObject = controllerClass.newInstance();
        componentMap.put(controllerClass.getSimpleName().toLowerCase(), controllerObject);
        handleAutowiredFields(controllerObject);
    }
}

在registerController方法中,我们首先检查控制器类是否被@Controller注解标注。如果被标注,则创建控制器类的实例对象,并将其注册到组件Map中,同时执行自动装配操作,为控制器类的@Autowired注解标记的属性注入相应的实例对象。

在IocContainer类的register方法中,添加对控制器类的注册操作:

public void register(Class<?>... componentClasses) throws Exception {
    for (Class<?> componentClass : componentClasses) {
        if (componentClass.isAnnotationPresent(Component.class)) {
            // 注册组件类
        } else if (componentClass.isAnnotationPresent(Controller.class)) {
            // 注册控制器类
            registerController(componentClass);
        }
    }
}

在register方法中,我们首先判断组件类是否有@Component注解,然后再判断控制器类是否有@Controller注解。如果是控制器类,则调用registerController方法来注册。

通过上述修改,我们就能够将控制器类注册到IOC容器中,并实现相应请求路径的控制器方法的自动路由了。


于是,我把相关的注册Controller的代码复制过去,然后再次运行,浏览器上访问 http://localhost:8080/hello,终于把Hello World显示出来了。

大致看一下最终代码的目录结构,麻雀虽小,但作为一个IOC容器,最基础的依赖查找和依赖注入的功能已经实现了。整个生成过程+调试过程耗时只花了20多分钟就完成了。

Pasted image 20230418004547.png

总结

毫无疑问,ChatGPT肯定会大大提升程序员的生产力,利用好ChatGPT,不仅仅是编程效率的提升,更加可以通过这个工具提升自己的知识面和学习能力,毫不夸张地说,无论水平如何,ChatGPT都做到了因材施教,而且是不知疲倦,不厌其烦的教导。上面的例子只是一个简单的demo,实际在工作中遇到的情况会更加复杂,因为需要了解较多的业务背景知识,这恰恰是现在ChatGPT的弱项,即需要人去告诉它尽可能多的背景知识,才能给出比较满意的答案。因此,如何在工作中利用好ChatGPT进行提效,是一个长期探索的过程,目前我也正在尝试将ChatGPT运用到业务设计、单元测试和编码当中。