SpringBoot深度探究(一)模拟启动Tomcat

12 篇文章 2 订阅
6 篇文章 0 订阅

前言

SpringBoot是目前最广泛的Web开发框架,其极度方便的开发特性得到市场的充分认可。这一系列的博客会进行一个详细的探讨,包括SpringBoot的原理模拟,SpringBoot源码解读,以及SpringBoot重要功能的官方文档的解读。俗话说了解一个组织就到打入这个组织内部,毕竟我就看一眼嘛。所以笔者会在Spring框架下,模拟一个SpringBoot的基本功能入手,然后在和SpringBoot的源码做对比,深度学习一下SpringBoot。本篇将会探究SpringBoot是怎么实现从开发到集成Tomcat一键完成,又是如何直接读到静态资源的,本篇结束以后将会后续进行SpringBoot源码的解读,深度对比SpringBoot做了哪些更深一层的功能。更多Spring内容进入【Spring解读系列目录】

添加依赖

当创建一个Maven-Web项目以后,首先自然要把Spring的包和Web相关的包添加进来。

<!--Spring 上下文包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
<!--Spring web依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
<!--servlet依赖-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

构建Spring MVC框架

其实这一部分笔者已经在【SpringMVC新版本(Spring 5.3.0)官网详解】博客里面详细的有过解释。所以这次就不多赘述,直接构建出来:配置类AppConfig,初始化类MyWebApplicationInitializer,完成一个Spring MVC的基本配置。

@ComponentScan("com.demo")
@Configuration
public class AppConfig {}
}
public class MyWebApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext anno=new AnnotationConfigWebApplicationContext();
        anno.register(AppConfig.class);
        anno.refresh();
        DispatcherServlet servlet=new DispatcherServlet(anno);
        ServletRegistration.Dynamic registration = servletContext.addServlet("myBootServlet", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

构建Web入口

SpringBoot是通过一个main()方法直接调用run()方法来的Web配置和Tomcat服务器。那么就把Web入口先创建出来MyBootApplicationStart,在它的main()方法里调用启动器。

public class MyBootApplicationStart {
    public static void main(String[] args) {
        MySpringBootApplication.run();
    }
}

构建启动器

这一步就要开始构架启动器的run()方法了。SpringBoot在run()方法里启动了Tomcat,所以我们也得这么做。

public class MySpringBootApplication {
    public static void run(){
    }
}

很明显此时应该在里面做Tomcat的启动,必然需要引入Tomcat相关依赖。因为需要new一个Tomcat并启动,就是这么简单。

<!—Tomcat核心-->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-core</artifactId>
    <version>9.0.13</version>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-el</artifactId>
    <version>9.0.13</version>
</dependency>
<!—Tomcat JSP支持-->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <version>9.0.13</version>
</dependency>

构建Tomcat

那么就直接new一个Tomcat,然后直接start()启动,Tomcat跑起来了,完事儿。

public class MySpringBootApplication {
    public static void run(){
		//new一个tomcat
        Tomcat tomcat=new Tomcat();
		//设置端口号
        tomcat.setPort(9098);
        try {
			//启动
            tomcat.start();
			//阻塞等待连接进入
            tomcat.getServer().await();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }
}
******************9.0.13启动Log:******************
Nov 09, 2020 5:52:28 PM org.apache.catalina.core.StandardService startInternal
INFO: Starting service [Tomcat]

******************8.5.5启动Log:******************
Nov 09, 2020 5:55:24 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-9098"]
Nov 09, 2020 5:55:24 PM org.apache.tomcat.util.net.NioSelectorPool getSharedSelector
INFO: Using a shared selector for servlet write/read
Nov 09, 2020 5:55:24 PM org.apache.catalina.core.StandardService startInternal
INFO: Starting service Tomcat
Nov 09, 2020 5:55:24 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler [http-nio-9098]

没错就是这么简单,但是现在我们启动的Tomcat非常的简单,既不能读取Jsp或者html这些静态资源,也不能加载SpringMVC的配置,只是把Tomcat启动了而已。此外,博主把8.5.5启动Log9.0.13启动Log都贴出来,还是推荐大家使用8.5.5作为学习的jar包,因为打印的日志更多。

完善静态资源访问

为了完善项目,我们需要给Tomcat加上一些必要的功能。首先要指明Class文件放在哪里,一般来说就是/src/main/java目录,这个只要指定好项目目录就可以做到。那么重要的就是要指明静态资源放在哪里,就是SpringBoot的/resources目录,其下还有三个子目录/resources/static/public

回顾一下SpringBoot的功能,就能发现要模拟SpringBoot面临一个很重要的问题,就是上面说的访问资源的问题。因为当新建一个SpringBoot项目以后,只要静态文件放到上述文件夹里面就可以直接访问,如同访问Java文件一样方便。Java文件还可以理解毕竟可以交给Spring去管理。但是静态文件是如何访问的呢?比如就可以直接访问index.html静态页面。

其实我们可以有这样的思路:首先一个内容如果要被解析,那么它一定要被加载到内存中去,然后才能被操作。那么当接收到一个请求的时候,就可以截取其后缀,然后根据项目路径拼出来文件路径。有了路径+文件名字以后,在项目重new一个文件对象出来。之后通过FileInputStream读到内存中,最后通过response响应输出出去。

重写DispatcherServlet

为了完成上面的构想,就需要重写DispatcherServlet,由我们自己的Servlet去完成拦截请求的功能,那么我们在上面MyWebApplicationInitializer.onStartup()方法里写的代码基本就作废了。这里需要重写ServletContainerInitializer.onStartup()方法。关于这俩的区别可以参考【SpringMVC源码Tomcat如何访问WebApplicationInitializer】。那么我们自己的DispatcherServlet就命名为MybootServlet

public class MybootServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //得到静态资源后缀"/index.jsp或者/index.html",由于这些文件内容在编译后都会放在classpath下。
        // 因此得到classpath也是必须的
        String fileName=request.getRequestURI();
        //得到classpath
        String basePath=MybootServlet.class.getResource("/").getPath();
        //拼出来文件路径完整路径,此时我们拿到的类似于D:/项目名/src/java/main/index.html这样的
        String filePath=basePath+fileName;
        //构造一个file对象,把静态资源读到file对象里
        File file=new File(filePath);
        //InputStream读入内存
        InputStream inputStream=new FileInputStream(file);
        byte[] bytes = new byte[1024];
        inputStream.read(bytes);
        inputStream.close();
        //创建相应内容
        String str=new String(bytes);
        //设置响应内容
        response.setContentType("text/html");
        //写出去
        response.getWriter().write(str);
    }
}

