提示:以下重点为部分流程介绍!!!
前言
提示:maven导入jar包和tomcat插件
提示:以下是本篇文章正文内容,下面案例可供参考
一、web.xml
二、执行步骤
tomcat解析web.xml后进入自定义mvc核心类(故事的开始)
核心类实现HttpServlet方法:public void init(ServletConfig config) throws ServletException {},进行初始化配置 代码如下(示例): public void init(ServletConfig config) throws ServletException { System.out.println("tomcat启动,进入HDispatcherServlet的int方法,初始化成功..."); //第一步:HApplicationContext为自定义上下文配置类 context = new HApplicationContext(BootstrapApplication.class); //第五步:加载springmvc的九大组件 initStrategies(context); }
HApplicationContext类: public HApplicationContext(Class<?> applicationClass) { HBeanDefinitionReader hBeanDefinitionReader = new HBeanDefinitionReader(applicationClass); //第二步:拿到第一步中实例化好的beanDefinitions,将beanName与BeanDefinition的关系重新维护到一个Map集合中) List<HBeanDefinition> beanDefinitions = hBeanDefinitionReader.getBeanDefinitions(); //把beanName与BeanDefinition的关系维护起来(本代码中不要这步也可以实现后续逻 辑,但Spring源码中有这一步,顾采用相同和源码方式) doBeanNameDefinition(beanDefinitions); //第三步:添加进IoC容器 doIoC(); //第四步:依赖注入 populateBean(); }
//第五步具体方法
private void initStrategies(HApplicationContext context) {//第六步: 把url与method之间的关系封装成对象,并且保存到list集合中
initHandlerMappings(context);//第七步:根据一个handlerMapping寻找具体的handlerAdapter进行处理
initHandlerAdapters(context);
//第八步: 初始化视图解析器:html freemark xxx等
initViewResolvers(context);
}
第一步后续详解====>>>执行流程
1.构建一个HBeanDefinitionReader类,作用:读取配置文件或者配置类(此处为读取配置类)
2.HBeanDefinitionReader类,执行doConfig (配置类) 方法,判断BootstrapApplication.class是否为一个配置类(利用反射判断类上是否有特定注解),否,则故事结束。
3..判断结果为配置类,拿到配置类下的@HComponentScand的值,执行
replace(".", "/") 拿到需要扫描的路径,执行 例中为:com/mky
.getFile() 获取文件获取文件后,对该文件下的子文件夹进行递归处理,判断最后最后文件是否以.class结尾。是,执行
.replace(".class","") 将所有以.class结尾的文件路径保存在 classNames的List集合中。
4.执行loadBeanDefinitions()方法,用一个循环套反射,判断classNames的List集合中每一 个路径对应的类上,是否有需要springIoC管理的注解配置(如@Controller,@Service 等), 如有,则表示要交给Spring管理)
5.定义一个HBeanDefinition类,储存对应的beanName和bean对应的实例化对象类。
判断4中需要管理的类,注解上是否有显示的定义value,如有则beanName等于该值,如没有,利用反射获取4中需要管理的类的类名(可定义一个工具类,将其改首字母小写)beanName为小写后的类名。(serviceImpl实现的接口别忘记也要处理,后面依赖注入时,要使用)。
bean的实例化对象可直接通过反射直接获取。将beanName和bean对应的实例化对象,赋值给一个HBeanDefinition对象,将每个HBeanDefinition对象用一个List集合(List<HBeanDefinition> beanDefinitions)管理起来。
启动类代码码如下(示例):
@HConfiguration // 表示我当前就是一个配置类
@HComponentScan(basePackage = "com.mky")
public class BootstrapApplication {
public static void main(String[] args) {
// 类比springboot 可将 apache tomcat启动的源码放到当前位置
}
}
第二步后续详解====>>>执行流程
定义一个Map<String, HBeanDefinition> beanDefinitionMap集合,遍历参数beanDefinitions, 利用 beanDefinitionMap.put(beanDefinition.getBeanName(),beanDefinition);将二者关系管理 到beanDefinitionMap集合中。
第三步后续详解====>>>执行流程
1.遍历第二步中实例化好的beanDefinitionMap集合,通过方法 .getValue()拿到 hBeanDefinition 对象,通过方法(默认返回false,表示非懒加载).isLazyInit() 判断是否懒 加 载。(不是懒加载,就表示要随着Spring启动而实例化)
2.自定义IoC缓存容器
// IoC容器[缓存]: 原生 key:beanName Object:instance clazz.newInstance
private Map<String, Object> beanOriginCache = new ConcurrentHashMap<>();
// IoC容器[缓存]: key value:instance[wrapper] 这里的Object就是包装后的bean
二级缓存
private Map<String, Object> beanWrapperCache = new ConcurrentHashMap<>();
// IoC容器[缓存]: key value:instance[proxy] 这里的Object就是通过动态代理创建出来的动态代理对象
private Map<String, Object> beanProxyCache = new ConcurrentHashMap<>();
3.获取beanDefinitionMap的key,判断IoC容器:beanOriginCache中是否已经存在。
4.不存在,beanOriginCache.put(beanName,instance);参数均可通过beanDefinitionMap加反 射获取。instance为实例化的对象。到此原生的bean注入成功。
5.包装一下bean。创建一个HBeanWrapper 类,用于封装4中得到的instance(实例化对象)和 实例化对象对应的类。
6.将5中封装好的实例放到oC缓存中 beanWrapperCache.put(beanName,beanWrapper);
第四步后续详解====>>>执行流程( 至此IoC完成)
1.通过方法 beanOriginCache.isEmpty() 判断IoC容器是否有值。
2.遍历 beanOriginCache容器,判断每一个有IoC管理的类下字段上(遍历一个类下所有字段) 是否有IoC注入的相关注解(如@Autowired)
3.如果有,判断该注解的value是否显示配置,有则:beanName为该值,没有则默认beanName 为字段类型的首字母小写值。
4.执行方法:字段.setAccessible(true); 反之私有情况,注入失败,
5.执行方法进行注入:字段.set(字段所在的类, 要注入的实例化对象)
(开始MVC了)!!!
核心类实现HttpServlet doGet和doPost方法:
@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) {
e.printStackTrace();
}
}
第五步后续详解====>>>执行流程(开始MVC)
科普:springmvc九大组件为:
HandlerMapping、HandlerAdapter、handlerExceptionResolver、ViewResolver、RequestToViewNameTranslator、 ThemResolver、FlasMapManager。(此处我们仅选择部分重构)
1.执行方法:private void initStrategies(HApplicationContext context){};进行九大组件配置;
第六步后续详解====>>>执行流程
1.创建一个HHandlerMapping对象,作用:将请求url (服务器默认设置以外部分,如:user/test), url 对应执行的方法,以及方法所在的类的实例(即Controller类的实例)封装在HHandlerMapping对象中。
2.通过方法:context.getBeanDefinitionCount(),判断第二步中 beanDefinitionMap集合是否 为空,如为空,则说明IoC容器为空,故事结束。
3.如不为空,则通过方法:context.getBeanNames()获取 beanDefinitionMap集合中所有key 值的数组(beanName)
4.遍历该数组,通过方法:context.getBean(beanName)获取 beanName对应的实例化对象 ( 方法中传入的beanName,与方法三中beanDefinitionMap (IoC)缓存容器中beanName匹配,即可拿到beanDefinitionMap中缓存的对应实例化对象)
5.通过方法:实例化对象.getClass() 拿到对应的类,判断类上是否有@RequestMapping注解,有,拿到value值;在遍历类中所有方法,判断方法上是否有@RequestMapping注解,有,拿到value值,和类上的value值拼接。
6.将5中得到的拼接后的值、对应方法、4中获取到的实例化对象封装到 hHandlerMapping 对象中,将遍历得到的每一个该对象,添加到 List<HHandlerMapping> handlerMappings 集合中。
第七步后续详解====>>>执行流程
1.创建一个HHandlerAdapter对象
2.遍历第七步中的,handlerMappings集合,执行方法handlerAdpaters.put(handlerMapping,new HHandlerAdapter()) 将handlerMappings与一个 对应的具体 hHandlerAdapte对象,添加到 Map<HHandlerMapping, HHandlerAdapter> handlerAdpaters集合中。
第八步后续详解====>>>执行流程
1.解析配置文件或者配置类,拿到视图在那个文件夹下。执行 this.getClass().getClassLoader().getResource(拿到的文件夹路径).getFile(),获取文件 路径,执行 viewResolver = new HViewResolver(file) 创建一个视图解析器viewResolver。
前端开始发送请求,执行doPost中的 doDispatch(req, resp)方法!!
第九步(处理请求)后续详解====>>>执行流程
1.执行 req.getRequestURI().replaceAll("/+", "/") 拿到请求中,项目路径后面的部分url.
2.写一个方法,作用:遍历第六步中初始化好的handlerMappings,判断其中是否有相同的url, 如没有,返回null, 有,则返回对应的handlerMappings 对象。
3.判断返回结果,是否为空。如为空,将视图名:404.html 传给步骤八中的视图解析器;
4.视图解析器,中构建一个方法,判断传入的视图名是否以.html结尾(此处不做否情况详解 ,均默认跳转到.html,如果不是,尾部默认拼接上.html);拿到步骤八中 viewResolver = new HViewResolver(file),传递的视图所在文件夹 file,执行file.getPath()+"/"+viewName).replaceAll("/+","/");拿到目标视图的路径.
5.构建一个HView,表示视图,创建一个hView对象,将4中获取的路径封装到hView中。
6.执行 hView.render(null, req, resp) 方法:对视图经行渲染(渲染代码在文章末尾给出,此处 不做过多讲解),返回给浏览器。
回到3中另一种情况!!!
1.如果返回结果非空。通过方法:handlerAdapter=getHandlerAdapter(handlerMapping),遍历步骤七中handlerAdpaters集合,对应的handlerAdapter实例对象
2. handlerAdapter实例对象执行.handle(req,resp,handlerMapping)方法(该方法的主要目的为 利用反射执行url对应目标方法,以及拿到方法返回类型)。
3.handle(req,resp,handlerMapping)方法中,通过handlerMapping拿到url对应需要执行方法
,方法所在类,以及所需参数(对参数赋值需要小心参数对应位置信息,此处不详述),执行 Object returnValue = method.invoke(obj, params),执行方法并拿到对应返回值。
4.构建一个类HModelAndView 作用封装返回的页面名称和携带数据。
5.判断3中的返回类型是否为HModelAndView类型,如过不是返回null (此处没做处理,可自己 丰富【可参考实现@ResponseBody】)
6.返回HModelAndView结果,执行方法:modelAndView.getViewName()拿到对应视图名,执行方法:Map<String, ?> model = modelAndView.getModel();拿到所要携带的参数,传给步骤八中的视图解析器;
同上逻辑!!!
7.视图解析器,中构建一个方法,判断传入的视图名是否以.html结尾(此处不做否情况详解 ,均默认跳转到.html,如果不是,尾部默认拼接上.html);拿到步骤八中 viewResolver = new HViewResolver(file),传递的视图所在文件夹 file,执行 file.getPath()+"/"+viewName).replaceAll("/+","/");拿到目标视图的路径.
8.构建一个HView,表示视图,创建一个view对象,将4中获取的路径封装到hView中。
9.执行view.render(model,req,resp)对视图经行渲染,返回给浏览器。
视图渲染代码
本例中为将视图中对应 !{}【参考;${}】部分替换成实际的值。
// TODO: 把结果输出到浏览器+把model中的key,value替换.html中的占位符
/*
<b>name: !{name}</b> <br/>
<b>age: !{age}</b> <br/>
<b>hobby: !{hobby}</b>
*/
public void render(Map<String, ?> model, HttpServletRequest req, HttpServletResponse resp) {
try {
StringBuffer sb = new StringBuffer();
RandomAccessFile ra = new RandomAccessFile(this.file, "r");
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()) {
// !{name}
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);
}
resp.setCharacterEncoding("utf-8");
resp.getWriter().write(sb.toString());
}catch (Exception e){
e.printStackTrace();
}
}
private String makeStringForRegExp(String str) {
return str.replace("\\", "\\\\").replace("*", "\\*")
.replace("+", "\\+").replace("|", "\\|")
.replace("{", "\\{").replace("}", "\\}")
.replace("(", "\\(").replace(")", "\\)")
.replace("^", "\\^").replace("$", "\\$")
.replace("[", "\\[").replace("]", "\\]")
.replace("?", "\\?").replace(",", "\\,")
.replace(".", "\\.").replace("&", "\\&");
}
总结
AoP部分正在开始码.....。本文仅为简单实现流程,实际Spring的使用,还有很多情况和细节。上面讲述的不太好,仅为个人的思路,可能看着很空洞,难懂。如果大家感兴趣可去研究spring源码。
当然,对该手写感兴趣的朋友,可加QQ:2722173496 免费提供该手写和AOP手写源码供大家参考!!! 以上如有错误,也欢迎大家在评论指出。 编程的学习是一个漫长的过程,建议大家多面向各类API、百度编程,切勿照搬讲解。