• 手写简单版SpringMVC


    本篇是看了慕课网上的教程仅需2小时 手写MINI Spring MVC框架,跟着手写了一次简单版的SpringMVC,项目由Gradle做项目依赖管理。

    项目实现如下功能:

    1. Bean扫描
    2. 控制翻转
    3. 依赖注入(循环依赖先不处理)
    4. 请求分发和响应

    大体流程

    1. 入口类扫描Class,并启动Tomcat服务
    2. Bean工厂扫描Class中的注解,创建实例,并且处理依赖注入
    3. 扫描Controller类,创建控制器内的方法和Url的映射关系
    4. 建立DispatchServlet,统管请求,请求来到时,遍历Controller类中的映射,找到后,反射调用控制器的方法,获取返回数据,写到浏览器
    • 新建Gradle项目,选择普通java项目即可。再新建framework模块,依赖如下

    集成Tomcat,Tomcat支持内嵌式在Java项目中。

    1. plugins {
    2. id 'java'
    3. }
    4. group 'zbs.mooc.com'
    5. version '1.0-SNAPSHOT'
    6. repositories {
    7. mavenCentral()
    8. }
    9. dependencies {
    10. testCompile group: 'junit', name: 'junit', version: '4.12'
    11. //集成Tomcat
    12. compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.5.23'
    13. }

    嵌入Tomcat以及统一请求入口DispatcherServlet

    • DispatcherServlet类

    建立web包,再在里面创建一个servlet包,新建DispatcherServlet类,用于分发请求。

    1. /**
    2. * 分发请求的Servlet
    3. */
    4. public class DispatcherServlet implements Servlet {
    5. private ServletConfig config;
    6. @Override
    7. public void init(ServletConfig config) throws ServletException {
    8. this.config = config;
    9. System.out.println("DispatcherServlet => init()... 初始化");
    10. }
    11. @Override
    12. public ServletConfig getServletConfig() {
    13. return config;
    14. }
    15. @Override
    16. public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    17. //...后续会在这里做文章,先留空
    18. }
    19. @Override
    20. public String getServletInfo() {
    21. return "";
    22. }
    23. @Override
    24. public void destroy() {
    25. System.out.println("DispatcherServlet => destroy()... 销毁");
    26. }
    27. }
    • TomcatServer类

    web包下,新建TomcatServer类,作为Tomcat的启动类,同时注册DispatcherServlet,让DispatcherServlet处理所有请求。

    1. 端口号是6699
    2. DispatcherServlet注册的请求路径为/,表示统配所有请求

    1. public class TomcatServer {
    2. /**
    3. * Tomcat实例
    4. */
    5. private Tomcat tomcat;
    6. /**
    7. * 启动参数,后续可以获取启动参数来进行配置
    8. */
    9. private String[] args;
    10. public TomcatServer(String[] args) {
    11. this.args = args;
    12. }
    13. /**
    14. * 开启Tomcat服务
    15. */
    16. public void startServer() throws LifecycleException {
    17. tomcat = new Tomcat();
    18. tomcat.setPort(6699);
    19. tomcat.start();
    20. //初始化容器
    21. Context context = new StandardContext();
    22. context.setPath("");
    23. context.addLifecycleListener(new Tomcat.FixContextListener());
    24. DispatcherServlet dispatcherServlet = new DispatcherServlet();
    25. //注册DispatcherServlet
    26. Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet)
    27. //设置支持异步
    28. .setAsyncSupported(true);
    29. //设置Servlet和URI的映射
    30. context.addServletMappingDecoded("/", "dispatcherServlet");
    31. //注册默认Host容器
    32. tomcat.getHost().addChild(context);
    33. //声明等待线程
    34. Thread awaitThread = new Thread(new Runnable() {
    35. @Override
    36. public void run() {
    37. //让Tomcat一直在等待
    38. tomcat.getServer().await();
    39. }
    40. }, "tomcat_await_thread");
    41. //设置为非守护进程
    42. awaitThread.setDaemon(false);
    43. awaitThread.start();
    44. }
    45. }
    • MiniApplication类

    新建starter包,新建MiniApplication类,作为框架的入口。
    需要调用方在main函数中调用MiniApplication的run()方法,传入启动入口类的Class和参数args。

    1. /**
    2. * 框架入口类
    3. */
    4. public class MiniApplication {
    5. public static void run(Class<?> cls, String[] args) {
    6. try {
    7. //创建Tomcat服务,启动服务
    8. TomcatServer tomcatServer = new TomcatServer(args);
    9. tomcatServer.startServer();
    10. } catch (Exception e) {
    11. e.printStackTrace();
    12. }
    13. }
    14. }

    获取当前包下的所有Class

    • ClassScanner扫描类

    建立core包,新建ClassScanner类,用于获取当前包下的所有Class。

    1. /**
    2. * 类扫描器,将指定包下的所有Class收集起来
    3. */
    4. public class ClassScanner {
    5. /**
    6. * 扫描指定包下的所有Class
    7. */
    8. public static List<Class<?>> scanClasses(String packageName) throws IOException, ClassNotFoundException {
    9. List<Class<?>> classList = new ArrayList<>();
    10. //将类的全路径名转换为文件路径
    11. String path = packageName.replace(".", "/");
    12. //获取类加载器
    13. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    14. Enumeration<URL> resources = classLoader.getResources(path);
    15. while (resources.hasMoreElements()) {
    16. URL resource = resources.nextElement();
    17. //如果是jar包,则获取jar包绝对路径
    18. if (resource.getProtocol().contains("jar")) {
    19. JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
    20. String jarFilePath = jarURLConnection.getJarFile().getName();
    21. //通过jar包的路径,获取jar包下所有的类
    22. classList.addAll(getClassesFromJar(jarFilePath, path));
    23. } else {
    24. //非jar包类型
    25. }
    26. }
    27. return classList;
    28. }
    29. /**
    30. * 通过jar包的路径,获取jar包下所有的类
    31. *
    32. * @param jarFilePath jar包的绝对路径
    33. * @param path 需要获取的类的相对路径,用来过滤
    34. */
    35. private static List<Class<?>> getClassesFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException {
    36. ArrayList<Class<?>> classes = new ArrayList<>();
    37. JarFile jarFile = new JarFile(jarFilePath);
    38. Enumeration<JarEntry> jarEntries = jarFile.entries();
    39. while (jarEntries.hasMoreElements()) {
    40. JarEntry jarEntry = jarEntries.nextElement();
    41. //com/mooc/zbs/test/Test.class
    42. String entryName = jarEntry.getName();
    43. if (entryName.startsWith(path) && entryName.endsWith(".class")) {
    44. //获取类的全类名
    45. String classFullName = entryName.replace("/", ".")
    46. .substring(0, entryName.length() - 6);
    47. classes.add(Class.forName(classFullName));
    48. }
    49. }
    50. return classes;
    51. }
    52. }
    • MiniApplication启动类中,添加调用

    1. /**
    2. * 框架入口类
    3. */
    4. public class MiniApplication {
    5. public static void run(Class<?> cls, String[] args) {
    6. try {
    7. //创建Tomcat服务,启动服务
    8. TomcatServer tomcatServer = new TomcatServer(args);
    9. tomcatServer.startServer();
    10. //获取所有的Class
    11. List<Class<?>> classList = ClassScanner.scanClasses(cls.getPackage().getName());
    12. } catch (Exception e) {
    13. e.printStackTrace();
    14. }
    15. }
    16. }

    提供控制翻转和依赖注入

    • Bean注解

    建立beans包,建立Bean注解,用于控制翻转

    1. /**
    2. * Bean注解,标识一个类被框架容器管理
    3. */
    4. @Documented
    5. //作用于类上
    6. @Retention(RetentionPolicy.RUNTIME)
    7. @Target(ElementType.TYPE)
    8. public @interface Bean {
    9. }
    • Controller注解

    和Bean注解类似,特指控制器的注解,给容器管理

    1. /**
    2. * 控制器注解
    3. */
    4. @Documented
    5. //保留到运行时
    6. @Retention(RetentionPolicy.RUNTIME)
    7. //作用到类上
    8. @Target(ElementType.TYPE)
    9. public @interface Controller {
    10. }
    • Service注解

    和Bean注解类似,特指业务层的注解,给容器管理,后续可能会添加特定功能

    1. /**
    2. * Service注解,标识这个类是Service层的对象
    3. */
    4. @Documented
    5. //作用于类上
    6. @Retention(RetentionPolicy.RUNTIME)
    7. @Target(ElementType.TYPE)
    8. public @interface Service {
    9. }
    • AutoWired注解

    建立AutoWired注解,用于依赖注入

    1. /**
    2. * 依赖注入注解
    3. */
    4. @Documented
    5. @Retention(RetentionPolicy.RUNTIME)
    6. //作用于类属性上
    7. @Target(ElementType.FIELD)
    8. public @interface AutoWired {
    9. }
    • BeanFactory工厂

    建立BeanFactory工厂类,该类主要是扫描类上的注解,遇到Bean、Service、Controller等注解,创建实例,并存入到容器中。

    1. /**
    2. * Bean工厂
    3. */
    4. public class BeanFactory {
    5. /**
    6. * Bean容器
    7. */
    8. private static final Map<Class<?>, Object> classToBean = new ConcurrentHashMap<>();
    9. /**
    10. * 获取一个Bean
    11. */
    12. public static Object getBean(Class<?> cls) {
    13. return classToBean.get(cls);
    14. }
    15. /**
    16. * 初始化Bean的方法
    17. *
    18. * @param classList 所有类列表
    19. */
    20. public static void initBean(List<Class<?>> classList) throws InstantiationException, IllegalAccessException {
    21. ArrayList<Class<?>> toCreate = new ArrayList<>(classList);
    22. while (toCreate.size() != 0) {
    23. int remainSize = toCreate.size();
    24. for (int i = 0; i < toCreate.size(); i++) {
    25. //创建完,就要移除掉
    26. if (finishCreate(toCreate.get(i))) {
    27. toCreate.remove(i);
    28. }
    29. }
    30. //陷入循环依赖的死循环,抛出异常
    31. if (toCreate.size() == remainSize) {
    32. throw new RuntimeException("cycle dependency!");
    33. }
    34. }
    35. }
    36. /**
    37. * 初始化Bean
    38. */
    39. private static boolean finishCreate(Class<?> cls) throws IllegalAccessException, InstantiationException {
    40. boolean hasBeanAnno = cls.isAnnotationPresent(Bean.class);
    41. boolean hasControllerAnno = cls.isAnnotationPresent(Controller.class);
    42. boolean hasServiceSAnno = cls.isAnnotationPresent(Service.class);
    43. //忽略,没有使用Bean注解和不是Controller、Service的类
    44. if (!hasBeanAnno && !hasControllerAnno && !hasServiceSAnno) {
    45. return true;
    46. }
    47. //创建Bean,处理对象中的属性,查看是否需要依赖注入
    48. Object bean = cls.newInstance();
    49. for (Field field : cls.getDeclaredFields()) {
    50. if (field.isAnnotationPresent(AutoWired.class)) {
    51. //获取属性的类型
    52. Class<?> fieldType = field.getType();
    53. //从工厂里面获取,获取不到,先返回
    54. Object reliantBean = BeanFactory.getBean(fieldType);
    55. if (reliantBean == null) {
    56. return false;
    57. }
    58. //从工厂获取到了,设置属性字段可接触
    59. field.setAccessible(true);
    60. //反射将对象设置到属性上
    61. field.set(bean, reliantBean);
    62. }
    63. }
    64. //缓存实例到工厂中
    65. classToBean.put(cls, bean);
    66. return true;
    67. }
    68. }
    • 在启动类中,添加调用

    1. /**
    2. * 框架入口类
    3. */
    4. public class MiniApplication {
    5. public static void run(Class<?> cls, String[] args) {
    6. try {
    7. //创建Tomcat服务,启动服务
    8. TomcatServer tomcatServer = new TomcatServer(args);
    9. tomcatServer.startServer();
    10. //获取所有的Class
    11. List<Class<?>> classList = ClassScanner.scanClasses(cls.getPackage().getName());
    12. //创建Bean工厂,扫描Class,创建被注解标注的类
    13. BeanFactory.initBean(classList);
    14. } catch (Exception e) {
    15. e.printStackTrace();
    16. }
    17. }
    18. }

    控制器接口方法和Url映射

    经过上面的代码,我们先使用ClassScanner获取到所有的类的Class,再通过BeanFactory,实例化所有注解标识的实例。接下来就是在扫描出使用了Controller注解的控制器。
    让控制器上的接口方法和Url产生映射关系。

    • RequestMapping注解

    该注解标识在Controller器的接口方法上,为每个接口方法绑定一个Url。

    1. /**
    2. * 接口方法注解,需要指定Url
    3. */
    4. @Documented
    5. @Retention(RetentionPolicy.RUNTIME)
    6. @Target(ElementType.METHOD)
    7. public @interface RequestMapping {
    8. /**
    9. * Url
    10. */
    11. String value();
    12. }
    • RequestParam注解

    该注解标识在接口方法的形参上,标识每个形参变量对应的字段值

    1. /**
    2. * 请求参数注解
    3. */
    4. @Documented
    5. @Retention(RetentionPolicy.RUNTIME)
    6. //需要作用于方法参数上
    7. @Target(ElementType.PARAMETER)
    8. public @interface RequestParam {
    9. /**
    10. * 指定请求参数的key
    11. */
    12. String value();
    13. }
    • MappingHandler

    新建MappingHandler类,用来保存Controller中接口方法和Url之间的关系,以及反射调用接口方法需要参数,例如形参列表、控制器Class、方法Method对象。

    提供一个handle方法,提供给DispatchServlet调用,请求发过来时,判断是否是该接口方法响应。是则反射调用方法,并获取到返回值,响应到请求方。

    1. /**
    2. * 保存每个URL和Controller的映射
    3. */
    4. public class MappingHandler {
    5. /**
    6. * 请求路径Uri
    7. */
    8. private final String uri;
    9. /**
    10. * Controller中对应的方法
    11. */
    12. private final Method method;
    13. /**
    14. * Controller类对象
    15. */
    16. private final Class<?> controller;
    17. /**
    18. * 调用方法时传递的参数
    19. */
    20. private final String[] args;
    21. public MappingHandler(String uri, Method method, Class<?> controller, String[] args) {
    22. this.uri = uri;
    23. this.method = method;
    24. this.controller = controller;
    25. this.args = args;
    26. }
    27. /**
    28. * 处理方法
    29. *
    30. * @param req 请求对象
    31. * @param res 响应对象
    32. * @return 是否处理了
    33. */
    34. public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
    35. //获取请求路径
    36. String requestUri = ((HttpServletRequest) req).getRequestURI();
    37. //不是当前的Controller处理,直接返回
    38. if (!requestUri.equals(uri)) {
    39. return false;
    40. }
    41. //是当前Controller要处理的,准备方法参数,从Request对象中获取,获取到的值给反射调用
    42. Object[] parameters = new Object[args.length];
    43. for (int i = 0; i < args.length; i++) {
    44. parameters[i] = req.getParameter(args[i]);
    45. }
    46. //从缓存中取出Controller,启动时就已经创建Controller实例了
    47. Object ctl = BeanFactory.getBean(controller);
    48. //调用对应的接口方法,并获取响应结果
    49. Object response = method.invoke(ctl, parameters);
    50. //将响应结果写到外面
    51. res.getWriter().println(response.toString());
    52. return true;
    53. }
    54. }
    • HandlerManager

    Handler管理器,每个Controller其实就是一个Handler,该管理器负责启动时扫描所有Controller类,组成映射关系,并存储起来,提供给DispatchServlet获取和使用。

    1. /**
    2. * Handler管理类
    3. */
    4. public class HandlerManager {
    5. /**
    6. * Controller类中所有类方法和uri映射关系
    7. */
    8. private static final List<MappingHandler> mappingHandlerList = new ArrayList<>();
    9. /**
    10. * 找到所有Controller类
    11. *
    12. * @param classList 类的Class集合
    13. */
    14. public static void resolveMappingHandler(List<Class<?>> classList) {
    15. for (Class<?> cls : classList) {
    16. //判断是否使用了Controller注解
    17. if (cls.isAnnotationPresent(Controller.class)) {
    18. parseHandlerFromController(cls);
    19. }
    20. }
    21. }
    22. /**
    23. * 解析Controller上的注解
    24. */
    25. private static void parseHandlerFromController(Class<?> cls) {
    26. //获取类上的所有方法
    27. Method[] methods = cls.getDeclaredMethods();
    28. for (Method method : methods) {
    29. //判断方法是否使用了RequestMapping注解,如果没有标识,不处理
    30. if (!method.isAnnotationPresent(RequestMapping.class)) {
    31. continue;
    32. }
    33. //获取RequestMapping注解上标识的uri值
    34. String uri = method.getDeclaredAnnotation(RequestMapping.class).value();
    35. //获取形参上的RequestParam注解,拿取注解上定义的值
    36. ArrayList<String> paramNameList = new ArrayList<>();
    37. for (Parameter parameter : method.getParameters()) {
    38. if (parameter.isAnnotationPresent(RequestParam.class)) {
    39. String value = parameter.getDeclaredAnnotation(RequestParam.class).value();
    40. paramNameList.add(value);
    41. }
    42. }
    43. //参数集合转换为数组
    44. String[] params = paramNameList.toArray(new String[paramNameList.size()]);
    45. //参数收集完毕,构建一个MappingHandler
    46. MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params);
    47. //保存到列表里
    48. mappingHandlerList.add(mappingHandler);
    49. }
    50. }
    51. public static List<MappingHandler> getMappingHandlerList() {
    52. return mappingHandlerList;
    53. }
    54. }
    • 启动类中,添加调用

    1. /**
    2. * 框架入口类
    3. */
    4. public class MiniApplication {
    5. public static void run(Class<?> cls, String[] args) {
    6. try {
    7. //创建Tomcat服务,启动服务
    8. TomcatServer tomcatServer = new TomcatServer(args);
    9. tomcatServer.startServer();
    10. //获取所有的Class
    11. List<Class<?>> classList = ClassScanner.scanClasses(cls.getPackage().getName());
    12. //创建Bean工厂,扫描Class,创建被注解标注的类
    13. BeanFactory.initBean(classList);
    14. //扫描所有的类,找到所有Controller,建立Controller中每个方法和Url的映射关系
    15. HandlerManager.resolveMappingHandler(classList);
    16. } catch (Exception e) {
    17. e.printStackTrace();
    18. }
    19. }
    20. }
    • 在DispatchServlet中补充遍历MappingHandler

    1. /**
    2. * 分发请求的Servlet
    3. */
    4. public class DispatcherServlet implements Servlet {
    5. private ServletConfig config;
    6. @Override
    7. public void init(ServletConfig config) throws ServletException {
    8. this.config = config;
    9. System.out.println("DispatcherServlet => init()... 初始化");
    10. }
    11. @Override
    12. public ServletConfig getServletConfig() {
    13. return config;
    14. }
    15. @Override
    16. public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    17. //获取所有Controller和内部定义的接口方法列表
    18. List<MappingHandler> mappingHandlerList = HandlerManager.getMappingHandlerList();
    19. //找到当前请求Url对应的Controller接口处理方法
    20. for (MappingHandler mappingHandler : mappingHandlerList) {
    21. try {
    22. if (mappingHandler.handle(req, res)) {
    23. return;
    24. }
    25. } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
    26. e.printStackTrace();
    27. }
    28. }
    29. }
    30. @Override
    31. public String getServletInfo() {
    32. return "";
    33. }
    34. @Override
    35. public void destroy() {
    36. System.out.println("DispatcherServlet => destroy()... 销毁");
    37. }
    38. }

    测试模块

    • 建立一个test模块,依赖framework模块,依赖如下

    注意要添加jar的配置,指定启动类,才能打包和运行成功

    1. plugins {
    2. id 'java'
    3. }
    4. group 'zbs.mooc.com'
    5. version '1.0-SNAPSHOT'
    6. repositories {
    7. mavenCentral()
    8. }
    9. dependencies {
    10. testCompile group: 'junit', name: 'junit', version: '4.12'
    11. //依赖自定义的SpringMVC
    12. compile(project(':framework'))
    13. }
    14. //标识启动类
    15. jar {
    16. manifest {
    17. attributes "Main-Class": "com.mooc.zbs.Application"
    18. }
    19. from {
    20. configurations.compile.collect {
    21. it.isDirectory() ? it : zipTree(it)
    22. }
    23. }
    24. }
    • 新建Application入口类,并初始化框架

    1. /**
    2. * 测试入口类
    3. */
    4. public class Application {
    5. public static void main(String[] args) {
    6. MiniApplication.run(Application.class, args);
    7. }
    8. }
    • 建立工具类,新增SalaryHelper工具类,提供一个按工龄计算工资的方法

    1. /**
    2. * 工资计算类
    3. */
    4. @Bean
    5. public class SalaryHelper {
    6. /**
    7. * 计算工资
    8. *
    9. * @param experience 工龄
    10. */
    11. public Integer calSalary(Integer experience) {
    12. return experience * 5000;
    13. }
    14. }
    • 建立Service层,提供SalaryService,业务层对象

    1. @Service
    2. public class SalaryService {
    3. @AutoWired
    4. private SalaryHelper salaryHelper;
    5. /**
    6. * 计算工资
    7. *
    8. * @param experience 工龄
    9. */
    10. public Integer calSalary(Integer experience) {
    11. return salaryHelper.calSalary(experience);
    12. }
    13. }
    • 建立Controller,提供getSalary()接口方法,提供计算工资的功能

    1. /**
    2. * 工资的控制器
    3. */
    4. @Controller
    5. public class SalaryController {
    6. /**
    7. * 依赖注入
    8. */
    9. @AutoWired
    10. private SalaryService salaryService;
    11. /**
    12. * 查询工资
    13. *
    14. * @param name 员工名称
    15. * @param experience 工龄
    16. */
    17. @RequestMapping("/getSalary")
    18. public Integer getSalary(@RequestParam("name") String name, @RequestParam("experience") String experience) {
    19. System.out.println("salaryService => " + salaryService);
    20. System.out.println("获取到的参数 => name=" + name + ",experience=" + experience);
    21. return salaryService.calSalary(Integer.parseInt(experience));
    22. }
    23. }

    启动

    • 选择Idea右侧的Gradle命令模板,点击jar命令,命令跑完后,在test模块下的build目录下,有一个libs目录,里面就存放着一个打包好的jar包。
    • java -jar test/build/libs/test-1.0-SNAPSHOT.jar,即可启动。
    • 浏览器访问:http://localhost:6699/getSalary?experience=5,即可调用到刚才的Controller,输出结果25000。

    项目源码

    项目源码我放到了Github,有兴趣的同学可以clone下来看下。

    https://github.com/hezihaog/mini-spring
    1. 作者:h2coder
    2. 链接:手写简单版SpringMVC - 简书

  • 相关阅读:
    好物,旅游,带货,门店,宣传怎么做批量混剪视频?视频闪闪视频批量剪辑软件帮你实现批量替换素材进行裂变
    计算机毕业设计之java+springboot基于vue的校园台球厅人员与设备管理系统
    Greenplum数据库故障分析——版本升级后gpstart -a为何返回失败
    加工制造业的升级突破必备系统
    地平线 自动化测试|测试开发 面试真题|面经 汇总
    从预训练损失的角度,理解语言模型的涌现能力
    QChartView显示实时更新的温度曲线图,即动态曲线图。
    高阶组件使用
    基于yolov5的电瓶车和自行车检测系统,可进行图像目标检测,也可进行视屏和摄像检测(pytorch框架)【python源码+UI界面+功能源码详解】
    vuepress(六)阿里云二级域名配置与添加SSL证书
  • 原文地址:https://blog.csdn.net/qq_34874784/article/details/125417911