Java开发Web相关基本离不开Spring家族了。前段时间跟着书撸了遍SpringMVC的源码,虽然代码挺繁琐的,但是核心思想还是基于Servlet处理HTTP请求,于是有了自己写一个简单版实现的想法。
首先理一下思路,特定路径的的HTTP请求交给Servlet去处理(这里拦截所有的请求),Servlet里重写doGet doPost方法根据url去分发给特定的方法去处理。这里采用注解的方式标识类和方法,让servlet能找到。那么类和方法需要提前实例化好放入容器里面以便调用。
确定一下需要几个注解。标识类是处理分发的Controller的注解 JesseController。这里加一个我的名字和Spring的区分开。servlet根据url分发处理,需要一个JesseMapping来标识类和方法是处理哪一个url请求的(Spring的RequestMapping)。以及标识参数名的JesseParam。这里的这个Param注解原本不想要,因为是极简版。但是写的过程中发现,方法的参数名没有办法通过反射获取。虽然Java8改JVM启动参数的方式能获取到。于是加了个Param注解,区分一下1个以上的请求参数。
接收到HTTP请求后,servlet根据url找到方法,需要一个 map,key是url,value是方法。方法调用需要指定类,那么需要一个map,key是url,value是方法所在的类对象。启动时扫描指定的包,获取所有jesseController注解的类,需要一个iocMap去装所有的controller。
如果一个url没有方法去处理,那么返回404提示。
上代码:
首先web.xml 拦截所有的请求
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>MyMVC</display-name>
<servlet>
<servlet-name>MyMVC</servlet-name>
<servlet-class>cn.jesseyang.servlet.MyServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MyMVC</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
注解的声明,应用目标不同,类 方法 参数
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JesseController {
String value() default "";
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JesseMapping {
String value() default "";
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JesseParam {
String value() default "";
}
myServlet比较简单的处理了String类型的参数
public class MyServlet extends HttpServlet {
private static final String packageNames="cn.jesseyang.controller";
// 根据类名找controller
private Map<String,Class > iocMap = new HashMap<>(64);
//根据url找处理的方法
private Map<String, Method> handlerMapping = new HashMap<>(64);
//根据url找到处理的类 反射调用方法的时候需要用到
private Map<String,Object> controllerMap = new HashMap<>(64);
@Override
public void init() throws ServletException {
try {
//扫描所有的包,将带有controller注解的放到ioc容器里待用
scanPackages();
//把ioc容器里的controller类遍历找到JesseMapping注解的方法,拼url,初始化handlerMapping和controllerMap
//这一步就可以把访问的路径和处理方法对应起来
dealHandlerMapping();
}catch (Exception e){
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPut(req,resp);
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
dispatch(req,resp);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private void dispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
String url =req.getRequestURI();
String contextPath = req.getContextPath();
url=url.replace(contextPath, "").replaceAll("/+", "/");
if(!handlerMapping.containsKey(url)){
resp.getWriter().write("404 !!! 服务器没有这个页面哦");
return;
}
Method method = handlerMapping.get(url);
//方法的参数类型
Class[] parameterTypes = method.getParameterTypes();
Parameter[] parameters = method.getParameters();
//从请求中获取参数列表 key是参数名,value是对象
Map<String,String[]> parameterMap = req.getParameterMap();
//方法反射调用需要的参数 Object[] 里面放从请求过来的参数
Object[] methodParameter = new Object[parameterTypes.length];
for (int i=0;i<parameterTypes.length;i++){
//获取方法名
String parameterType = parameterTypes[i].getSimpleName();
if(parameterType.equals("HttpServletRequest")){
methodParameter[i] = req;
continue;
}
if(parameterType.equals("HttpServletResponse")){
methodParameter[i] = resp;
continue;
}
//只简单处理String类型的
if(parameterType.equals("String")){
//获取参数的注解
JesseParam jesseParam = parameters[i].getAnnotation(JesseParam.class);
//获取到请求的参数,是个数组类型的,我一查原来是防止参数重名的
String[] requestParam = parameterMap.get(jesseParam.value());
if(requestParam != null && requestParam.length>0){
//简单的将数组转换为String然后加到反射用的方法参数数组里
methodParameter[i]= Arrays.toString(requestParam).replaceAll("[\\[\\]]","")
.replaceAll(",","");
}
}
}
method.invoke(controllerMap.get(url),methodParameter);
}
private void dealHandlerMapping() throws IllegalAccessException, InstantiationException {
if(iocMap.isEmpty())return;
for (Map.Entry<String,Class> entry :iocMap.entrySet()){
Class controllerClazz = entry.getValue();
//接下来拼url
String requestUrl = "";
if (controllerClazz.isAnnotationPresent(JesseMapping.class)){
JesseMapping jesseMapping = (JesseMapping) controllerClazz.getAnnotation(JesseMapping.class);
requestUrl = jesseMapping.value();
}
Method[] controllerMethods = controllerClazz.getMethods();
for (Method method : controllerMethods){
if (method.isAnnotationPresent(JesseMapping.class)){
requestUrl += method.getAnnotation(JesseMapping.class).value();
handlerMapping.put(requestUrl,method);
controllerMap.put(requestUrl,controllerClazz.newInstance());
}
}
}
}
private void scanPackages() throws IllegalAccessException, ClassNotFoundException, InstantiationException {
String packagePath = this.getClass().getResource("/"+packageNames.replaceAll("\\.","/")).getPath();
findClassWithAnnotation(packagePath);
}
private void findClassWithAnnotation(String filePath) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
File file = new File(filePath);
File[] files = file.listFiles();
for(File f:files){
if (f.isDirectory()){
findClassWithAnnotation(f.getPath());
}else{
//通过反射实例化类,放到map里面,key是类名
putClass2IOCMap(f);
}
}
}
private void putClass2IOCMap(File f) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class clazz = Class.forName(transferPath2PackagePath(f));
if (clazz!=null && clazz.isAnnotationPresent(JesseController.class)){
iocMap.put(clazz.getSimpleName(),clazz);
}
}
private String transferPath2PackagePath(File f ){
return packageNames+"."+f.getName().replace(".class","");
}
}
测试Controller 把参数格式化一下返回回去
@JesseController
@JesseMapping("/jesse")
public class TestController {
@JesseMapping("/login")
public void index(
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
@JesseParam("user") String user, @JesseParam("password")String password ){
try {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("user:");
stringBuilder.append(user);
stringBuilder.append("<br>");
stringBuilder.append("password:");
stringBuilder.append(password);
httpServletResponse.getWriter().write(stringBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
测试一下
ok,测一下空页面
一个简单的springMVC就搞定了。
gitee地址
https://gitee.com/jesseyang/jesse-springMVC-simple