看了一个自定义的实现spring mvc的文章,自己敲了一下。原文地址:https://mp.weixin.qq.com/s/36F_fFbGKkRL20DJgX4ahg
Spring mvc流程图:
请求流程:
⑴ 用户发送请求至前端控制器DispatcherServlet
⑵ DispatcherServlet收到请求调用HandlerMapping处理器映射器。
⑶ 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
⑷ DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
⑸ 执行处理器(Controller,也叫后端控制器)。
⑹ Controller执行完成返回ModelAndView
⑺ HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
⑻ DispatcherServlet将ModelAndView传给ViewReslover视图解析器
⑼ ViewReslover解析后返回具体View
⑽ DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
⑾ DispatcherServlet响应用户。从上面可以看出,DispatcherServlet有接收请求,响应结果,转发等作用。有了DispatcherServlet之后,可以减少组件之间的耦合度。
DispatcherServlet类结构图:
主要组件:
protected void initStrategies(ApplicationContext context) {
//用于处理上传请求。处理方法是将普通的request包装成 MultipartHttpServletRequest,后者可以直接调用getFile方法获取File.
initMultipartResolver(context);
//SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。
initLocaleResolver(context);
//用于解析主题。SpringMVC中一个主题对应 一个properties文件,里面存放着跟当前主题相关的所有资源、//如图片、css样式等。SpringMVC的主题也支持国际化,
initThemeResolver(context);
//用来查找Handler的。
initHandlerMappings(context);
//从名字上看,它就是一个适配器。Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。//如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情
initHandlerAdapters(context);
//其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?//这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。
initHandlerExceptionResolvers(context);
//有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,//如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。
initRequestToViewNameTranslator(context);
//ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。//View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。
initViewResolvers(context);
//用来管理FlashMap的,FlashMap主要用在redirect重定向中传递参数。
initFlashMapManager(context);
}
接下来实现mvc
1.新建一个web项目,web.xml里引入自定义的MyDispatcherServlet类以及配置文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>MySpringMVC</servlet-name>
<servlet-class>com.uiao.servlet.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MySpringMVC</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
application.properties里只配置了扫描路径
scanPackage=com.uiao.core
2.先来自定义几个注解模仿Comtroller,RequestMapping,RequestParam注解的功能
/**
* 控制层注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
String value() default "";
}
/**
* 方法路径注解
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
String value() default "";
}
/**
* 参数注解
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
String value();
}
3.自定义DispatcherServlet类
首先通过配置扫描需要的类,得到类名后通过反射实例化类并存入IOC容器内(模仿Spring),再维护一个Mapping表记录请求路径和类(方法)的对应。加载阶段完成之后就可以发起请求了。
/**
* 自定义DispatcherServlet
*/
public class MyDispatcherServlet extends HttpServlet {
private Properties properties = new Properties();
private List<String> classNames = new ArrayList<>();
private Map<String, Object> ioc = new HashMap<>();
private Map<String, Method> handlerMapping = new HashMap<>();
private Map<String, Object> controllerMapping = new HashMap<>();
@Override
public void init(ServletConfig config) {
//加载配置文件进properties
doloadConfig(config.getInitParameter("contextConfigLocation"));
//扫描用户包下面所有的类放到classNames里
doScanner(properties.getProperty("scanPackage"));
//将扫描到的类通过反射实例化,并存放进IOC容器内
doInstance();
//将url和映射存进handlerMapping和controllerMapping
initHandlerMapping();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
doDispatch(req, resp);
} catch (Exception e) {
resp.getWriter().write("500 Server Exception");
}
}
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (handlerMapping.isEmpty()) {
return;
}
String url = request.getRequestURI();
String contextPath = request.getContextPath();
url = url.replace(contextPath, "").replaceAll("/+", "/");
if (!this.handlerMapping.containsKey(url)) {
response.getWriter().write("404 not found");
}
Method method = this.handlerMapping.get(url);
//获取方法的参数列表
Class<?>[] parameterTypes = method.getParameterTypes();
//获取请求的参数列表(只有这一个)
Map<String, String[]> parameterMap = request.getParameterMap();
//保存参数值
Object[] paramValues = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
String requestParam = parameterTypes[i].getSimpleName();
if (requestParam.equals("HttpServletRequest")) {
paramValues[i] = request;
continue;
}
if (requestParam.equals("HttpServletResponse")) {
paramValues[i] = response;
continue;
}
if (requestParam.equals("String")) {
for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
String value = Arrays.toString(param.getValue()).replaceAll("[|]", "").replaceAll(",", ",");
paramValues[i] = value;
}
}
}
try {
method.invoke(this.controllerMapping.get(url), paramValues);//第一个参数为method所对应的实例,在ioc容器中
} catch (Exception e) {
e.printStackTrace();
}
}
private void doloadConfig(String location) {
InputStream inputStream = this.getClass().getResourceAsStream(location);
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != inputStream) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void doScanner(String packageName) {
URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll(".", "/"));
File dir = new File(url.getFile());
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
doScanner(packageName + "." + file.getName());
} else {
String className = packageName + "." + file.getName().replaceAll(".class", "");
classNames.add(className);
}
}
}
private void doInstance() {
if (classNames.isEmpty()) {
return;
}
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(MyController.class)) {
ioc.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance());
} else {
continue;
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
private void initHandlerMapping() {
if (ioc.isEmpty()) {
return;
}
try {
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<? extends Object> clazz = entry.getValue().getClass();
if (clazz.isAnnotationPresent(MyController.class)) {
continue;
}
String baseUrl = "";
if (clazz.isAnnotationPresent(MyRequestMapping.class))//用在类上或直接用在方法上
{
MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
baseUrl = annotation.value();
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(MyRequestMapping.class)) {//用在方法上
continue;
}
MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
String url = annotation.value();
url = (baseUrl + "/" + url).replace("/+", "/");
handlerMapping.put(url, method);
controllerMapping.put(url, clazz.newInstance());
System.out.println(url + "," + method);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 把字符串的首字母小写
*
* @param name
* @return
*/
private String toLowerFirstWord(String name) {
char[] charArray = name.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);
}
}
4.写个请求测试一下
@MyController
@MyRequestMapping("/test")
public class TestController {
@MyRequestMapping("/testa")
public void testA(HttpServletRequest request, HttpServletResponse response, @MyRequestParam("name") String name) {
System.out.println(name);
try {
response.getWriter().write("dotest methoda success ! param:" + name);
} catch (Exception e) {
e.printStackTrace();
}
}
@MyRequestMapping("/testb")
public void testB(HttpServletRequest request, HttpServletResponse response) {
try {
response.getWriter().write("dotestb method success ! ");
} catch (Exception e) {
e.printStackTrace();
}
}
}