有了手写SpringMVC的思路,不知道大家是否已经开始实现呢?现在就为大家开始以上一篇文章《手写SpringMVC-思路篇》的思路来一步步的实现SpringMVC的手写,让大家也能在SpringMVC的实现上有个初步的认识。此篇文章可能有些长,希望大家可以一边看,一边动手按照提供的代码(此篇文章代码有些多)来实现。
一、先还是给大家看看代码的结构
上述的就是目前的服务端段代码,在项目的src->main->webapp->WEB-INF下创建一个web.xml文件,在项目启动的时候,加载web.xml文件,注入DispatcherServlet,实现后续的实例注入和初始化,并实现web的URL访问服务端,实现数据的响应。
二、考虑定义注解类
在SpringMVC的框架实现中,少不了一些基本的注入类,例如:@Controller、@RequestMapping、@RequestParam等等,在上一篇文章中,根据需要实现的代码块,需要自定义一些注解类:
@CustomController
@CustomRequestMapping
@CustomQualifier
@CustomRequestParam
@CustomService
这些分别对应着Spring+SpringMVC调用的实现注解,我们现在手动来写入这些注解。此处不过多的讲述,大家可以看文章手写SpringMVC-思路篇》中的第一部分,与SpringMVC的注解来对应理解。
注解--@CustomController
package com.ygsoft.custom.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomController {
String value() default "";
}
注解--@CustomRequestMapping
package com.ygsoft.custom.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomRequestMapping {
String value() default "";
}
注解--@CustomQualifier
package com.ygsoft.custom.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomQualifier {
String value() default "";
}
注解--@CustomRequestParam
package com.ygsoft.custom.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomRequestParam {
String value() default "";
}
注解--@CustomService
package com.ygsoft.custom.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomService {
String value() default "";
}
如果大家对注解定义不熟悉,最好去百度下,理解下如何自定义注解。
三、创建Controller和Service层
此部分和SpringMVC的实现代码一样,只是这部分的注解都是上一步定义的注解类来实现。
Controller层:
package com.ygsoft.custom.controller;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ygsoft.custom.annotation.CustomController;
import com.ygsoft.custom.annotation.CustomQualifier;
import com.ygsoft.custom.annotation.CustomRequestMapping;
import com.ygsoft.custom.annotation.CustomRequestParam;
import com.ygsoft.custom.service.MyService;
@CustomController
@CustomRequestMapping("/custom")
public class MyController {
@CustomQualifier("MyServiceImpl")
private MyService myService;
@CustomRequestMapping("/query")
public void query(HttpServletRequest request, HttpServletResponse response,
@CustomRequestParam("name")String name, @CustomRequestParam("age")String age) {
PrintWriter pw;
try {
PrintWriter writer = response.getWriter();
String result = myService.query(name, age);
writer.write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Service层:
package com.ygsoft.custom.service;
public interface MyService {
public String query(String name, String age);
}
Service的impl层:
package com.ygsoft.custom.service.impl;
import com.ygsoft.custom.annotation.CustomService;
import com.ygsoft.custom.service.MyService;
@CustomService("MyServiceImpl")
public class MyServiceImpl implements MyService{
@Override
public String query(String name, String age) {
return "name:" + name + ";age:" + age;
}
}
四、创建DispatcherServlet类,并通过web.xml加载
web.xml:加载我们自己写的MyDispatcherServlet和读取配置文件。
maven_handmvc
DispatcherServlet
com.ygsoft.custom.servlet.DispatcherServlet
DispatcherServlet
/
DispatcherServlet:本质上,我们自定义的DispatcherServlet是继承了HttpServlet,主要是实现init()、doGet()、doPost()方法,init()方法,就是初始化基本的beans,并实例化controller层中的定义的service的变量,同时实现映射URL请求的Path和方法;doGet()、doPost()目前给的是doGet()调用doPost(),所以doPost()主要是实现参数的解析,并通过反射的机制实现方法的调用。
package com.ygsoft.custom.servlet;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ygsoft.custom.annotation.CustomController;
import com.ygsoft.custom.annotation.CustomQualifier;
import com.ygsoft.custom.annotation.CustomRequestMapping;
import com.ygsoft.custom.annotation.CustomService;
import com.ygsoft.custom.controller.MyController;
import com.ygsoft.custom.handlerAdapter.HandlerAdapterService;
public class DispatcherServlet extends HttpServlet {
// 存在当前加载的所有的类
List classNames = new ArrayList();
// 存储IOC容器的MAP
Map beans = new HashMap<>();
// 存储路径和方法的映射关系
Map handlerMap = new HashMap();
public DispatcherServlet() {
System.out.println("DispatchServlet()........");
}
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("init()............");
// 1.扫描需要的实例化的类
doScanPackage("com.ygsoft.custom");
System.out.println("当前文件下所有的class类.......");
for(String name: classNames) {
System.out.println(name);
}
// 2.实例化
doInstance();
System.out.println("当前实例化的对象信息.........");
for(Map.Entry map: beans.entrySet()) {
System.out.println("key:" + map.getKey() + "; value:" + map.getValue());
}
// 3.将IOC容器中的service对象设置给controller层定义的field上
doIoc();
// 4.建立path与method的映射关系
handlerMapping();
System.out.println("Controller层的path和方法映射.........");
for(Map.Entry map: handlerMap.entrySet()) {
System.out.println("key:" + map.getKey() + "; value:" + map.getValue());
}
}
private void doScanPackage(String basePackage) {
URL resource = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.", "/"));
String fileStr = resource.getFile();
// System.out.println(fileStr);
File file = new File(fileStr);
String[] listFiles = file.list();
for(String path: listFiles) {
File filePath = new File(fileStr + path);
// 如果当前是目录,则递归
if(filePath.isDirectory()) {
doScanPackage(basePackage + "." + path);
// 如果是文件,则直接添加到classNames
} else {
classNames.add(basePackage + "." + filePath.getName());
}
}
}
// 通过存储的classnames的类字符串来反射实例化对象,并存储与beans的Map中
// com.ygsoft.custom.annotation.CustomController.class =>com.ygsoft.custom.annotation.CustomController
private void doInstance() {
if(classNames.isEmpty()) {
System.out.println("doScanner Fail....");
}
// 开始实例化对象,通过反射来实现
for(String className: classNames) {
String cn = className.replaceAll(".class", "");
try {
Class> clazz = Class.forName(cn);
// 判断当前类是否有注解CustomController类,获取设置的CustomeRequestMapping的值
if(clazz.isAnnotationPresent(CustomController.class)) {
// 通过CustomeRequestMapping获取值,作为beans的key
CustomRequestMapping requestMapping = clazz.getAnnotation(CustomRequestMapping.class);
String key = requestMapping.value();
// beans的vaue为实例化对象
Object value = clazz.newInstance();
beans.put(key, value);
// 判断当前的类是否有注解CustomService(考虑Service层),获取值
} else if(clazz.isAnnotationPresent(CustomService.class)) {
// 通过CustomService获取值,作为beans的key
CustomService service = clazz.getAnnotation(CustomService.class);
String key = service.value();
// beans的vaue为实例化对象
Object value = clazz.newInstance();
beans.put(key, value);
} else {
continue;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void doIoc() {
if(beans.isEmpty()) {
System.out.println("no class is instance......");
return;
}
for(Map.Entry map: beans.entrySet()) {
// 获取实例
Object instance = map.getValue();
// 获取类
Class> clazz = instance.getClass();
// 如果当前是CustomController类,则获取类中定义的field来设置其对象
if(clazz.isAnnotationPresent(CustomController.class)) {
// 获取全部的成员变量
Field[] fields = clazz.getDeclaredFields();
for(Field field: fields) {
// 如果当前的成员变量使用注解CustomRequestMapping进行处理
if(field.isAnnotationPresent(CustomQualifier.class)) {
// 获取当前成员变量的注解值
CustomQualifier qualifier = field.getAnnotation(CustomQualifier.class);
String value = qualifier.value();
// 由于此类成员变量设置为private,需要强行设置
field.setAccessible(true);
// 将beans的实例化对象赋值给当前的变量
try {
field.set(instance, beans.get(value));
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else {
continue;
}
}
}
}
}
private void handlerMapping() {
if(beans.isEmpty()) {
System.out.println("no class is instance......");
return;
}
for(Map.Entry map: beans.entrySet()) {
// 获取当前的对象
Object instance = map.getValue();
// 获取当前的类
Class> clazz = instance.getClass();
// 获取注解当前为Controller的类
if(clazz.isAnnotationPresent(CustomController.class)) {
// 获取类上的路径
CustomRequestMapping clazzRm = clazz.getAnnotation(CustomRequestMapping.class);
String clazzPath = clazzRm.value();
// 处理方法
Method[] methods = clazz.getMethods();
for(Method method: methods) {
// 判断注解为RequestMapping
if(method.isAnnotationPresent(CustomRequestMapping.class)) {
// 获取方法上的路径
CustomRequestMapping methodRm = method.getAnnotation(CustomRequestMapping.class);
String methodPath = methodRm.value();
// 将类上的路径+方法上的路径 设置为key,方法设置为value
handlerMap.put(clazzPath + methodPath, method);
} else {
continue;
}
}
} else {
continue;
}
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("doGet()............");
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("doPost()............");
// 通过req获取请求的uri /maven_handmvc/custom/query
String uri = req.getRequestURI();
// /maven_handmvc
String context = req.getContextPath();
String path = uri.replaceAll(context, "");
// 通过当前的path获取handlerMap的方法名
Method method = (Method) handlerMap.get(path);
// 获取beans容器中的bean
MyController instance = (MyController) beans.get("/" + path.split("/")[1]);
// 处理参数
HandlerAdapterService ha = (HandlerAdapterService) beans.get("customHandlerAdapter");
Object[] args = ha.handle(req, resp, method, beans);
// 通过反射来实现方法的调用
try {
method.invoke(instance, args);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
@Override
public void destroy() {
System.out.println("destroy()............");
}
}
DispatcherServlet类的实现中涉及到下面几个步骤:
// 1.扫描需要的实例化的类
doScanPackage("com.ygsoft.custom");
// 2.实例化
doInstance();
// 3.将IOC容器中的service对象设置给controller层定义的field上
doIoc();
// 4.建立path与method的映射关系
handlerMapping();
主要是处理方法的参数,其中使用了策略模式来实现参数的处理,具体的可以看后续的代码。
// 处理参数
HandlerAdapterService ha = (HandlerAdapterService) beans.get("customHandlerAdapter");
Object[] args = ha.handle(req, resp, method, beans);
// 通过反射来实现方法的调用
try {
method.invoke(instance, args);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
五、参数处理涉及的类
其实此部分的实现,可以不用这么复杂,由于个人学习,想将设计模式-策略模式使用到此部分,所以就有了下面的代码,其实这部分大家认为理解比较困难,或者不好理解,大家可以使用简单的方式来实现。
使用反射调用实现的代码为:method.invoke(instance, args); 其中涉及的参数变量为args,目前的实现中请求的参数为HttpServletRequest request, HttpServletResponse response,
@CustomRequestParam("name")String name, @CustomRequestParam("age")String age
可以通过Annotation[][] annotations = method.getParameterAnnotations();获取所有的参数的注解 与Class>[] paramTypes = method.getParameterTypes();获取所有参数的类型进行遍历将args赋值,然后再通过上面的反射进行调用。
下面的代码是使用了策略模式来实现,大家可以借鉴,可以研究下。
HandlerAdapterService:定义一个处理参数接口
package com.ygsoft.custom.handlerAdapter;
import java.lang.reflect.Method;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface HandlerAdapterService {
public Object[] handle(HttpServletRequest req, HttpServletResponse resp,
Method method, Map beans);
}
CustomHandlerAdapter:处理参数的实现类
package com.ygsoft.custom.handlerAdapter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ygsoft.custom.annotation.CustomService;
import com.ygsoft.custom.argumentResolver.ArgumentResolver;
@CustomService("customHandlerAdapter")
public class CustomHandlerAdapter implements HandlerAdapterService {
@Override
public Object[] handle(HttpServletRequest req, HttpServletResponse resp,
Method method, Map beans) {
//获取方法中含义的参数
Class>[] paramClazzs = method.getParameterTypes();
System.out.println("======当前需要解析的参数对应的类=========");
for(Class> clazz: paramClazzs) {
System.out.println(clazz);
}
// 定义一个返回参数的结果集
Object[] args = new Object[paramClazzs.length];
// Object[] args = {req, resp, "chenyanwu", "20"};
// 定义一个ArgumentResolver实现类的Map
Map argumentResolvers = getBeansOfType(beans, ArgumentResolver.class);
System.out.println("======当前需要解析的参数对应的类实例化=========");
for(Map.Entry map: argumentResolvers.entrySet()) {
System.out.println("key:" + map.getKey() + "; value:" + map.getValue());
}
//定义参数索引
int paramIndex = 0;
//定义数组下标索引
int i = 0;
// 开始处理参数
for(Class> paramClazz: paramClazzs) {
//哪个参数对应了哪个参数解析类,用策略模式来找
for (Map.Entry entry : argumentResolvers.entrySet()) {
ArgumentResolver ar = (ArgumentResolver)entry.getValue();
if (ar.support(paramClazz, paramIndex, method)) {
args[i++] = ar.argumentResolver(req,
resp,
paramClazz,
paramIndex,
method);
}
}
paramIndex++;
}
return args;
}
/**
* @param beans IOC容器中全部的bean
* @param intfType 定义的ArgumentResolver类
* @return
*/
private Map getBeansOfType(Map beans,
Class intfType) {
Map resultBeans = new HashMap<>();
for(Map.Entry map: beans.entrySet()) {
// 获取满足ArgumentResolver接口的bean
Class>[] intfs = map.getValue().getClass().getInterfaces();
if(intfs != null && intfs.length >0) {
for(Class> intf: intfs) {
// 将满足的bean存储在resultBeans中
if(intf.isAssignableFrom(intfType)) {
resultBeans.put(map.getKey(), map.getValue());
}
}
}
}
return resultBeans;
}
}
参数类:
ArgumentResolver:
package com.ygsoft.custom.argumentResolver;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface ArgumentResolver {
/**
* 判断当前的类是继承于ArgumentResolver
* @param type 当前参数注解的类对象
* @param paramIndex 参数下标
* @param method 当前的方法
* @return
*/
public boolean support(Class> type, int paramIndex, Method method);
/**
*
* @param request
* @param response
* @param type
* @param paramIndex
* @param method
* @return
*/
public Object argumentResolver(HttpServletRequest request,
HttpServletResponse response, Class> type,
int paramIndex,
Method method);
}
HttpServletRequestArgumentResolver:处理Request请求参数
package com.ygsoft.custom.argumentResolver;
import java.lang.reflect.Method;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ygsoft.custom.annotation.CustomService;
@CustomService("httpServletRequestArgumentResolver")
public class HttpServletRequestArgumentResolver implements ArgumentResolver {
@Override
public boolean support(Class> type, int paramIndex, Method method) {
return ServletRequest.class.isAssignableFrom(type);
}
@Override
public Object argumentResolver(HttpServletRequest request,
HttpServletResponse response, Class> type, int paramIndex,
Method method) {
return request;
}
}
HttpServletResponseArgumentResolver:处理Response参数
package com.ygsoft.custom.argumentResolver;
import java.lang.reflect.Method;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ygsoft.custom.annotation.CustomService;
@CustomService("httpServletResponseArgumentResolver")
public class HttpServletResponseArgumentResolver implements ArgumentResolver {
@Override
public boolean support(Class> type, int paramIndex, Method method) {
return ServletResponse.class.isAssignableFrom(type);
}
@Override
public Object argumentResolver(HttpServletRequest request,
HttpServletResponse response, Class> type, int paramIndex,
Method method) {
return response;
}
}
RequestParamArgumentResolver:处理自定义的参数
package com.ygsoft.custom.argumentResolver;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ygsoft.custom.annotation.CustomRequestParam;
import com.ygsoft.custom.annotation.CustomService;
@CustomService("requestParamArgumentResolver")
public class RequestParamArgumentResolver implements ArgumentResolver {
@Override
public boolean support(Class> type, int paramIndex, Method method) {
// type = class java.lang.String
// @CustomRequestParam("name")String name
//获取当前方法的参数
Annotation[][] an = method.getParameterAnnotations();
Annotation[] paramAns = an[paramIndex];
for (Annotation paramAn : paramAns) {
//判断传进的paramAn.getClass()是不是 CustomRequestParam 类型
if (CustomRequestParam.class.isAssignableFrom(paramAn.getClass())) {
return true;
}
}
return false;
}
@Override
public Object argumentResolver(HttpServletRequest request,
HttpServletResponse response, Class> type, int paramIndex,
Method method) {
//获取当前方法的参数
Annotation[][] an = method.getParameterAnnotations();
Annotation[] paramAns = an[paramIndex];
for (Annotation paramAn : paramAns) {
//判断传进的paramAn.getClass()是不是 CustomRequestParam 类型
if (CustomRequestParam.class.isAssignableFrom(paramAn.getClass())) {
CustomRequestParam cr = (CustomRequestParam) paramAn;
String value = cr.value();
return request.getParameter(value);
}
}
return null;
}
}
6.启动服务,在URL输入请求地址:
http://localhost:8080/maven_handmvc/custom/query?name=chenyanwu&&age=21
返回的结果为:
扫描关注:全栈工程师成长记
一个可以交流的平台,目的是为了做一个影响最有影响力的人的平台。