• 原来浏览器发送请求到服务器
      服务器接收请求
      参考web.xml 或 @WebServlet 找到请求与controller之间的映射关系
      现在浏览器发送请求到服务器
      服务器接收请求
      将请求交给web框架
      框架根据一些请求映射关系,找到与之匹配的controller实现映调用。
      正因为服务器现在将请求交给了web框架
      所以才能在框架中完成一些常用功能的封装
      1. 多个请求,可以访问同一个controller对象的不同方法 (请求映射)。
      2. 框架负责接收参数(普通参数,文件参数)
      3. 框架负责传递参数 (类型转换)
      【具体业务实现 - 业务程序实现】
      4. 框架负责响应
      直接响应
      间接响应
      转发(携带数据)
      重定向

      编码实现
      1. DispatcherServlet
      按照上面的分析
      有了web框架以后
      浏览器给服务器的请求,服务器会交给框架
      反之也就是说框架可以接收服务器的请求
      从语法而言,可以接收服务器请求的组件:Servlet , Filter
      所以框架需要一个请求入口
      2. 请求映射的配置
      现在请求都交给了框架
      所以需要框架实现请求映射
      哪个请求对应哪个controller的方法需要业务程序员按照框架的要求配置
      暂时有2种规范
      第1种,在xml文件中配置映射关系
      在web.xml中配置DispatcherServlet时
      通过设置servlet的参数指定框架的配置文件名称(默认要求配置文件必须放在src目录下)

      mvc
      org.duyi.web7.DispatcherServlet

      classpath
      mvc.xml


      认为规定xml中的映射关系配置如下:
      <?xml version=”1.0” ?>




      int


      java.lang.String
      com.domain.Car



      第2种,在注解中配置映射关系
      框架自定义@RequestMapping
      该注解作用controller的方法上,表示请求与方法的映射关系
      @RequestMapping(“/test2”)
      public void t2(String s , Car car){}
      之前可以从xml文件中获得请求映射关系
      现在需要从注解中获得映射关系
      要想找到注解,就需要找到方法,需要找到controller类
      为了更方便的找到controller类
      1. 在web.xml文件中,指定寻找controller的基本包位置

      controller-scan
      com.controller,com.controller2

      2. 框架自定义@Controller
      该注解作用在controller类上
      具有该注解的controller类,才会通过反射寻找RequestMapping
      3. 初始化映射关系
      有了框架以后
      所有请求就会交给框架
      框架会根据映射关系,最终调用controller的指定方法
      映射关系可以使用xml配置,也可以使用注解配置
      每次请求映射时都需要读取一次配置信息么?
      当然不需要
      只需在服务器启动时读取一次,并存储缓存即可
      static{}
      servlet.init
      配置信息来自于2部分
      1. 配置文件
      目前只有mapping请求映射信息
      2. 注解
      ====================================================================================================
      3. 初始化映射关系
      有了框架以后
      所有请求就会交给框架
      框架会根据映射关系,最终调用controller的指定方法
      映射关系可以使用xml配置,也可以使用注解配置
      每次请求映射时都需要读取一次配置信息么?
      当然不需要
      只需在服务器启动时读取一次,并存储缓存即可
      static{}
      servlet.init
      配置信息来自于2部分
      1. 配置文件
      目前只有mapping请求映射信息
      2. 注解
      根据web.xml配置的包路径 com.controller
      获得对应的文件夹相对路径 com/controller
      配合Thread.currentThread()…. 获得文件夹的绝对路径
      创建File对象表示文件夹dir
      获得文件夹中所有子内容名字(包括class文件名字)
      获得类名(去掉.class后缀)
      重新包装成完成的类路径 com.controller.TestController
      反射获得类注解
      反射获得类方法
      反射获得方法注解
      获得请求映射的信息:
      @RequestMapping(“/test”) -> path
      反射创建controller对象
      Method
      4. 映射请求
      根据浏览器发送的请求,找到与之匹配的请求映射信息MappingInfo(目标对象,目标方法)
      浏览器请求至服务器,服务器参考web.xml,将请求交给框架,调用框架的service方法
      获得此次请求
      String uri = req.getRequestURI() ;// /web-demo/test1
      String root = req.getContextPath() ;// /web-demo
      String path = uri.replace(root,””); // /test1
      根据请求找到与之匹配的映射关系
      如果没有找到映射关系
      表示不是一个操作资源
      有可能是一个文件资源
      使用io流读取文件并响应给浏览器
      注意:找到资源文件的服务器路径
      //path =”F:\dmc\idea_workspace\web-demo\out\artifacts\web_demo_war_exploded\index.html”
      String fpath = req.getServletContext().getRealPath(path);
      如果找到映射关系(待续。。)
      获取参数
      处理参数
      调用目标对象的方法,传递参数,完成请求映射过程。 ```java package org.duyi.web7;

    import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.duyi.web7.annotation.Controller; import org.duyi.web7.annotation.RequestMapping;

    import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map;

    public class DispatcherServlet extends HttpServlet {

    1. /*
    2. 缓存请求映射信息
    3. key = "/test1"
    4. value = mappingInfo{目标对象,目标方法}
    5. */
    6. Map<String,MappingInfo> mappingInfoMap = new HashMap<>();
    7. /*
    8. 单实例存储controller对象
    9. key="com.controller.TestController"
    10. value= new Object
    11. */
    12. Map<String,Object> controllerMap = new HashMap<>();
    13. /**
    14. * 读取各种配置信息
    15. * (目前只有请求映射信息)
    16. * @throws ServletException
    17. */
    18. @Override
    19. public void init() throws ServletException {
    20. //目前配置信息来自于两个部分,而且两个部分都可能存在
    21. String xmlPath = super.getInitParameter("classpath");
    22. if(xmlPath != null && !"".equals(xmlPath)){
    23. //指定了配置文件,需要读取配置文件
    24. readXml(xmlPath);
    25. }
    26. String packagePath = super.getInitParameter("controller-scan") ;
    27. if(packagePath != null && !"".equals(packagePath)){
    28. try {
    29. //指定了包路径,需要读取包下类中的注解信息
    30. readAnnotation(packagePath);
    31. } catch (ClassNotFoundException e) {
    32. e.printStackTrace();
    33. } catch (IllegalAccessException e) {
    34. e.printStackTrace();
    35. } catch (InstantiationException e) {
    36. e.printStackTrace();
    37. }
    38. }
    39. }
    40. //读取xml配置文件
    41. public void readXml(String path){
    42. //path="mvc.xml"
    43. //D:\ideaworkspace2\web-demo\out\artifacts\web_demo_war_exploded\WEB-INF\classes\mvc.xml
    44. path = Thread.currentThread().getContextClassLoader()
    45. .getResource(path).getPath();
    46. try {
    47. InputStream is = new FileInputStream(path) ;
    48. //使用dom,sax读取xml内容
    49. SAXReader reader = new SAXReader() ;
    50. Document document = reader.read(is) ;
    51. //解析mapping标签
    52. parseMappingElement(document);
    53. //解析其他标签
    54. } catch (FileNotFoundException e) {
    55. e.printStackTrace();
    56. } catch (DocumentException e) {
    57. e.printStackTrace();
    58. } catch (IllegalAccessException e) {
    59. e.printStackTrace();
    60. } catch (InstantiationException e) {
    61. e.printStackTrace();
    62. } catch (ClassNotFoundException e) {
    63. e.printStackTrace();
    64. } catch (NoSuchMethodException e) {
    65. e.printStackTrace();
    66. }
    67. }
    68. //读取xml中<mapping>请求映射信息
    69. private void parseMappingElement(Document document) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException {
    70. //按照标签的嵌套规则,找到需要的所有mapping标记
    71. List<Element> mappingElements = document.selectNodes("mvc/mapping") ;
    72. for(Element mappingElement : mappingElements ){
    73. /*
    74. 获得标签指定的属性值
    75. 标签中记载着请求与响应对应关系
    76. 此时(初始化)获得这个对应关系,不是次使用
    77. 后面请求的时候用
    78. 所以需要存储起来,组成对象
    79. */
    80. String path = mappingElement.attributeValue("path") ;
    81. String classname = mappingElement.attributeValue("class") ; // com.controller.TestController
    82. String methodname = mappingElement.attributeValue("method") ; // t1
    83. //反射创建目标对象
    84. Class clazz = Class.forName(classname) ;
    85. Object controller = getSingleController(classname);
    86. //通过反射,根据方法名获得方法对象 t1() , t2(int , String)
    87. //获得mapping标签的子标签<type> 存储目标方法对应的参数列表
    88. List<Element> typeElements = mappingElement.selectNodes("type") ;
    89. //创建一个与配置文件中<type>数量相同的Class数组,准备装载目标方法对应的参数列表类型
    90. Class[] types = new Class[typeElements.size()] ;
    91. int i = 0 ;
    92. for(Element typeElement : typeElements){
    93. //<type> 获得标签的文本内容 "int"
    94. String typeStr = typeElement.getText() ;
    95. //"int" ->int.class
    96. types[i++] = castStringToClass(typeStr) ;
    97. }
    98. Method method = clazz.getMethod(methodname,types ) ;
    99. MappingInfo info = new MappingInfo(path,controller,method) ;
    100. mappingInfoMap.put(path,info) ;
    101. }
    102. }
    103. /*
    104. * 将字符串表示的类型转换成对应的Class类型
    105. * "int" -> int.class
    106. * "java.lang.String" -> String.class -> Class.forName("java.lang.String")
    107. */
    108. private Class castStringToClass(String typeStr) throws ClassNotFoundException {
    109. if("int".equals(typeStr)){
    110. return int.class;
    111. }
    112. if("long".equals(typeStr)){
    113. return long.class;
    114. }
    115. if("double".equals(typeStr)){
    116. return double.class;
    117. }
    118. //除去8种基本类型,以外的都是引用类型,写的都是全名,反射生成Class , java.lang.Integer
    119. return Class.forName(typeStr) ;
    120. }
    121. public Object getSingleController(String classname) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    122. Class clazz = Class.forName(classname);
    123. Object controller = controllerMap.get(classname);
    124. if(controller == null){
    125. controller = clazz.newInstance() ;
    126. controllerMap.put(classname,controller);
    127. }
    128. return controller ;
    129. }
    130. //读取注解
    131. public void readAnnotation(String packagePath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    132. //packagePath = "com.controller,com.controller2,.."
    133. //需要框架从指定的2个包中找到请映射的注解
    134. //packagePathArray = {"com.controller","com.controller2"};
    135. String[] packagePathArray = packagePath.split(",") ;
    136. for(String ppath : packagePathArray){
    137. //ppath = "com.controller"
    138. //要想完成反射,只有包路径还不够,还需要类名 com.controller + TestController
    139. //如何获得类名
    140. //不能通过反射获得包中类
    141. //可以通过File操作获得文件夹中的文件名->去掉后缀,就是类名
    142. // "com.controller" -> "com/controller"
    143. String dirPath = ppath.replace(".","/");
    144. // dirPath ="F:\dmc\idea_workspace\web-demo\out\artifacts\web_demo_war_exploded\WEB-INF\classes\com\controller"
    145. try {
    146. dirPath = Thread.currentThread().getContextClassLoader()
    147. .getResource(dirPath).getPath();
    148. }catch(NullPointerException e){
    149. //指定的包路径有错误
    150. System.out.println("logging : package path not found ["+dirPath+"]");
    151. //当前不存在包就不在检索了
    152. continue ;
    153. //throw new FileNotFoundException(dirPath);
    154. }
    155. File dir = new File(dirPath);
    156. //获得文件夹中所有子内容名字 (子文件夹,子文件)
    157. String[] fnames = dir.list() ;
    158. for(String fname : fnames){
    159. //fname="TestController.class
    160. if(fname.endsWith(".class")){
    161. int i = fname.indexOf(".") ;
    162. //className="TestController"
    163. String className = fname.substring(0,i);
    164. //className = "com.controller.TestController"
    165. //获取完整类路径
    166. className = ppath + "." + className ;
    167. Class clazz = Class.forName(className);
    168. Controller c = (Controller) clazz.getAnnotation(Controller.class);
    169. if(c == null){
    170. //当前包中的这个类没有设置@Controller,类中没有请求映射信息
    171. continue ;
    172. }
    173. //有@Controller,类中有请求映射关系
    174. //遍历类中所有的方法,找到@RequestMapping
    175. Method[] methodArray = clazz.getMethods() ;
    176. for(Method method : methodArray){
    177. RequestMapping rm = method.getAnnotation(RequestMapping.class);
    178. if(rm == null){
    179. continue ;
    180. }
    181. //方法中有@RequestMapping,表示是一个请求映射的方法
    182. String path = rm.value();
    183. Object controller = getSingleController(className);
    184. MappingInfo info = new MappingInfo(path,controller,method) ;
    185. Object obj = mappingInfoMap.get(path);
    186. if(obj != null ){
    187. //当前请求映射关系已经存在
    188. //去重 continue
    189. //覆盖 map.put
    190. //抛出异常 throw new Exception
    191. }
    192. mappingInfoMap.put(path,info) ;
    193. }
    194. }
    195. }
    196. }
    197. }
    198. @Override
    199. protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    200. //请求映射,根据请求调用controller的具体方法
    201. //获得请求 /test1
    202. //req.getRequestURL(); // http://localhost:8080/web-demo/test1
    203. String uri = req.getRequestURI() ;// /web-demo/test1
    204. // 根路径
    205. String root = req.getContextPath() ;// /web-demo
    206. String path = uri.replace(root,""); // /test1
    207. //根据请求找到对应的mappinginfo 映射信息
    208. MappingInfo info = mappingInfoMap.get(path) ;
    209. if(info == null){
    210. //没有找到与之匹配映射请求 (xml和注解)
    211. //没有需要调用的controller和方法
    212. //有可能此次请求的是一些文件资源.html , .css , .js , .jpg
    213. //之前在没有框架的时候,为什么可以访问这些静态文件呢
    214. //原因是tomcat提供了一个专门处理静态资源访问的DefaultServlet
    215. //该servlet处理的请求模式就是 /
    216. //现在我们的DispatcherServlet一般也建议配置成 /
    217. //就会覆盖tomcat自带的DefaultServlet
    218. //就不会再自动处理静态资源了
    219. //就需要框架来处理静态资源
    220. handleStaticResource(path,req,resp);
    221. }else{
    222. //找到映射信息了,就按照映射信息分发请求,调用controller.method
    223. try {
    224. handleDynamicResource(info);
    225. } catch (InvocationTargetException e) {
    226. e.printStackTrace();
    227. } catch (IllegalAccessException e) {
    228. e.printStackTrace();
    229. }
    230. }
    231. }
    232. //动态资源 mapping映射信息 -- 通过对象调方法
    233. private void handleDynamicResource(MappingInfo info) throws InvocationTargetException, IllegalAccessException {
    234. //获得参数
    235. //处理参数
    236. //调用mappinginfo中的对象的方法,传递参数
    237. }
    238. /*
    239. 处理静态资源,在mapping映射信息中没有找到就表示静态资源
    240. */
    241. private void handleStaticResource(String path,HttpServletRequest req,HttpServletResponse resp) throws IOException {
    242. if(path.equals("/")){
    243. //获得/表示的默认资源->/index.html
    244. // 相对路径
    245. path = req.getServletPath();
    246. }
    247. //path="/index.html"
    248. //根据请求的相对路径,找到对应文件在服务器端存储的绝对路径
    249. //Thread.currentThread...()获得classes目录下的文件路径
    250. //request.getServletContext().realPath()获得服务器目录下的文件路径
    251. //path ="F:\dmc\idea_workspace\web-demo\out\artifacts\web_demo_war_exploded\index.html"
    252. // 用相对路径也可以
    253. String fpath = req.getServletContext().getRealPath(path);
    254. File file = new File(fpath) ;
    255. if(!file.exists()){
    256. //能执行当前方法,表示此次请求的不是一个动态资源
    257. //文件不存在,表示此次请求的静态资源不存在
    258. //表示此次没有找到对应的资源 -- 发送一个错误提示 404
    259. resp.sendError(404,"["+path+"]");
    260. return ;
    261. }
    262. InputStream is = new FileInputStream(file);
    263. //做一个响应
    264. OutputStream os = resp.getOutputStream() ;
    265. // 增加效率
    266. byte[] bs = new byte[0x100] ;//16*16=256
    267. while(true){
    268. int len = is.read(bs) ;
    269. if(len == -1)break ;
    270. os.write(bs,0,len);
    271. }
    272. os.flush();
    273. is.close();
    274. }

    } ```