Spring框架进阶Spring V2.0 MVC
Posted 烟锁迷城
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring框架进阶Spring V2.0 MVC相关的知识,希望对你有一定的参考价值。
MVC是负责渲染页面和匹配URL与Controller函数的组件
1、MVC九大组件
MVC共有九大组件
- MulitipartResolver:多文件上传的组件
- LocaleResolver:本地语言环境
- TemeResolver:主题模板处理器
- HandlerMapping:保存URL映射关系
- HandlerAdapter:动态参数适配器
- HandlerExceptionResolver:异常拦截
- RequestToViewNameTranslator:视图提取器
- ViewResolvers:视图转换器,模板引擎
- FlashMapManager:参数缓存器
2、实现思路
MVC有九个组件,但并非所有的组件都非常重要,在这里只用三个组件完成基础的匹配渲染。
需要了解的,就是HandlerMapping,HandlerAdapter,ViewResolvers三个组件,实现步骤可分为四步
- 根据URL拿到对应的HandlerMapping
- 根据HandlerMapping拿到HandlerAdapter
- 根据HandlerAdapter拿到对应的ModelAndView
- 根据ViewResolver找到对应View对象,通过View对象渲染页面
3、代码实现
优化HandlerMapping的结构,Map的底层就是数组,依旧要遍历,所以将HandlerMapping抽取出来,保存所在的类,方法,以及对应URL的正则规则
public class MyHandlerMapping
private Object controller;
private Method method;
private Pattern pattern;
public MyHandlerMapping(Method method, Object controller, Pattern pattern)
this.method = method;
this.controller = controller;
this.pattern = pattern;
public Method getMethod()
return method;
public Pattern getPattern()
return pattern;
public Object getController()
return controller;
抽取HandlerAdapter
public class MyHandlerAdapter
抽取ViewResolver
public class MyViewResolver
设置容器
private List<MyHandlerMapping> handlerMappings = new ArrayList<MyHandlerMapping>();
private Map<MyHandlerMapping, MyHandlerAdapter> handlerAdpaters = new HashMap<MyHandlerMapping, MyHandlerAdapter>();
private List<MyViewResolver> viewResolvers = new ArrayList<MyViewResolver>();
将Spring源码的初始化策略模仿写入init方法中
@Override
public void init(ServletConfig config) throws ServletException
myApplicationContext = new MyApplicationContext(config.getInitParameter("contextConfigLocation"));
initStrategies(myApplicationContext);
MVC初始化策略,初始化映射,初始化参数适配,初始化视图转换
//MVC初始化
private void initStrategies(MyApplicationContext context)
//初始化方法映射
initHandlerMappings(context);
//初始化参数适配器
initHandlerAdapters(context);
//初始化视图转换器
initViewResolvers(context);
HandlerMapping的初始化方式与原本的初始化方式没有什么区别,正常写入即可,但是在放入MyHandlerMapping时,需要注意,将所属的Controller,Method,URL的Pattern
private void initHandlerMappings(MyApplicationContext context)
if (myApplicationContext.getBeanDefinitionCount() == 0)
return;
try
for (String beanName : myApplicationContext.getBeanDefinitionNames())
Object instance = myApplicationContext.getBean(beanName);
Class aClass = instance.getClass();
if (!aClass.isAnnotationPresent(MyController.class))
continue;
String baseUrl = "";
if (aClass.isAnnotationPresent(MyRequestMapping.class))
MyRequestMapping myRequestMapping = (MyRequestMapping) aClass.getAnnotation(MyRequestMapping.class);
baseUrl = "/" + myRequestMapping.value();
for (Method method : aClass.getMethods())
if (!method.isAnnotationPresent(MyRequestMapping.class))
continue;
MyRequestMapping myRequestMapping = (MyRequestMapping) method.getAnnotation(MyRequestMapping.class);
String url = (baseUrl + "/" + myRequestMapping.value())
.replaceAll("\\\\*", ".*")
.replaceAll("/+", "/");
Pattern pattern = Pattern.compile(url);
handlerMappings.add(new MyHandlerMapping(method, instance, pattern));
catch (Exception e)
e.printStackTrace();
以handlerMapping作为Key,以HandlerAdapter作为Value,完成初始化操作
private void initHandlerAdapters(MyApplicationContext context)
for (MyHandlerMapping handlerMapping : handlerMappings)
handlerAdapters.put(handlerMapping, new MyHandlerAdapter());
初始化视图解析器,采用类似BeanDefinitionReader的方式对
private void initViewResolvers(MyApplicationContext context)
String templateRoot = context.getConfig().getProperty("templateRoot");
URL templateRootPath = this.getClass().getClassLoader().getResource(templateRoot);
File file = new File(templateRootPath.getFile());
this.viewResolvers.add(new MyViewResolver(file));
MVC初始化应该放在ApplicationContext后面
@Override
public void init(ServletConfig config) throws ServletException
myApplicationContext = new MyApplicationContext(config.getInitParameter("contextConfigLocation"));
initStrategies(myApplicationContext);
原来的doDispatcher方法就可以改为:
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws InvocationTargetException, IllegalAccessException, IOException
//1、根据URL拿到对应的Handler
MyHandlerMapping handlerMapping = getHandlerMapping(req);
if (null == handlerMapping)
processDispatchResult(req, resp, new MyModelAndView("404"));
return;
//2、根据HandlerMapping拿到HandlerAdapter
MyHandlerAdapter handlerAdapter = getHandlerAapter(handlerMapping);
//3、根据HandlerAdapter拿到对应的ModelAndView
MyModelAndView modelAndView = handlerAdapter.handle(req, resp, handlerMapping);
//4、根据ViewResolver找到对应View对象,通过View对象渲染页面
processDispatchResult(req, resp, modelAndView);
实现起来也是非常的简单
首先第一个方法getHandlerMapping可以从request中获取到URL,然后轮询HandlerMapping的容器,获取匹配,得到对应的HandlerMapping。
private MyHandlerMapping getHandlerMapping(HttpServletRequest req)
String uri = req.getRequestURI();
String contextPath = req.getContextPath();
uri = uri.replaceAll(contextPath, "").replaceAll("/+", "/");
for (MyHandlerMapping handlerMapping : handlerMappings)
Matcher matcher = handlerMapping.getPattern().matcher(uri);
if (!matcher.matches())
continue;
return handlerMapping;
return null;
获取到对应的参数匹配,可以从HandlerMapping和HandlerAdapter的匹配获取到
private MyHandlerAdapter getHandlerAapter(MyHandlerMapping handlerMapping)
if (this.handlerAdpaters.isEmpty())
return null;
MyHandlerAdapter handlerAdapter = this.handlerAdpaters.get(handlerMapping);
return handlerAdapter;
从参数匹配器中获取ModelAndView
MyModelAndView modelAndView = handlerAdapter.handle(req, resp, handlerMapping);
先创建ModelAndView,保存视图名称和映射
public class MyModelAndView
private String ViewName;
private Map<String,?> model;
public MyModelAndView(String viewName)
ViewName = viewName;
public MyModelAndView(String viewName, Map<String, ?> model)
ViewName = viewName;
this.model = model;
public String getViewName()
return ViewName;
public Map<String, ?> getModel()
return model;
将URL映射关系设置出来
public class MyHandlerAdapter
public MyModelAndView handle(HttpServletRequest req, HttpServletResponse resp, MyHandlerMapping handlerMapping) throws InvocationTargetException, IllegalAccessException
Method method = handlerMapping.getMethod();
Map<String, Integer> paramMapping = new HashMap<String, Integer>();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++)
for (Annotation annotation : parameterAnnotations[i])
if (annotation instanceof MyRequestParam)
String value = ((MyRequestParam) annotation).value();
if (!"".equals(value))
paramMapping.put(value, i);
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++)
Class<?> parameterType = parameterTypes[i];
if (HttpServletRequest.class == parameterType || HttpServletResponse.class == parameterType)
paramMapping.put(parameterType.getName(), i);
Object[] paramValues = new Object[paramMapping.size()];
Map<String, String[]> params = req.getParameterMap();
for (Map.Entry<String, String[]> param : params.entrySet())
String value = Arrays.toString(param.getValue())
.replaceAll("\\\\[|\\\\]", "")
.replaceAll("\\\\s", "");
if (!paramMapping.containsKey(param.getKey()))
continue;
paramValues[paramMapping.get(param.getKey())] = value;
if (paramMapping.containsKey(HttpServletRequest.class.getName()))
int index = paramMapping.get(HttpServletRequest.class.getName());
paramValues[index] = req;
if (paramMapping.containsKey(HttpServletResponse.class.getName()))
int index = paramMapping.get(HttpServletResponse.class.getName());
paramValues[index] = resp;
Object result = method.invoke(handlerMapping.getController(), paramValues);
if (null == result || result instanceof Void)
return null;
if (result.getClass() == MyModelAndView.class)
return (MyModelAndView) result;
return null;
根据ViewResolver找到对应View对象,通过View对象渲染页面
private void processDispatchResult(HttpServletRequest req, HttpServletResponse resp, MyModelAndView modelAndView) throws IOException
if (modelAndView == null)
return;
if (viewResolvers.isEmpty())
return;
for (MyViewResolver viewResolver : viewResolvers)
MyView view = viewResolver.resolverViewName(modelAndView.getViewName());
view.render(modelAndView.getModel(),req,resp);
锁定要渲染的页面
public class MyViewResolver
private final String DEFAULT_TEMPLATE_SUFFIX = ".html";
private File templateRootDir;
public MyViewResolver(File file)
templateRootDir = file;
public MyView resolverViewName(String viewName)
viewName = viewName.endsWith(DEFAULT_TEMPLATE_SUFFIX) ? viewName : (viewName + DEFAULT_TEMPLATE_SUFFIX);
for (File file : templateRootDir.listFiles())
if (file.getName() == viewName || file.getName().equals(viewName))
return new MyView(file);
return null;
渲染页面的法则,读取对应读数
public class MyView
private File viewFile;
public MyView(File file)
viewFile = file;
public void render(Map<String, ?> modelAndView, HttpServletRequest req, HttpServletResponse resp) throws IOException
StringBuffer sb = new StringBuffer();
RandomAccessFile ra = new RandomAccessFile(this.viewFile, "r");
String line = null;
while (null != (line = ra.readLine()))
line = new String(line.getBytes("iso-8859-1"), "utf-8");
Pattern pattern = Pattern.compile("#\\\\[^\\\\]+\\\\", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(line);
while (matcher.find())
String paramName = matcher.group();
paramName = paramName.replaceAll("#\\\\|\\\\", "");
Object paramValue = modelAndView.get(paramName);
if (null == paramValue)
continue;
line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString()));
matcher = pattern.matcher(line);
sb.append(line);
resp.setCharacterEncoding("utf-8");
resp.getWriter().write(sb.toString());
//处理特殊字符
public static String makeStringForRegExp(String str)
return str.replace("\\\\", "\\\\\\\\").replace("*", "\\\\*")
.replace("+", "\\\\+").replace("|", "\\\\|")
.replace("", "\\\\").replace("", "\\\\")
.replace("(", "\\\\(").replace(")", "\\\\)")
.replace("^", "\\\\^").replace("$", "\\\\$")
.replace("[", "\\\\[").replace("]", "\\\\]")
.replace("?", "\\\\?").replace(",", "\\\\,")
.replace(".", "\\\\.").replace("&", "\\\\&");
这样组件就完成了
可以通过http://localhost:8080/user/teacher.html?teacher=x访问
源码地址:springwrite: 手写框架
以上是关于Spring框架进阶Spring V2.0 MVC的主要内容,如果未能解决你的问题,请参考以下文章