先放一张网上的很好的一张原理图
图片出自,这篇博客原理也写的很清晰明了。我的实现也是借鉴了这张图
先说一下我的实现思路:
1 在MyDispatcherServlet中的servlet初始化的时候,绑定标有@MyController注解类下面的@MyRequestMappign的value值和对应的方法。绑定的方式是放在map集合中。这个map集合就是上图说的handlerMapping,返回的handler也就是一组键值对。
2 找到对应的方法后,反射执行方法,在方法中创建一个modelandview对象,model也就是我们说的数据域,view返回的是一个视图名称,也就是我们说的视图域,当然,我这里只有jsp,spring做的很复杂。支持多种类型。最后所谓的渲染,也就是将这个数据域中的数据会添加到request请求中,然后转发。返回客户端。
3 绑定参数模型这一部分略为复杂。在下面讲解
下面是MyDispatcherServlet
这个servlet的作用就是接收用户请求,然后派发注意标红处bingdingMethodParamters方法,这个方法实现了参数的绑定。
package spring.servlet; import lombok.extern.slf4j.Slf4j; import spring.factory.InitBean; import spring.springmvc.Binding; import spring.springmvc.Handler; import spring.springmvc.MyModelAndView; import spring.springmvc.ViewResolver; import javax.servlet.ServletContext; 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 java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.Set; import static spring.springmvc.BindingRequestAndModel.bindingRequestAndModel; /** * Created by Xiao Liang on 2018/6/27. */ @WebServlet(name = "MyDispatcherServlet") @Slf4j public class MyDispatcherServlet extends HttpServlet { /** * 初始化servlet,将bean容器和HandlerMapping放到servlet的全局变量中 */ @Override public void init() { InitBean initBean = new InitBean(); initBean.initBeans(); //根据bean容器中注册的bean获得HandlerMapping Map<String, Method> bindingRequestMapping = Handler.bindingRequestMapping(initBean.beanContainerMap); ServletContext servletContext = this.getServletContext(); servletContext.setAttribute("beanContainerMap", initBean.beanContainerMap); servletContext.setAttribute("bindingRequestMapping", bindingRequestMapping); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { doDispatch(request, response); } catch (Exception e) { log.error("控制器处理异常"); e.printStackTrace(); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } //接收到请求后转发到相应的方法上 private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws IOException, InvocationTargetException, IllegalAccessException, InstantiationException { ServletContext servletContext = this.getServletContext(); //获取扫描controller注解后url和方法绑定的mapping,也就是handlerMapping Map<String, Method> bindingRequestMapping = (Map<String, Method>) servletContext.getAttribute("bindingRequestMapping"); //获取实例化的bean容器 Map<String, Object> beanContainerMap = (Map<String, Object>) servletContext.getAttribute("beanContainerMap"); String url = request.getServletPath(); Set<Map.Entry<String, Method>> entries = bindingRequestMapping.entrySet(); List<Object> resultParameters = Binding.bingdingMethodParamters(bindingRequestMapping, request); for (Map.Entry<String, Method> entry : entries) { if (url.equals(entry.getKey())) { Method method = entry.getValue(); Class<?> returnType = method.getReturnType(); //如果返回值是MyModelAndView,开始绑定 if ("MyModelAndView".equals(returnType.getSimpleName())){ Object object = beanContainerMap.get(method.getDeclaringClass().getName()); //获取springmvc.xml中配置的视图解析器 ViewResolver viewResolver = (ViewResolver) beanContainerMap.get("spring.springmvc.ViewResolver"); String prefix = viewResolver.getPrefix(); String suffix = viewResolver.getSuffix(); MyModelAndView myModelAndView = (MyModelAndView) method.invoke(object, resultParameters.toArray()); //将request和model中的数据绑定,也就是渲染视图 bindingRequestAndModel(myModelAndView,request); String returnViewName = myModelAndView.getView(); //返回的路径 String resultAddress = prefix + returnViewName + suffix; try { request.getRequestDispatcher(resultAddress).forward(request,response); } catch (ServletException e) { e.printStackTrace(); } } } } } }
首先是绑定方法和url,是Handler类,用如下对象绑定
Map<String, Method> handlerMapping = new ConcurrentHashMap<>();
package spring.springmvc; import lombok.extern.slf4j.Slf4j; import spring.Utils.AnnotationUtils; import spring.annotation.MyController; import spring.annotation.MyRequestMapping; import spring.exception.springmvcException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * @ClassName Handler * @Description 遍历bean容器,在有controller注解的类中有requestmapping扫描的方法,则将方法和url和方法绑定 * @Data 2018/7/3 * @Author xiao liang */ @Slf4j public class Handler { public static Map<String, Method> bindingRequestMapping(Map<String, Object> beanContainerMap){ Map<String, Method> handlerMapping = new ConcurrentHashMap<>(); if (beanContainerMap != null){ Set<Map.Entry<String, Object>> entries = beanContainerMap.entrySet(); for (Map.Entry<String, Object> entry : entries) { Class aClass = entry.getValue().getClass(); Annotation annotation = aClass.getAnnotation(MyController.class); Method[] methods = aClass.getMethods(); if (!AnnotationUtils.isEmpty(annotation) && methods != null){ for (Method method: aClass.getMethods()) { MyRequestMapping requestMappingAnnotation = method.getAnnotation(MyRequestMapping.class); if (!AnnotationUtils.isEmpty(requestMappingAnnotation)){ String key = requestMappingAnnotation.value(); handlerMapping.put(key,method); } } } } } else{ throw new springmvcException("实例化bean异常,没有找到容器"); } return handlerMapping; } }
参数绑定支持
- @MyRequestMapping(用来绑定简单数据类型)
- @MyModelAndAttribute(绑定实体类)
- 不写注解,直接写实体类。
下面先贴一下这一部分的结构关系图
这里用多态的设计思想,对于bindingParamter方法写了两种实现,方便大家自行扩展
package spring.springmvc; import spring.Utils.AnnotationUtils; import spring.Utils.isBasicTypeUtils; import spring.annotation.MyModelAttribute; import spring.annotation.MyRequstParam; import spring.exception.springmvcException; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; /** * @ClassName Binding * @Description * @Data 2018/7/4 * @Author xiao liang */ public class Binding { public static List<Object> bingdingMethodParamters(Map<String, Method> bindingRequestMapping, HttpServletRequest request) { List<Object> resultParameters = new ArrayList<>(); Set<Map.Entry<String, Method>> entries = bindingRequestMapping.entrySet(); for (Map.Entry<String, Method> entry : entries) { Method method = entry.getValue(); Parameter[] parameters = method.getParameters(); for (Parameter parameter : parameters) { //遍历每个参数,如果参数存在注解,将这个参数添加到resultParameters中 if (!AnnotationUtils.isEmpty(parameter.getAnnotations())){ Object resultParameter = null; try { resultParameter = bingdingEachParamter(parameter, request); } catch (IllegalAccessException e) { e.printStackTrace(); throw new springmvcException("绑定参数异常"); } catch (NoSuchMethodException e) { e.printStackTrace(); throw new springmvcException("绑定参数异常"); } catch (InstantiationException e) { e.printStackTrace(); throw new springmvcException("绑定参数异常"); } resultParameters.add(resultParameter); } } } return resultParameters; } private static Object bingdingEachParamter(Parameter parameter, HttpServletRequest request) throws IllegalAccessException, NoSuchMethodException, InstantiationException { //如果注解是MyRequstParam,则用BindingByMyRequstParam来执行装配 if (!AnnotationUtils.isEmpty(parameter.getAnnotation(MyRequstParam.class))){ BindingParamter bindingParamter = new BindingByMyRequstParam(); Object resultParameter = bindingParamter.bindingParamter(parameter, request); return resultParameter; } //如果注解是MyModelAttribute,则用BindingByMyModelAttribute来执行装配 else if (!AnnotationUtils.isEmpty(parameter.getAnnotation(MyModelAttribute.class))){ BindingParamter bindingParamter = new BindingByMyModelAttribute(); Object resultParameter = bindingParamter.bindingParamter(parameter,request); return resultParameter; } //在没有注解的时候,自动识别,如果是基本数据类型用MyRequstParam装配,如果是用户自定义类型用MyModelAttribute装配 else if(parameter.getAnnotations() == null || parameter.getAnnotations().length ==0){ boolean flag = isBasicTypeUtils.isBasicType(parameter.getType().getSimpleName()); if (flag){ BindingParamter bindingParamter = new BindingByMyRequstParam(); Object resultParameter = bindingParamter.bindingParamter(parameter, request); return resultParameter; } else{ BindingParamter bindingParamter = new BindingByMyModelAttribute(); Object resultParameter = bindingParamter.bindingParamter(parameter,request); return resultParameter; } } return null; } }
下面是接口BindingParamter 和两个实现类BindingByMyModelAttribute和BindingByMyRequstParam
package spring.springmvc; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Parameter; /** * @ClassName BindingRoles * @Description * @Data 2018/7/4 * @Author xiao liang */ public interface BindingParamter { Object bindingParamter(Parameter parameter, HttpServletRequest request) throws IllegalAccessException, InstantiationException, NoSuchMethodException; }
package spring.springmvc; import spring.Utils.StringUtils; import spring.annotation.MyRequstParam; import spring.exception.springmvcException; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Parameter; /** * @ClassName BindingByMyRequstParam * @Description 参数注解是MyMyRequstParam时,绑定数据的类 * @Data 2018/7/4 * @Author xiao liang */ public class BindingByMyRequstParam implements BindingParamter { @Override public Object bindingParamter(Parameter parameter, HttpServletRequest request) { MyRequstParam myRequstParam = parameter.getAnnotation(MyRequstParam.class); //获得注解的value值 String MyRequstParamValue = myRequstParam.value(); //获得参数的类名 String parameterType = parameter.getType().getSimpleName(); String parameter1 = request.getParameter(MyRequstParamValue); if (StringUtils.isEmpty(parameter1)) { throw new springmvcException("绑定参数异常"); } //parameter1赋值 if (parameterType.equals("String")) { return parameter1; } else if (parameterType.equals("Integer") || parameterType.equals("int")) { Integer binddingParameter = Integer.valueOf(parameter1); return binddingParameter; } return null; } }
package spring.springmvc; import lombok.extern.slf4j.Slf4j; import spring.Utils.AnnotationUtils; import spring.Utils.ConvertUtis; import spring.Utils.GetMethodName; import spring.Utils.StringUtils; import spring.annotation.MyModelAttribute; import spring.exception.springmvcException; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; /** * @ClassName BindingByMyModelAttribute * @Description 参数注解是MyModelAttribute时,绑定数据的类 * @Data 2018/7/4 * @Author xiao liang */ @Slf4j public class BindingByMyModelAttribute implements BindingParamter { @Override public Object bindingParamter(Parameter parameter, HttpServletRequest request) throws IllegalAccessException, InstantiationException, NoSuchMethodException { MyModelAttribute myModelAttribute = parameter.getAnnotation(MyModelAttribute.class); //获得参数的类名 Class<?> aClass = parameter.getType(); if (!AnnotationUtils.isEmpty(myModelAttribute)){ if (!aClass.getSimpleName().equals(myModelAttribute.value())){ throw new springmvcException("实体类绑定异常,请重新检查"); } } Field[] fields = aClass.getDeclaredFields(); Object object = aClass.newInstance(); //遍历每个属性,用set注入将值注入到对象中 for (Field field : fields) { //获得用户传来的值 String parameter1 = request.getParameter(field.getName()); if (!StringUtils.isEmpty(parameter1)){ //将用户传过来的值转换成对应的参数类型 Object setObject = ConvertUtis.convert(field.getType().getSimpleName(),parameter1); String methodName = GetMethodName.getSetMethodNameByField(field.getName()); Method method = aClass.getMethod(methodName, field.getType()); try { //反射set注入 method.invoke(object,setObject); } catch (InvocationTargetException e) { log.error("{}属性赋值异常",field.getName()); e.printStackTrace(); } } } //返回对注入值后的对象 return object; } }
绑定完参数,就该返回ModelAndView了,
package spring.springmvc; import lombok.Data; /** * @ClassName MyModelAndView * @Description * @Data 2018/7/4 * @Author xiao liang */ @Data public class MyModelAndView { private String view; private MyModelMap modelMap; public MyModelAndView(String view) { this.view = view; } }
view是视图名称,还有viewResolver,用来接收xml文件中定义的前缀和后缀。modelMap是数据域,最后渲染的时候要绑定到request中。
package spring.springmvc; import lombok.Data; /** * @ClassName ViewResolver * @Description 视图解析器 前缀和后缀 * @Data 2018/7/4 * @Author xiao liang */ @Data public class ViewResolver { private String prefix = ""; private String suffix = ""; }
最后的渲染类
package spring.springmvc; import javax.servlet.http.HttpServletRequest; import java.util.Map; import java.util.Set; /** * @ClassName BindingRequestAndModel * @Description * @Data 2018/7/6 * @Author xiao liang */ public class BindingRequestAndModel { //遍历modelMap,然后将model中的数据绑定到requst中 public static void bindingRequestAndModel(MyModelAndView myModelAndView, HttpServletRequest request) { MyModelMap myModelMap = myModelAndView.getModelMap(); if (!myModelMap.isEmpty()){ Set<Map.Entry<String, Object>> entries1 = myModelMap.entrySet(); for (Map.Entry<String, Object> entryMap : entries1) { String key = entryMap.getKey(); Object value = entryMap.getValue(); request.setAttribute(key,value); } } } }
至此,最后在MyDispatcherServlet中用转发操作将试图返回。
request.getRequestDispatcher(resultAddress).forward(request,response);
我将此项目上传到了github,需要的童鞋可以自行下载。