0%

手写SpringMVC,剑指优秀开源框架灵魂


由于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
5
dependencies {
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
39
public 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"){
@Override
public void run() {
TomcatServer.this.tomcat.getServer().await();
}
};

//设置成非守护线程
awaitThread.setDaemon(false);
awaitThread.start();
}
}


然后我们可以看到上述代码向tomcat中set了一个dispatchServlet用于处理请求,我们看看DispatchServlet如何去处理请求

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
public class DispatcherServlet 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 {
for(MappingHandler mappingHandler : HandlerManager.mappingHandlerList){
try {
if(mappingHandler.handle(req, res)){
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}

}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}
}

我们现在已经成功创建了一个Tomcat的服务类,下面我们就可以在主类中启动tomcat服务了


然后我们看一些framework的主类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public 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
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
}

1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface RequestMapping {
String value() default "";
}
1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface RequestParam {
String value() default "";
}

然后我们看一下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
public 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
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
String value() default "";
}

1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Bean {
String value() default "";
}

下面我们看看如何去初始化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
public 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
37
public 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;
}
}


我们在创建一个管理器去管理这些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
public 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业务相同只是为了测试我们手写框架的几个功能。