在乎的是,自己以及他所在乎的人,能不能够秉持着自己的心意与信念,在人生中大部分时间里都能自在、快乐
项目github地址:https://github.com/wmsheng/MINI-SpringMVC
项目框架搭建
搭建基于Gradle的项目
首先进行项目的”骨架”的搭建。
使用Gradle作为依赖创建项目,相比Maven,Gradle的优势在于:
- 发扬Maven的约定大于配置的思想(为了迁移方便,其可以直接使用Maven的配置)
- 使用DSL语言 提供函数支持
- 方便性上:Json格式,而且免安装
先创建Gradle项目,然后创建子的Module
模块.
子模块需要创建多个。这里创建这么几个:framework框架模块、test测试模块,创建好后如下图:

因为我们不需要在项目的父模块中写代码,只需要在子模块中写,所以可以删除父模块层中的src包。

可以看到,右侧中有配置文件,默认使用了很多配置项。Maven中一行就能完成的配置,在Gradle中一行就可以完成。
在配置subprojects的时候,其中的所有配置项都会被子模块继承。但是可以注意,当子模块中有父模块中重复的配置项时,会以范围更近的子模块为主,会覆盖父模块的配置。
在配置好之后,可以进行打包,命令是
在打包成功后,各个子模块中都会多出一个build文件夹,而在libs文件夹下就是jar包文件。如下图:

仿Spring搭建项目结构
我们已经创建好了framework和test这两个模块,分别对应框架模块
和应用模块
。应用模块主要用来测试,框架可以支撑应用模块,而应用模块可以反馈框架。
重点在于框架模块。Spring包的模块划分主要可以分为两层。一层是核心层(Core),它是Spring框架的基石。其包括:
- Beans包:负责Spring的Bean的维护和管理
- Core包:Spring中经常用到的工具包
- Context包:提供Spring根据不同产品的需求所完成的接口,是进入Spring的入口
- SpEL:Spring的R式语言包,可以提供查询、操作数据的功能
再往上,一个是数据层,Data。这里主要用于对数据的增删改查。其中有JDBC、ORM的针对数据操作的模型。
还有一个是对外的Web应用包,其中有MVC模型和对Servlet的封装。
如下图:

我们这个项目主要是MINI,所以目的是实现SpringMVC的一部分重要模块。
- 实现Core模块,包括core、beans、context包
- 实现Web,集成web和webmvc
- 添加starter,实现类spring-boot的启动方式
开发中,我们将test模块和frameword模块进行了关联,重点是在test模块的gradle配置文件中进行配置。
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
| plugins { id 'java' }
group 'wms.mooc.com' version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories { mavenCentral() }
dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' compile(project(':framework')) }
subprojects { } jar{ manifest{ attributes "Main-Class":"com.mooc.wms.Application" } from{ configurations.compile.collect{ it.isDirectory() ? it : zipTree(it) } } }
|
dependencies中配置了编译过程中要关联的其他子模块的名字,注意写法。这里关联的是framework模块。
集成服务器
接下来进行服务器Web服务器和Servelet的集成,可以被称作是项目的载体。
首先回顾一下JavaWeb模型。我们的程序想要对外提供服务,必须借助Web服务器。常见的web服务器有Tomcat、Netty、Nginx等。
- 监听一个TCP端口(本地启动一般监听localhost,80,8080)
- 负责转发请求,回收相应(Nginx转发给PHP程序、Tomcat转发给Java程序)
- 本身没有业务逻辑,只负责连接操作系统和应用程序代码
系统Web服务模型
首先,客户端会向web服务器发送一个请求,通过网络到达web服务器所在的操作系统。
但是从网络传输过去的信息只能是一些bit流,我们无法直接读取其中的信息。这些bit流通过网卡到达了操作系统。然后操作系统的TCP/IP栈专门负责解析这些bit流,再之后操作系统会把这些信息(端口、IP、字节流)都发送给Web服务器来进行处理。
到达Web服务器之后,Web服务器会连接到应用程序,由应用程序处理其中的逻辑和进行操作。
处理完之后,会把结果原路返回,再相应回客户端。
具体流程如下图所示:

请求分发流程

