目录
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: 手写框架