仿照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 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 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 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 config() {
map 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 "";
}
