前言
在从零开始实现一个简易的Java MVC框架(七)–实现MVC中实现了doodle框架的MVC的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。
优化的目标是1.去除DispatcherServlet
请求分发器中的http逻辑代码;2.将ControllerHandler
和ResultRender
中代码按功能细分出来,使其各司其职。
修改DispatcherServlet
创建接口
先在com.zbw.mvc包下创建两个包handler和render,分别用于放ControllerHandler
和ResultRender
拆分出来的功能类。
再在这两个包下创建两个接口,以便后面的功能类都按这个接口规范。
package com.zbw.mvc.handler;
import com.zbw.mvc.RequestHandlerChain;
/**
* 请求执行器 Handler
*/
public interface Handler {
/**
* 请求的执行器
*/
boolean handle(final RequestHandlerChain handlerChain) throws Exception;
}
package com.zbw.mvc.render;
import com.zbw.mvc.RequestHandlerChain;
/**
* 渲染请求结果 interface
*/
public interface Render {
/**
* 执行渲染
*/
void render(RequestHandlerChain handlerChain) throws Exception;
}
实现RequestHandlerChain
上面两个接口都有个参数RequestHandlerChain
,这个类是整个请求的执行链,用于存储整个请求需要保存的一些属性和串联整个请求。
在com.zbw.mvc下创建这个类
package com.zbw.mvc;
import ...
/**
* http请求处理链
*/
@Data
@Slf4j
public class RequestHandlerChain {
/**
* Handler迭代器
*/
private Iterator<Handler> handlerIt;
/**
* 请求request
*/
private HttpServletRequest request;
/**
* 请求response
*/
private HttpServletResponse response;
/**
* 请求http方法
*/
private String requestMethod;
/**
* 请求http路径
*/
private String requestPath;
/**
* 请求状态码
*/
private int responseStatus;
/**
* 请求结果处理器
*/
private Render render;
public RequestHandlerChain(Iterator<Handler> handlerIt, HttpServletRequest request, HttpServletResponse response) {
this.handlerIt = handlerIt;
this.request = request;
this.response = response;
this.requestMethod = request.getMethod();
this.requestPath = request.getPathInfo();
this.responseStatus = HttpServletResponse.SC_OK;
}
/**
* 执行请求链
*/
public void doHandlerChain() {
try {
while (handlerIt.hasNext()) {
if (!handlerIt.next().handle(this)) {
break;
}
}
} catch (Exception e) {
log.error("doHandlerChain error", e);
render = new InternalErrorRender();
}
}
/**
* 执行处理器
*/
public void doRender() {
if (null == render) {
render = new DefaultRender();
}
try {
render.render(this);
} catch (Exception e) {
log.error("doRender", e);
throw new RuntimeException(e);
}
}
}
在这个类中除了存储http请求信息以外,还有Handler迭代器handlerIt
和请求结果处理器Render
。
doHandlerChain()
方法就会迭代执行handlerIt
中的Handler的handle()
方法,并且会根据每个Handler返回的值来判断是否继续往下执行下一个Handler。
doRender()
方法用于调用Render中的render()
方法。
更改DispatcherServlet
接下来就可以修改DispatcherServlet
请求转发器了。
package com.zbw.mvc;
import ...
/**
* DispatcherServlet 所有http请求都由此Servlet转发
*/
@Slf4j
public class DispatcherServlet extends HttpServlet {
/**
* 请求执行链
*/
private final List<Handler> HANDLER = new ArrayList<>();
/**
* 执行请求
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
RequestHandlerChain handlerChain = new RequestHandlerChain(HANDLER.iterator(), req, resp);
handlerChain.doHandlerChain();
handlerChain.doRender();
}
}
可以看到现在DispatcherServlet
已经很简洁了,把请求的逻辑代码交给RequestHandlerChain
处理,自己没有多余的http逻辑代码。
实现几种Handler
上面只创建了Handler的接口没有实现类,现在就实现几个Handler的实现类。这些实现类只实现了简单的一些http请求的功能,大家可以自己根据情况开发更多的实现类。
PreRequestHandler
首先是PreRequestHandler
,用于预处理http的一些信息,比如设置http编码,处理请求url,打印一些信息等。
package com.zbw.mvc.handler;
import ...
/**
* 请求预处理
*/
@Slf4j
public class PreRequestHandler implements Handler {
@Override
public boolean handle(final RequestHandlerChain handlerChain) throws Exception {
// 设置请求编码方式
handlerChain.getRequest().setCharacterEncoding("UTF-8");
String requestPath = handlerChain.getRequestPath();
if (requestPath.length() > 1 && requestPath.endsWith("/")) {
handlerChain.setRequestPath(requestPath.substring(0, requestPath.length() - 1));
}
log.info("[Doodle] {} {}", handlerChain.getRequestMethod(), handlerChain.getRequestPath());
return true;
}
}
SimpleUrlHandler
接下来是SimpleUrlHandler
,用于处理静态资源,当碰到资源是静态资源时就直接转发请求到Tomcat默认的servlet去。
package com.zbw.mvc.handler;
import ...
/**
* 普通url请求执行
* 主要处理静态资源
*/
@Slf4j
public class SimpleUrlHandler implements Handler {
/**
* tomcat默认RequestDispatcher的名称
* TODO: 其他服务器默认的RequestDispatcher.如WebLogic为FileServlet
*/
private static final String TOMCAT_DEFAULT_SERVLET = "default";
/**
* 默认的RequestDispatcher,处理静态资源
*/
private RequestDispatcher defaultServlet;
public SimpleUrlHandler(ServletContext servletContext) {
defaultServlet = servletContext.getNamedDispatcher(TOMCAT_DEFAULT_SERVLET);
if (null == defaultServlet) {
throw new RuntimeException("没有默认的Servlet");
}
log.info("The default servlet for serving static resource is [{}]", TOMCAT_DEFAULT_SERVLET);
}
@Override
public boolean handle(final RequestHandlerChain handlerChain) throws Exception {
if (isStaticResource(handlerChain.getRequestPath())) {
defaultServlet.forward(handlerChain.getRequest(), handlerChain.getResponse());
return false;
}
return true;
}
/**
* 是否为静态资源
*/
private boolean isStaticResource(String url) {
return url.startsWith(Doodle.getConfiguration().getAssetPath());
}
}
JspHandler
然后是处理jsp页面的实现类JspHandler
,当碰到资源是jsp页面时就直接转发请求到Tomcat的jsp的servlet去。
package com.zbw.mvc.handler;
import ...
/**
* jsp请求处理
* 主要负责jsp资源请求
*/
public class JspHandler implements Handler {
/**
* jsp请求的RequestDispatcher的名称
*/
private static final String JSP_SERVLET = "jsp";
/**
* jsp的RequestDispatcher,处理jsp资源
*/
private RequestDispatcher jspServlet;
public JspHandler(ServletContext servletContext) {
jspServlet = servletContext.getNamedDispatcher(JSP_SERVLET);
if (null == jspServlet) {
throw new RuntimeException("没有jsp Servlet");
}
}
@Override
public boolean handle(final RequestHandlerChain handlerChain) throws Exception {
if (isPageView(handlerChain.getRequestPath())) {
jspServlet.forward(handlerChain.getRequest(), handlerChain.getResponse());
return false;
}
return true;
}
/**
* 是否为jsp资源
*/
private boolean isPageView(String url) {
return url.startsWith(Doodle.getConfiguration().getViewPath());
}
}
ControllerHandler
最后就是ControllerHandler
,这个和从零开始实现一个简易的Java MVC框架(七)–实现MVC中的ControllerHandler功能一样,用于处理请求中数据和controller对应的关系。
package com.zbw.mvc.handler;
import ...
/**
* Controller请求处理
*/
@Slf4j
public class ControllerHandler implements Handler {
/**
* 请求信息和controller信息关系map
*/
private Map<PathInfo, ControllerInfo> pathControllerMap = new ConcurrentHashMap<>();
/**
* bean容器
*/
private BeanContainer beanContainer;
public ControllerHandler() {
beanContainer = BeanContainer.getInstance();
Set<Class<?>> mappingSet = beanContainer.getClassesByAnnotation(RequestMapping.class);
this.initPathControllerMap(mappingSet);
}
@Override
public boolean handle(final RequestHandlerChain handlerChain) throws Exception {
String method = handlerChain.getRequestMethod();
String path = handlerChain.getRequestPath();
ControllerInfo controllerInfo = pathControllerMap.get(new PathInfo(method, path));
if (null == controllerInfo) {
handlerChain.setRender(new NotFoundRender());
return false;
}
Object result = invokeController(controllerInfo, handlerChain.getRequest());
setRender(result, controllerInfo, handlerChain);
return true;
}
/**
* 执行controller方法
*/
private Object invokeController(ControllerInfo controllerInfo, HttpServletRequest request) {
Map<String, String> requestParams = getRequestParams(request);
List<Object> methodParams = instantiateMethodArgs(controllerInfo.getMethodParameter(), requestParams);
Object controller = beanContainer.getBean(controllerInfo.getControllerClass());
Method invokeMethod = controllerInfo.getInvokeMethod();
invokeMethod.setAccessible(true);
Object result;
try {
if (methodParams.size() == 0) {
result = invokeMethod.invoke(controller);
} else {
result = invokeMethod.invoke(controller, methodParams.toArray());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}
/**
* 设置请求结果执行器
*/
private void setRender(Object result, ControllerInfo controllerInfo, RequestHandlerChain handlerChain) {
if (null == result) {
return;
}
Render render;
boolean isJson = controllerInfo.getInvokeMethod().isAnnotationPresent(ResponseBody.class);
if (isJson) {
render = new JsonRender(result);
} else {
render = new ViewRender(result);
}
handlerChain.setRender(render);
}
/**
* 初始化pathControllerMap
*/
private void initPathControllerMap(Set<Class<?>> mappingSet) {
mappingSet.forEach(this::addPathController);
}
/**
* 添加controllerInfo到pathControllerMap中
*/
private void addPathController(Class<?> clz) {
RequestMapping requestMapping = clz.getAnnotation(RequestMapping.class);
String basePath = requestMapping.value();
if (!basePath.startsWith("/")) {
basePath = "/" + basePath;
}
for (Method method : clz.getDeclaredMethods()) {
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping methodRequest = method.getAnnotation(RequestMapping.class);
String methodPath = methodRequest.value();
if (!methodPath.startsWith("/")) {
methodPath = "/" + methodPath;
}
String url = basePath + methodPath;
Map<String, Class<?>> methodParams = this.getMethodParams(method);
String httpMethod = String.valueOf(methodRequest.method());
PathInfo pathInfo = new PathInfo(httpMethod, url);
if (pathControllerMap.containsKey(pathInfo)) {
log.warn("url:{} 重复注册", pathInfo.getHttpPath());
}
ControllerInfo controllerInfo = new ControllerInfo(clz, method, methodParams);
this.pathControllerMap.put(pathInfo, controllerInfo);
log.info("mapped:[{},method=[{}]] controller:[{}@{}]",
pathInfo.getHttpPath(), pathInfo.getHttpMethod(),
controllerInfo.getControllerClass().getName(), controllerInfo.getInvokeMethod().getName());
}
}
}
/**
* 获取执行方法的参数
*/
private Map<String, Class<?>> getMethodParams(Method method) {
Map<String, Class<?>> map = new HashMap<>();
for (Parameter parameter : method.getParameters()) {
RequestParam param = parameter.getAnnotation(RequestParam.class);
// TODO: 不使用注解匹配参数名字
if (null == param) {
throw new RuntimeException("必须有RequestParam指定的参数名");
}
map.put(param.value(), parameter.getType());
}
return map;
}
/**
* 获取HttpServletRequest中的参数
*/
private Map<String, String> getRequestParams(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
//GET和POST方法是这样获取请求参数的
request.getParameterMap().forEach((paramName, paramsValues) -> {
if (ValidateUtil.isNotEmpty(paramsValues)) {
paramMap.put(paramName, paramsValues[0]);
}
});
// TODO: Body、Path、Header等方式的请求参数获取
return paramMap;
}
/**
* 实例化方法参数
*/
private List<Object> instantiateMethodArgs(Map<String, Class<?>> methodParams, Map<String, String> requestParams) {
return methodParams.keySet().stream().map(paramName -> {
Class<?> type = methodParams.get(paramName);
String requestValue = requestParams.get(paramName);
Object value;
if (null == requestValue) {
value = CastUtil.primitiveNull(type);
} else {
value = CastUtil.convert(type, requestValue);
// TODO: 实现非原生类的参数实例化
}
return value;
}).collect(Collectors.toList());
}
}
初始化HANDLER列表和去除TomcatServer
的多余代码
刚才实现的几个HANDLER还需要初始化,就在DispatcherServlet
的init()
方法中初始化。注意初始化的顺序会决定其在RequestHandlerChain
执行链中执行的先后。
...
@Slf4j
public class DispatcherServlet extends HttpServlet {
...
/**
* 初始化Servlet
*/
@Override
public void init() throws ServletException {
HANDLER.add(new PreRequestHandler());
HANDLER.add(new SimpleUrlHandler(getServletContext()));
HANDLER.add(new JspHandler(getServletContext()));
HANDLER.add(new ControllerHandler());
}
...
}
然后去除TomcatServer
中JspServlet
和DefaultServlet
两个servlet的初始化,因为已经在 JspHandler
和SimpleUrlHandler
中初始化了这两个servlet。
...
@Slf4j
public class TomcatServer implements Server {
...
public TomcatServer(Configuration configuration) {
try {
this.tomcat = new Tomcat();
tomcat.setBaseDir(configuration.getDocBase());
tomcat.setPort(configuration.getServerPort());
File root = getRootFolder();
File webContentFolder = new File(root.getAbsolutePath(), configuration.getResourcePath());
if (!webContentFolder.exists()) {
webContentFolder = Files.createTempDirectory("default-doc-base").toFile();
}
log.info("Tomcat:configuring app with basedir: [{}]", webContentFolder.getAbsolutePath());
StandardContext ctx = (StandardContext) tomcat.addWebapp(configuration.getContextPath(), webContentFolder.getAbsolutePath());
ctx.setParentClassLoader(this.getClass().getClassLoader());
WebResourceRoot resources = new StandardRoot(ctx);
ctx.setResources(resources);
// 去除了JspHandler和SimpleUrlHandler这两个servlet的注册
tomcat.addServlet(configuration.getContextPath(), "dispatcherServlet", new DispatcherServlet()).setLoadOnStartup(0);
ctx.addServletMappingDecoded("/*", "dispatcherServlet");
} catch (Exception e) {
log.error("初始化Tomcat失败", e);
throw new RuntimeException(e);
}
}
}
实现几种Render
上面创建的Render接口也需要一些实现类。同样的,这些Render也只是实现基本的功能 ,大家可以自己根据情况开发更多。
DefaultRender
这个是默认的Render,设置HttpServletResponse中的status为RequestHandlerChain
中StatusCode。
package com.zbw.mvc.render;
import ...
/**
* 默认渲染 200
*/
public class DefaultRender implements Render {
@Override
public void render(RequestHandlerChain handlerChain) throws Exception {
int status = handlerChain.getResponseStatus();
handlerChain.getResponse().setStatus(status);
}
}
InternalErrorRender
这个Render返回StatusCode为500
package com.zbw.mvc.render;
import ...
/**
* 渲染500
*/
public class InternalErrorRender implements Render {
@Override
public void render(RequestHandlerChain handlerChain) throws Exception {
handlerChain.getResponse().sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
NotFoundRender
这个Render返回StatusCode为404
package com.zbw.mvc.render;
import ...
/**
* 渲染404
*/
public class NotFoundRender implements Render {
@Override
public void render(RequestHandlerChain handlerChain) throws Exception {
handlerChain.getResponse().sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
JsonRender
这个Render返回json数据,当Handler请求发现返回数据为json格式时,就用这个Render
package com.zbw.mvc.render;
import ...
/**
* 渲染json
*/
@Slf4j
public class JsonRender implements Render {
private Object jsonData;
public JsonRender(Object jsonData) {
this.jsonData = jsonData;
}
@Override
public void render(RequestHandlerChain handlerChain) throws Exception {
// 设置响应头
handlerChain.getResponse().setContentType("application/json");
handlerChain.getResponse().setCharacterEncoding("UTF-8");
// 向响应中写入数据
try (PrintWriter writer = handlerChain.getResponse().getWriter()) {
writer.write(JSON.toJSONString(jsonData));
writer.flush();
}
}
}
ViewRender
这个Render跳转到页面,将ModelAndView
中的信息存到HttpServletRequest中并跳转到对应页面
package com.zbw.mvc.render;
import ...
/**
* 渲染页面
*/
@Slf4j
public class ViewRender implements Render {
private ModelAndView mv;
public ViewRender(Object mv) {
if (mv instanceof ModelAndView) {
this.mv = (ModelAndView) mv;
} else if (mv instanceof String) {
this.mv = new ModelAndView().setView((String) mv);
} else {
throw new RuntimeException("返回类型不合法");
}
}
@Override
public void render(RequestHandlerChain handlerChain) throws Exception {
HttpServletRequest req = handlerChain.getRequest();
HttpServletResponse resp = handlerChain.getResponse();
String path = mv.getView();
Map<String, Object> model = mv.getModel();
model.forEach(req::setAttribute);
req.getRequestDispatcher(Doodle.getConfiguration().getViewPath() + path).forward(req, resp);
}
}
结语
至此,MVC的优化完成了,同时整个doodle框架的代码也算是完成了。
虽然doodle早已完成,但是讲解的文章托托延延到现在才完成。
在刚完成doodle时感觉整个框架已经成型了,但是在写这个系列文章的过程中才真正发现欠缺的还有非常非常多,甚至觉得把它称为框架都有些抬举它了呢。
只能说在实现它然后再写这个系列的文章之后对spring的崇拜之心更加深了,其被javaer广泛使用和拜读果然是有原因的。
另外也感谢大家阅读这个系列的文章,如果对大家有所帮助的话可以去给我的项目加个star,有什么问题和建议也可以提出来交流交流。
这个系列的所有文章我都放在我的博客上了:http://zzzzbw.cn/
源码地址:doodle