实现一个简单SpringMVC(借鉴廖雪峰web-mvc源码)

第一步:使用java注解定义GetMapping

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(METHOD)
public @interface GetMapping {

   String value();
}

第二步:定义ModelAndView(view表示路径,model为传的数据)

public class ModelAndView {

   Map<String, Object> model;
   String view;

   public ModelAndView(String view) {
      this.view = view;
      this.model = Map.of();
   }

   public ModelAndView(String view, String name, Object value) {
      this.view = view;
      this.model = new HashMap<>();
      this.model.put(name, value);
   }

   public ModelAndView(String view, Map<String, Object> model) {
      this.view = view;
      this.model = new HashMap<>(model);
   }
}

第三步:编写Controller类

public class UserController {

   private Map<String, User> userDatabase = new HashMap<>() {
      {
         List<User> users = List.of( //
               new User("bob@example.com", "bob123", "Bob", "This is bob."),
               new User("tom@example.com", "tomcat", "Tom", "This is tom."));
         users.forEach(user -> {
            put(user.email, user);
         });
      }
   };

   @GetMapping("/signin")
   public ModelAndView signin() {
      return new ModelAndView("/signin.html");
   }

   @PostMapping("/signin")
   public ModelAndView doSignin(SignInBean bean, HttpServletResponse response, HttpSession session)
         throws IOException {
      User user = userDatabase.get(bean.email);
      if (user == null || !user.password.equals(bean.password)) {
         response.setContentType("application/json");
         PrintWriter pw = response.getWriter();
         pw.write("{\"error\":\"Bad email or password\"}");
         pw.flush();
      } else {
         session.setAttribute("user", user);
         response.setContentType("application/json");
         PrintWriter pw = response.getWriter();
         pw.write("{\"result\":true}");
         pw.flush();
      }
      return null;
   }

   @GetMapping("/signout")
   public ModelAndView signout(HttpSession session) {
      session.removeAttribute("user");
      return new ModelAndView("redirect:/");
   }

   @GetMapping("/user/profile")
   public ModelAndView profile(HttpSession session) {
      User user = (User) session.getAttribute("user");
      if (user == null) {
         return new ModelAndView("redirect:/signin");
      }
      return new ModelAndView("/profile.html", "user", user);
   }
}

第四步:编写模板引擎进行渲染(实现render方法)

public class ViewEngine {

   private final PebbleEngine engine;

   public ViewEngine(ServletContext servletContext) {
      ServletLoader loader = new ServletLoader(servletContext);
      loader.setCharset("UTF-8");
      loader.setPrefix("/WEB-INF/templates");
      loader.setSuffix("");
      this.engine = new PebbleEngine.Builder().autoEscaping(true).cacheActive(false) // no cache for dev
            .loader(loader).build();
   }

   public void render(ModelAndView mv, Writer writer) throws IOException {
      PebbleTemplate template = this.engine.getTemplate(mv.view);
      template.evaluate(writer, mv.model);
   }
}

第五步:编写DispatcherServlet用做处理所有get请求(init扫描所有Controller,然后放入Map,)process方法为根据每一个路径找到具体的dispatcher(get,post),用具体的dispatcher处理返回数据。

@WebServlet(urlPatterns = "/")
public class DispatcherServlet extends HttpServlet {

   private final Logger logger = LoggerFactory.getLogger(getClass());

   private Map<String, GetDispatcher> getMappings = new HashMap<>();

   private Map<String, PostDispatcher> postMappings = new HashMap<>();

   // TODO: 可指定package并自动扫描:
   private List<Class<?>> controllers = List.of(IndexController.class, UserController.class);

   private ViewEngine viewEngine;

