由于Spring官方就是选择gradle作为自动化构建工具,所以我们在本次尝试中就按照spring的选择也是用gradle
在整个项目中,我们一共包含两个模块framework模块用于首先实现我们springmvc的常见功能,test模块则是用来测试我们手写的模块是否正确
项目链接:https://github.com/ZhangJia97/Mini-Spring
下面是项目结构,只保留了我们用到的文件结构1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42├── build.gradle
├── framework
│   ├── build.gradle
│   └── src
│       ├── main
│           ├── java
│               └── xyz
│                   └── suiwo
│                       └── imooc
│                           ├── beans
│                           │   ├── Autowired.java
│                           │   ├── Bean.java
│                           │   └── BeanFactory.java
│                           ├── core
│                           │   └── ClassScanner.java
│                           ├── starter
│                           │   └── MiniApplication.java
│                           └── web
│                               ├── handler
│                               │   ├── HandlerManager.java
│                               │   └── MappingHandler.java
│                               ├── mvc
│                               │   ├── Controller.java
│                               │   ├── RequestMapping.java
│                               │   └── RequestParam.java
│                               ├── server
│                               │   └── TomcatServer.java
│                               └── servlet
│                                   └── DispatcherServlet.java
└── test
    ├── build.gradle
    └── src
        ├── main
             ├── java
                └── xyz
                    └── suiwo
                        └── imooc
                            ├── Application.java
                            ├── controller
                            │   └── SalaryController.java
                            └── service
                                └── SalaryService.java
