前言:
在spring全家桶流行的当下,只要你做的是Java技术栈基本上95%以上都使用的spring或springboot框架,剩下的5%基本上是一些老项目,政府项目,银行项目之类对安全性要求比较高的项目,比如之前前段时间的log4j2,Spring Getway远程代码执行漏洞;总有意想不到的BUG,所以有些公司也会自己封装框架。那么我们也根据SpringMVC的基本实现原理,基于Servlet类封装一个自己的MVC框架。
正题:
聊聊技术选型:
为了是框架足够简单且可以实现mvc基本功能,我们这里只引用少量的外部类库,项目基于JDK8,JavaWeb,并采用Maven的方式进行构建。
采用的设计模式:策略模式,观察者模式
使用技术:JavaEE,Maven,反射技术
项目结构如下:
com.kexun annotation #mvc及orm所使用的到的注解 controller #基于框架的示例代码 dao #数据库操作类,存放sql db #orm框架源码 entity #实现类 mvc #mvc框架实现源码 utils #依赖的工具类
基本实现逻辑:
- 实现容器监听器,项目启动时扫描@ReqrestMapping标记的方法以请求路径为key 对应的方法Method为value put到map,并同时初始化参数解析器创建统一拦截Servlet,根据请求路径去map寻找对应的处理器方法通过反射获取所要调用的方法参数,遍历参数解析器寻找合适的解析器方法,对参数进行解析注入controller处理相关业务逻辑,并标记相应注解根据注解标记判断是转发到页面还是返回文本数据
代码实现:
- 创建容器类,初始化时的数据
public class Containers {
//用于存储controller对应的路径及方法对象
public static HashMap mappingMethods = new HashMap<>();
//存储参数解析器
public static List resolvers = new ArrayList<>();
}
2.初始化mapping 和 resolvers
public class InitMapping implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//扫描controller包到容器
Set> classes = ClassUtils.getClasses("com.kexun.controller");
//遍历controller包下所有的类
for (Class> aClass : classes) {
//获取类注解RequestMapping
RequestMapping classMapping = aClass.getAnnotation(RequestMapping.class);
String classMappingVal = null;
if (classMapping != null) {
//对类上的RequestMapping值进行拼接 并对多余的 / 进行处理
classMappingVal = classMapping.value().replace("/", "");
}
//反射获取类所有方法
Method[] methods = aClass.getMethods();
for (Method method : methods) {
RequestMapping mapping = method.getAnnotation(RequestMapping.class);
//获取并且判断是否存在RequestMapping 注解 如不存在则不做操作
if (mapping != null) {
String methodMappingVal = mapping.value().replace("/", "");
//String key = "/" + classMappingVal + "/" + methodMappingVal;
String key = "";
//组装方法请求路径
if (classMappingVal != null) {
key += ("/" + classMappingVal);
}
key += ("/" + methodMappingVal);
boolean b = Containers.mappingMethods.containsKey(key);
if (b) {
throw new RuntimeException("存在相同方法路径" + key);
} else {
System.out.println("初始化Controller:" + key);
//put到map容器
Containers.mappingMethods.put(key, method);
}
}
}
}
//初始化解析器到容器
Containers.resolvers.add(new IntResolver());
Containers.resolvers.add(new baseEntityResolver());
Containers.resolvers.add(new HttpServletRequestResolver());
Containers.resolvers.add(new HttpServletResponseResolver());
Containers.resolvers.add(new HttpSessionResolver());
Containers.resolvers.add(new StringResolver());
Containers.resolvers.add(new RequestBodyResolver());
}
}
3.参数解析器
根据目标方法的参数类型进行解析,我这里实现了几个常用的参数类型解析器,如需扩展可以实现 baseResolver接口
//解析策略接口
public interface baseResolver {
boolean supportsParameter(Parameter parameter);
Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp);
}
**boolean supportsParameter方法:**判断参数是否符合预期类型
**Object resolveArgument方法:**完成参数的解析逻辑
各解析器实现如下:
//String类型参数解析器
public class StringResolver implements baseResolver {
@Override
public boolean supportsParameter(Parameter parameter) {
return parameter.getType().isAssignableFrom(String.class);
}
@Override
public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
return req.getParameter(parameter.getName());
}
}
//int类型的参数解析器
public class IntResolver implements baseResolver {
@Override
public boolean supportsParameter(Parameter parameter) {
return parameter.getType().isAssignableFrom(int.class) || parameter.getType().isAssignableFrom(Integer.class);
}
@Override
public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
System.out.println("设置参数:" + parameter.getName());
return Integer.parseInt(req.getParameter(parameter.getName()));
}
}
//有标记 RequestBody 参数的解析
public class RequestBodyResolver implements baseResolver {
@Override
public boolean supportsParameter(Parameter parameter) {
return parameter.getAnnotation(RequestBody.class) != null;
}
@Override
public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
String bodyData = getBodyData(req);
System.out.println("body:"+bodyData);
try {
//将JSON类型的参数映射到Jav实体类
return JSON.toJavaObject(JSON.parseObject(bodyData), parameter.getType().newInstance().getClass());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//获取请求body数据
private String getBodyData(HttpServletRequest request) {
StringBuffer data = new StringBuffer();
String line = null;
BufferedReader reader = null;
try {
reader = request.getReader();
while (null != (line = reader.readLine())) {
data.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return data.toString();
}
}
//HttpSession类型参数解析器
public class HttpSessionResolver implements baseResolver {
@Override
public boolean supportsParameter(Parameter parameter) {
return parameter.getType().isAssignableFrom(HttpSession.class);
}
@Override
public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
return req.getSession();
}
}
//HttpServletResponse 类型的参数解析器
public class HttpServletResponseResolver implements baseResolver {
@Override
public boolean supportsParameter(Parameter parameter) {
return parameter.getType().isAssignableFrom(HttpServletResponse.class);
}
@Override
public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
return resp;
}
}
//HttpServletRequest类型的参数解析器
public class HttpServletRequestResolver implements baseResolver {
@Override
public boolean supportsParameter(Parameter parameter) {
return parameter.getType().isAssignableFrom(HttpServletRequest.class);
}
@Override
public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
return req;
}
}
//baseEntity 实现了baseEntity的类的解析器 解析参数到实体类
public class baseEntityResolver implements baseResolver {
@Override
public boolean supportsParameter(Parameter parameter) {
try {
return parameter.getType().newInstance() instanceof baseEntity;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return false;
}
@Override
public Object resolveArgument(Parameter parameter, HttpServletRequest req, HttpServletResponse resp) {
Enumeration parameterNames = req.getParameterNames();
Object o = null;
try {
o = parameter.getType().newInstance();
while (parameterNames.hasMoreElements()) {
String s = parameterNames.nextElement();
String parameter1 = req.getParameter(s);
BeanUtils.setProperty(o, s, parameter1);
}
} catch (Exception e) {
e.printStackTrace();
}
return o;
}
}
4.注册Servlet拦截所有请求,并根据请求路径分发到对应处理器方法 代码实现如下:
public class WebServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
Enumeration parameterNames = req.getParameterNames();
String contextPath = req.getServletPath();
System.out.println(contextPath);
Method method = Containers.mappingMethods.get(contextPath);
if (method == null) {
//请求路径不存在时 返回404 也可以重定向或转发到404页面
resp.sendError(404, "页面不存在");
} else {
//获取请求类型
String contentType = req.getContentType();
String method1 = req.getMethod();
System.out.println("method:" + method1 + " contentType:" + contentType);
try {
//获取方法所在的类并实例化
Object o = method.getDeclaringClass().newInstance();
//获取方法所有的对象
Parameter[] parameters = method.getParameters();
//存放方法参数解析注入后的值 参数列表
ArrayList
5.所用到的注解标记
RequestBody
@Target({ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface RequestBody {
}
RequestMapping
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface RequestMapping {
String value() default "";
}
ResponseBody
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface ResponseBody {
}
6.所用到工具类
ClassUtils
public class ClassUtils {
public static Set> getClasses(String pack) {
// 第一个class类的集合
Set> classes = new linkedHashSet>();
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageName = pack;
String packageDirName = packageName.replace('.', '/');
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(
packageDirName);
// 循环迭代下去
while (dirs.hasMoreElements()) {
// 获取下一个元素
URL url = dirs.nextElement();
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
System.err.println("file类型的扫描");
// 获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath,
recursive, classes);
} else if ("jar".equals(protocol)) {
// 如果是jar包文件
// 定义一个JarFile
System.err.println("jar类型的扫描");
JarFile jar;
try {
// 获取jar
jar = ((JarURLConnection) url.openConnection())
.getJarFile();
// 从此jar包 得到一个枚举类
Enumeration entries = jar.entries();
// 同样的进行循环迭代
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如meta-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == '/') {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx)
.replace('/', '.');
}
// 如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive) {
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class")
&& !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(
packageName.length() + 1, name
.length() - 6);
try {
// 添加到classes
classes.add(Class
.forName(packageName + '.'
+ className));
} catch (ClassNotFoundException e) {
// log
// .error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
} catch (IOException e) {
// log.error("在扫描用户定义视图时从jar包获取文件出错");
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
public static void findAndAddClassesInPackageByFile(String packageName,
String packagePath, final boolean recursive, Set> classes) {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
// log.warn("用户定义包名 " + packageName + " 下没有任何文件");
return;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
@Override
public boolean accept(File file) {
return (recursive && file.isDirectory())
|| (file.getName().endsWith(".class"));
}
});
// 循环所有文件
for (File file : dirfiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(packageName + "."
+ file.getName(), file.getAbsolutePath(), recursive,
classes);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0,
file.getName().length() - 6);
try {
// 添加到集合中去
//classes.add(Class.forName(packageName + '.' + className));
//经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
} catch (ClassNotFoundException e) {
// log.error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
7.web.xml 配置:
Archetype Created Web Application com.kexun.mvc.listen.InitMapping webServlet com.kexun.mvc.servlet.WebServlet 1 default org.apache.catalina.servlets.DefaultServlet debug 0 listings false 0 default /static/* webServlet /
8.项目所用到的类库
javax.servlet javax.servlet-api 3.1.0 commons-beanutils commons-beanutils 1.9.3 mysql mysql-connector-java 5.1.47 com.alibaba fastjson 1.2.73 javax.servlet jstl 1.2 javax.servlet jstl 1.2 org.apache.tomcat tomcat-catalina 8.5.61
9.基本使用
使用方法基本与SpringMvc一致,注解大同小异 。
示例代码如下:
public class IndexController {
//获取字符串,int类型参数 返回页面
@RequestMapping("index")
public String index(HttpServletRequest request, String username, Integer age) throws Exception {
request.setAttribute("username", username);
request.setAttribute("age", age);
return "index";
}
//获取对象参数 返回对象
@ResponseBody
@RequestMapping("addManage")
public Map addManage(Manage manage) throws Exception {
System.out.println("manage:" + manage);
Map result = new HashMap<>();
result.put("code", 0);
result.put("message", "添加成功");
return result;
}
//获取JSON请求 解析为对象 返回对象类型
@ResponseBody
@RequestMapping("addManageJSON")
public Map addManageJSON(@RequestBody Manage manage) throws Exception {
System.out.println("manage:" + manage);
Map result = new HashMap<>();
result.put("code", 0);
result.put("message", "添加成功");
return result;
}
}
此框架实用与一些小项目,案例,需要快速搭建的,开箱即用的场景;没有复杂的配置,同时也适用于学习,不允许适用开源框架的场景下,这便是个很好的选择
项目源码已上传至码云:
https://gitee.com/gdianqimeng/kexun-mvc-orm
个人博客地址:https://muzidong.com
大家有什么好的建议欢迎在下方评论区留言,我会逐条回复
同时也欢迎大家关注我的微信公众号:
微信搜【程序员MuziDong】或扫描下方二维码,实时获取最新动态。
程序员MuziDong



