if (!method.isAnnotationPresent(GPRequestMapping.class)) {
continue;
}
GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class);
String regex = (“/” + baseUrl + requestMapping.value().replaceAll(“\", ".”)).replaceAll(“/+”, “/”);
Pattern pattern = Pattern.compile(regex);
this.handlerMappings.add(new GPHandlerMapping(pattern, controller, method));
log.info("Mapping: " + regex + " , " + method);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
private void initHandlerAdapters(GPApplicationContext context) {
//在初始化阶段,我们能做的就是,将这些参数的名字或者类型按一定的顺序保存下来
//因为后面用反射调用的时候,传的形参是一个数组
//可以通过记录这些参数的位置index,逐个从数组中取值,这样就和参数的顺序无关了
for (GPHandlerMapping handlerMapping : this.handlerMappings){
//每个方法有一个参数列表,这里保存的是形参列表
this.handlerAdapters.put(handlerMapping,new GPHandlerAdapter());
}
}
private void initViewResolvers(GPApplicationContext context) {
//在页面中输入http://localhost/first.html
//解决页面名字和模板文件关联的问题
String templateRoot = context.getConfig().getProperty(“templateRoot”);
String templateRootPath = this.getClass().getClassLoader().getResource (templateRoot).getFile();
File templateRootDir = new File(templateRootPath);
for (File template : templateRootDir.listFiles()) {
this.viewResolvers.add(new GPViewResolver(templateRoot));
}
}
在上面的代码中,我们只实现了九大组件中的三大核心组件的基本功能,分别是HandlerMapping、HandlerAdapter、ViewResolver,完成MVC最核心的调度功能。其中HandlerMapping就是策略模式的应用,用输入URL间接调用不同的Method已达到获取结果的目的。顾名思义,HandlerAdapter应用的是适配器模式,将Request的字符型参数自动适配为Method的Java实参,主要实现参数列表自动适配和类型转换功能。ViewResolver也算一种策略,根据不同的请求选择不同的模板引擎来进行页面的渲染。
接下来看service()方法,它主要负责接收请求,得到Request和Response对象。在Servlet子类中service()方法被拆分成doGet()方法和doPost()方法。我们在doGet()方法中直接调用doPost()方法,在doPost()方法中调用doDispatch()方法,真正的调用逻辑由doDispatch()来执行。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatch(req, resp);
}catch (Exception e){
resp.getWriter().write(“500 Exception
Details:
” + Arrays.toString(e.getStackTrace()).replaceAll(“\[|\]”,“”)
.replaceAll(“\s”,“\r\n”) + “Copyright@GupaoEDU ”);
e.printStackTrace();
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
//根据用户请求的URL来获得一个Handler
GPHandlerMapping handler = getHandler(req);
if(handler == null){
processDispatchResult(req,resp,new GPModelAndView(“404”));
return;
}
GPHandlerAdapter ha = getHandlerAdapter(handler);
//这一步只是调用方法,得到返回值
GPModelAndView mv = ha.handle(req, resp, handler);
//这一步才是真的输出
processDispatchResult(req,resp, mv);
}
private void processDispatchResult(HttpServletRequest request,HttpServletResponse response, GPModelAndView mv) throws Exception {
//调用viewResolver的resolveViewName()方法
if(null == mv){ return;}
if(this.viewResolvers.isEmpty()){ return;}
if (this.viewResolvers != null) {
for (GPViewResolver viewResolver : this.viewResolvers) {
GPView view = viewResolver.resolveViewName(mv.getViewName(), null);
if (view != null) {
view.render(mv.getModel(),request,response);
return;
}
}
}
}
private GPHandlerAdapter getHandlerAdapter(GPHandlerMapping handler) {
if(this.handlerAdapters.isEmpty()){return null;}
GPHandlerAdapter ha = this.handlerAdapters.get(handler);
if (ha.supports(handler)) {
return ha;
}
return null;
}
private GPHandlerMapping getHandler(HttpServletRequest req) {
if(this.handlerMappings.isEmpty()){ return null;}
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replace(contextPath,“”).replaceAll(“/+”,“/”);
for (GPHandlerMapping handler : this.handlerMappings) {
Matcher matcher = handler.getPattern().matcher(url);
if(!matcher.matches()){ continue;}
return handler;
}
return null;
}
GPDisptcherServlet的完整代码请关注微信公众号回复“Spring”。下面补充实现上面的代码中缺失的依赖类。
1.2 GPHandlerMapping
我们已经知道HandlerMapping主要用来保存URL和Method的对应关系,这里其实使用的是策略模式。
package com.tom.spring.formework.webmvc;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
public class GPHandlerMapping {
private Object controller; //目标方法所在的contrller对象
private Method method; //URL对应的目标方法
private Pattern pattern; //URL的封装
public GPHandlerMapping(Pattern pattern,Object controller, Method method) {
this.controller = controller;
this.method = method;
this.pattern = pattern;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Pattern getPattern() {
return pattern;
}
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
}
1.3 GPHandlerAdapter
原生Spring的HandlerAdapter主要完成请求传递到服务端的参数列表与Method实参列表的对应关系,完成参数值的类型转换工作。核心方法是handle(),在handle()方法中用反射来调用被适配的目标方法,并将转换包装好的参数列表传递过去。
package com.tom.spring.formework.webmvc;
import com.tom.spring.formework.annotation.GPRequestParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
//专人干专事
public class GPHandlerAdapter {
public boolean supports(Object handler){
return (handler instanceof GPHandlerMapping);
}
public GPModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception{
GPHandlerMapping handlerMapping = (GPHandlerMapping)handler;
//每个方法有一个参数列表,这里保存的是形参列表
Map<String,Integer> paramMapping = new HashMap<String, Integer>();
//这里只是给出命名参数
Annotation[][] pa = handlerMapping.getMethod().getParameterAnnotations();
for (int i = 0; i < pa.length ; i ++) {
for (Annotation a : pa[i]) {
if(a instanceof GPRequestParam){
String paramName = ((GPRequestParam) a).value();
if(!“”.equals(paramName.trim())){
paramMapping.put(paramName,i);
}
}
}
}
//根据用户请求的参数信息,跟Method中的参数信息进行动态匹配
//resp 传进来的目的只有一个:将其赋值给方法参数,仅此而已
//只有当用户传过来的ModelAndView为空的时候,才会新建一个默认的
//1. 要准备好这个方法的形参列表
//方法重载时形参的决定因素:参数的个数、参数的类型、参数顺序、方法的名字
//只处理Request和Response
Class<?>[] paramTypes = handlerMapping.getMethod().getParameterTypes();
for (int i = 0;i < paramTypes.length; i ++) {
Class<?> type = paramTypes[i];
if(type == HttpServletRequest.class ||
type == HttpServletResponse.class){
paramMapping.put(type.getName(),i);
}
}
//2. 得到自定义命名参数所在的位置
//用户通过URL传过来的参数列表
Map<String,String[]> reqParameterMap = req.getParameterMap();
//3. 构造实参列表
Object [] paramValues = new Object[paramTypes.length];
for (Map.Entry<String,String[]> param : reqParameterMap.entrySet()) {
String value = Arrays.toString(param.getValue()).replaceAll(“\[|\]”,“”). replaceAll(“\s”,“”);
if(!paramMapping.containsKey(param.getKey())){continue;}
int index = paramMapping.get(param.getKey());
//因为页面传过来的值都是String类型的,而在方法中定义的类型是千变万化的
//所以要针对我们传过来的参数进行类型转换
paramValues[index] = caseStringValue(value,paramTypes[index]);
}
if(paramMapping.containsKey(HttpServletRequest.class.getName())) {
int reqIndex = paramMapping.get(HttpServletRequest.class.getName());
paramValues[reqIndex] = req;
}
if(paramMapping.containsKey(HttpServletResponse.class.getName())) {
int respIndex = paramMapping.get(HttpServletResponse.class.getName());
paramValues[respIndex] = resp;
}
//4. 从handler中取出Controller、Method,然后利用反射机制进行调用
Object result = handlerMapping.getMethod().invoke(handlerMapping.getController(), paramValues);
if(result == null){ return null; }
boolean isModelAndView = handlerMapping.getMethod().getReturnType() == GPModelAndView.class;
if(isModelAndView){
return (GPModelAndView)result;
}else{
return null;
}
}
private Object caseStringValue(String value,Class<?> clazz){
if(clazz == String.class){
return value;
}else if(clazz == Integer.class){
return Integer.valueOf(value);
}else if(clazz == int.class){
return Integer.valueOf(value).intValue();
}else {
return null;
}
}
}
1.4 GPModelAndView
原生Spring中ModelAndView类主要用于封装页面模板和要往页面传送的参数的对应关系。
package com.tom.spring.formework.webmvc;
import java.util.Map;
public class GPModelAndView {
private String viewName; //页面模板的名称
private Map<String,?> model; //往页面传送的参数
public GPModelAndView(String viewName) {
this(viewName,null);
}
public GPModelAndView(String viewName, Map<String, ?> model) {
this.viewName = viewName;
this.model = model;
}
public String getViewName() {
return viewName;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
public Map<String, ?> getModel() {
return model;
}
public void setModel(Map<String, ?> model) {
this.model = model;
}
}
1.5 GPViewResolver
原生Spring中的ViewResolver主要完成模板名称和模板解析引擎的匹配。通过在Serlvet中调用resolveViewName()方法来获得模板所对应的View。在这个Mini版本中简化了实现,只实现了一套默认的模板引擎,语法也是完全自定义的。
package com.tom.spring.formework.webmvc;
import java.io.File;
import java.util.Locale;
//设计这个类的主要目的是:
//1. 将一个静态文件变为一个动态文件
//2. 根据用户传送不同的参数,产生不同的结果
//最终输出字符串,交给Response输出
public class GPViewResolver {
private final String DEFAULT_TEMPLATE_SUFFIX = “.html”;
private File templateRootDir;
private String viewName;
public GPViewResolver(String templateRoot){
String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot). getFile();
this.templateRootDir = new File(templateRootPath);
}
public GPView resolveViewName(String viewName, Locale locale) throws Exception {
this.viewName = viewName;
if(null == viewName || “”.equals(viewName.trim())){ return null;}
viewName = viewName.endsWith(DEFAULT_TEMPLATE_SUFFIX) ? viewName : (viewName + DEFAULT_TEMPLATE_SUFFIX);
File templateFile = new File((templateRootDir.getPath() + “/” + viewName).replaceAll (“/+”, “/”));
return new GPView(templateFile);
}
public String getViewName() {
return viewName;
}
}
1.6 GPView
这里的GPView就是前面所说的自定义模板解析引擎,其核心方法是render()。在render()方法中完成对模板的渲染,最终返回浏览器能识别的字符串,通过Response输出。
package com.tom.spring.formework.webmvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.RandomAccessFile;
import java.util.Map;
import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GPView {
public static final String DEFAULT_CONTENT_TYPE = “text/html;charset=utf-8”;
private File viewFile;
public GPView(File viewFile){
this.viewFile = viewFile;
}
public String getContentType(){
return DEFAULT_CONTENT_TYPE;
}
public void render(Map<String, ?> model,HttpServletRequest request, HttpServletResponse response) throws Exception{
StringBuffer sb = new StringBuffer();
RandomAccessFile ra = new RandomAccessFile(this.viewFile,“r”);
try {
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 = model.get(paramName);
if (null == paramValue) { continue; }
//要把¥{}中间的这个字符串取出来
line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString()));
matcher = pattern.matcher(line);
}
sb.append(line);
}
}finally {
ra.close();
}
response.setCharacterEncoding(“utf-8”);
//response.setContentType(DEFAULT_CONTENT_TYPE);
response.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(“&”, “\&”);
}
}
从上面的代码可以看出,GPView是基于HTML文件来对页面进行渲染的。但是加入了一些自定义语法,例如在模板页面中扫描到¥{name}这样的表达式,就会从ModelAndView的Model中找到name所对应的值,并且用正则表达式将其替换(外国人喜欢用美元符号$,我们的模板引擎就用人民币符号¥)。
2.1 IQueryService
定义一个负责查询业务的顶层接口IQueryService,提供一个query()方法:
package com.tom.spring.demo.service;
/**
-
查询业务
*/
public interface IQueryService {
/**
- 查询
*/
public String query(String name);
}
2.2 QueryService
查询业务的实现QueryService也非常简单,就是打印一下调用时间和传入的参数,并封装为JSON格式返回:
package com.tom.spring.demo.service.impl;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.tom.spring.demo.service.IQueryService;
import com.tom.spring.formework.annotation.GPService;
import lombok.extern.slf4j.Slf4j;
/**
-
查询业务
*/
@GPService
@Slf4j
public class QueryService implements IQueryService {
/**
- 查询
*/
public String query(String name) {
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
String time = sdf.format(new Date());
String json = “{name:”" + name + “”,time:“” + time + “”}";
log.info(“这是在业务方法中打印的:” + json);
return json;
}
}
2.3 IModifyService
定义一个增、删、改业务的顶层接口IModifyService:
package com.tom.spring.demo.service;
/**
- 增、删、改业务
*/
public interface IModifyService {
/**
- 增加
*/
public String add(String name, String addr) ;
/**
- 修改
*/
public String edit(Integer id, String name);
/**
- 删除
*/
public String remove(Integer id);
}
2.4 ModifyService
增、删、改业务的实现ModifyService也非常简单,主要是打印传过来的参数:
package com.tom.spring.demo.service.impl;
import com.tom.spring.demo.service.IModifyService;
import com.tom.spring.formework.annotation.GPService;
/**
- 增、删、改业务
*/
@GPService
public class ModifyService implements IModifyService {
/**
- 增加
*/
public String add(String name,String addr) {
return “modifyService add,name=” + name + “,addr=” + addr;
}
/**
- 修改
*/
public String edit(Integer id,String name) {
return “modifyService edit,id=” + id + “,name=” + name;
}
/**
- 删除
*/
public String remove(Integer id) {
return “modifyService id=” + id;
}
}
2.5 MyAction
Controller的主要功能是负责调度,不做业务实现。业务实现方法全部在Service层,一般我们会将Service实例注入Controller。MyAction中主要实现对IQueryService和IModifyService的调度,统一返回结果:
package com.tom.spring.demo.action;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.tom.spring.demo.service.IModifyService;
import com.tom.spring.demo.service.IQueryService;
import com.tom.spring.formework.annotation.GPAutowired;
import com.tom.spring.formework.annotation.GPController;
import com.tom.spring.formework.annotation.GPRequestMapping;
import com.tom.spring.formework.annotation.GPRequestParam;
import com.tom.spring.formework.webmvc.GPModelAndView;
/**
- 公布接口URL
*/
@GPController
@GPRequestMapping(“/web”)
public class MyAction {
@GPAutowired IQueryService queryService;
@GPAutowired IModifyService modifyService;
@GPRequestMapping(“/query.json”)
public GPModelAndView query(HttpServletRequest request, HttpServletResponse response,
@GPRequestParam(“name”) String name){
String result = queryService.query(name);
return out(response,result);
}
@GPRequestMapping(“/add*.json”)
public GPModelAndView add(HttpServletRequest request,HttpServletResponse response,
@GPRequestParam(“name”) String name,@GPRequestParam(“addr”) String addr){
String result = modifyService.add(name,addr);
return out(response,result);
}
@GPRequestMapping(“/remove.json”)
public GPModelAndView remove(HttpServletRequest request,HttpServletResponse response,
@GPRequestParam(“id”) Integer id){
String result = modifyService.remove(id);
return out(response,result);
}
@GPRequestMapping(“/edit.json”)
public GPModelAndView edit(HttpServletRequest request,HttpServletResponse response,
@GPRequestParam(“id”) Integer id,
@GPRequestParam(“name”) String name){
String result = modifyService.edit(id,name);