   /**
    * 当Servlet容器创建当前Servlet实例后,会自动调用init(ServletConfig)方法
    */
   @Override
   public void init() throws ServletException {
      logger.info("init {}...", getClass().getSimpleName());
      ObjectMapper objectMapper = new ObjectMapper();
      objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
      // 依次处理每个Controller:
      for (Class<?> controllerClass : controllers) {
         try {
            Object controllerInstance = controllerClass.getConstructor().newInstance();
            // 依次处理每个Method:
            for (Method method : controllerClass.getMethods()) {
               if (method.getAnnotation(GetMapping.class) != null) {
                  // 处理@Get:
                  if (method.getReturnType() != ModelAndView.class && method.getReturnType() != void.class) {
                     throw new UnsupportedOperationException(
                           "Unsupported return type: " + method.getReturnType() + " for method: " + method);
                  }
                  for (Class<?> parameterClass : method.getParameterTypes()) {
                     if (!supportedGetParameterTypes.contains(parameterClass)) {
                        throw new UnsupportedOperationException(
                              "Unsupported parameter type: " + parameterClass + " for method: " + method);
                     }
                  }
                  String[] parameterNames = Arrays.stream(method.getParameters()).map(p -> p.getName())
                        .toArray(String[]::new);
                  String path = method.getAnnotation(GetMapping.class).value();
                  logger.info("Found GET: {} => {}", path, method);
                  this.getMappings.put(path, new GetDispatcher(controllerInstance, method, parameterNames,
                        method.getParameterTypes()));
               } else if (method.getAnnotation(PostMapping.class) != null) {
                  // 处理@Post:
                  if (method.getReturnType() != ModelAndView.class && method.getReturnType() != void.class) {
                     throw new UnsupportedOperationException(
                           "Unsupported return type: " + method.getReturnType() + " for method: " + method);
                  }
                  Class<?> requestBodyClass = null;
                  for (Class<?> parameterClass : method.getParameterTypes()) {
                     if (!supportedPostParameterTypes.contains(parameterClass)) {
                        if (requestBodyClass == null) {
                           requestBodyClass = parameterClass;
                        } else {
                           throw new UnsupportedOperationException("Unsupported duplicate request body type: "
                                 + parameterClass + " for method: " + method);
                        }
                     }
                  }
                  String path = method.getAnnotation(PostMapping.class).value();
                  logger.info("Found POST: {} => {}", path, method);
                  this.postMappings.put(path, new PostDispatcher(controllerInstance, method,
                        method.getParameterTypes(), objectMapper));
               }
            }
         } catch (ReflectiveOperationException e) {
            throw new ServletException(e);
         }
      }
      // 创建ViewEngine:
      this.viewEngine = new ViewEngine(getServletContext());
   }

   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      process(req, resp, this.getMappings);
   }

   @Override
   protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      process(req, resp, this.postMappings);
   }

   private void process(HttpServletRequest req, HttpServletResponse resp,
         Map<String, ? extends AbstractDispatcher> dispatcherMap) throws ServletException, IOException {
      resp.setContentType("text/html");
      resp.setCharacterEncoding("UTF-8");
      String path = req.getRequestURI().substring(req.getContextPath().length());
      AbstractDispatcher dispatcher = dispatcherMap.get(path);
      if (dispatcher == null) {
         resp.sendError(404);
         return;
      }
      ModelAndView mv = null;
      try {
            mv = dispatcher.invoke(req, resp);
      } catch (ReflectiveOperationException e) {
         throw new ServletException(e);
      }
      if (mv == null) {
         return;
      }
      if (mv.view.startsWith("redirect:")) {
         resp.sendRedirect(mv.view.substring(9));
         return;
      }
      PrintWriter pw = resp.getWriter();
      this.viewEngine.render(mv, pw);
      pw.flush();
   }

   private static final Set<Class<?>> supportedGetParameterTypes = Set.of(int.class, long.class, boolean.class,
         String.class, HttpServletRequest.class, HttpServletResponse.class, HttpSession.class);

   private static final Set<Class<?>> supportedPostParameterTypes = Set.of(HttpServletRequest.class,
         HttpServletResponse.class, HttpSession.class);
}

abstract class AbstractDispatcher {

   public abstract ModelAndView invoke(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ReflectiveOperationException;
}

class GetDispatcher extends AbstractDispatcher {

   final Object instance;
   final Method method;
   final String[] parameterNames;
   final Class<?>[] parameterClasses;

   public GetDispatcher(Object instance, Method method, String[] parameterNames, Class<?>[] parameterClasses) {
      super();
      this.instance = instance;
      this.method = method;
      this.parameterNames = parameterNames;
      this.parameterClasses = parameterClasses;
   }

   @Override
   public ModelAndView invoke(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ReflectiveOperationException {
      Object[] arguments = new Object[parameterClasses.length];
      for (int i = 0; i < parameterClasses.length; i++) {
         String parameterName = parameterNames[i];
         Class<?> parameterClass = parameterClasses[i];
         if (parameterClass == HttpServletRequest.class) {
            arguments[i] = request;
         } else if (parameterClass == HttpServletResponse.class) {
            arguments[i] = response;
         } else if (parameterClass == HttpSession.class) {
            arguments[i] = request.getSession();
         } else if (parameterClass == int.class) {
            arguments[i] = Integer.valueOf(getOrDefault(request, parameterName, "0"));
         } else if (parameterClass == long.class) {
            arguments[i] = Long.valueOf(getOrDefault(request, parameterName, "0"));
         } else if (parameterClass == boolean.class) {
            arguments[i] = Boolean.valueOf(getOrDefault(request, parameterName, "false"));
         } else if (parameterClass == boolean.class) {
            arguments[i] = Boolean.valueOf(getOrDefault(request, parameterName, "false"));
         } else if (parameterClass == String.class) {
            arguments[i] = getOrDefault(request, parameterName, "");
         } else {
            throw new RuntimeException("Missing handler for type: " + parameterClass);
         }
      }
      return (ModelAndView) this.method.invoke(this.instance, arguments);
   }

   private String getOrDefault(HttpServletRequest request, String name, String defaultValue) {
      String s = request.getParameter(name);
      return s == null ? defaultValue : s;
   }
}

class PostDispatcher extends AbstractDispatcher {

   final Object instance;
   final Method method;
   final Class<?>[] parameterClasses;
   final ObjectMapper objectMapper;

   public PostDispatcher(Object instance, Method method, Class<?>[] parameterClasses, ObjectMapper objectMapper) {
      this.instance = instance;
      this.method = method;
      this.parameterClasses = parameterClasses;
      this.objectMapper = objectMapper;
   }

   @Override
   public ModelAndView invoke(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ReflectiveOperationException {
      Object[] arguments = new Object[parameterClasses.length];
      for (int i = 0; i < parameterClasses.length; i++) {
         Class<?> parameterClass = parameterClasses[i];
         if (parameterClass == HttpServletRequest.class) {
            arguments[i] = request;
         } else if (parameterClass == HttpServletResponse.class) {
            arguments[i] = response;
         } else if (parameterClass == HttpSession.class) {
            arguments[i] = request.getSession();
         } else {
            BufferedReader reader = request.getReader();
            arguments[i] = this.objectMapper.readValue(reader, parameterClass);
         }
      }
      return (ModelAndView) this.method.invoke(instance, arguments);
   }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值