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 "";
}
JSP学习笔记(6)—— 自定义MVC框架的更多相关文章
- PyQt 学习笔记1——自定义窗口框架
自定义一个属于自己的窗口,初始化时自动设置好在屏幕中央显示,重写退出事件的触发器 closeEvent(),并增加了设置图标,简化设置标题的函数名,其它类可以继承它: # -*- coding: ut ...
- ASP.NET MVC 学习笔记-7.自定义配置信息 ASP.NET MVC 学习笔记-6.异步控制器 ASP.NET MVC 学习笔记-5.Controller与View的数据传递 ASP.NET MVC 学习笔记-4.ASP.NET MVC中Ajax的应用 ASP.NET MVC 学习笔记-3.面向对象设计原则
ASP.NET MVC 学习笔记-7.自定义配置信息 ASP.NET程序中的web.config文件中,在appSettings这个配置节中能够保存一些配置,比如, 1 <appSettin ...
- jsp学习笔记:mvc开发模式
jsp学习笔记:mvc开发模式2017-10-12 22:17:33 model(javabe)与view层交互 view(视图层,html.jsp) controller(控制层,处理用户提交的信息 ...
- 自定义MVC框架
我们在学习自定义MVC框架的时候常常会听到Model1 ,Model2和MVC.那么什么是Model1 什么是Model2什么又是MVC呢? 什么是Model1? Model1就是一种纯jsp开发技术 ...
- JSP学习笔记
JSP学习笔记 Jsp网页主要分为Elements与Template Data两部分. Template Data:JSP Container不处理的部分,例如HTML内容 Elements:必须经由 ...
- Struts2 自定义MVC框架
一.Model1与Model2: Model1:就是一种纯jsp开发技术,将业务逻辑代码和视图渲染代码杂糅在一起. Model2:Model2是在Model1的基础上,将业务逻辑的代码分离开来,单独形 ...
- 第一章 自定义MVC框架
第一章 自定义MVC框架1.1 MVC模式设计 组成:Model:模型,用于数据和业务的处理 View :视图,用于数据的显示 Controller:控制器 ...
- Java Web自定义MVC框架详解 (转)
转自:http://blog.csdn.net/jackfrued/article/details/42774459 最近给学生讲Java Web,希望他们能够在学完这部分内容后自己实现一个MVC框架 ...
- JSP 学习笔记1
JSP 学习笔记 JSP是Servlet的一种特殊形式,每个JSP页面就是一个Servlet实例--JSP页面有系统编译成Servlet,Servlet再负责响应用户请求. 1.JSP注释 < ...
- [转载]SharePoint 2013搜索学习笔记之自定义结果源
搜索中心新建好之后在搜索结果页上会默认有所有内容,人员,对话,视频这四个结果分类,每个分类会返回指定范围的搜索结果,这里我再添加了部门日志结果分类,搜索这个分类只会返回部门日志内容类型的搜索结果,要实 ...
随机推荐
- Netty基础系列(4) --堆外内存与零拷贝详解
前言 到目前为止,我们知道Nio当中有三个最最核心的组件,分别是:Selelctor,Channel,Buffer.在Netty基础系列(3) --彻底理解NIO 这一篇文章中只是进行了大致的介绍. ...
- Java并发编程实战笔记—— 并发编程3
1.实例封闭 class personset{ private final Set<Person> myset = new HashSet<Person>(); public ...
- dart的基本语法(一)
Hello world 安装dart的环境就不赘述了,无脑安装就可以了,安装过程中好像需要梯子(vpn),我装的时候失败好多次,我的梯子不能用了,准备不装了的时候,莫名其妙的装好了.迷の操作.惯例 ...
- 解决微信二次分享失败--后面被加上from=singlemessage&isappinstalled=0的解决方案
首次分享成功,点开后再次分享或第三次分享就失败了 1.检查你分享的链接,看是否多了两个参数,微信分享会根据分享的不同,为原始链接拼接: 朋友圈 from=timeline&isappins ...
- 昂贵的聘礼 POJ - 1062
题目链接:https://vjudge.net/problem/POJ-1062 如图,我们可以把交换的情况,抽象为一个有向图, 先抛去等级限制,那么就是一个最短路,从①出发,到达其他点的最短路中 最 ...
- Selenium+java - Page Object设计模式
前言 Page Object(页面对象)模式,是Selenium实战中最为流行,并且被自动化测试同学所熟悉和推崇的一种设计模式之一.在设计测试时,把页面元素定位和元素操作方法按照页面抽象出来,分离成一 ...
- spring-boot-plus项目打包(七)
spring-boot-plus项目打包 项目打包 spring-boot-plus项目使用maven assembly插件进行打包 根据不同环境进行打包部署 包含启动.重启命令,配置文件提取到外部c ...
- activemq的下载与安装
一.介绍 Apache ActiveMQ™是最流行的开源,多协议,基于Java的消息服务器.它支持行业标准协议,因此用户可以通过广泛的语言和平台获得客户选择的好处.可以使用C,C ++,Python, ...
- Net微信网页开发之使用微信JS-SDK获取当前地理位置
前言: 前段时间有一个关于通过获取用户当前经纬度坐标,计算出该用户距离某指定地点之间的距离.因为做这个项目需要能够获取到比较精确的经纬度坐标,刚开始使用的是百度地图结果发现百度地图地位不太准确(有时候 ...
- python 36 进程池、线程池
目录 1. 死锁与递归锁 2. 信号量Semaphor 3. GIL全局解释器锁:(Cpython) 4. IO.计算密集型对比 4.1 计算密集型: 4.2 IO密集型 5. GIL与Lock锁的区 ...