JSP学习笔记(6)—— 自定义MVC框架
仿照springmvc,实现一个轻量级mvc框架,知识涉及到了反射机制、注解的使用和一些第三方工具包的使用
思路
主要的总体流程如下图所示
和之前一样,我们定义了一个dispatchservlet,用于拦截请求(这里一般拦截.do结尾的url请求);
之后,dispatchservlet会根据url,找到controller中对应的方法并执行,返回一个结果。
我们根据返回的结果,来dispatchservlet执行不同的操作(请求转发、页面重定向、返回json数据)
看完这里,总体的思路应该很明确了,问题来了:
- 如何实现让dispatchservlet根据url去找到对应的方法?
- 如何根据返回的结果,让dispatchservlet执行不同的操作?
对于第一个问题,我们可以使用注解来实现
- 定义一个
controller
注解,用来标记controller类 - 定义一个
requestmapping
注解,用来标记url匹配的方法
对于第二个问题,我们可以设置这样的一套规则:
- 返回的结果是string,且以
redirect:
开头,则表明dispatchservlet要执行页面重定向操作 - 返回的结果是string,不以
redirect:
开头,则表明dispatchservlet要执行请求转发操作 - 返回的结果是一个bean类,则自动将其转为json数据
一般我们会让doget方法也调用dopost方法,所以我们只需要重写servlet中的dopost方法
由上面的思路,我们可以得到细化的流程图(也就是dopost方法中的流程)
关键点实现
处理请求url
//获得url地址 string servletpath = req.getservletpath(); string requesturl = stringutils.substringbefore(servletpath,".do");
使用注解找到对应的方法
之前有说过,我们定义了两个注解,一个是controller和requestmapping
标记一个类和方法
@controller public class usercontroller{ @requestmapping("/user/login") public string login(httpservletrequest request){ return "login.jsp"; } ... }
之后我们就可以使用lang3
开源库去查找类中是否有controller
标记,然后再去寻找是否被requestmapping标记的方法,并把requestmapping注解上的标记的url、controller全类名和方法名存起来
这里我是用来一个类classmapping
来存放全类名和方法名,之后,使用url作为key,classmapping
对象作为value,存入一个hashmap中
这里虽然也可以放在dopost方法中,但是会造成资源的浪费,因为dopost方法可能会被执行多次。所以,更好的做法是,可以放在servlet的初始化方法init()
里面,这样,只会执行一次。
我封装了一个配置类configuration
,专门用来查找url及其对应的方法
判断类是否包含有controller注解
controllerclass.isannotationpresent(controller.class)
获得类中包含有requestmapping注解的方法列表
method[] methods = methodutils.getmethodswithannotation(controllerclass, requestmapping.class, true, true);
更多代码请看下面的代码部分configuration类
传参以及反射调用方法
先获得方法的参数列表类型,之后,把对象转入到object数组中,反射调用方法
避免错误,之前还需有个判空操作和是否包含有key,下面的贴出的代码就省略了
classmapping classmapping = classmappingmap.get(requesturl); class<?> controllerclass = classmapping.getcontrollerclass(); method method = classmapping.getmethod(); //获得方法的参数类型列表,之后根据此类型列表,对request传来的参数进行处理 class<?>[] parametertypes = method.getparametertypes(); //存放着之后需要传入到方法的参数值 object[] paramvalues = new object[parametertypes.length]; //对request传来的参数进行处理,将参数传给controller中的对应的方法,调用该方法 try { //实例化controller类 object o = controllerclass.newinstance(); for (int i = 0; i < parametertypes.length; i++) { //这里我们只考虑了四种情况,所以controller种的方法参数类型也只有四种 if (classutils.isassignable(parametertypes[i], httpservletrequest.class)) { paramvalues[i] = req; } else if (classutils.isassignable(parametertypes[i], httpservletresponse.class)) { paramvalues[i] = resp; } else if (classutils.isassignable(parametertypes[i], httpsession.class)) { paramvalues[i] = req.getsession(true); } else { //转为javabean if (parametertypes[i] != null) { //获得request传来的参数值,转为javabean map<string, string[]> parametermap = req.getparametermap(); //实例化这个javabean类型 object bean = parametertypes[i].newinstance(); //把数值快速转为bean beanutils.populate(bean, parametermap); paramvalues[i] =bean; } } } //调用方法,获得返回值,根据返回值,执行页面跳转或者是返回json数据等操作 object returnvalue = methodutils.invokemethod(o, true,method.getname(), paramvalues);
根据返回结果执行不同操作
这里使用google的gson框架,用于把实体类或者是list转为json数据
if (returnvalue instanceof string) { string value = (string) returnvalue; if (value.startswith("redirect:")) { //重定向 resp.sendredirect(req.getcontextpath()+ stringutils.substringafter(value,"redirect:")); } else { //请求转发 req.getrequestdispatcher(value).forward(req,resp); } } else { //返回一个对象 if (returnvalue != null) { //转为json,ajax操作 string json = new gson().tojson(o); resp.getwriter().print(json); }
使用注意点
不要忘了配置servlet,和之前的servlet一样,可以使用配置web.xml或者是注解方式进行配置
在方法requestmapping上的注解上的需要有"/开头",网页的请求url不需要"/"开头,但是需要.do结尾
由于我们只实现了四种类型的封装,所以controller类中里面的方法参数只能是四种类型,request、response、session、一个javabean类
代码
dispatchservlet
package mvc; import com.google.gson.gson; import org.apache.commons.beanutils.beanutils; import org.apache.commons.lang3.classutils; import org.apache.commons.lang3.stringutils; import org.apache.commons.lang3.reflect.methodutils; import org.apache.log4j.logger; import java.io.ioexception; import java.lang.reflect.invocationtargetexception; import java.lang.reflect.method; import java.util.map; import javax.servlet.servletconfig; import javax.servlet.servletexception; import javax.servlet.http.httpservlet; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import javax.servlet.http.httpsession; /** * @author starsone * @date create in 2019/8/9 0009 10:11 * @description */ public class dispatcherservlet extends httpservlet { private map<string, classmapping> classmappingmap =null; private logger logger = logger.getlogger(dispatcherservlet.class); @override public void init(servletconfig config) throws servletexception { super.init(config); //在servlet的初始化获得map数据 classmappingmap = new configuration().config(); } @override protected void doget(httpservletrequest req, httpservletresponse resp) throws servletexception, ioexception { dopost(req, resp); } @override protected void dopost(httpservletrequest req, httpservletresponse resp) throws servletexception, ioexception { //获得url地址 string servletpath = req.getservletpath(); string requesturl = stringutils.substringbefore(servletpath,".do"); //根据url地址去和map中的匹配,找到对应的controller类以及方法,之后反射传参调用 if (classmappingmap != null && classmappingmap.containskey(requesturl)) { classmapping classmapping = classmappingmap.get(requesturl); class<?> controllerclass = classmapping.getcontrollerclass(); method method = classmapping.getmethod(); //获得方法的参数类型列表,之后根据此类型列表,对request传来的参数进行处理 class<?>[] parametertypes = method.getparametertypes(); //存放着之后需要传入到方法的参数值 object[] paramvalues = new object[parametertypes.length]; //对request传来的参数进行处理,将参数传给controller中的对应的方法,调用该方法 try { //实例化controller类 object o = controllerclass.newinstance(); for (int i = 0; i < parametertypes.length; i++) { if (classutils.isassignable(parametertypes[i], httpservletrequest.class)) { paramvalues[i] = req; } else if (classutils.isassignable(parametertypes[i], httpservletresponse.class)) { paramvalues[i] = resp; } else if (classutils.isassignable(parametertypes[i], httpsession.class)) { paramvalues[i] = req.getsession(true); } else { //转为javabean if (parametertypes[i] != null) { //获得request传来的参数值,转为javabean map<string, string[]> parametermap = req.getparametermap(); //实例化这个javabean类型 object bean = parametertypes[i].newinstance(); //把数值快速转为bean beanutils.populate(bean, parametermap); paramvalues[i] =bean; } } } //调用方法,获得返回值,根据返回值,执行页面跳转或者是返回json数据等操作 object returnvalue = methodutils.invokemethod(o, true,method.getname(), paramvalues); if (returnvalue instanceof string) { string value = (string) returnvalue; if (value.startswith("redirect:")) { //重定向 resp.sendredirect(req.getcontextpath()+ stringutils.substringafter(value,"redirect:")); } else { //请求转发 req.getrequestdispatcher(value).forward(req,resp); } } else { //返回一个对象 if (returnvalue != null) { //转为json,ajax操作 string json = new gson().tojson(o); resp.getwriter().print(json); } } } catch (instantiationexception e) { logger.error("实例化controller对象错误!"); } catch (illegalaccessexception e) { logger.error("非法访问方法!"); } catch (invocationtargetexception e) { logger.error("invocation target exception"); } catch (nosuchmethodexception e) { logger.error("调用的方法不存在!"); } } else { throw new runtimeexception("url不存在" + requesturl); } } }
classmapping
用来存放全类名和方法名
package mvc; import java.lang.reflect.method; /** * 类class和对应的方法method * @author starsone * @date create in 2019/8/8 0008 22:13 * @description */ public class classmapping { private class<?> controllerclass; private method method; public classmapping(class<?> controllerclass, method method) { this.controllerclass = controllerclass; this.method = method; } public class<?> getcontrollerclass() { return controllerclass; } public void setcontrollerclass(class<?> controllerclass) { this.controllerclass = controllerclass; } public method getmethod() { return method; } public void setmethod(method method) { this.method = method; } @override public string tostring() { return controllerclass.getname() + "." + method.getname(); } }
configuration
package mvc; import org.apache.commons.lang3.classutils; import org.apache.commons.lang3.stringutils; import org.apache.commons.lang3.reflect.methodutils; import org.apache.log4j.logger; import java.io.file; import java.lang.reflect.method; import java.net.urisyntaxexception; import java.util.collections; import java.util.hashmap; import java.util.map; import java.util.resourcebundle; import mvc.annotation.controller; import mvc.annotation.requestmapping; /** * @author starsone * @date create in 2019/8/8 0008 22:11 * @description */ public class configuration { logger logger = logger.getlogger(configuration.class); private string getcontrollerpackage() { return resourcebundle.getbundle("package").getstring("packagepath"); } public map<string, classmapping> config() { map<string, classmapping> classmappingmap = collections.synchronizedmap(new hashmap<>()); try { //根据资源文件中定义的包名,找到控制器的文件夹,得到类名 file file = new file(getclass().getresource("/").touri()); string controllerpackage = getcontrollerpackage(); string controllerfullpath = file.getpath() + file.separator +controllerpackage.replaceall("\\.",file.separator); //controller类所在的文件夹 file = new file(controllerfullpath); //过滤文件,只找class文件 string[] classnames = file.list((dir, name) -> name.endswith(".class")); for (string classname : classnames) { //拼接全类名 string controllerfullname = controllerpackage + "." + stringutils.substringbefore(classname,".class"); class controllerclass = classutils.getclass(controllerfullname); //类是否有controller注解 if (controllerclass.isannotationpresent(controller.class)) { //找到controller类中标明requestmapping注解的所有方法 method[] methods = methodutils.getmethodswithannotation(controllerclass, requestmapping.class, true, true); for (method method : methods) { //获得注解上的路径 string path = method.getannotation(requestmapping.class).value(); //路径为key,保存 classmappingmap.put(path,new classmapping(controllerclass,method)); } } } } catch (urisyntaxexception | classnotfoundexception e) { e.printstacktrace(); } return classmappingmap; } public static void main(string[] args) { new configuration().config(); } }
注解
controller
package mvc.annotation; import java.lang.annotation.elementtype; import java.lang.annotation.retention; import java.lang.annotation.retentionpolicy; import java.lang.annotation.target; /** * @author stars-one at 2019-08-09 08:50 */ @target(elementtype.type) @retention(retentionpolicy.runtime) public @interface controller { string value() default ""; }
requestmapping
package mvc.annotation; import java.lang.annotation.elementtype; import java.lang.annotation.retention; import java.lang.annotation.retentionpolicy; import java.lang.annotation.target; /** * @author stars-one at 2019-08-09 08:50 */ @target(elementtype.method) @retention(retentionpolicy.runtime) public @interface requestmapping { string value() default ""; }
推荐阅读
-
Spring框架学习笔记(6)——阿里云服务器部署Spring Boot项目(jar包)
-
MVC使用Memcache+Cookie解决分布式系统共享登录状态学习笔记6
-
学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC路由(三)
-
JSP学习笔记(6)—— 自定义MVC框架
-
从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---08.框架的AOP功能和IOC功能测试
-
荐 从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---10. MVC 结果渲染器的编写
-
学习ASP.NET MVC框架揭秘笔记-PV与SC
-
struts2框架学习笔记6:拦截器
-
spring框架学习笔记6:JDBC模板
-
ASP.NET MVC 学习笔记-7.自定义配置信息(后续)