30个类手写Spring核心原理之MVC映射功能(4),java基础代码解读

initLocaleResolver(context);//本地化解析

initThemeResolver(context);//主题解析

/** 我们自己会实现 */

//GPHandlerMapping 用来保存Controller中配置的RequestMapping和Method的对应关系

initHandlerMappings(context);//通过HandlerMapping将请求映射到处理器

/** 我们自己会实现 */

//HandlerAdapters 用来动态匹配Method参数,包括类转换、动态赋值

initHandlerAdapters(context);//通过HandlerAdapter进行多类型的参数动态匹配

initHandlerExceptionResolvers(context);//如果执行过程中遇到异常,将交给HandlerExceptionResolver来解析

initRequestToViewNameTranslator(context);//直接将请求解析到视图名

/** 我们自己会实现 */

//通过ViewResolvers实现动态模板的解析

//自己解析一套模板语言

initViewResolvers(context);//通过viewResolver将逻辑视图解析到具体视图实现

initFlashMapManager(context);//Flash映射管理器

}

private void initFlashMapManager(GPApplicationContext context) {}

private void initRequestToViewNameTranslator(GPApplicationContext context) {}

private void initHandlerExceptionResolvers(GPApplicationContext context) {}

private void initThemeResolver(GPApplicationContext context) {}

private void initLocaleResolver(GPApplicationContext context) {}

private void initMultipartResolver(GPApplicationContext context) {}

//将Controller中配置的RequestMapping和Method进行一一对应

private void initHandlerMappings(GPApplicationContext context) {

//按照我们通常的理解应该是一个Map

//Map<String,Method> map;

//map.put(url,Method)

//首先从容器中获取所有的实例

String [] beanNames = context.getBeanDefinitionNames();

try {

for (String beanName : beanNames) {

//到了MVC层,对外提供的方法只有一个getBean()方法

//返回的对象不是BeanWrapper,怎么办?

Object controller = context.getBean(beanName);

//Object controller = GPAopUtils.getTargetObject(proxy);

Class<?> clazz = controller.getClass();

if (!clazz.isAnnotationPresent(GPController.class)) {

continue;

}

String baseUrl = “”;

if (clazz.isAnnotationPresent(GPRequestMapping.class)) {

GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class);

baseUrl = requestMapping.value();

}

//扫描所有的public类型的方法

Method[] methods = clazz.getMethods();

for (Method method : methods) {

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){

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

写在最后

作为一名即将求职的程序员,面对一个可能跟近些年非常不同的 2019 年,你的就业机会和风口会出现在哪里?在这种新环境下,工作应该选择大厂还是小公司?已有几年工作经验的老兵,又应该如何保持和提升自身竞争力,转被动为主动?

就目前大环境来看,跳槽成功的难度比往年高很多。一个明显的感受:今年的面试,无论一面还是二面,都很考验Java程序员的技术功底。

最近我整理了一份复习用的面试题及面试高频的考点题及技术点梳理成一份“Java经典面试问题(含答案解析).pdf和一份网上搜集的“Java程序员面试笔试真题库.pdf”(实际上比预期多花了不少精力),包含分布式架构、高可扩展、高性能、高并发、Jvm性能调优、Spring,MyBatis,Nginx源码分析,Redis,ActiveMQ、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多个知识点高级进阶干货!

由于篇幅有限,为了方便大家观看,这里以图片的形式给大家展示部分的目录和答案截图!

Java经典面试问题(含答案解析)

阿里巴巴技术笔试心得

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-0iPzzvyg-1712598269519)]

写在最后

作为一名即将求职的程序员,面对一个可能跟近些年非常不同的 2019 年,你的就业机会和风口会出现在哪里?在这种新环境下,工作应该选择大厂还是小公司?已有几年工作经验的老兵,又应该如何保持和提升自身竞争力,转被动为主动?

就目前大环境来看,跳槽成功的难度比往年高很多。一个明显的感受:今年的面试,无论一面还是二面,都很考验Java程序员的技术功底。

最近我整理了一份复习用的面试题及面试高频的考点题及技术点梳理成一份“Java经典面试问题(含答案解析).pdf和一份网上搜集的“Java程序员面试笔试真题库.pdf”(实际上比预期多花了不少精力),包含分布式架构、高可扩展、高性能、高并发、Jvm性能调优、Spring,MyBatis,Nginx源码分析,Redis,ActiveMQ、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多个知识点高级进阶干货!

由于篇幅有限,为了方便大家观看,这里以图片的形式给大家展示部分的目录和答案截图!
[外链图片转存中…(img-CvcJe2qI-1712598269519)]

Java经典面试问题(含答案解析)

[外链图片转存中…(img-ZNRgXoHw-1712598269519)]

阿里巴巴技术笔试心得

[外链图片转存中…(img-RwaJoglW-1712598269520)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-3i8RunAv-1712598269520)]

  • 18
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值