重写Initializer

自己的Servlet有了以后还需要注册到Tomcat里才行,因此还要重写Initializer

public class MybootInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        //注册我们自己的Servlet到Tomcat里
        ServletRegistration.Dynamic registration = ctx.addServlet("mybootServlet", new MybootServlet());
        //拦截所有的请求。
        registration.addMapping("/");
    }
}

最后还需要配置一下SPI,什么是SPI【SpringMVC源码】链接里面有介绍。

创建出来文件:javax.servlet.ServletContainerInitializer
配置上自己的服务:com.demo.app.MybootInitializer

以及一个web测试文件index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
asdfadasdasdasdasd
</body>
</html>

运行查看结果,我们静态资源就被输出在网页上了
在这里插入图片描述

总结

本篇博客基本上模拟了一下SpringBoot是如何启动Tomcat,以及如何读取到静态资源的。本篇博客最重要的一个点,其实在于使用Servlet3.x添加的SPI功能进行了一个Tomcat外部服务的插入功能。后面笔者还会针对SpringBoot在这一块的源码做一个解读对比,看看大神们构思的SpringBoot和模拟的在思路上有什么区别。

附:完整的路径以及代码

路径
在这里插入图片描述
AppConfig

@ComponentScan("com.demo")
@Configuration
public class AppConfig {
}

MybootInitializer

public class MybootInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        ServletRegistration.Dynamic registration = ctx.addServlet("mybootServlet", new MybootServlet());
        registration.addMapping("/");
    }
}

MySpringBootApplication

public class MySpringBootApplication {
        public static void run() {
        String relativePath=System.getProperty("user.dir");
        //拿到项目路径,也就是当前项目的target\classes目录,项目编译后的路径
        String sourcePath=MySpringBootApplication.class.getResource("/").getPath();
        String webAppDirLocation=relativePath+"/src/main/webapp";
        Tomcat tomcat=new Tomcat();
        //tomcat.setBaseDir();
        tomcat.setPort(9080);
        try {
            //告诉tomcat,源码在哪里,得到Context
            StandardContext ctx = (StandardContext) tomcat.addWebapp("/",new File(webAppDirLocation).getAbsolutePath());
            //class文件所在的位置
            WebResourceRoot resourceRoot=new StandardRoot(ctx);
            //源码的路径resourceRoot,发布后的路径/WEB-INF/classes,项目编译后的路径sourcePath
            resourceRoot.addPreResources(new DirResourceSet(resourceRoot,"/WEB-INF/classes",sourcePath,"/"));
            ctx.setResources(resourceRoot);
            //启动Tomcat
            tomcat.start();
            //等待连接进入
            tomcat.getServer().await();
        } catch (LifecycleException | ServletException e) {
            e.printStackTrace();
        }
    }
}

MyBootApplicationStart

public class MyBootApplicationStart {
    public static void main(String[] args) {
        MySpringBootApplication.run();
    }
}

MybootServlet

public class MybootServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String fileName=request.getRequestURI();
        String basePath=MybootServlet.class.getResource("/").getPath();
        String filePath=basePath+fileName;
        File file=new File(filePath);
        InputStream inputStream=new FileInputStream(file);
        byte[] bytes = new byte[1024];
        inputStream.read(bytes);
        inputStream.close();
        String str=new String(bytes);
        response.setContentType("text/html");
        response.getWriter().write(str);
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值