仿照SpringMVC,实现一个轻量级MVC框架,知识涉及到了反射机制、注解的使用和一些第三方工具包的使用
主要的总体流程如下图所示
和之前一样,我们定义了一个DispatchServlet,用于拦截请求(这里一般拦截.do结尾的url请求);
之后,DispatchServlet会根据url,找到Controller中对应的方法并执行,返回一个结果。
我们根据返回的结果,来DispatchServlet执行不同的操作(请求转发、页面重定向、返回json数据)
Controller RequestMapping
redirect: redirect:
一般我们会让doGet方法也调用doPost方法,所以我们只需要重写Servlet中的doPost方法
由上面的思路,我们可以得到细化的流程图(也就是doPost方法中的流程)
//获得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及其对应的方法
controllerClass.isAnnotationPresent(Controller.class)
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类
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); } } }
用来存放全类名和方法名
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(); } }
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(); } }
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 ""; }
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 ""; }