在上章分享中我们完成了基础的构建,已经可以根据url访问到controller并对相应的参数根据参数名匹配完成赋值,将参数信息打印到网页中,下面我们需要根据ModelAndView中的视图信息字符串,跳转到相应的页面中,并把model中相对应的参数信息替换到页面的指定符号。
代码:
@XxxController
@XxxRequestMapping("/user")
public class UserController {
@XxxAutowired
private UserService userService;
@XxxRequestMapping("/getuser")
public ModleAndView getUserInfo(String userName, Long age){
ModleAndView modleAndView=new ModleAndView();
//设置要跳转的视图字符串
modleAndView.setViewData("mvc");
//设置model,绑定参数
HashMap mode=new HashMap();
mode.put("userName",userName);
mode.put("age",age);
modleAndView.setModel(mode);
userService.getUserInfo("张三");
return modleAndView;
}
@XxxRequestMapping("/getJson")
public String getJson(String userName, Long age){
return "userName="+userName+"age="+age;
}
}
设置请求路径,绑定参数信息和视图信息。通过视图解析器解析。
@Data
public class ModleAndView {
//视图名称
private Object viewData;
//响应参数
private HashMap model;
//是否为一个视图对象
private boolean hasView;
}
模型与视图实体类,绑定参数信息。
public class DispatchServlet extends HttpServlet {
private Properties vewConfig;
private List<View> viewList = new ArrayList();
private ConcurrentHashMap<String, HandleMapping> handleMappings = new ConcurrentHashMap();
private ConcurrentHashMap<HandleMapping, HandlerAdapter> handleradapters = new ConcurrentHashMap();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
processRequest(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
processRequest(req, resp);
}
private void processRequest(HttpServletRequest req, HttpServletResponse resp) {
//获取初始化的语言环境
// 把request和response包装成ServletRequestAttributes对象
//异步请求管理
//统一调用doService
doService(req, resp);
}
private void doService(HttpServletRequest req, HttpServletResponse resp) {
doDispatch(req, resp);
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
try {
//获取请求url
String requestUrl = req.getRequestURI();
//根据URL获得HandleMapping
HandleMapping handleMapping = getHandleMapping(requestUrl);
//返回404
if (handleMapping == null) {
resp.getWriter().write("404");
return;
}
//获得参数适配器
HandlerAdapter ha = handleradapters.get(handleMapping);
//执行方法,得到视图信息
ModleAndView modleAndView = ha.handle(req, resp, handleMapping);
//把视图名称解析成对应文件名
applyDefaultViewName(modleAndView);
//返回json
if (!modleAndView.isHasView()) {
resp.getWriter().write(modleAndView.getViewData().toString());
return;
}
//根据视图名称找到视图文件
View view = getView(modleAndView.getViewData().toString());
//读取jsp文件,替换${}内容
view.render(modleAndView, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
private View getView(String viewName) {
for (View view : viewList) {
if (view.getViewName().equals(viewName)) return view;
}
return null;
}
/**
* 把返回字符串解析成文件名mvc=>mvc.jsp
*
* @param modleAndView
*/
private void applyDefaultViewName(ModleAndView modleAndView) {
if (modleAndView.isHasView()) {
//mvc
String viewName = modleAndView.getViewData().toString();
String prefix = vewConfig.get("view.prefix").toString();
String suffix = vewConfig.get("view.suffix").toString();
viewName = prefix + viewName + suffix;
modleAndView.setViewData(viewName);
}
}
private HandleMapping getHandleMapping(String requestUrl) {
return this.handleMappings.get(requestUrl);
}
@Override
public void init() throws ServletException {
loadProperties();
//初始化IOC容器
AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext("com.spring.mvc.bean");
//初始化HandleMapping 保存controller,Method ,url关系
initHandlerMappings(configApplicationContext);
//初始化参数信息
initHandlerAdapter();
//初始化视图信息
initViewResolver();
System.out.println("=init");
}
private void loadProperties() {
try {
InputStream is = this.getClass().getClassLoader().getResourceAsStream("dispatcherServlet.properties");
vewConfig = new Properties();
vewConfig.load(is);
} catch (Exception e) {
e.printStackTrace();
}
}
private void initViewResolver() {
try {
//jsp文件路径
String path = vewConfig.getProperty("view.rootPath");
//遍历文件夹获取到所有jsp文件
URL url = this.getClass().getClassLoader().getResource(path);
//拿到文件夹
File file = new File(url.toURI());
doLoadFile(file);
} catch (Exception e) {
e.printStackTrace();
}
}
private void doLoadFile(File file) {
String suffix = vewConfig.getProperty("view.suffix");
//文件夹
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
doLoadFile(f);
}
} else {
//是否为jsp结尾的文件
if (file.getName().endsWith(suffix)) {
View view = new View(file.getName(), file);
viewList.add(view);//保存文件
}
}
}
private void initHandlerAdapter() {
try {
//遍历所有方法,获取参数名
for (HandleMapping handleMapping : handleMappings.values()) {
//参数类型
Class<?>[] parameterTypes = handleMapping.getMethod().getParameterTypes();
//参数名
List<String> parameterNames = MethodHelper.getMethodParamNames(handleMapping.getController().getClass(), handleMapping.getMethod());
List<Parameter> parameters = new ArrayList<>();
//保存对应方法所有参数
for (int i = 0; i < parameterNames.size(); i++) {
Parameter parameter = new Parameter(parameterNames.get(i), parameterTypes[i]);
parameters.add(parameter);
}
HandlerAdapter handlerAdapter = new HandlerAdapter(parameters);
handleradapters.put(handleMapping, handlerAdapter);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void loadViewConfig(String path) {
try {
InputStream is = this.getClass().getClassLoader().getResourceAsStream(path);
vewConfig = new Properties();
vewConfig.load(is);
} catch (IOException e) {
e.printStackTrace();
}
}
private void initHandlerMappings(AnnotationConfigApplicationContext configApplicationContext) {
//拿到所有controller
Map<String, BeanDefinition> beanDefinitionMap = configApplicationContext.getBeanDefinitions();
for (BeanDefinition beanDefinition : beanDefinitionMap.values()) {
if (!beanDefinition.getBeanClass().isAnnotationPresent(XxxController.class)) continue;
//获取controller所有method
Class cls = beanDefinition.getBeanClass();
Method[] methods = cls.getMethods();
for (Method method : methods) {
//判断带有RequesMapping 注解method
if (!method.isAnnotationPresent(XxxRequestMapping.class)) continue;
//获取RequesMapping value
String reqUrl = "";
String controllerUrl = "";
XxxRequestMapping methodRequestMapping = method.getAnnotation(XxxRequestMapping.class);
String methodUrl = methodRequestMapping.value();
if (cls.isAnnotationPresent(XxxRequestMapping.class)) {
XxxRequestMapping controllerRequestMapping = (XxxRequestMapping) cls.getAnnotation(XxxRequestMapping.class);
controllerUrl = controllerRequestMapping.value();
}
reqUrl = reqUrl + controllerUrl + methodUrl;
HandleMapping handleMapping = new HandleMapping(reqUrl, configApplicationContext.getBean(beanDefinition.getBeanName()), method);
handleMappings.put(reqUrl, handleMapping);
}
}
}
}
在init()方法中添加
initHandlerAdapter()方法初始化参数适配器信息:将hadleMapping匹配的参数信息HandlerAdapter储存到map中。
HandlerAdapter:
public class HandlerAdapter {
private List<Parameter> parameterList;
public HandlerAdapter(List<Parameter> parameterList) {
this.parameterList = parameterList;
}
public ModleAndView handle(HttpServletRequest request, HttpServletResponse response, HandleMapping handleMapping){
ModleAndView modleAndView=new ModleAndView();
try {
//获取请求参数
Map<String, String[]> parameterMap= request.getParameterMap();
//最终传到方法的参数
Object [] params=new Object[parameterList.size()];
//遍历方法的参数
for(int i=0;i<parameterList.size();i++){
Parameter parameter= parameterList.get(i);
boolean flag=false;
for(String key:parameterMap.keySet()){
if (key.equals(parameter.getParameterName())){
//赋值,转型
params[i]= ClassHelper.typeConversion(parameter.getParameterType(),request.getParameter(key));
flag=true;
}
}
//请求传参则赋默认值
if(!flag){
params[i]=null;
}
}
Object result= handleMapping.getMethod().invoke(handleMapping.getController(),params);
//需要返回视图
if( result instanceof ModleAndView){
modleAndView= (ModleAndView)result;
modleAndView.setHasView(true);
}else{
//非视图返回Json
modleAndView.setHasView(false);
modleAndView.setViewData(result);
}
}catch (Exception e){
e.printStackTrace();
}
return modleAndView;
}
}
Parameter:
@Data
public class Parameter {
public Parameter(String parameterName, Class<?> parameterType) {
this.parameterName = parameterName;
this.parameterType = parameterType;
}
private String parameterName;
private Class<?> parameterType;
}
initViewResolver()方法初始化视图信息:
dispatcherServlet.properties:
view.rootPath=/view
view.prefix=
view.suffix=.jsp
根据视图的根路径和后缀名,找到视图,并根据名称匹配%{}中的参数
mvc.jsp:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>mvc page userName is %{userName},age is %{age}</title>
</head>
<body>
</body>
</html>
请求路径/user/getuser,请求会先经过dispatcher的inti方法,保存好路径的匹配信息,根据拿到的Handlmapping获取HandlerAdapter对象,调用对象的处理方法,将ModelAndView返回,最后将ModelAndView中的View匹配页面并替换页面中的关键字,将页面输出。