首先我们需要在framework的依赖中添加tomcat的依赖,因为springboot就是通过加入tomcat依赖来实现的1
2
3
4
5dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    // https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core
    compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.5.23'
}
接下来让我们看看如何去创建一个tomcat服务1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public class TomcatServer {
    
    private Tomcat tomcat;
    private String[] args;
    public TomcatServer(String[] args) {
        this.args = args;
    }
    public void startServer() throws LifecycleException {
        tomcat = new Tomcat();
        tomcat.setPort(8080);
        Context context = new StandardContext();
        context.setPath("");
        context.addLifecycleListener(new Tomcat.FixContextListener());
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        // servlet注册到tomcat容器内并开启异步支持
        Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet).setAsyncSupported(true);
        context.addServletMappingDecoded("/", "dispatcherServlet");
        // 注册到默认host容器
        tomcat.getHost().addChild(context);
        tomcat.start();
        Thread awaitThread = new Thread("tomcat_await_thread"){
            
            public void run() {
                TomcatServer.this.tomcat.getServer().await();
            }
        };
        //设置成非守护线程
        awaitThread.setDaemon(false);
        awaitThread.start();
    }
}
然后我们可以看到上述代码向tomcat中set了一个dispatchServlet用于处理请求,我们看看DispatchServlet如何去处理请求
| 1 | public class DispatcherServlet implements Servlet { | 
我们现在已经成功创建了一个Tomcat的服务类,下面我们就可以在主类中启动tomcat服务了
然后我们看一些framework的主类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class MiniApplication {
    public static void run(Class<?> cls, String[] args){
        System.out.println("Hello Mini-Spring!");
        // 创建一个Tomcat服务
        TomcatServer tomcatServer = new TomcatServer(args);
        try {
            // 启动tomcat
            tomcatServer.startServer();
            // 扫描项目中当前cls目录下的所有包
            List<Class<?>> classList = ClassScanner.scannerClass(cls.getPackage().getName());
            // 初始化所有bean
            BeanFactory.init(classList);
            // 初始化所有的MappingHandler
            HandlerManager.resolveMappingHandler(classList);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
我们再创建三个mvc相关的注解1
2
3
4
5
(RetentionPolicy.RUNTIME)
(ElementType.TYPE)
public  Controller {
}
| 1 | 
 | 
| 1 | 
 | 
然后我们看一下ClassScanner类,这个类主要用于扫描包1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50public class ClassScanner {
    public static List<Class<?>> scannerClass(String packageName) throws IOException, ClassNotFoundException {
        List<Class<?>> classList= new ArrayList<>();
        String path = packageName.replaceAll("\\.", "/");
        // 获取默认类加载器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // 获取资源文件的路径
        Enumeration<URL> resources = classLoader.getResources(path);
        while(resources.hasMoreElements()){
            URL resource = resources.nextElement();
            // 判断资源类型
            if(resource.getProtocol().contains("jar")){
                // 如果资源类型是jar包,则我们先获取jar包的绝对路径
                JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
                String jarFilePath = jarURLConnection.getJarFile().getName();
                // 获取这个jar包下所有的类
                classList.addAll(getClassesFromJar(jarFilePath, path));
            }else {
                // todo 处理非jar包的情况
            }
        }
        return classList;
    }
    private static List<Class<?>> getClassesFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException {
        //初始化一个容器用于存储类
        List<Class<?>> classes = new ArrayList<>();
        // 通过路径获取JarFile实例
        JarFile jarFile = new JarFile(jarFilePath);
        // 遍历jar包,每个jarEntry都是jar包里的一个文件
        Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
        while(jarEntryEnumeration.hasMoreElements()){
            JarEntry jarEntry = jarEntryEnumeration.nextElement();
            String entryName = jarEntry.getName();  // xyz/suiwo/imooc/test/Test.class
            if(entryName.startsWith(path) && entryName.endsWith(".class")){
                // 把分隔符换成点,并去除.class后缀
                String classFullName = entryName.replace("/", ".").substring(0, entryName.length() - 6);
                classes.add(Class.forName(classFullName));
            }
        }
        return classes;
    }
}
作为spring的经典ioc思想,初始化创建bean是重中之重,下面让我们看看如何实现吧
对于常见与Bean相关的注解就是@Bean还有@Autowired
所以我们首先创建两个注解1
2
3
4
5
6
(RetentionPolicy.RUNTIME)
(ElementType.FIELD)
public  Autowired {
    String value() default "";
}
| 1 | 
 | 
下面我们看看如何去初始化bean吧1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52public class BeanFactory {
    private static Map<Class<?>, Object> classToBean = new ConcurrentHashMap<>();
    public static Object getBean(Class<?> cls){
        return classToBean.get(cls);
    }
    public static void init(List<Class<?>> classList) throws Exception {
        List<Class<?>> toCreate = new ArrayList<>(classList);
        while (toCreate.size() > 0){
            int remainSize = toCreate.size();
            for(int i = 0; i < toCreate.size(); i++){
                // 返回true则说明创建成功或者说当前类不是一个bean
                // 返回false则此时可能存存在当前需要创建的bean的依赖还没有创建所以暂时先跳过
                if(finishCreate(toCreate.get(i))){
                    toCreate.remove(i);
                }
            }
            // 如果数量没有改变则说明出现了死循环,抛出异常
            if(toCreate.size() == remainSize){
                throw new Exception("死循环");
            }
        }
    }
    private static boolean finishCreate(Class<?> cls) throws IllegalAccessException, InstantiationException {
        // 如果没有满足的注解,则直接返回true
        if(!cls.isAnnotationPresent(Bean.class) && !cls.isAnnotationPresent(Controller.class)){
            return true;
        }
        Object bean = cls.newInstance();
        for(Field field : cls.getDeclaredFields()){
            if(field.isAnnotationPresent(Autowired.class)){
                Class<?> fieldType = field.getType();
                Object reliantBean = BeanFactory.getBean(fieldType);
                
                // 如果为空,则说明当前类中的字段所依赖的类还没有注入,所以返回false,先跳过,等到所需要依赖注入之后再创建
                if(reliantBean == null){
                    return false;
                }
                
                field.setAccessible(true);
                field.set(bean, reliantBean);
            }
        }
        
        // 将创建好的bean放入容器中
        classToBean.put(cls, bean);
        return true;
    }
}
然后我们来看一下控制器,每一个MappingHandler都是一个请求映射器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37public class MappingHandler {
    // 需要处理的uri
    private String uri;
    // 所对应的方法
    private Method method;
    // 所对应的方法
    private Class<?> controller;
    // 所需要的参数
    private String[] args;
    public MappingHandler(String uri, Method method, Class<?> controller, String[] args) {
        this.uri = uri;
        this.method = method;
        this.controller = controller;
        this.args = args;
    }
    // 若与MappingHandler匹配成功,执行方法
    public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
        String requestUri = ((HttpServletRequest)req).getRequestURI();
        if(!uri.equals(requestUri)){
            return false;
        }
        Object[] parameters = new Object[args.length];
        for(int i = 0; i < args.length; i++){
            parameters[i] = req.getParameter(args[i]);
        }
        Object ctl = BeanFactory.getBean(controller);
        Object response = method.invoke(ctl, parameters);
        res.getWriter().println(response.toString());
        return true;
    }
}
我们在创建一个管理器去管理这些MappingHandler1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public class HandlerManager {
    public static List<MappingHandler> mappingHandlerList = new ArrayList<>();
    // 把Controller类挑选出来,并将类中的带有@RequestMapping方法初始化成MappingHandler
    public static void resolveMappingHandler(List<Class<?>> classList){
        for(Class<?> cls : classList){
            if(cls.isAnnotationPresent(Controller.class)){
                parseHandlerFromController(cls);
            }
        }
    }
    // 解析controller类
    private static void parseHandlerFromController(Class<?> cls) {
        Method[] methods = cls.getDeclaredMethods();
        for(Method method : methods){
            if(!method.isAnnotationPresent(RequestMapping.class)){
                continue;
            }
            String uri = method.getDeclaredAnnotation(RequestMapping.class).value();
            List<String> paramNameList = new ArrayList<>();
            for(Parameter parameter : method.getParameters()){
                if(parameter.isAnnotationPresent(RequestParam.class)){
                    paramNameList.add(parameter.getDeclaredAnnotation(RequestParam.class).value());
                }
            }
            String[] params = paramNameList.toArray(new String[paramNameList.size()]);
            MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params);
            HandlerManager.mappingHandlerList.add(mappingHandler);
        }
    }
}
至此,我们就已经成功的将整个框架大致完成了,对于test模块中的代码,我就不在这里在书写了,因为和我们日常写springboot业务相同只是为了测试我们手写框架的几个功能。