简介
springmvc中有九大组件
MultipleResolver、 LocalResolver、ThemeResolver、HandlerMapping、HandlerAdapter、HandlerExceptionResolver、RequestToViewNameTranstor、ViewResolver、FlashMapping
接上文,ioc与di过程已经处理完成,接下来就是mvc部分,mvc需要初始化九大组件,此处挑选三个组件进行初始化,包括HandlerMapping、HandlerAdapter、ViewResolver。
主要类如下:HandlerMapping、HandlerAdapter、ModelAndView、ViewResolver、View
流程如下:DispatcherServlet初始化时,先通过ApplicationContext进行ioc和di,而后进行mvc初始化。此处mvc初始化过程包括初始化HandlerMapping(保存控制层方法url和方法的映射关系)、初始化HandlerAdapter(用来反射调用方法并返回ModelAndView)、初始化ViewResolver(一种模板引擎对应一个解析器实例)。客户端进行访问时,通过请求url找到HandlerMapping ,再找到对应的HandlerAdapter,HandlerAdapter根据请求实参反射调用方法返回ModelAndView,ViewResolver对ModelAndView进行解析,找到对应的视图(模板引擎),视图再对页面进行渲染。
mvc
application.properties
scanPackage=com.yjf.spring.demo
#两种模板引擎
templateRoot=layouts
anotherTemplateRoot=templates/
resources目录下,layouts/dir/tes1.html
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>手写mvc</title>
</head>
<center>
<h3>Hello,My name is €{name}</h3>
<div>€{date} test1</div>
</center>
</html>
resources目录下,layouts/404.html
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>页面去火星了</title>
</head>
<body>
<font size='25' color='red'>404 Not Found</font><br/><font color='green'>6</font>
</body>
</html>
resources目录下,layouts/500.html
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>服务器好像累了</title>
</head>
<body>
<font size='25' color='blue'>500 服务器好像有点累了,需要休息一下</font><br/>
<b>Message:€{detail}</b><br/>
<b>StackTrace:€{stackTrace}</b><br/>
</body>
</html>
resources目录下,layouts/index.html
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>手写mvc</title>
</head>
<center>
<h3>Hello,My name is €{ name }</h3>
<div>€{date}</div>
</center>
</html>
resources目录下,templates/anotherindex.html
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>手写mvc</title>
</head>
<center>
<h3>Hello,My name is £{ name },this is another ViewResolver</h3>
<div>£{date}</div>
</center>
</html>
HandlerMapping 控制层方法的信息
@Data
public class MyHandlerMapping {
/**
* 反射调用对象===控制层对象
**/
private Object instance;
/**
* 反射调用方法===控制层方法
**/
private Method method;
/**
* 方法的url
**/
private Pattern pattern;
/**
* 方法的形参数组
**/
private Parameter[] parameters;
public MyHandlerMapping(Object instance, Method method, Pattern pattern) {
this.instance = instance;
this.method = method;
this.pattern = pattern;
this.parameters = method.getParameters();
}
}
HandlerAdapter 建立方法形参名和形参位置的关系,并根据实参通过反射调用方法获取ModelAndView
public class MyHandlerAdapter {
/**
* 形参名-->形参位置 形参名: 1.@MyRequestParam中的value 2.当前形参的名字
**/
private Map<String, Integer> paramIndexMapping = new HashMap<>(3);
//必须形参位置
int[] indexes;
public MyHandlerAdapter(Parameter[] parameters) {
indexes = new int[parameters.length];
Arrays.fill(indexes,-1);
initParamIndexMapping(parameters);
}
/**
* 初始化paramIndexMapping
*
* @return void
**/
private void initParamIndexMapping(Parameter[] parameters) {
for (int i = 0; i < parameters.length; i++) {
if (parameters[i].getType() == HttpServletRequest.class || parameters[i].getType() == HttpServletResponse.class) {
paramIndexMapping.put(parameters[i].getType().getName(), i);
continue;
}
//获取@MyRequestParam注解的值,若值存在,则paramIndexMapping中键为该值,若不存在,则paramIndexMapping中键为形参参数名
MyRequestParam param = parameters[i].getAnnotation(MyRequestParam.class);
if (null == param) {
paramIndexMapping.put(parameters[i].getName(), i);
} else if(StringUtil.isEmpty(param.value())){
paramIndexMapping.put(parameters[i].getName(), i);
indexes[i] = 1;
} else {
paramIndexMapping.put(param.value(),i);
if(param.required() == true) {
indexes[i] = 1;
}
}
}
}
/**
* 反射执行方法并返回ModelAndView
*
* @param req
* @param resp
* @param handler
* @return com.yjf.spring.mvcframework.v2.mvc.servlet.MyModelAndView
**/
public MyModelAndView handler(HttpServletRequest req, HttpServletResponse resp, MyHandlerMapping handler) throws InvocationTargetException, IllegalAccessException {
//取出方法
Method method = handler.getMethod();
//形参数组
Parameter[] parameters = handler.getParameters();
//实参数组
Object[] args = new Object[parameters.length];
Arrays.fill(args,new Object());
//控制层对象
Object instance = handler.getInstance();
//遍历形参,设置实参
for (Map.Entry<String, Integer> stringIntegerEntry : paramIndexMapping.entrySet()) {
//形参参数名
String key = stringIntegerEntry.getKey();
//参数名对应的位置
Integer integer = stringIntegerEntry.getValue();
if (parameters[integer].getType().isArray()) {
args[integer] = convert(parameters[integer], req.getParameterValues(key));
} else {
args[integer] = convert(parameters[integer], req.getParameter(key));
}
}
//req、resp注入
if (this.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
args[this.paramIndexMapping.get(HttpServletRequest.class.getName())] = req;
}
if (this.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
args[this.paramIndexMapping.get(HttpServletResponse.class.getName())] = resp;
}
//判断必须参数在实参中是否存在
for (int i = 0; i < indexes.length; i++) {
int val = indexes[i];
if(val == 1 && args[i] == null){
throw new Error(parameters[i].getName() + " is required!");
}
}
Object result = method.invoke(instance, args);
if(null == result || method.getReturnType() == Void.class || method.getReturnType() == void.class){
return null;
}else {
return (MyModelAndView)result;
}
}
/**
* 转换实参值的数据类型,当请求中没有形参需要的值时,形参设定初始值避免反射调用异常
*
* @param param 形参
* @param value 实参值
* @return java.lang.Object 转换后的实参
**/
private Object convert(Parameter param, String... value) {
if(param.getType().isArray()){
if (value == null || value.length == 0) {
return null;
}else{
Class<?> componentType = param.getType().getComponentType();
IConvertStrategy convertStrategy = ConvertStrategyFactory.getConvertStrategy(componentType.getSimpleName());
Object result = Array.newInstance(componentType, value.length);
for (int i = 0; i < value.length; i++) {
Array.set(result,i,convertStrategy.convertString(value[i]));
}
return result;
}
}else{
Class<?> type = param.getType();
IConvertStrategy convertStrategy = ConvertStrategyFactory.getConvertStrategy(type.getSimpleName());
if (value == null || value.length == 0) {
return convertStrategy.convertString(null);
}else{
return convertStrategy.convertString(value[0]);
}
}
}
}
ModelAndView 后端需要返回的视图以及需要设置的数据
@Data
public class MyModelAndView {
private String viewName;
private Map<String,?> models;
public MyModelAndView(String viewName) {
this.viewName = viewName;
}
public MyModelAndView(String viewName, Map<String, ?> models) {
this.viewName = viewName;
this.models = models;
}
}
view接口
public interface View {
/**
* 视图渲染
*
* @param models 后端需要设置的参数
* @param req
* @param resp
* @return void
* @throws IOException
**/
void render(Map<String,?> models, HttpServletRequest req, HttpServletResponse resp) throws IOException;
}
ViewResolver接口
public interface ViewResolver {
/**
* 根据逻辑视图找到真实视图
*
* @param viewName
* @return com.yjf.spring.mvcframework.v2.mvc.servlet.View
**/
View resolveViewName(String viewName);
}
视图解析器
public class MyViewResolver implements ViewResolver{
private static final String DEFAULT_TEMPLATE_SUFFIX = ".html";
private String rootPath;
public MyViewResolver(String rootPath) {
//存储模板路径
this.rootPath = this.getClass().getClassLoader().getResource(rootPath).getPath();
}
/**
* 根据名称找到对应的视图
*
* @param viewName
* @return com.yjf.spring.mvcframework.v2.mvc.servlet.View
**/
@Override
public View resolveViewName(String viewName){
if(StringUtil.isEmpty(viewName)){
return null;
}
//添加后缀
viewName = viewName.endsWith(DEFAULT_TEMPLATE_SUFFIX) ? viewName : (viewName + DEFAULT_TEMPLATE_SUFFIX);
//拼接模板路径和逻辑视图找寻文件
String filePath = (rootPath + "/" + viewName).replaceAll("/+","/");
File file = new File(filePath);
//判断当前视图是否存在
if(file.exists()){
return new MyView(file);
}else {
return null;
}
}
}
视图解析器
public class MyAnotherViewResolver implements ViewResolver{
private static final String DEFAULT_TEMPLATE_SUFFIX = ".html";
private String rootPath;
public MyAnotherViewResolver(String rootPath) {
//存储模板路径
this.rootPath = this.getClass().getClassLoader().getResource(rootPath).getPath();
}
/**
* 根据名称找到对应的视图
*
* @param viewName
* @return com.yjf.spring.mvcframework.v2.mvc.servlet.View
**/
@Override
public View resolveViewName(String viewName){
if(StringUtil.isEmpty(viewName)){
return null;
}
//添加后缀
viewName = viewName.endsWith(DEFAULT_TEMPLATE_SUFFIX) ? viewName : (viewName + DEFAULT_TEMPLATE_SUFFIX);
//拼接模板路径和逻辑视图找寻文件
String filePath = (rootPath + "/" + viewName).replaceAll("/+","/");
File file = new File(filePath);
//判断当前视图是否存在
if(file.exists()){
return new MyAnotherView(file);
}else {
return null;
}
}
}
具体的视图,对应一个文件,模板引擎
public class MyView implements View{
private File viewFile;
public MyView(File viewFile) {
this.viewFile = viewFile;
}
/**
* 根据后端设置的参数渲染页面
*
* @param models
* @param req
* @param resp
* @return void
**/
@Override
public void render(Map<String,?> models, HttpServletRequest req, HttpServletResponse resp) throws IOException {
RandomAccessFile raf = new RandomAccessFile(viewFile,"r");
StringBuilder sb = new StringBuilder();
String line = "";
while(null != (line = raf.readLine())){
line = new String(line.getBytes("iso-8859-1"),"utf-8");
Pattern regex = Pattern.compile("(?<=€\\{).+?(?=})");
Matcher matcher = regex.matcher(line);
while (matcher.find()){
String res = matcher.group();
Object val = models.get(res.trim());
if(null == val){continue;}
line = line.replaceFirst("€\\{" + res + "}",makeStringForRegExp(val.toString()));
matcher = regex.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("&", "\\&");
}
}
具体的视图,对应一个文件,模板引擎
public class MyAnotherView implements View{
private File viewFile;
public MyAnotherView(File viewFile) {
this.viewFile = viewFile;
}
/**
* 根据后端设置的参数渲染页面
*
* @param models
* @param req
* @param resp
* @return void
**/
@Override
public void render(Map<String,?> models, HttpServletRequest req, HttpServletResponse resp) throws IOException {
RandomAccessFile raf = new RandomAccessFile(viewFile,"r");
StringBuilder sb = new StringBuilder();
String line = "";
while(null != (line = raf.readLine())){
line = new String(line.getBytes("iso-8859-1"),"utf-8");
Pattern regex = Pattern.compile("(?<=£\\{).+?(?=})");
Matcher matcher = regex.matcher(line);
while (matcher.find()){
String res = matcher.group();
Object val = models.get(res.trim());
if(null == val){continue;}
line = line.replaceFirst("£\\{" + res + "}",makeStringForRegExp(val.toString()));
matcher = regex.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("&", "\\&");
}
}
控制层
@MyController
@MyRequestMapping("/web")
public class ModelAndViewController {
@MyRequestMapping("/index")
public MyModelAndView test(@MyRequestParam String name,@MyRequestParam(value = "index",required = false)Integer index){
System.out.println(index);
String viewName = "index";
MyModelAndView myModelAndView = new MyModelAndView(viewName);
Map<String,Object> models = new HashMap<>();
models.put("name","hx");
models.put("date",new Date().toLocaleString());
myModelAndView.setModels(models);
return myModelAndView;
}
@MyRequestMapping("/anotherIndex")
public MyModelAndView test2(@MyRequestParam String name,@MyRequestParam(value = "index",required = false)Integer index){
System.out.println(index);
String viewName = "anotherindex";
MyModelAndView myModelAndView = new MyModelAndView(viewName);
Map<String,Object> models = new HashMap<>();
models.put("name","hx");
models.put("date",new Date().toLocaleString());
myModelAndView.setModels(models);
return myModelAndView;
}
@MyRequestMapping("/test")
public MyModelAndView test3(@MyRequestParam String name,@MyRequestParam(value = "index",required = false)Integer index){
System.out.println(index);
String viewName = "dir/test1";
MyModelAndView myModelAndView = new MyModelAndView(viewName);
Map<String,Object> models = new HashMap<>();
models.put("name","hx");
models.put("date",new Date().toLocaleString());
myModelAndView.setModels(models);
return myModelAndView;
}
@MyRequestMapping("/add*")
public MyModelAndView testRegex(HttpServletRequest req){
System.out.println(req.getRequestURI().replace(req.getContextPath(),""));
return null;
}
@MyRequestMapping("/404")
public MyModelAndView test404(HttpServletRequest req){
return new MyModelAndView("404");
}
}