<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在日常的 web 開發中,熟悉 java 的同學一定知道,Spring MVC 可以說是目前最流行的框架,之所以如此的流行,原因很簡單:程式設計簡潔、上手簡單!
我記得剛開始入行的時候,最先接觸到的是Struts1 + Hibernate + Spring來web系統的整體開發框架,簡單的描述一下當時的程式設計心情:超難用,各種設定項很多,而且不容易快速入手!
之後,新的專案換成了Struts2 + hibernate + spring來作為主體開發框架,Struts2相比Struts1程式設計要簡單很多,而且加強了對攔截器與IoC的支援,而在Struts1中,這些特性是很難做的的!
然而隨著Struts2的使用量越來越廣,業界爆出關於Struts2的bug和安全漏洞卻越來越多!
駭客們可以輕易的利用安全漏洞直接繞開安全防線,獲取用的隱私資料,網名因個人資訊洩露造成的經濟損失高達 915 億元!
至此很多開發者開始轉到SpringMVC框架陣營!
今天我們要介紹的主角就是SpringMVC框架,剛開始玩這個的時候,給我最直接的感覺就是:很容易簡單!
直接通過幾個註解就可以完成方法的暴露,比起Struts2中繁瑣的xml設定,SpringMVC的使用可以說更加友好!
熟悉SpringMVC框架的同學一定清楚下面這張圖,
這張圖就是 SpringMVC 在處理 http 請求的整個流程中所做的一些事情。
DispatcherServlet 主要承擔接收請求、響應結果、轉發等作用,剩下的就交給容器來處理!
基於上面的流程,我們可以編寫出一款簡化版的Spring MVC框架,話不多說,直接擼起來!
首先上圖!
這個就是我們簡易版的Spring MVC框架的實現流程圖!
1、首先建立一個DispatcherServlet類,在服務啟動的時候,讀取要掃描的包路徑,然後通過反射將類資訊儲存到ioc容器,同時通過@Autowired註解,實現自動依賴注入,最後讀取@RequestMapping註解中的方法,將對映路徑與類的關係儲存到對映容器中。
2、當用戶發起請求的時候,通過請求路徑到對映容器中找到對應的執行類,然後呼叫具體的方法,發起邏輯處理,最後將處理結果返回給前端使用者!
以下是具體實踐過程!
因為Spring MVC基本全部都是基於註解開發,因此我們事先也需要建立對應的註解,各個含義與Spring MVC一致!
控制層註解
/** * 控制層註解 * @Controller */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Controller { String value() default ""; }
請求路徑註解
/** * 請求路徑註解 * @RequestMapping */ @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestMapping { String value() default ""; }
引數註解
/** * 引數註解 * @RequestParam */ @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestParam { String value() default ""; }
服務層註解
/** * 服務層註解 * @Controller */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Service { String value() default ""; }
自動裝載註解
/** * 自動裝載註解 * @Autowrited */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { String value() default ""; }
DispatcherServlet是一個Servlet類,主要承擔的任務是:接受前端使用者的請求,然後進行轉發,最後響應結果給前端使用者!
詳細程式碼如下:
/** * servlet跳轉層 */ @WebServlet(name = "DispatcherServlet",urlPatterns = "/*", loadOnStartup = 1, initParams = {@WebInitParam(name="scanPackage", value="com.example.mvc")}) public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class); /**請求方法對映容器*/ private static List<RequestHandler> handlerMapping = new ArrayList<>(); /** * 服務啟動的時候,進行初始化,流程如下: * 1、掃描指定包下所有的類 * 2、通過反射將類範例,放入ioc容器 * 3、通過Autowired註解,實現自動依賴注入,也就是set類中的屬性 * 4、通過RequestMapping註解,獲取需要對映的所有方法,然後將類資訊存放到容器中 * @param config * @throws ServletException */ @Override public void init(ServletConfig config) throws ServletException { try { //1、掃描指定包下所有的類 String scanPackage = config.getInitParameter("scanPackage"); //1、掃描指定包下所有的類 List<String> classNames = doScan(scanPackage); //2、初始化所有類範例,放入ioc容器,也就是map物件中 Map<String, Object> iocMap = doInstance(classNames); //3、實現自動依賴注入 doAutowired(iocMap); //5、初始化方法mapping initHandleMapping(iocMap); } catch (Exception e) { logger.error("dispatcher-servlet類初始化失敗!",e); throw new ServletException(e.getMessage()); } } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { doPost(request, response); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { //跳轉 doDispatch(request, response); } /** * 掃描指定包下的類檔案 * @param packageName * @return */ private List<String> doScan(String packageName){ if(StringUtils.isBlank(packageName)){ throw new RuntimeException("mvc組態檔中指定掃描包名為空!"); } return PackageHelper.getClassName(packageName); } private Map<String, Object> doInstance(List<String> classNames) { Map<String, Object> iocMap = new HashMap<>(); if(!CollectionUtils.isNotEmpty(classNames)){ throw new RuntimeException("獲取的類為空!"); } for (String className : classNames) { try { //通過反射機制構造物件 Class<?> clazz = Class.forName(className); if(clazz.isAnnotationPresent(Controller.class)){ //將類名第一個字母小寫 String baneName = firstLowerCase(clazz.getSimpleName()); iocMap.put(baneName, clazz.newInstance()); }else if(clazz.isAnnotationPresent(Service.class)){ //服務層註解判斷 Service service = clazz.getAnnotation(Service.class); String beanName = service.value(); //如果該註解上沒有自定義類名,則預設首字母小寫 if(StringUtils.isBlank(beanName)){ beanName = clazz.getName(); } Object instance = clazz.newInstance(); iocMap.put(beanName, instance); //如果注入的是介面,可以巧妙的用介面的型別作為key Class<?>[] interfaces = clazz.getInterfaces(); for (Class<?> clazzInterface : interfaces) { iocMap.put(clazzInterface.getName(), instance); } } } catch (Exception e) { logger.error("初始化mvc-ioc容器失敗!",e); throw new RuntimeException("初始化mvc-ioc容器失敗!"); } } return iocMap; } /** * 實現自動依賴注入 * @throws Exception */ private void doAutowired(Map<String, Object> iocMap) { if(!MapUtils.isNotEmpty(iocMap)){ throw new RuntimeException("初始化實現自動依賴失敗,ioc為空!"); } for(Map.Entry<String, Object> entry : iocMap.entrySet()){ //獲取物件下所有的屬性 Field[] fields = entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { //判斷欄位上有沒有@Autowried註解,有的話才注入 if(field.isAnnotationPresent(Autowired.class)){ try { Autowired autowired = field.getAnnotation(Autowired.class); //獲取註解上有沒有自定義值 String beanName = autowired.value().trim(); if(StringUtils.isBlank(beanName)){ beanName = field.getType().getName(); } //如果想要存取到私有的屬性,我們要強制授權 field.setAccessible(true); field.set(entry.getValue(), iocMap.get(beanName)); } catch (Exception e) { logger.error("初始化實現自動依賴注入失敗!",e); throw new RuntimeException("初始化實現自動依賴注入失敗"); } } } } } /** * 初始化方法mapping */ private void initHandleMapping(Map<String, Object> iocMap){ if(!MapUtils.isNotEmpty(iocMap)){ throw new RuntimeException("初始化實現自動依賴失敗,ioc為空"); } for(Map.Entry<String, Object> entry:iocMap.entrySet()){ Class<?> clazz = entry.getValue().getClass(); //判斷是否是controller層 if(!clazz.isAnnotationPresent(Controller.class)){ continue; } String baseUrl = null; //判斷類有沒有requestMapping註解 if(clazz.isAnnotationPresent(RequestMapping.class)){ RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); baseUrl= requestMapping.value(); } Method[] methods = clazz.getMethods(); for (Method method : methods) { //判斷方法上有沒有requestMapping if(!method.isAnnotationPresent(RequestMapping.class)){ continue; } RequestMapping requestMethodMapping = method.getAnnotation(RequestMapping.class); //"/+",表示將多個"/"轉換成"/" String regex = (baseUrl + requestMethodMapping.value()).replaceAll("/+", "/"); Pattern pattern = Pattern.compile(regex); handlerMapping.add(new RequestHandler(pattern, entry.getValue(), method)); } } } /** * servlet請求跳轉 * @param request * @param response * @throws IOException */ private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws IOException { try { request.setCharacterEncoding("UTF-8"); response.setHeader("Cache-Control", "no-cache"); response.setHeader("Pragma", "no-cache"); response.setDateHeader("Expires", -1); response.setContentType("text/html"); response.setHeader("content-type", "text/html;charset=UTF-8"); response.setCharacterEncoding("UTF-8"); RequestHandler handle = getHandleMapping(request); if(Objects.isNull(handle)){ //異常請求地址 logger.warn("異常請求地址!地址:" + request.getRequestURI()); response.getWriter().append("error request url"); return; } //獲取參數列 Object[] paramValues = RequestParamHelper.buildRequestParam(handle, request, response); Object result = handle.getMethod().invoke(handle.getController(), paramValues); if(result != null){ PrintWriter out = response.getWriter(); out.println(result); out.flush(); out.close(); } } catch (Exception e) { logger.error("介面請求失敗!",e); PrintWriter out = response.getWriter(); out.println("請求異常,請稍後再試"); out.flush(); out.close(); } } /** * 將類名第一個字母小寫 * @param clazzName * @return */ private String firstLowerCase(String clazzName){ char[] chars = clazzName.toCharArray(); chars[0] += 32; return String.valueOf(chars); } /** * 獲取使用者請求方法名 * 與handlerMapping中的路徑名進行匹配 * @param request * @return */ private RequestHandler getHandleMapping(HttpServletRequest request){ if(CollectionUtils.isNotEmpty(handlerMapping)){ //獲取使用者請求路徑 String url = request.getRequestURI(); String contextPath = request.getContextPath(); String serviceUrl = url.replace(contextPath, "").replaceAll("/+", "/"); for (RequestHandler handle : handlerMapping) { //正則匹配請求方法名 Matcher matcher = handle.getPattern().matcher(serviceUrl); if(matcher.matches()){ return handle; } } } return null; } }
這裡要重點介紹一下初始化階段所做的操作!
DispatcherServlet在服務啟動階段,會呼叫init方法進行服務初始化,此階段所做的事情主要有以下內容:
當DispatcherServlet編寫完成之後,緊接著我們需要編寫對應的controller控制類來接受前端使用者請求,下面我們以使用者登入為例,程式範例如下:
編寫一個LoginController控制類,接受前端使用者呼叫
@Controller @RequestMapping("/user") public class LoginController { @Autowired private UserService userService; /** * 使用者登入 * @param request * @param response * @param userName * @param userPwd * @return */ @RequestMapping("/login") public String login(HttpServletRequest request, HttpServletResponse response, @RequestParam("userName") String userName, @RequestParam("userPwd") String userPwd){ boolean result = userService.login(userName, userPwd); if(result){ return "登入成功!"; } else { return "登入失敗!"; } } }
編寫一個UserService服務類,用於判斷賬戶、密碼是否正確
public interface UserService { /** * 登入 * @param userName * @param userPwd * @return */ boolean login(String userName, String userPwd); }
@Service public class UserServiceImpl implements UserService { @Override public boolean login(String userName, String userPwd) { if("zhangsan".equals(userName) && "123456".equals(userPwd)){ return true; } else { return false; } } }
最後,將專案打包成war,通過tomcat啟動服務!
在瀏覽器中存取http://localhost:8080/user/login?userName=hello&userPwd=123
,結果顯示如下:
當我們將userName和userPwd換成正確的資料,存取地址如下:http://localhost:8080/user/login?userName=zhangsan&userPwd=123456
可以很清晰的看到,服務呼叫正常!
本文主要以Spring MVC框架為背景,手寫了一個簡易版的Spring MVC框架,雖然功能簡陋了一點,但是基本無張俱全,裡面講解了ioc和自動依賴注入的實現過程,還有前端發起一個路徑請求,是如何對映到對應的controller類中的方法上!
當然實際的Spring MVC框架的跳轉流程比這個複雜很多很多,裡面包括各種攔截器、許可權安全管理等等,在後面的文章,小編也會陸續進行詳細介紹!
到此這篇關於寫一個SpringMVC框架的文章就介紹到這了,更多相關寫個SpringMVC框架內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45