上图是一个抽象的比喻,发信人和收信人的关系。
邮局就好像是TCP端口,不论有没有信件往来,它都在那里等着。一旦有信件往来,它就把这个信件交给一个快递员,也就是web服务器。这个web服务器每天守着一个TCP端口,如果有消息派发给他,他就赶快把消息送出,再把回复收回,再把回复发到给发信人。
就好像快递员不能拆开快递一样,服务器只能充当操作系统和应用程序的连接着,他们不能负责具体业务,只负责高效而准确地发送任务。
那么,web服务器是怎么分发请求的呢?——这就不得不介绍Servlet
实际上,Servlet是一种复合的含义。
- Servlet是一种规范:它约束了Java服务器与业务类的通信方式。试想不同的服务器有不同的通信方式,那么你每一台服务器都要单独写一套逻辑,那么学习成本高,迁移也会非常困难
- Servlet是一个接口:javax.servlet.Servlet,这个是用代码来表达Servlet这种规范的方法
- Servlet是一种Java类:实现了Servlet接口的应用程序类。每一个实现了Servlet应用接口的类都可以成为是一个Servlet。大型服务一般需要多个Servlet共同合作来完成。
到这里,可以再补充一下系统Web服务模型,里面的Servlet实际上可以更细化:

实际上,SpringBoot中通过向框架中集成Web服务器来实现的。我们这里使用Tomcat来进行集成。
使用Tomcat服务器有以下一些特点:
- Java原生,运行在JVM上
- 多种并发模型,高性能
- 支持嵌入式应用程序
具体流程很简单,我们只需要在项目中:
- 引入Tomcat包
- 实例化一个Tomcat类
- 调用Tomcat的start方法
走这三步,就可以启动Tomcat服务器。
集成Tomcat服务器
首先,我们为tomcat引入一个tomcat包,写在gradle的配置文件中。
在Maven官网查找Tomcat Embed Core, https://mvnrepository.com/search?q=tomcat。选一个版本,这里选8.5.23,配置`framework`的gradle中。
导入成功后,在framework的web包下创建server包,然后完成相应逻辑任务。
TomcatServer.java
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
| package com.mooc.wms.web.server;
import org.apache.catalina.LifecycleException; import org.apache.catalina.startup.Tomcat;
public class TomcatServer { public Tomcat tomcat; public String[] args;
public TomcatServer(String[] args) { this.args = args; }
public void startServer() throws LifecycleException { tomcat = new Tomcat(); tomcat.setPort(6699); tomcat.start();
Thread awaitThread = new Thread("tomcat_await_thread") { @Override public void run() { TomcatServer.this.tomcat.getServer().await(); } }; awaitThread.setDaemon(false); awaitThread.run(); } }
|
TestServlet.java
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
| package com.mooc.wms.web.servlet;
import javax.servlet.*; import java.io.IOException;
public class TestServlet implements Servlet { @Override public void init(ServletConfig config) throws ServletException {
}
@Override public ServletConfig getServletConfig() { return null; }
@Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { res.getWriter().println("test"); }
@Override public String getServletInfo() { return null; }
@Override public void destroy() {
} }
|
在Tomcat容器内,Servlet容器并不是直接依附在Tomcat容器里的,而是会分成四个级别,Tomcat用这四个级别进行容器的解耦。这四个级别分别是:
- Engine容器:最顶级容器,可以理解为Tomcat的总控中心
- Host容器:管理主机信息和他们的子容器
- Context:最接近Servlet的容器,可以通过它设置资源属性和管理骨架
- Wrapper:负责Servlet的加载、初始化、执行、销毁
我们这里只往Context容器内注册一个Servlet即可。
为了建立Tomcat和Servlet的关联,我们需要在TomcatServer.java类中加入如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Context context = new StandardContext(); context.setPath(""); context.addLifecycleListener(new Tomcat.FixContextListener());
TestServlet servlet = new TestServlet();
Tomcat.addServlet(context,"testServlet",servlet) .setAsyncSupported(true);
context.addServletMappingDecoded("/test.json","testServlet"); tomcat.getHost().addChild(context);
|
此时,启动之后,可以用localhost:6699/test.json
访问到页面,当然,结果只有一个输出的”test”,即为TestServlet.java
中的service函数:
1 2 3 4
| @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { res.getWriter().println("test"); }
|
集成服务器总结
总而言之,这里使用了Web服务模型,其中最重要的是使用了Servlet标准,它规定了Web服务器和应用程序之间的通信方式。
最后,要把这个流程真正运行起来,要一个Web服务器。这里我们仿照Spring Boot,往框架里嵌入了一个Tomcat的Web服务器,实现了Servlet接口,往Tomcat中注册了一个Servlet实例,实现了真正的请求响应。即上面往TomcatServer.java
中加入的代码。
控制器Controller的实现
Controller可以看做是项目的入口了。
传统项目的实现
传统项目中,为了解决请求分发,需要配置Servlet,因为Servlet经常会有很多,所以经常需要配置大量内容到web.xml中(在classpath下的web.xml中配置URI到Servlet的映射,Tomcat会在启动时自动加载这个文件),每个业务都需要添加一个Servlet,而每个Servlet都对应一个URI。
这样在小型项目中可能没太多不妥。但是随着项目发展,这样传统的配置方式的问题会逐渐暴露:
- 配置集中,大而杂,不易管理(特别是Servlet的职责没有明确界限时,很容易把URI配置错)
- 需要多次实现Servlet接口,不必要(Servlet接口有五个方法,虽然我们一般只需要实现service方法,但是实现了接口,其他方法哪怕是空的,也要写上啊)
Spring框架
Spring框架解决这个问题的方式,就是引入了DispatcherServlet
作为”总管”,它直接在Tomcat中配置了根目录路径符,表示所有的请求都由它来受理。
当然,我们不能把所有业务都写在DispatcherServlet
中。Spring做的另一件事,是把所有的Servlet都简化成了一个一个的”Mapping Handler”。在代码中,就是Controller类中的一个方法。一般用requestMapping
来注解。所以实际中会有请求打到DispatcherServlet
上,然后通过DispatcherServlet
发送到Mapping Handler上,再由Mapping Handler处理,之后返回。
总结起来,用Spring框架进行调度的优势:
- 用注解,实现简单,按需实现(需要用哪个方法,就用哪个注解即可)
- 配置分散,不杂乱(可以通过多个Controller把业务逻辑的界限定义的很清晰)
- 容器内实现,易控制(真正的分发变成了框架处理,不再受web服务器的控制,修改和拓展更方便)
实战添加web组件
首先将之前定义的TestServlet该名字,改成DispatcherServlet。
然后修改Tomcat和Servlet联系的方式,名字都改成dispatcherServlet。并且修改MappingDecoded的路径,改成”/“,表示根目录符,表示根目录下所有的URI。
1 2 3 4 5 6 7 8 9
| DispatcherServlet servlet = new DispatcherServlet(); Tomcat.addServlet(context,"dispatcherServlet",servlet) .setAsyncSupported(true);
context.addServletMappingDecoded("/","dispatcherServlet"); tomcat.getHost().addChild(context);
|
接下来再在web包下面添加MVC包。MVC包中添加Controller、RequestMapping和RequestParam这三个我们常用的注解。
@Controller
1 2 3 4 5 6 7 8 9 10 11
| package com.mooc.wms.web.mvc;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) public @interface Controller { }
|
@RequestMapping
1 2 3 4 5 6 7 8 9 10 11 12
| package com.mooc.wms.web.mvc;
import java.lang.annotation.*;
@Documented @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) public @interface RequestMapping { String value(); }
|
@RequestParam
1 2 3 4 5 6 7 8 9 10 11 12
| package com.mooc.wms.web.mvc;
import java.lang.annotation.*;
@Documented @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER) public @interface RequestParam { String value(); }
|
然后,在测试类中创建一个controllers包,里面创建一个SalaryController
类,对上面的注解进行使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.mooc.wms.controllers;
import com.mooc.wms.web.mvc.Controller; import com.mooc.wms.web.mvc.RequestMapping; import com.mooc.wms.web.mvc.RequestParam;
@Controller public class SalaryController { @RequestMapping("/get_salary.json") public Integer getSalary(@RequestParam("name") String name, @RequestParam("experience") String experience) { return 10000; } }
|
但是如果此时将程序运行起来,访问localhost:6699/get_salary.json
,还是只会显示”test”。也就是说,这些Controller并没有生效。
为什么?因为现在框架并不知道我们添加了哪些controller。想要让框架知道我们定义了哪些controller,这时候就需要使用Java的类加载器了。
类加载器,即ClassLoader。ClassLoader的作用:
- 通过类全限定名获取类的二进制字节流(全限定名是包括了包路径的类名)
- 解析二进制字节流,获取Class类实例——实际上,所有的.java文件在被编译成.class二进制文件后,都已经成为了二进制的字节码。得到类的二进制字节码后,类加载器通过虚拟机来解析这些二进制字节码,把它初始化成类,然后就可以获取到这个类的class实例了。
- 加载classpath下的静态资源——这个特性是我们获取类全限定名的关键,虽然我们可以使用类加载器来获取需要的类,但我们不知道项目下所有的类的全限定名。想要获取到这些全限定名,还需要从类文件入手,因为我们不可能自己定义一个类名列表,每次有新的类文件添加都往列表中添加。
需要指出,我们能够通过Java的类文件来获取到类的全限定名,这要归功于Java类文件规范。
- 统一的Resource抽象——H5页面、类等等,都是资源
- 每个Java类文件和类名对应——类名末尾加上.class后缀,就是文件名,这个规范也很巧妙
- 类包名和文件夹路径对应
手写类扫描获取类定义
在core包中添加类扫描器——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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| package com.mooc.wms.core;
import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile;
public class ClassScanner { public static List<Class<?>> scanClasses(String packageName) throws IOException, ClassNotFoundException { List<Class<?>> classList = new ArrayList<>(); String path = packageName.replace(".","/"); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Enumeration<URL> resources = classLoader.getResources(path); while(resources.hasMoreElements()) { URL resource = resources.nextElement(); if(resource.getProtocol().contains("jar")) { JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection(); String jarFilePath = jarURLConnection.getJarFile().getName(); classList.addAll(getClassesFromJar(jarFilePath,path));
} else { } } return classList; }
private static List<Class<?>> getClassesFromJar(String jarFilePath,String path) throws IOException, ClassNotFoundException { List<Class<?>> classes = new ArrayList<>(); JarFile jarFile = new JarFile(jarFilePath); Enumeration<JarEntry> jarEntries = jarFile.entries(); while(jarEntries.hasMoreElements()) { JarEntry jarEntry = jarEntries.nextElement(); String entryName = jarEntry.getName(); if(entryName.startsWith(path) && entryName.endsWith(".class")) { String classFullName = entryName.replace("/",".").substring(0,entryName.length() - 6); classes.add(Class.forName(classFullName)); } } return classes; } }
|
试用一下这个类扫描器,在框架的入口类MiniApplication
中尝试获取所有的class
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class MiniApplication { public static void run(Class<?> cls, String[] args) { System.out.println("Hello mini-spring!"); TomcatServer tomcatServer = new TomcatServer(args); try { tomcatServer.startServer(); List<Class<?>> classList = ClassScanner.scanClasses(cls.getPackage().getName()); classList.forEach(it -> System.out.println(it.getName())); } catch (Exception e) { e.printStackTrace(); } } }
|
运行时可以发现控制台会输出各个以我们包路径开头的类。
控制器的初始化
上一节获取了所有controller类,但是这些类并不能真正响应我们的服务,我们还需要把Controller中定义的Mapping Handler提取出来才能使用。提取的方式可以使用反射。
反射:Java中的高级特性,框架实现的关键,经常用于框架中的动态设计。
反射的特点:
- 活跃于运行时(Runtime)——普通程序在编译前就已经确定了代码该使用哪个属性,调用哪个方法等等。但是反射在运行时可以动态地获取类的属性和方法实例。
- 获取属性和方法实例——上面已经提到,反射可以在运行时动态地获取属性和方法实例
- 动态实例化类——不用new关键字了。可以用反射保存Controller里面的Mapping Handler了,有请求到来时再次调用
具体实现:先定义Mapping Handler的数据结构,在框架的web包中再添加一个handler子包。在子包下定义一个MappingHandler,每个MappingHandler都是一个请求映射器。在它的属性里保存它要处理的URI、对应的Controller方法等等。
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
| package com.mooc.wms.web.handler;
import java.lang.reflect.Method;
public class MappingHandler { private String uri; private Method method; private Class<?> controller; private String[] args;
MappingHandler(String uri, Method method,Class<?> cls,String[] args) { this.uri = uri; this.method = method; this.controller = cls; this.args = args; }
}
|
接下来,添加一些管理器来管理这些handler。取名为HandlerManager
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 52 53 54 55 56
| package com.mooc.wms.web.handler;
import com.mooc.wms.web.mvc.Controller; import com.mooc.wms.web.mvc.RequestMapping; import com.mooc.wms.web.mvc.RequestParam;
import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.List;
public class HandlerManager { public static List<MappingHandler> mappingHandlerList = new ArrayList<>();
public static void resolveMappingHandler(List<Class<?>> classList) { for(Class<?> cls : classList) { if(cls.isAnnotationPresent(Controller.class)) { parseHandlerFromController(cls); } } }
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); } } }
|
接下来,在DispatcherHandler类里面使用这个handler。在service()方法中进行定义,当一个请求发送过来之后,我们依次判断这些handler能不能处理这些请求,如果能处理,就响应结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { for(MappingHandler mappingHandler : HandlerManager.mappingHandlerList) { try { if(mappingHandler.handle(req,res)) { return ; } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
|
完善一下MappingHandler中的方法,添加handle方法。
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
| public class MappingHandler { private String uri; private Method method; private Class<?> controller; private String[] args;
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 = controller.newInstance(); Object response = method.invoke(ctl,parameters); res.getWriter().println(response.toString()); return true;
}
MappingHandler(String uri, Method method,Class<?> cls,String[] args) { this.uri = uri; this.method = method; this.controller = cls; this.args = args; }
}
|
最后别忘了在入口的MiniApplication中调用HandlerManager来初始化所有的MappingHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class MiniApplication { public static void run(Class<?> cls, String[] args) { System.out.println("Hello mini-spring!"); TomcatServer tomcatServer = new TomcatServer(args); try { tomcatServer.startServer(); List<Class<?>> classList = ClassScanner.scanClasses(cls.getPackage().getName());
HandlerManager.resolveMappingHandler(classList);
classList.forEach(it -> System.out.println(it.getName())); } catch (Exception e) { e.printStackTrace(); } } }
|
控制器实现章节的重点
当servlet太多时,使用web服务器配置来维护servlet是很困难的,由此提出了DispatcherServlet。
- DispatcherServlet有很重要的意义
- 通过类加载机制实现类的扫描,并且从中挑选出了controller
- 通过反射实现mappingHandler,真正实现了Spring中的DispatcherServlet
Bean管理(IOC&DI)
Bean的管理,当然是Spring框架能够运行的基石。
bean管理
之前已经实现了对controller的解析,可以使用框架来添加controller来搭建web服务了。但仅有一个controller是不够的,要实现MVC,还需要将项目复杂的逻辑抽离到Module里。在Spring中一般用Service来表示。而当service较多的时候,每个请求重复创建会有较大的性能损耗,所以Spring抽象出了Bean这么一个概念。
什么是bean?
bean本质也是一种对象,但是比较特殊:
- 生命周期较长——从JVM启动,服务的初始化开始,在服务结束,JVM初始化结束的时候结束。
- 可见性较大——普通对象只是在创建的代码块中可见,但bean在整个虚拟机中都是可见的
- 维护成本高,默认是单例模式
bean的优势
- 运行期效率高——使用bean之前不需要再初始化,而且不需要每次都是用service对象生成,可以让代码更干净
- 统一维护,便于管理和扩展——让一些比如添加类代理等的操作变得更加简单
- 单例模式可以让每次使用bean的时候不需要做各种setProperty,也不用处理各种链式依赖
普通类创建方式和bean的对比

如图所示,A和B类都含有一个Z属性,当我们实例化A和B的时候,会自动实例化Z;情况更复杂一些的话,有一个类C,里面有属性B,那么实例化C的时候也会实例化B,并且初始化了属性Z。
当我们实例化完A、B、C三个对象之后,整个虚拟机中就有了三个Z对象,造成极大的性能浪费。
Spring的实现方式
- 包扫描并自动装配(反射),不使用显示的”new”来创建对象
- bean在虚拟机中会通过beanFactory统一管理维护,beanFactory是接口,可以通过类型、名字获取到bean
- 依赖注入
控制反转/依赖注入
依赖注入和控制反转是有区别的。
- IOC(Inversion Of Control):一种思想,用来降低代码之间的耦合度
- DI(Dependency Injection):实现IOC的方式,类A中定义对象B,初始化A的时候B也会被定义好
用控制反转创建对象如下图所示:

每创建一个对象,就把该对象放入到BeanFactory中,下次再需要这个对象的时候可以直接去BeanFactory中去找。这样每个对象都只有一个,节约了大量内存空间。
实现依赖注入的方式
具体步骤为:
- 扫描包,获得类定义(已完成)
- 初始化Bean,并实现依赖注入
- 解决Bean初始化顺序问题
流程示意图:

具体代码实现
创建两个Annotation注解类:@Bean和@Autowired
@Bean:
1 2 3 4 5 6 7 8 9
| package com.mooc.wms.beans;
import java.lang.annotation.*;
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Bean { }
|
@Autowired:
1 2 3 4 5 6 7 8 9
| package com.mooc.wms.beans;
import java.lang.annotation.*;
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Autowired { }
|
创建一个BeanFactory用来实现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 52 53 54 55 56 57 58 59 60 61 62
| package com.mooc.wms.beans;
import com.mooc.wms.web.mvc.Controller;
import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap;
public class BeanFactory { private static Map<Class<?>, Object> classToBean = new ConcurrentHashMap<>();
public static Object getBean(Class<?> cls) { return classToBean.get(cls); }
public static void initBean(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++) { if(finishCreate(toCreate.get(i))) { toCreate.remove(i); } } if(toCreate.size() == remainSize) { throw new Exception("cycle dependency!"); } } }
private static boolean finishCreate(Class<?> cls) throws IllegalAccessException, InstantiationException { 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); if(reliantBean == null) { return false; } field.setAccessible(true); field.set(bean,reliantBean); } } classToBean.put(cls,bean); return true; } }
|
然后在启动类MiniApplication入口中初始化Bean工厂,初始化Bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class MiniApplication { public static void run(Class<?> cls, String[] args) { System.out.println("Hello mini-spring!"); TomcatServer tomcatServer = new TomcatServer(args); try { tomcatServer.startServer(); List<Class<?>> classList = ClassScanner.scanClasses(cls.getPackage().getName());
BeanFactory.initBean(classList);
HandlerManager.resolveMappingHandler(classList);
classList.forEach(it -> System.out.println(it.getName())); } catch (Exception e) { e.printStackTrace(); } } }
|
然后在MappingHandler中使用Bean工厂来利用controller来初始化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 52 53 54 55 56 57 58 59
| public class BeanFactory { private static Map<Class<?>, Object> classToBean = new ConcurrentHashMap<>();
public static Object getBean(Class<?> cls) { return classToBean.get(cls); }
public static void initBean(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++) { if(finishCreate(toCreate.get(i))) { toCreate.remove(i); } } if(toCreate.size() == remainSize) { throw new Exception("cycle dependency!"); } } }
private static boolean finishCreate(Class<?> cls) throws IllegalAccessException, InstantiationException { 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); if(reliantBean == null) { return false; } field.setAccessible(true); field.set(bean,reliantBean); } } classToBean.put(cls,bean); return true; } }
|
至此,框架部分就完成了。
测试模块
测试部分是很重要的。
在测试模块中,我们再添加一个service包,在这个包中添加SalaryService类
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.mooc.wms.service;
import com.mooc.wms.beans.Bean;
@Bean public class SalaryService { public Integer calSalary(Integer experience) { return experience * 5000; } }
|
接下来在controller中使用这个service
使用流程:首先在SalaryController中声明SalaryService的依赖,也就是让两者有组合的关系,添加Autowired注解。
然后再SalaryController中使用SalaryService的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.mooc.wms.controllers;
import com.mooc.wms.beans.Autowired; import com.mooc.wms.service.SalaryService; import com.mooc.wms.web.mvc.Controller; import com.mooc.wms.web.mvc.RequestMapping; import com.mooc.wms.web.mvc.RequestParam;
@Controller public class SalaryController {
@Autowired private SalaryService salaryService;
@RequestMapping("/get_salary.json") public Integer getSalary(@RequestParam("name") String name, @RequestParam("experience") String experience) { return salaryService.calSalary(Integer.parseInt(experience)); } }
|
之后gradle build项目,再访问http://localhost:6699/get_salary.json?experience=3&name=listl,可以得到结果。
小结
- Bean的介绍
- 依赖注入和控制反转
- 解决依赖顺序的简单策略
总结
- 搭建了框架,主要使用gradle进行,和Spring各个包的功能等
- 嵌入集成服务器Servlet
- 使用类加载器进行资源扫描,获取项目下的类定义,以及controller的解析以及请求映射器的初始化
- Bean管理,Bean的使用和控制反转,是Spring框架的核心