- 原来浏览器发送请求到服务器
服务器接收请求
参考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 {
/*缓存请求映射信息key = "/test1"value = mappingInfo{目标对象,目标方法}*/Map<String,MappingInfo> mappingInfoMap = new HashMap<>();/*单实例存储controller对象key="com.controller.TestController"value= new Object*/Map<String,Object> controllerMap = new HashMap<>();/*** 读取各种配置信息* (目前只有请求映射信息)* @throws ServletException*/@Overridepublic void init() throws ServletException {//目前配置信息来自于两个部分,而且两个部分都可能存在String xmlPath = super.getInitParameter("classpath");if(xmlPath != null && !"".equals(xmlPath)){//指定了配置文件,需要读取配置文件readXml(xmlPath);}String packagePath = super.getInitParameter("controller-scan") ;if(packagePath != null && !"".equals(packagePath)){try {//指定了包路径,需要读取包下类中的注解信息readAnnotation(packagePath);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}}}//读取xml配置文件public void readXml(String path){//path="mvc.xml"//D:\ideaworkspace2\web-demo\out\artifacts\web_demo_war_exploded\WEB-INF\classes\mvc.xmlpath = Thread.currentThread().getContextClassLoader().getResource(path).getPath();try {InputStream is = new FileInputStream(path) ;//使用dom,sax读取xml内容SAXReader reader = new SAXReader() ;Document document = reader.read(is) ;//解析mapping标签parseMappingElement(document);//解析其他标签} catch (FileNotFoundException e) {e.printStackTrace();} catch (DocumentException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();}}//读取xml中<mapping>请求映射信息private void parseMappingElement(Document document) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException {//按照标签的嵌套规则,找到需要的所有mapping标记List<Element> mappingElements = document.selectNodes("mvc/mapping") ;for(Element mappingElement : mappingElements ){/*获得标签指定的属性值标签中记载着请求与响应对应关系此时(初始化)获得这个对应关系,不是次使用后面请求的时候用所以需要存储起来,组成对象*/String path = mappingElement.attributeValue("path") ;String classname = mappingElement.attributeValue("class") ; // com.controller.TestControllerString methodname = mappingElement.attributeValue("method") ; // t1//反射创建目标对象Class clazz = Class.forName(classname) ;Object controller = getSingleController(classname);//通过反射,根据方法名获得方法对象 t1() , t2(int , String)//获得mapping标签的子标签<type> 存储目标方法对应的参数列表List<Element> typeElements = mappingElement.selectNodes("type") ;//创建一个与配置文件中<type>数量相同的Class数组,准备装载目标方法对应的参数列表类型Class[] types = new Class[typeElements.size()] ;int i = 0 ;for(Element typeElement : typeElements){//<type> 获得标签的文本内容 "int"String typeStr = typeElement.getText() ;//"int" ->int.classtypes[i++] = castStringToClass(typeStr) ;}Method method = clazz.getMethod(methodname,types ) ;MappingInfo info = new MappingInfo(path,controller,method) ;mappingInfoMap.put(path,info) ;}}/** 将字符串表示的类型转换成对应的Class类型* "int" -> int.class* "java.lang.String" -> String.class -> Class.forName("java.lang.String")*/private Class castStringToClass(String typeStr) throws ClassNotFoundException {if("int".equals(typeStr)){return int.class;}if("long".equals(typeStr)){return long.class;}if("double".equals(typeStr)){return double.class;}//除去8种基本类型,以外的都是引用类型,写的都是全名,反射生成Class , java.lang.Integerreturn Class.forName(typeStr) ;}public Object getSingleController(String classname) throws ClassNotFoundException, IllegalAccessException, InstantiationException {Class clazz = Class.forName(classname);Object controller = controllerMap.get(classname);if(controller == null){controller = clazz.newInstance() ;controllerMap.put(classname,controller);}return controller ;}//读取注解public void readAnnotation(String packagePath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {//packagePath = "com.controller,com.controller2,.."//需要框架从指定的2个包中找到请映射的注解//packagePathArray = {"com.controller","com.controller2"};String[] packagePathArray = packagePath.split(",") ;for(String ppath : packagePathArray){//ppath = "com.controller"//要想完成反射,只有包路径还不够,还需要类名 com.controller + TestController//如何获得类名//不能通过反射获得包中类//可以通过File操作获得文件夹中的文件名->去掉后缀,就是类名// "com.controller" -> "com/controller"String dirPath = ppath.replace(".","/");// dirPath ="F:\dmc\idea_workspace\web-demo\out\artifacts\web_demo_war_exploded\WEB-INF\classes\com\controller"try {dirPath = Thread.currentThread().getContextClassLoader().getResource(dirPath).getPath();}catch(NullPointerException e){//指定的包路径有错误System.out.println("logging : package path not found ["+dirPath+"]");//当前不存在包就不在检索了continue ;//throw new FileNotFoundException(dirPath);}File dir = new File(dirPath);//获得文件夹中所有子内容名字 (子文件夹,子文件)String[] fnames = dir.list() ;for(String fname : fnames){//fname="TestController.classif(fname.endsWith(".class")){int i = fname.indexOf(".") ;//className="TestController"String className = fname.substring(0,i);//className = "com.controller.TestController"//获取完整类路径className = ppath + "." + className ;Class clazz = Class.forName(className);Controller c = (Controller) clazz.getAnnotation(Controller.class);if(c == null){//当前包中的这个类没有设置@Controller,类中没有请求映射信息continue ;}//有@Controller,类中有请求映射关系//遍历类中所有的方法,找到@RequestMappingMethod[] methodArray = clazz.getMethods() ;for(Method method : methodArray){RequestMapping rm = method.getAnnotation(RequestMapping.class);if(rm == null){continue ;}//方法中有@RequestMapping,表示是一个请求映射的方法String path = rm.value();Object controller = getSingleController(className);MappingInfo info = new MappingInfo(path,controller,method) ;Object obj = mappingInfoMap.get(path);if(obj != null ){//当前请求映射关系已经存在//去重 continue//覆盖 map.put//抛出异常 throw new Exception}mappingInfoMap.put(path,info) ;}}}}}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//请求映射,根据请求调用controller的具体方法//获得请求 /test1//req.getRequestURL(); // http://localhost:8080/web-demo/test1String uri = req.getRequestURI() ;// /web-demo/test1// 根路径String root = req.getContextPath() ;// /web-demoString path = uri.replace(root,""); // /test1//根据请求找到对应的mappinginfo 映射信息MappingInfo info = mappingInfoMap.get(path) ;if(info == null){//没有找到与之匹配映射请求 (xml和注解)//没有需要调用的controller和方法//有可能此次请求的是一些文件资源.html , .css , .js , .jpg//之前在没有框架的时候,为什么可以访问这些静态文件呢//原因是tomcat提供了一个专门处理静态资源访问的DefaultServlet//该servlet处理的请求模式就是 ///现在我们的DispatcherServlet一般也建议配置成 ///就会覆盖tomcat自带的DefaultServlet//就不会再自动处理静态资源了//就需要框架来处理静态资源handleStaticResource(path,req,resp);}else{//找到映射信息了,就按照映射信息分发请求,调用controller.methodtry {handleDynamicResource(info);} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}}//动态资源 mapping映射信息 -- 通过对象调方法private void handleDynamicResource(MappingInfo info) throws InvocationTargetException, IllegalAccessException {//获得参数//处理参数//调用mappinginfo中的对象的方法,传递参数}/*处理静态资源,在mapping映射信息中没有找到就表示静态资源*/private void handleStaticResource(String path,HttpServletRequest req,HttpServletResponse resp) throws IOException {if(path.equals("/")){//获得/表示的默认资源->/index.html// 相对路径path = req.getServletPath();}//path="/index.html"//根据请求的相对路径,找到对应文件在服务器端存储的绝对路径//Thread.currentThread...()获得classes目录下的文件路径//request.getServletContext().realPath()获得服务器目录下的文件路径//path ="F:\dmc\idea_workspace\web-demo\out\artifacts\web_demo_war_exploded\index.html"// 用相对路径也可以String fpath = req.getServletContext().getRealPath(path);File file = new File(fpath) ;if(!file.exists()){//能执行当前方法,表示此次请求的不是一个动态资源//文件不存在,表示此次请求的静态资源不存在//表示此次没有找到对应的资源 -- 发送一个错误提示 404resp.sendError(404,"["+path+"]");return ;}InputStream is = new FileInputStream(file);//做一个响应OutputStream os = resp.getOutputStream() ;// 增加效率byte[] bs = new byte[0x100] ;//16*16=256while(true){int len = is.read(bs) ;if(len == -1)break ;os.write(bs,0,len);}os.flush();is.close();}
} ```
