环境描述
idea
java 8
1. POM文件
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.feng hand-springmvc 1.0-SNAPSHOT war hand-springmvc Maven Webapp Example Domain UTF-8 1.8 1.8 junit junit 4.11 test org.slf4j slf4j-log4j12 1.7.25 log4j log4j 1.2.17 javax.servlet javax.servlet-api 3.0.1 hand-springmvc
2. log4j.properties
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
* 核心控制器: *实现思路 *
@code class} 文件的路径;其实是为了获取完整类名 *- 先扫描基础包,获取 {
根据上边的完整类名以及判断是否有指定创建实例(通过有无注解和注解的类型)并保存实例到 { @code map} 中 *依赖注入变量,从实例获得类对象,然后解析 { @code Field} 并赋值给标注了{@link Autowired}的{@code Field} *获取方法上的参数,通过{ @link HttpServletRequest}获取 * * @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 ListclassPathList = new ArrayList<>(); /** * IOC容器:存放对象;使用{@link ConcurrentHashMap}保证线程安全 */ private Map beans = new ConcurrentHashMap<>(16); /** * 存放方法映射:使用{@link ConcurrentHashMap}保证线程安全
* 用于存储方法
* {@code key = classpath + methodPath; value = method} */ private MaphandlerMap = new ConcurrentHashMap<>(16); /** * 初始化数据: * *
@code beans} *- 扫描所有的类
*- 创建实例并存储进 {
依赖注入:使用 { @code Autowired} *拼接请求地址初始化、方法映射 * */ @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中
* 判断:是否被指定注解标注了 **
@link org.feng.annotation.Service}注解的类,若是则判断有没有传入的{@code service}名称 *- 首先判断是不是{
其他情况,包括是{ @link org.feng.annotation.Controller}的情况,全部使用小写类名为{@code key} * */ 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); } /** * 先通过传入的包名拼接出文件路径: ** {
@code "org.feng".replace(".", "/");} * * 递归扫描指定路径下的所有{@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