映射请求-映射动态资源-参数处理
服务器接收参数后
需要按照映射关系调用controller的方法,同时将获取的请求参数,以方法参数的形式传递
controller方法在接收参数时,希望框架可以按需求实现类型转换
框架在接收参数,传递参数中,还需按照controller的需求要处理参数
controller方法的参数列表
public void t1(String bname,@RequestParam(“num”)int num , int[] nums , HttpServletRequest req , Car car,MuliptartFile excel)响应
框架参数处理完毕后,就可以根据映射关系调用controller方法,传参
controller中获得参数后的业务处理,就有具体的业务程序完成(controller,service,dao)
业务程序处理完毕后,需要响应
直接响应
直接响应文本字符串
直接响应集合对象(需要转换成json格式的字符串再响应)
间接响应
转发 (携带数据)
重定向 (携带数据)
原来响应直接在业务程序中完成
现在响应由框架完成
框架实现哪种响应,需要由业务程序告知
业务程序如何告知框架,需要按照框架的规则告知
1. 响应内容以返回值的形式返回给框架
2. 如果是间接响应,直接返回值
如果不需要携带数据
return “05.jsp” 转发
return “redirect:05.jsp” 重定向
如果需要携带数据
返回一个框架提供ModelAndView对象 ```java package org.duyi.web7;
import java.util.HashMap; import java.util.Map; import java.util.Set;
public class ModelAndView {
private String viewName ;// 05.jsp
//request.setAttribute(key1,value1);
//request.setAttribute(key2,value2);
//request.setAttribute(key3,value3);
private Map
public String getViewName() {return viewName;}public void setViewName(String viewName) {this.viewName = viewName;}public void addObject(String key,Object value){values.put(key,value) ;}public Object getObject(String key){return values.get(key) ;}public Set<String> getObjectNames(){return values.keySet() ;}
}
* 转发: 数据会request.setAttribute()<br /> * 重定向:数据会以?的形式跟在url的后面 (只支持String)<br /> 3. 如果是直接响应,在返回值同时,需要为方法增加一个@ResponseBody的注解<br />只表示一种状态不需要属性```javapackage org.duyi.web7.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.METHOD) //在方法上写@Retention(RetentionPolicy.RUNTIME)public @interface ResponseBody {}
* 如果返回String,int,double等简单类型,直接以String返回<br /> * 如果是其他类型,则认为需要转换成json
package com.controller;import com.domain.Car;import org.duyi.web7.ModelAndView;import org.duyi.web7.MulitpartFile;import org.duyi.web7.annotation.Controller;import org.duyi.web7.annotation.RequestMapping;import org.duyi.web7.annotation.RequestParam;import org.duyi.web7.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;@Controllerpublic class TestController {//test1public void t1(){System.out.println("测试1");}//test11public void t1(int i,String s){}//test2@RequestMapping("/test2")public void t2(@RequestParam("cname")String s , @RequestParam("price") int[] price,Car car, HttpServletRequest req){//request}@RequestMapping("/test3")public void t3(Car car){System.out.println(car);}@RequestMapping("/test4")public void t4(@RequestParam("cname") String cname , @RequestParam("file") MulitpartFile file){System.out.println(cname);}@RequestMapping("/test5")public String t5(){return "07.jsp" ;}@RequestMapping("/test6")public ModelAndView t6(){ModelAndView mv = new ModelAndView();mv.setViewName("07.jsp");mv.addObject("cname","benz");mv.addObject("price",400000);return mv ;}@RequestMapping("/test7")public ModelAndView t7(){ModelAndView mv = new ModelAndView();mv.setViewName("redirect:07.jsp");mv.addObject("cname","benz");mv.addObject("price",400000);return mv ;}@RequestMapping("/test8")@ResponseBodypublic String t8(){return "hello" ;}@RequestMapping("/test9")@ResponseBodypublic Car t9(){Car car = new Car(1,"audi","white",50000);return car ;}}
package org.duyi.web7;import com.alibaba.fastjson.JSON;import org.apache.commons.fileupload.FileItem;import org.apache.commons.fileupload.FileUploadException;import org.apache.commons.fileupload.disk.DiskFileItemFactory;import org.apache.commons.fileupload.servlet.ServletFileUpload;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 org.duyi.web7.annotation.RequestParam;import org.duyi.web7.annotation.ResponseBody;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import java.io.*;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.lang.reflect.Parameter;import java.util.*;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);//解析multipart-encodingparseMultipartEncodingElement(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();}}private String multipartEncoding ;//读取<multipart-encoding>标签private void parseMultipartEncodingElement(Document document){Element mulipartEncodingElement = (Element) document.selectSingleNode("mvc/multipart-encoding");if(mulipartEncodingElement == null)return ;multipartEncoding = mulipartEncodingElement.getText() ;}//读取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/test1System.out.println(uri);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{try {//找到映射信息了,就按照映射信息分发请求,调用controller.methodhandleDynamicResource(info,req,resp);} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}}}/*处理动态资源请求*/private void handleDynamicResource(MappingInfo info,HttpServletRequest req,HttpServletResponse resp) throws InvocationTargetException, IllegalAccessException, InstantiationException, IOException, ServletException {//获得参数Map<String,Object> paramMap = receiveParams(req);//处理参数Object[] paramValues = handleParam(paramMap,info,req,resp) ;//调用mappinginfo中的对象的方法,传递参数Object result = info.getMethod().invoke( info.getController() , paramValues ) ;//根据result返回值,实现响应handleResponse(result,info.getMethod(),req,resp);}private void handleResponse(Object result,Method method,HttpServletRequest req,HttpServletResponse resp) throws IOException, ServletException {if(result == null){//不需要框架响应return ;}ResponseBody rb = method.getAnnotation(ResponseBody.class);if(rb != null){//有@ResponseBody 直接响应if(result instanceof String ||result instanceof Integer ||result instanceof Long ||result instanceof Boolean){//简单类型,都变成字符串,直接响应resp.getWriter().write(result.toString());}else{//不是简单类型,就是集合对象,需要转换成json再响应// 直接把返回值转换为jsonString json = JSON.toJSONString(result) ;resp.setContentType("text/json"); //类型转换resp.getWriter().write(json); //响应回去}}else{// 间接响应if(result instanceof String){//不需要携带数据String _result = (String) result;if(_result.startsWith("redirect:")){//重定向 "redirect:05.jsp"// "05.jsp"_result = _result.replace("redirect:","");resp.sendRedirect(_result);}else{//转发 "05.jsp"req.getRequestDispatcher(_result).forward(req,resp);}}else{//就是ModelAndView,需要携带数据ModelAndView _result = (ModelAndView) result;// path="05.jsp" / "redirect:05.jsp"String path = _result.getViewName() ;if(path.startsWith("redirect:")){//重定向携带数据 05.jsp?...path = path.replace("redirect:","");path+="?" ;Set<String> names = _result.getObjectNames() ;for(String name : names){Object value = _result.getObject(name);path+=name+"="+value+"&";}//05.jsp?a=1&b=2&c=3resp.sendRedirect(path);}else{//转发携带数据,//req.setAttribute(name,value)Set<String> names = _result.getObjectNames() ;for(String name : names){Object value = _result.getObject(name);req.setAttribute(name,value);}req.getRequestDispatcher(path).forward(req,resp);}}}}/*按照请求映射关系要调用的那个目标方法获得目标方法的参数列表根据参数列表获得所需要的参数并将参数组装Object[]中*/private Object[] handleParam(Map<String,Object> paramMap,MappingInfo info,HttpServletRequest req , HttpServletResponse resp) throws IllegalAccessException, InstantiationException, InvocationTargetException {Method method = info.getMethod() ;Parameter[] parameters = method.getParameters() ;//创建一个与方法参数列表同长度的数组装载参数数据(实参)Object[] paramValues = new Object[ parameters.length ] ;int i = 0 ;for(Parameter parameter : parameters){RequestParam rp = parameter.getAnnotation(RequestParam.class);Class paramType = parameter.getType() ;if(rp != null){//证明有这个注解 @RequestParam("cname") String cname//表示需要请求传递的一个叫cname名字的参数//请求传递的参数之前已经处理过了//处理后将请求传递的参数存入了paramMap集合中,key=cname,value=[]String key = rp.value() ;// cnameObject value = paramMap.get(key) ;if(value == null){//当前所需要的的参数没有值,默认是null,之间找下一个参数值i++ ;continue ;}//如果参数值存在,起初只有2种表现形成 String[] , MulitpartFile[]//将此次需要的参数value,转换成方法中定义的需要参数类型paramValues[i++] = castType(value,paramType);}else{//没有@RequestParam注解//可能是request,response,session,domain(组装参数User,Car,Goods)if(paramType == HttpServletRequest.class){paramValues[i++] = req ;}else if(paramType == HttpServletResponse.class){paramValues[i++] = resp ;}else if(paramType == HttpSession.class){paramValues[i++] = req.getSession() ;}else{//是一个domain类型,假设是paramType = Car.class//将此次请求的多个参数,最终组成一个car对象Object paramObject = paramType.newInstance() ; //new Car()//通过反射获得对象中的属性名,根据属性名找到与之同名的参数,为属性赋值//为了更好的符合java封装的特性,不建议通过反射找属性,建议通过反射找对应的set方法Method[] methods = paramType.getMethods() ;for(Method m : methods){String mname = m.getName() ;if(mname.startsWith("set")){//是一个set方法,有对应的属性 setCname --> cnameString key = mname.substring(3) ;//去掉set-->Cnamekey = key.substring(0,1).toLowerCase() + key.substring(1) ;// cnameObject value = paramMap.get(key) ;if(value == null){i++ ;continue ;}//根据domain的set方法(属性名)找到了与之对应的参数//该参数起初只是String[],MultipartFile[]类型//需要转换成domain中属性的类型Class domainParamType = m.getParameterTypes()[0] ; // void setCname(String cname) -> StringObject v = castType(value,domainParamType) ;m.invoke(paramObject,v) ;// car.setCname(cname);}}//循环结束,找到了所有的set方法,为对象的属性赋值,赋值结束将对象参数保存paramValues[i++] = paramObject;}}}return paramValues ;}/*将原始类型的参数数据,转换成目标方法所需要的类型原始类型:String[] , MulipatrFile[]目标类型:int,long,double,String,int[],long[],String[],Integer,Integer[],MultiparFile , MultipartFile[]*/private Object castType(Object value , Class paramType){if(paramType == String.class){String str = ((String[])value)[0] ;return str ;}if(paramType == int.class || paramType==Integer.class){String str = ((String[])value)[0] ;int num = Integer.parseInt(str) ;return num ;}if(paramType == long.class || paramType==Long.class){String str = ((String[])value)[0] ;long num = Long.parseLong(str) ;return num ;}if(paramType == double.class || paramType==Double.class){String str = ((String[])value)[0] ;double num = Double.parseDouble(str) ;return num ;}if(paramType == String[].class){return value ;}//url?cname=1&cname=2&cname3 =》框架会存储成数组[1,2,3]if(paramType == int[].class){String[] ss = (String[])value ;int[] nums = new int[ss.length];for(int j=0;j< ss.length;j++){nums[j] = Integer.parseInt(ss[j]);}return nums ;}if(paramType == Integer[].class){String[] ss = (String[])value ;Integer[] nums = new Integer[ss.length];for(int j=0;j< ss.length;j++){nums[j] = Integer.valueOf(ss[j]);}return nums ;}if(paramType == MulitpartFile.class){return ((MulitpartFile[])value)[0];}if(paramType == MulitpartFile[].class)return value ;return null ;}/** 接收请求传递从参数* 将参数处理后装入map* map.key就是请求传递参数key <input type="uname" /> map.key=uname* map.value 可能是一个 String[] 也可能是一个 文件[] -> Object表示*/private Map<String,Object> receiveParams(HttpServletRequest req){Map<String,Object> paramMap = new HashMap<>();//参数有2种可能 (普通请求,文件上传请求)//如果普通请求按照文件上传方式处理,会抛出异常//如果文件上传请求按照普通请求处理会得不到参数try{//假设是文件上传方式传递参数DiskFileItemFactory factory = new DiskFileItemFactory();ServletFileUpload upload = new ServletFileUpload(factory) ;//该行会判断是否是文件上传请求,是否配置了enctype="multipart/form-data",如果不是会抛出异常List<FileItem> fis = upload.parseRequest(req) ;//确实是一个文件上传的请求,同时因为上一行代码,已经将请求的参数都组成了FileItem对象,装入了List集合for(FileItem fi : fis){//拿到了一个参数if(fi.isFormField()){// 是一个普通参数,StringString key = fi.getFieldName() ;// <input name="uname" />String value = fi.getString(multipartEncoding) ;String[] values = (String[]) paramMap.get(key);if(values == null){//还没有存储过当前这个名字的参数values = new String[]{value} ;paramMap.put(key,values) ;}else{//当前名字的参数之前存储过,将当前的数据装入数组,数组扩容+1values = Arrays.copyOf(values,values.length+1) ;values[ values.length-1 ] = value ;paramMap.put(key,values) ;}}else{// 是一个文件参数String key = fi.getFieldName() ; // <input type="file" name="excel" />String fileName = fi.getName() ; //文件名 1.jpglong size = fi.getSize() ;//获得文件大小String contentType = fi.getContentType() ; //文件的类型InputStream is = fi.getInputStream() ;//文件内容MulitpartFile file = new MulitpartFile(key,fileName,size,contentType,is);MulitpartFile[] files = (MulitpartFile[]) paramMap.get(key);if(files == null){//当前名字的文件参数之前没有files = new MulitpartFile[]{file} ;paramMap.put(key,files);}else{//之前存储过这个名字的文件参数files = Arrays.copyOf(files,files.length+1);files[ files.length-1 ] = file ;paramMap.put(key,files) ;}}}}catch(FileUploadException e){//出现异常表示不是文件上传方式,再按照普通方式处理Enumeration<String> names = req.getParameterNames();while(names.hasMoreElements()){String name = names.nextElement() ; // name="uname"String[] values = req.getParameterValues(name);paramMap.put(name,values) ;}} catch (UnsupportedEncodingException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return paramMap ;}/*处理静态资源,在mapping映射信息中没有找到就表示静态资源*/private void handleStaticResource(String path,HttpServletRequest req,HttpServletResponse resp) throws IOException {if(path.equals("/")){//获得/表示的默认资源->/index.htmlpath = 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()){//能执行当前方法,表示此次请求的不是一个动态资源//文件不存在,表示此次请求的静态资源不存在//表示此次没有找到对应的资源resp.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();}}
