- 原来浏览器发送请求到服务器
服务器接收请求
参考web.xml 或 @WebServlet 找到请求与controller之间的映射关系
现在浏览器发送请求到服务器
服务器接收请求
将请求交给web框架
框架根据一些请求映射关系,找到与之匹配的controller实现映调用。
正因为服务器现在将请求交给了web框架
所以才能在框架中完成一些常用功能的封装
1. 多个请求,可以访问同一个controller对象的不同方法 (请求映射)。
2. 框架负责接收参数(普通参数,文件参数)
3. 框架负责传递参数 (类型转换)
【具体业务实现 - 业务程序实现】
4. 框架负责响应
直接响应
间接响应
转发(携带数据)
重定向
编码实现
1. DispatcherServlet
按照上面的分析
有了web框架以后
浏览器给服务器的请求,服务器会交给框架
反之也就是说框架可以接收服务器的请求
从语法而言,可以接收服务器请求的组件:Servlet , Filter
所以框架需要一个请求入口
补充:
/ 一进来就交给框架
/ 是先看看有没有处理他的,没有再交给框架
- 请求映射的配置
现在请求都交给了框架
所以需要框架实现请求映射
哪个请求对应哪个controller的方法需要业务程序员按照框架的要求配置
暂时有2种规范
* 第1种,在xml文件中配置映射关系
在web.xml中配置DispatcherServlet时
通过设置servlet的参数指定框架的配置文件名称(默认要求配置文件必须放在src目录下)<!--web.xml--><servlet><servlet-name>mvc</servlet-name><servlet-class>org.duyi.web7.DispatcherServlet</servlet-class><init-param><param-name>classpath</param-name> <!-- 用来指定配置文件名字的从参数固定的 --><param-value>mvc.xml</param-value> <!-- 配置文件名子可以自定义 --></init-param></servlet>
补充:
context-param和init-param区别
web.xml里面可以定义两种参数:
(1)application范围内的参数,存放在servletcontext中,在web.xml中配置如下:
(2)servlet范围内的参数,只能在servlet的init()方法中取得,在web.xml中配置如下:
详情见:https://www.cnblogs.com/tiancai/p/9305381.html
* 人为规定xml中的映射关系配置如下:
<!--mvc.xml--><?xml version="1.0" ?><mvc><!--浏览器发送的/test1请求,由TestController类的t1方法处理--><mapping path="/test1" class="com.controller.TestController" method="t1"></mapping><mapping path="/test11" class="com.controller.TestController" method="t1"><type>int</type></mapping><mapping path="/test2" class="com.controller.TestController" method="t2"><!--引用数据类型需要指明全路径--><type>java.lang.String</type><type>com.domain.Car</type></mapping><mapping path="/test3" class="com.controller.TestController" method="t3" ></mapping></mvc>
* 第2种,在注解中配置映射关系
框架自定义*@RequestMapping
约定优于配置
package org.duyi.web7.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/*** 配置请求映射关系* 指定哪个请求对应哪个controller的方法* 作用在哪个方法上,就表示映射哪个方法* value属性用来设置请求路径*/@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME) //设置存活时间,在运行时用public @interface RequestMapping {public String value();}
* 该注解作用controller的方法上,表示请求与方法的映射关系<br /> @RequestMapping("/test2")<br /> public void t2(String s , Car car){}<br /> * 之前可以从xml文件中获得请求映射关系<br /> * 现在需要从注解中获得映射关系<br /> * 要想找到注解,就需要找到方法,需要找到controller类(反射)<br /> * 为了更方便的找到controller类<br /> 1. 在web.xml文件中,指定寻找controller的基本包位置
<-- 参数,在web.xml里 --><init-param><param-name>controller-scan</param-name><param-value>com.controller,com.controller2</param-value></init-param>
2. 框架自定义@Controller <br />目的:降低扫描类的工作量<br /> 该注解作用在controller类上<br /> 具有该注解的controller类,才会通过反射寻找RequestMapping
package org.duyi.web7.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/*** 作用在controller类上,表示这个类中有方法可以实现请求,可以扫描*/@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Controller {}
<--web.xml--><?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><servlet><servlet-name>mvc</servlet-name><servlet-class>org.duyi.web7.DispatcherServlet</servlet-class><init-param><param-name>classpath</param-name><param-value>mvc.xml</param-value></init-param><init-param><param-name>controller-scan</param-name><param-value>com.controller,com.controller2</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>mvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>
- 初始化映射关系
有了框架以后
所有请求就会交给框架
框架会根据映射关系,最终调用controller的指定方法
映射关系可以使用xml配置,也可以使用注解配置
每次请求映射时都需要读取一次配置信息么?
当然不需要
只需在服务器启动时读取一次,并存储缓存即可
static{}
servlet.init
在servlet(单例)创建的时候,创建的初始化方法(只创建一次)
配置信息来自于两个部分
配置了5个部分 ```java package org.duyi.web7;
import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader;
import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; 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<>();/*** 读取各种配置信息* (目前只有请求映射信息)* @throws ServletException*/@Overridepublic void init() throws ServletException {//目前配置信息来自于两个部分,而且两个部分都可能存在//读配置文件String xmlPath = super.getInitParameter("classpath");if(xmlPath != null && !"".equals(xmlPath)){//指定了配置文件,需要读取配置文件readXml(xmlPath);}//读注解// 包路径 一个controller扫描String packagePath = super.getInitParameter("controller-scan") ;if(packagePath != null && !"".equals(packagePath)){//指定了包路径,需要读取包下类中的注解信息}}//读取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();// 可以直接写这个路径,但是这个路径不是固定的//其实找的不是src目录下的,而是classes下的//new File(path) 的相对路径通常是在web_demo_war_exploded目录下// 找mvc.xml,是找不到的,而且名字也可能会改,所以不建议用这个try {// 读的是字节InputStream is = new FileInputStream(path) ;//使用dom,sax读取xml内容// 解析只有sax和dom,原生解析方式//sax读写器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标记// mapping标记 一堆标签(mvc.xml)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 = clazz.newInstance() ;//通过反射,根据方法名获得方法对象 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.class 将字符串变成int.class//Class.forName("int"); --> 不行// 索引位置 找小弟判断types[i++] = castStringToClass(typeStr) ;}//通过class类型就是寻找方法所对应的类型列表// 而这个就是对应的方法 可变参数列表// 而这个方法可能存在重载,所以除了方法名字外,还需要类型Method method = clazz.getMethod(methodname,types ) ;// 组成对象MappingInfo info = new MappingInfo(path,controller,method) ;//将一个mappingInfo标签的信息就存成一个具体的对象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.Integer包装类//Class c = Class.forName("java.lang.String");//Class c2 = String.class ; 类型.属性//Class c3 = 对象.getClass方法return Class.forName(typeStr) ;//最终产生的是class对象的方法}//读取注解public void readAnnotation(){}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//请求映射,根据请求调用controller的具体方法//获得请求 /test1//根据请求找到对应的mappinginfo 映射信息//获得参数//处理参数//调用mappinginfo中的对象的方法,传递参数}
}
```java<--mvc.xml--><?xml version="1.0" ?><mvc><!--浏览器发送的/test1请求,由TestController类的t1方法处理--><--无参--><mapping path="/test1" class="com.controller.TestController" method="t1"></mapping><--有参--><mapping path="/test11" class="com.controller.TestController" method="t1"><type>int</type><type>java.lang.String</type></mapping><!--在web.xml中配置也可--><controller-scan package="com.controller,com.controller2"></controller-scan></mvc>
package org.duyi.web7;import java.lang.reflect.Method;/*** 存储请求映射关系*/public class MappingInfo {private String path ;// /test1 请求命令private Object controller ; // TestController 目标对象private Method method ;// t1() 目标方法 方法对象,自然包括对象(如果有参数,就有)public MappingInfo() {}public MappingInfo(String path, Object controller, Method method) {this.path = path;this.controller = controller;this.method = method;}public String getPath() {return path;}public void setPath(String path) {this.path = path;}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;}}
补充:
将集合转成字符串(JSON)
fastjson阿里巴巴,gson谷歌,json-lib,jackson — StringMVC底层使用的方式
- 配置文件
* 目前只有mapping请求映射信息
2. 注解
