在生成式人工智能的时代,徒手撕一个简单的框架已经不再是难事。甚至可以在半小时内完成一个简单的IOC容器。不仅如此,从它给出的代码中,还可以学到框架最基本的原理。ChatGPT不仅是编程好帮手,也是一个好老师。
仿照Spring框架实现一个IOC容器,对于熟练的人来说,可能需要半天到一天,就可以实现最基本的功能。对于没那么熟练的人,则可能需要好几天。但是在ChatGPT的帮助下,只要懂得基本的Java语法,就可以在半小时内完成,并且可运行。下面来看看到底如何实现。
生成IOC容器代码
首先很简单粗暴地,给它一个一句话需求:
用Java实现一个简单的IOC容器
紧接着ChatGPT会输出如下的回答:
IOC(Inversion of Control)容器是一种用于简化Java应用程序开发的技术。它实现了对象的创建、管理、依赖注入等功能,使得开发者可以更加关注业务逻辑而不需要太多的关注底层细节。本篇文章将会介绍如何使用Java实现一个简单的IOC容器。
- 定义注解
我们需要定义一个注解来标注需要被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中。
- 使用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的定义也没有
Request和Response也没有定义
等等,问完一堆之后,补上相应的类,编译没有报错了。
基础框架有了,如何测试?
此时问题又来了,如何测试呢?同理,依然可以让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生成的代码还是挺靠谱的。
还有另外的问题
但是访问 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多分钟就完成了。
总结
毫无疑问,ChatGPT肯定会大大提升程序员的生产力,利用好ChatGPT,不仅仅是编程效率的提升,更加可以通过这个工具提升自己的知识面和学习能力,毫不夸张地说,无论水平如何,ChatGPT都做到了因材施教,而且是不知疲倦,不厌其烦的教导。上面的例子只是一个简单的demo,实际在工作中遇到的情况会更加复杂,因为需要了解较多的业务背景知识,这恰恰是现在ChatGPT的弱项,即需要人去告诉它尽可能多的背景知识,才能给出比较满意的答案。因此,如何在工作中利用好ChatGPT进行提效,是一个长期探索的过程,目前我也正在尝试将ChatGPT运用到业务设计、单元测试和编码当中。