简介

    以前用了下SpringMVC感觉挺不错了,前段事件也简单了写了一些代码来实现了SpringMVC简单的请求分发功能,实现的主要思想如下:
  • 将处理请求的类在系统启动的时候加载起来,相当于SpringMVC中的Controller
  • 读取Controller中的配置并对应其处理的URL
  • 通过调度Servlet进行拦截请求,并找到相应的Controller进行处理

主要代码

首先得标识出来哪些类是Controller类,这里我自己定义的是ServletHandler,通过Annotation的方式进行标识,并配置每个类和方法处理的URL:
package com.meet58.base.servlet.annotation;

public @interface ServletHandler {

}

这里注解主要是声明这个类是一个ServletHandler类,用于处理请求的类,系统启动的时候就会加载这些类。

package com.meet58.base.servlet.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import com.meet58.base.servlet.types.RequestMethod;
import com.meet58.base.servlet.types.ResponseType; @Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HandlerMapping {
String value(); }

这个注解是配置处理请求的注解,定义了要处理的路径。

定义了注解之后就是要在系统启动的时候扫描并加载这些类,下面是如何进行扫描的代码:
package com.meet58.base.servlet.mapping;

import java.io.IOException;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils; import com.meet58.base.servlet.annotation.ServletHandler; public class ServletHandlerMappingResolver { private static final String RESOURCE_PATTERN = "/**/*.class"; private String[] packagesToScan; private ResourcePatternResolver resourcePatternResolver; private static final TypeFilter[] ENTITY_TYPE_FILTERS = new TypeFilter[] {
new AnnotationTypeFilter(ServletHandler.class, false)}; public ServletHandlerMappingResolver(){
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(new PathMatchingResourcePatternResolver());
} public ServletHandlerMappingResolver scanPackages(String[] packagesToScan){
try {
for (String pkg : packagesToScan) {
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;
Resource[] resources; resources = this.resourcePatternResolver.getResources(pattern); MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
for (Resource resource : resources) {
if (resource.isReadable()) {
MetadataReader reader = readerFactory.getMetadataReader(resource);
String className = reader.getClassMetadata().getClassName();
if (matchesFilter(reader, readerFactory)) {
ServletHandlerMappingFactory.addClassMapping(this.resourcePatternResolver.getClassLoader().loadClass(className));
}
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return this;
} public String[] getPackagesToScan() {
return packagesToScan;
} public void setPackagesToScan(String[] packagesToScan) {
this.packagesToScan = packagesToScan;
this.scanPackages(packagesToScan);
} private boolean matchesFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException {
for (TypeFilter filter : ENTITY_TYPE_FILTERS) {
if (filter.match(reader, readerFactory)) {
return true;
}
}
return false;
} }

这段代码是Spring中如何扫描Hibernate持久化对象的代码,拿过来借鉴了一下,下面要处理的就是把要处理的URL和相对应的ServletHandler进行匹配:

package com.meet58.base.servlet.mapping;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map; import org.apache.log4j.Logger; import com.meet58.base.servlet.annotation.HandlerMapping;
import com.meet58.base.servlet.context.ServletHandlerFactory; public class ServletHandlerMappingFactory { private static Logger logger = Logger.getLogger(ServletHandlerMappingFactory.class); private static Map<String, Method> servletHandlerMapping = new HashMap<String, Method>(); public static void addClassMapping(Class<?> clazz) {
String url = null;
HandlerMapping handlerMapping = clazz.getAnnotation(HandlerMapping.class);
if (handlerMapping != null) {
url = handlerMapping.value();
} else {
String classSimpleName = clazz.getSimpleName().toLowerCase();
url = "/" + classSimpleName.substring(0,
classSimpleName.indexOf("servlet"));
}
if (url != null) {
if(url.endsWith("/")){
url = url.substring(url.length() - 1);
}
ServletHandlerFactory.put(clazz);
logger.info(" Load servlet handler class:" + clazz.getName() + " url:" + url);
scanHandlerMethod(clazz,url);
}
} public static void scanHandlerMethod(Class<?> clazz,String classMapping) {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
HandlerMapping handlerMapping = method.getAnnotation(HandlerMapping.class);
if (handlerMapping != null && handlerMapping.value() != null) {
String mapping = handlerMapping.value();
if(!mapping.startsWith("/")){
mapping = "/" + mapping;
}
mapping = classMapping + mapping;
addMethodMapping( mapping,method);
}
}
} public static void addMethodMapping(String url,Method method) {
logger.info(" Load servlet handler mapping, method:" + method.getName() + " for url:" + url);
Method handlerMethod = servletHandlerMapping.get(url);
if(handlerMethod != null){
throw new IllegalArgumentException(" url :" + url + " is already mapped by :" + handlerMethod);
}else{
servletHandlerMapping.put(url, method);
}
} public static Method getMethodMapping(String url) {
return servletHandlerMapping.get(url);
}
}

在这个类中扫描了每个ServletHandler类中的方法,并记录他们的要处理的URL,接下来就是通过容器实例化这些ServletHandler类了:

package com.meet58.base.servlet.context;

import java.util.HashMap;
import java.util.Map; import org.apache.log4j.Logger; public class ServletHandlerFactory { private static Logger logger = Logger.getLogger(ServletHandlerFactory.class); private static Map<String,Object> classes = new HashMap<String,Object>(); public static void put(Class<?> clazz){
try {
logger.info("初始化ServletHandler类:"+ clazz.getName());
Object servlet = clazz.newInstance();
classes.put(clazz.getName(), servlet);
} catch (InstantiationException e) {
logger.error("初始化Servlet类:" + clazz.getName() + "失败:" + e.getMessage());
} catch (IllegalAccessException e) {
logger.error("初始化Servlet类:" + clazz.getName() + "失败:" + e.getMessage());
}
} @SuppressWarnings("unchecked")
public static <T> T get(String className){
return (T)classes.get(className);
}
}

在ServletHandler类处理完成,并知道他们分别处理哪些URL之后,就可以通过一个调度器进行对对应的URL进行请求的分发了:

package com.meet58.base.servlet;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List; import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import com.meet58.base.context.WebHttpRequestContext;
import com.meet58.base.servlet.context.ServletHandlerFactory;
import com.meet58.base.servlet.mapping.ServletHandlerMappingFactory;
import com.meet58.util.WebUtils; @WebServlet(urlPatterns = { "*.do" })
public class WebHttpDispatchServlet extends HttpServlet { private static final long serialVersionUID = 1L; private Logger logger = Logger.getLogger(this.getClass()); private List<String> excludeUrls = new ArrayList<String>(); @Override
public void init() throws ServletException {
// 屏蔽websocket地址
excludeUrls.add("/meet.do");
super.init();
} public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
} public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
String url = request.getRequestURI().replace(
request.getContextPath(), "");
if (excludeUrls.contains(url)) {
return;
}
Method handlerMethod = ServletHandlerMappingFactory.getMethodMapping(url);
if (handlerMethod == null) {
response.sendError(404, "No handler found for " + url);
logger.error("No handler found for " + url);
return;
} Object servlet = ServletHandlerFactory.get(handlerMethod
.getDeclaringClass().getName()); if (servlet == null) {
response.sendError(404, "No handler class found for " + url);
logger.error("No handler class found for " + url);
return;
} Object result = invokeHandlerMethod(servlet, handlerMethod);
handleInvokeResult(result); // this.doService();
} catch (Throwable e) {
handlerException(e);
}
} public void handleInvokeResult(Object result) {
String location = "";
if (result instanceof String) {
if (((String) result).startsWith("redirect:")) {
location = ((String) result).substring("redirect:".length(),
((String) result).length());
WebUtils.redirect(location);
} else if (((String) result).startsWith("forward:")) {
location = ((String) result).substring("forward:".length(),
((String) result).length());
WebUtils.forward(location);
}
}
} public Object invokeHandlerMethod(Object object, Method method)
throws Throwable {
Object result = null;
if (method != null) {
try {
result = method.invoke(object);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
return result;
} public void handlerException(Throwable e) {
String message = e.getMessage() != null ? e.getMessage() : e.toString();
e.printStackTrace();
if (WebHttpRequestContext.isAsyncRequest()) {
WebUtils.writeFailure(message);
} else {
try {
WebHttpRequestContext.getResponse().sendError(500, message);
} catch (IOException e1) {
e1.printStackTrace();
}
}
} public String getMappingClass(String url) {
return null;
} }

这段代码中就是通过URL找到对应的处理方法来进行处理,并且捕获异常。

这种方法Struts也是用到了,不过这个只是简单的兴趣研究并没有在实际项目中运用,可能会存在线程安全的问题。

SpringMVC请求分发的简单实现的更多相关文章

  1. SpringMVC源码学习:容器初始化+MVC初始化+请求分发处理+参数解析+返回值解析+视图解析

    目录 一.前言 二.初始化 1. 容器初始化 根容器查找的方法 容器创建的方法 加载配置文件信息 2. MVC的初始化 文件上传解析器 区域信息解析器 handler映射信息解析 3. Handler ...

  2. 2.5万字长文简单总结SpringMVC请求参数接收

    这是公众号<Throwable文摘>发布的第22篇原创文章,暂时收录于专辑<架构与实战>.暂定下一篇发布的长文是<图文分析JUC同步器框架>,下一篇发布的短文是&l ...

  3. SpringMVC核心分发器DispatcherServlet分析[附带源码分析]

    目录 前言 DispatcherServlet初始化过程 DispatcherServlet处理请求过程 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不 ...

  4. SpringMVC核心分发器DispatcherServlet分析

    本文将分析SpringMVC的核心分发器DispatcherServlet的初始化过程以及处理请求的过程,让读者了解这个入口Servlet的作用. DispatcherServlet初始化过程 在分析 ...

  5. java面试记录二:spring加载流程、springmvc请求流程、spring事务失效、synchronized和volatile、JMM和JVM模型、二分查找的实现、垃圾收集器、控制台顺序打印ABC的三种线程实现

    注:部分答案引用网络文章 简答题 1.Spring项目启动后的加载流程 (1)使用spring框架的web项目,在tomcat下,是根据web.xml来启动的.web.xml中负责配置启动spring ...

  6. SpringMVC请求流程源码分析

    一.SpringMVC使用 1.工程创建 创建maven工程. 添加java.resources目录. 引入Spring-webmvc 依赖. <dependency> <group ...

  7. Spring框架系列(5) - 深入浅出SpringMVC请求流程和案例

    前文我们介绍了Spring框架和Spring框架中最为重要的两个技术点(IOC和AOP),那我们如何更好的构建上层的应用呢(比如web 应用),这便是SpringMVC:Spring MVC是Spri ...

  8. jQuery源码分析系列(33) : AJAX中的前置过滤器和请求分发器

    jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求,分别是: 1.前置过滤器 jQuery. ajaxPrefilter 2.请求分发器 jQuery. ajaxTran ...

  9. 详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析]

    目录 前言 源码分析 重要接口介绍 SpringMVC初始化的时候做了什么 HandlerExecutionChain的获取 实例 资源文件映射 总结 参考资料 前言 SpringMVC是目前主流的W ...

随机推荐

  1. push 栈顶sp=sp-2 可以把立着的栈,向左侧倒下,那么形态就和反汇编时,内存的形态是一样的。小偏移的字节在前, 大的偏移字节在后

    push  栈顶sp=sp-2 可以把立着的栈,向左侧倒下,那么形态就和反汇编时,内存的形态是一样的.小偏移的字节在前, 大的偏移字节在后. 1 2 3 4 5 1 2 3 4 5

  2. cocos-html5 JS 写法基础 语言核心

    转载:http://blog.csdn.net/leasystu/article/details/18735797 cocos2dx 3.0 js继承:John Resiq的继承写法解析 CCClas ...

  3. [译]GotW #4 Class Mechanics

    你对写一个类的细节有多在行?这条款不仅注重公然的错误,更多的是一种专业的风格.了解这些原则将会帮助你设计易于使用和易于管理的类. JG Question 1. 什么使得接口“容易正确使用,错误使用却很 ...

  4. JAVA信号量使用示例程序

    JAVA里也有强大的信号量工具Semaphore,使用信号量可以很方便的实现线程同步功能,以解决类似于 生产者——消费者 之类的典型问题. 下面是我的测试程序,大部分看注释就可以明白用法: impor ...

  5. 【Python Network】分解DNS查询结果

    针对DNS查询records,通过NS.PTR.CNAME和MX类别不同,返回数据将包含另外主机名.为了解最终的IP地址,通过将返回信息分解.继续使用PyDNS获取详细信息. #! /usr/bin/ ...

  6. GL_INTERFACE

    prompt ****************************************************************************** 总账接口主要完成其他模块的总 ...

  7. 函数lock_rec_find_similar_on_page

    /*********************************************************************//** Looks for a suitable type ...

  8. Reflector+Reflexil 相结合实现对DLL文件修改

    在工作过程中,我们有可能遇到这样的问题:公司发给客户的软件包突然报错了,但是你知道哪里报错了,而这个代码已经编译成DLL文件了,源代码不在自己这里.怎么办呢?还好现在有Reflexil插件,这个插件只 ...

  9. BZOJ_1612_[Usaco2008_Jan]_Cow_Contest_奶牛的比赛_(dfs)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1612 \(n\)头奶牛比赛,给出一些胜负情况,问可以确定多少头奶牛的排名. 分析 无论胜负,只 ...

  10. BZOJ_1609_[Usaco2008_Feb]_Eating_Together_麻烦的聚餐_(动态规划,LIS)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1609 给出一串由1,2,3组成的数,求最少需要改动多少个数,使其成为不降或不升序列. 分析 法 ...