環境描述 idea java 8 1. POM文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/ ...
環境描述
idea
java 8
1. POM文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.feng</groupId>
<artifactId>hand-springmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>hand-springmvc Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>Example Domain</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--測試-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--日誌-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
<build>
<finalName>hand-springmvc</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
</pluginManagement>
</build>
</project>
log4j.rootLogger=INFO, console log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %-60c %x - %m%n
3. 項目目錄
核心內容是註解+servlet
註解類
org.feng.annotation.Autowired
package org.feng.annotation; import java.lang.annotation.*; /** * Created by Feng on 2019/12/16 17:58 * CurrentProject's name is hand-springmvc * Autowired 用在變數上 * @author Feng */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { String value() default ""; }
org.feng.annotation.Controller
package org.feng.annotation; import java.lang.annotation.*; /** * Created by Feng on 2019/12/16 18:01 * CurrentProject's name is hand-springmvc * Controller 用在類上 * @author Feng */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Controller { String value() default ""; }
org.feng.annotation.RequestMapping
package org.feng.annotation; import java.lang.annotation.*; /** * Created by Feng on 2019/12/16 18:02 * CurrentProject's name is hand-springmvc * RequestMapping可以用在類、方法上 * @author Feng */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestMapping { String value() default ""; }
org.feng.annotation.RequestParam
package org.feng.annotation; import java.lang.annotation.*; /** * Created by Feng on 2019/12/16 18:06 * CurrentProject's name is hand-springmvc * RequestParam 用在參數上 * @author Feng */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestParam { String value() default ""; }
org.feng.annotation.Service
package org.feng.annotation; import java.lang.annotation.*; /** * Created by Feng on 2019/12/16 18:05 * CurrentProject's name is hand-springmvc * Service 用在類上 * @author Feng */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Service { String value() default ""; }
核心控制器
package org.feng.servlet; import org.feng.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Created by Feng on 2019/12/16 17:58 * CurrentProject's name is hand-springmvc<br> * 核心控制器: * <ul>實現思路 * <li>先掃描基礎包,獲取 {@code class} 文件的路徑;其實是為了獲取完整類名</li> * <li>根據上邊的完整類名以及判斷是否有指定創建實例(通過有無註解和註解的類型)並保存實例到 {@code map} 中</li> * <li>依賴註入變數,從實例獲得類對象,然後解析 {@code Field} 並賦值給標註了{@link Autowired}的{@code Field}</li> * <li>獲取方法上的參數,通過{@link HttpServletRequest}獲取</li> * </ul> * @author Feng */ public class DispatcherServlet extends HttpServlet { /** * 掃描包:基本的包,掃描該路徑下的所有類 */ private static final String BASE_PACKAGE = "org.feng"; private static final String WAR_NAME = "/hand_springmvc"; /**日誌*/ private static final Logger LOGGER = LoggerFactory.getLogger(DispatcherServlet.class); /** * 保存class文件的路徑 */ private List<String> classPathList = new ArrayList<>(); /** * IOC容器:存放對象;使用{@link ConcurrentHashMap}保證線程安全 */ private Map<String, Object> beans = new ConcurrentHashMap<>(16); /** * 存放方法映射:使用{@link ConcurrentHashMap}保證線程安全<br> * 用於存儲方法<br> * {@code key = classpath + methodPath; value = method} */ private Map<String, Method> handlerMap = new ConcurrentHashMap<>(16); /** * 初始化數據: * <ul> * <li>掃描所有的類</li> * <li>創建實例並存儲進 {@code beans}</li> * <li>依賴註入:使用 {@code Autowired}</li> * <li>拼接請求地址初始化、方法映射</li> * </ul> */ @Override public void init() { LOGGER.info("starting scan package into classpath list"); scanPackage(BASE_PACKAGE); LOGGER.info("scan package end"); LOGGER.info("classPathList:"+classPathList); LOGGER.info("starting create instance into bean map"); createInstance(); LOGGER.info("create instance end"); LOGGER.info("beans:" + beans); LOGGER.info("starting autowired field"); autowiredField(); LOGGER.info("autowired field end"); LOGGER.info("starting mapping to url"); urlMapping(); LOGGER.info("mapping to url end"); LOGGER.info("handler mapping:" + handlerMap); } /** * 方法映射 */ private void urlMapping() { beans.forEach((key, value) -> { Class<?> clazz = value.getClass(); if(clazz.isAnnotationPresent(Controller.class)){ RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); String classPath = requestMapping.value(); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if(method.isAnnotationPresent(RequestMapping.class)){ RequestMapping requestMapping1 = method.getAnnotation(RequestMapping.class); String methodPath = requestMapping1.value(); handlerMap.put(classPath + methodPath, method); } } } }); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) { this.doPost(req, resp); } /** * 解析調用方法後的返回值:當為String類型時,得到其是轉發還是重定向; * @param invokeReturn invoke方法時得到的返回值 * @param req 請求對象 * @param resp 響應對象 */ private void forwardOrRedirect(Object invokeReturn, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { final String forward = "forward:"; final String redirect = "redirect:"; // 當反向調用方法有返回值 if(invokeReturn != null){ // 返回值是字元串:解析字元串 if(invokeReturn.getClass() == String.class){ req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); String returnStr = invokeReturn.toString(); if(returnStr.startsWith(forward)){ returnStr = returnStr.substring(8); LOGGER.info(forward + returnStr); req.getRequestDispatcher(WAR_NAME + returnStr).forward(req, resp); } else if(returnStr.startsWith(redirect)){ returnStr = returnStr.substring(9); LOGGER.info(redirect + returnStr); resp.sendRedirect(WAR_NAME + returnStr); } else { LOGGER.info(redirect + returnStr); resp.sendRedirect(returnStr); } } } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) { // 請求的地址 String uri = req.getRequestURI(); uri = uri.replace(WAR_NAME, ""); int index = uri.indexOf("/", 1); String controllerUrl = uri.substring(0, index); Method method = handlerMap.get(uri); LOGGER.info("get method " + method); beans.forEach((key, value) -> { Class<?> clazz = value.getClass(); if(clazz.isAnnotationPresent(Controller.class)){ RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); String valueTemp = requestMapping.value(); if(controllerUrl.equals(valueTemp)){ try { LOGGER.info("invoking " + controllerUrl + "." + value); Object invokeReturn = method.invoke(value, getArgs(req, resp, method)); // 控制:轉發或重定向 forwardOrRedirect(invokeReturn, req, resp); } catch (IllegalAccessException | InvocationTargetException e) { LOGGER.error("invoke error in " + controllerUrl); } catch (ServletException | IOException e) { e.printStackTrace(); } } } }); } /** * 解析標註有{@link RequestParam}的方法參數,並賦值 * @param req 請求對象 * @param resp 響應對象 * @param method 方法對象 * @return 賦值後的參數 */ private Object[] getArgs(HttpServletRequest req, HttpServletResponse resp, Method method) { // 拿到當前類待執行的方法參數 Class<?>[] clazzParams = method.getParameterTypes(); // 定義存儲參數的數組 Object[] args = new Object[clazzParams.length]; int argsIndex = 0; // 判定此 class 對象所表示的類或介面與指定的 class 參數所表示的類或介面是否相同 // 或是否是其超類或超介面 for (int index = 0; index < clazzParams.length; index++) { if(ServletRequest.class.isAssignableFrom(clazzParams[index])){ args[argsIndex ++] = req; } if(ServletResponse.class.isAssignableFrom(clazzParams[index])){ args[argsIndex ++] = resp; } Annotation[] annotations = method.getParameterAnnotations()[index]; if(annotations.length > 0){ for (Annotation annotation : annotations) { if(RequestParam.class.isAssignableFrom(annotation.getClass())){ RequestParam requestParam = (RequestParam) annotation; // 找到註解的名字 args[argsIndex ++] = req.getParameter(requestParam.value()); } } } } return args; } /** * 依賴註入:對帶有{@link org.feng.annotation.Autowired}的屬性賦值 */ private void autowiredField() { beans.forEach((key, value) ->{ Class<?> clazz = value.getClass(); if(clazz.isAnnotationPresent(Controller.class)){ // 獲取屬性 Field[] declaredFields = clazz.getDeclaredFields(); for (Field declaredField : declaredFields) { if(!declaredField.isAnnotationPresent(Autowired.class)){ continue; } // 當存在 Autowired 標註的屬性時 Autowired autowired = declaredField.getAnnotation(Autowired.class); String beanName; if("".equals(autowired.value())){ beanName = lowerFirstChar(declaredField.getType().getSimpleName()); } else { beanName = autowired.value(); } // 設置訪問控制許可權:原先是 private 不能訪問 declaredField.setAccessible(true); // 自定義介面實現類:以Impl結尾,前邊拼接介面名(首字母小寫) if(beanName.endsWith("Impl")){ beanName = beanName.replace("Impl", ""); } if(beans.get(beanName) != null){ try { // 給聲明的變數賦值:註入實例 declaredField.set(value, beans.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } }); } /** * 創建實例:遍歷所有的{@code class}文件,創建需要創建的實例存儲到beans中<br> * 判斷:是否被指定註解標註了 * <ul> * <li>首先判斷是不是{@link org.feng.annotation.Service}註解的類,若是則判斷有沒有傳入的{@code service}名稱</li> * <li>其他情況,包括是{@link org.feng.annotation.Controller}的情況,全部使用小寫類名為{@code key}</li> * </ul> */ private void createInstance() { try { // 遍歷所有的.class文件;將需要實例化的類創建實例 for (String classPath : classPathList) { Class<?> clazz = Class.forName(classPath.replace(".class", "")); if(clazz.isAnnotationPresent(Service.class)){ Service service = clazz.getAnnotation(Service.class); String key = service.value(); // 當傳入了註解中參數時 if(!"".equals(key)){ beans.put(key, clazz.newInstance()); LOGGER.info("created instance by " + classPath); } else { // 獲取第一個介面的簡單名稱,首字母小寫 beans.put(lowerFirstChar(clazz.getInterfaces()[0].getSimpleName()), clazz.newInstance()); LOGGER.info("created instance by " + classPath); } } else if(clazz.isAnnotationPresent(Controller.class)){ // 以類名小寫首字母為key beans.put(lowerFirstChar(clazz.getSimpleName()), clazz.newInstance()); LOGGER.info("created instance by " + classPath); } } } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { LOGGER.error("error in createInstance", e); } } /** * 將類名中的首字母小寫 * @param simpleName 類名(不含包名) */ private String lowerFirstChar(String simpleName) { char[] chars = simpleName.toCharArray(); chars[0] += 32; return new String(chars); } /** * 先通過傳入的包名拼接出文件路徑: * <p> * {@code "org.feng".replace(".", "/");} * </p> * 遞歸掃描指定路徑下的所有{@code class}文件; * 存儲{@code class}文件路徑到集合中 * @param basePackage 掃描包的包名 */ private void scanPackage(String basePackage) { // 將包名轉換為class文件路徑 String resourceName = "/" + basePackage.replace(".", "/"); URL url = this.getClass().getClassLoader().getResource(resourceName); // 獲取文件 assert url != null; String filename = url.getFile(); File file = new File(filename); // 獲取所有文件 String[] files = file.list(); assert files != null; for (String path : files) { File fileTemp = new File(filename + path); // 當前如果是目錄,遞歸掃描包 String packageName = basePackage + "." + path; if(fileTemp.isDirectory()){ scanPackage(packageName); } else { // 當掃描到文件(.class文件),增加到類路徑集合 classPathList.add(packageName); LOGGER.info("scan " + packageName + " into classpath list"); } } } }
測試
org.feng.service.MyService
package org.feng.service; /** * Created by Feng on 2019/12/17 9:23 * CurrentProject's name is hand-springmvc * @author Feng */ public interface MyService { /** * 說 * @return 字元串 */ String say();
org.feng.service.impl.MyServiceImpl
package org.feng.service.impl; import org.feng.annotation.Service; import org.feng.service.MyService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Created by Feng on 2019/12/17 9:25 * CurrentProject's name is hand-springmvc * @author Feng */ @Service public class MyServiceImpl implements MyService { private static final Logger LOGGER = LoggerFactory.getLogger(MyServiceImpl.class); public MyServiceImpl(){ LOGGER.info("no args constructor MyServiceImpl.class"); } @Override public String say() { return "MyServiceImpl invoking say()"; } }
org.feng.controller.MyController
package org.feng.controller; import org.feng.annotation.Autowired; import org.feng.annotation.Controller; import org.feng.annotation.RequestMapping; import org.feng.annotation.RequestParam; import org.feng.service.MyService; /** * Created by Feng on 2019/12/17 9:27 * CurrentProject's name is hand-springmvc */ @RequestMapping("/MyController") @Controller public class MyController { @Autowired private MyService myService; @RequestMapping("/say.do") public String say(@RequestParam("name") String name, @RequestParam("info") String info){ System.out.println("name = " + name + ", info = " + info); myService.say(); return "redirect:/index.jsp"; } }
運行
配置tomcat
運行結果:
————————————————
本人免費整理了Java高級資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分散式等教程,一共30G,需要自己領取。
傳送門:https://mp.weixin.qq.com/s/osB-BOl6W-ZLTSttTkqMPQ