java实现mvc模式代码,从零开始实现一个简易的Java MVC框架(九)--优化MVC代码

前言

在从零开始实现一个简易的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 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 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 = 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 pathControllerMap = new ConcurrentHashMap<>();

/**

* bean容器

*/

private BeanContainer beanContainer;

public ControllerHandler() {

beanContainer = BeanContainer.getInstance();

Set> 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 requestParams = getRequestParams(request);

List 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> 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> 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> getMethodParams(Method method) {

Map> 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 getRequestParams(HttpServletRequest request) {

Map 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 instantiateMethodArgs(Map> methodParams, Map 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 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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值