从零开始实现一个简易的Java MVC框架(八)--制作Starter

栏目: 服务器 · 发布时间: 7年前

内容简介:一个项目总是要有一个启动的地方,当项目部署在tomcat中的时候,经常就会用tomcat的而在spring-boot的web项目中基本会有类似于这样子的启动代码:这个方法实际上会调用spring-boot的

一个项目总是要有一个启动的地方,当项目部署在tomcat中的时候,经常就会用tomcat的 startup.sh(startup.bat) 的启动脚本来启动web项目

而在spring-boot的web项目中基本会有类似于这样子的启动代码:

@SpringBootApplication
public class SpringBootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}
复制代码

这个方法实际上会调用spring-boot的 SpringApplication 类的一个run方法:

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        // 1.加载环境变量、参数等
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                                                                 applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        // 2.加载Bean(IOC、AOP)等
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(context, environment, listeners, applicationArguments,
                       printedBanner);
        //会调用一个AbstractApplicationContext@refresh()方法,主要就是在这里加载Bean,方法的最后还会启动服务器
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                .logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}
复制代码

这段代码还是比较长的,不过实际上主要就做了两个事情:1.加载环境变量、参数等 2.加载Bean(IOC、AOP)等。3.如果获得的 ApplicationContextServletWebServerApplicationContext ,那么在 refresh() 之后会启动服务器,默认的就是tomcat服务器。

我觉得spring-boot启动器算是spring-boot中相对来说代码清晰易懂的,同时也非常容易了解到整个spring-boot的流程结构,建议大家能够去看一下。

实现Starter

了解到spring-boot的启动器的作用和原理之后,我们可以开始实现 doodle 的启动器了。

根据刚才提到的,启动器要做以下几件事

  1. 加载一些参数变量
  2. 加载Bean(IOC、AOP)等工作
  3. 启动服务器

Configuration保存变量

在com.zbw包下创建类 Configuration 用于保存一些全局变量,目前这个类只保存了现在实现的功能所需的变量。

package com.zbw;
import ...

/**
 * 服务器相关配置
 */
@Builder
@Getter
public class Configuration {

    /**
     * 启动类
     */
    private Class<?> bootClass;

    /**
     * 资源目录
     */
    @Builder.Default
    private String resourcePath = "src/main/resources/";

    /**
     * jsp目录
     */
    @Builder.Default
    private String viewPath = "/templates/";

    /**
     * 静态文件目录
     */
    @Builder.Default
    private String assetPath = "/static/";

    /**
     * 端口号
     */
    @Builder.Default
    private int serverPort = 9090;

    /**
     * tomcat docBase目录
     */
    @Builder.Default
    private String docBase = "";

    /**
     * tomcat contextPath目录
     */
    @Builder.Default
    private String contextPath = "";
}
复制代码

实现内嵌Tomcat服务器

在上一章文章 从零开始实现一个简易的Java MVC框架(七)--实现MVC 已经在pom.xml文件中引入了 tomcat-embed 依赖,所以这里就不用引用了。

先在com.zbw.mvc下创建一个包server,然后再server包下创建一个接口 Server

package com.zbw.mvc.server;

/**
 * 服务器 interface
 */
public interface Server {
    /**
     * 启动服务器
     */
    void startServer() throws Exception;

    /**
     * 停止服务器
     */
    void stopServer() throws Exception;
}

复制代码

因为服务器有很多种,虽然现在只用tomcat,但是为了方便扩展和修改,就先创建一个通用的server接口,每个服务器都要实现这个接口。

接下来就创建 TomcatServer 类,这个类实现 Server

package com.zbw.mvc.server;
import ...

/**
 * Tomcat 服务器
 */
@Slf4j
public class TomcatServer implements Server {

    private Tomcat tomcat;

    public TomcatServer() {
        new TomcatServer(Doodle.getConfiguration());
    }

    public TomcatServer(Configuration configuration) {
        try {
            this.tomcat = new Tomcat();
            tomcat.setBaseDir(configuration.getDocBase());
            tomcat.setPort(configuration.getServerPort());

            File root = getRootFolder();
            File webContentFolder = new File(root.getAbsolutePath(), configuration.getResourcePath());
            if (!webContentFolder.exists()) {
                webContentFolder = Files.createTempDirectory("default-doc-base").toFile();
            }

            log.info("Tomcat:configuring app with basedir: [{}]", webContentFolder.getAbsolutePath());
            StandardContext ctx = (StandardContext) tomcat.addWebapp(configuration.getContextPath(), webContentFolder.getAbsolutePath());
            ctx.setParentClassLoader(this.getClass().getClassLoader());

            WebResourceRoot resources = new StandardRoot(ctx);
            ctx.setResources(resources);
			// 添加jspServlet,defaultServlet和自己实现的dispatcherServlet
            tomcat.addServlet("", "jspServlet", new JspServlet()).setLoadOnStartup(3);
            tomcat.addServlet("", "defaultServlet", new DefaultServlet()).setLoadOnStartup(1);
            tomcat.addServlet("", "dispatcherServlet", new DispatcherServlet()).setLoadOnStartup(0);
            ctx.addServletMappingDecoded("/templates/" + "*", "jspServlet");
            ctx.addServletMappingDecoded("/static/" + "*", "defaultServlet");
            ctx.addServletMappingDecoded("/*", "dispatcherServlet");
            ctx.addServletMappingDecoded("/*", "dispatcherServlet");
        } catch (Exception e) {
            log.error("初始化Tomcat失败", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public void startServer() throws Exception {
        tomcat.start();
        String address = tomcat.getServer().getAddress();
        int port = tomcat.getConnector().getPort();
        log.info("local address: http://{}:{}", address, port);
        tomcat.getServer().await();
    }

    @Override
    public void stopServer() throws Exception {
        tomcat.stop();
    }

    private File getRootFolder() {
        try {
            File root;
            String runningJarPath = this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().getPath().replaceAll("\\\\", "/");
            int lastIndexOf = runningJarPath.lastIndexOf("/target/");
            if (lastIndexOf < 0) {
                root = new File("");
            } else {
                root = new File(runningJarPath.substring(0, lastIndexOf));
            }
            log.info("Tomcat:application resolved root folder: [{}]", root.getAbsolutePath());
            return root;
        } catch (URISyntaxException ex) {
            throw new RuntimeException(ex);
        }
    }
}

复制代码

这个类主要就是配置tomcat,和配置普通的外部tomcat有点类似只是这里是用代码的方式。注意的是在 getRootFolder() 方法中获取的是当前项目目录下的target文件夹,即idea默认的编译文件保存的位置,如果修改了编译文件保存位置,这里也要修改。

特别值得一提的是这部分代码:

// 添加jspServlet,defaultServlet和自己实现的dispatcherServlet
tomcat.addServlet("", "jspServlet", new JspServlet()).setLoadOnStartup(3);
tomcat.addServlet("", "defaultServlet", new DefaultServlet()).setLoadOnStartup(1);
tomcat.addServlet("", "dispatcherServlet", new DispatcherServlet()).setLoadOnStartup(0);
ctx.addServletMappingDecoded("/templates/" + "*", "jspServlet");
ctx.addServletMappingDecoded("/static/" + "*", "defaultServlet");
ctx.addServletMappingDecoded("/*", "dispatcherServlet");
ctx.addServletMappingDecoded("/*", "dispatcherServlet");
复制代码

这部分代码就相当于原来的web.xml配置的文件,而且 defaultServletjspServlet 这两个servlet是tomcat内置的servlet,前者用于处理静态资源如css、js文件等,后者用于处理jsp。如果有安装tomcat可以去tomcat目录下的conf文件夹里有个web.xml文件,里面有几行就是配置 defaultServletjspServlet

<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>0</param-value>
    </init-param>
    <init-param>
        <param-name>listings</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet>
    <servlet-name>jsp</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
    <init-param>
        <param-name>fork</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>xpoweredBy</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>3</load-on-startup>
</servlet>
复制代码

而dispatcherServlet就是 从零开始实现一个简易的Java MVC框架(七)--实现MVC 这一节中实现的分发器。这三个servlet都设置了LoadOnStartup,当这个值大于等于0时就会随tomcat启动也实例化。

实现启动器类

在com.zbw包下创建一个类作为启动器类,就是类似于 SpringApplication 这样的。这里起名叫做 Doodle ,因为这个框架就叫doodle嘛。

package com.zbw;
import ...

/**
 * Doodle Starter
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
public final class Doodle {

    /**
     * 全局配置
     */
    @Getter
    private static Configuration configuration = Configuration.builder().build();

    /**
     * 默认服务器
     */
    @Getter
    private static Server server;

    /**
     * 启动
     */
    public static void run(Class<?> bootClass) {
        run(Configuration.builder().bootClass(bootClass).build());
    }

    /**
     * 启动
     */
    public static void run(Class<?> bootClass, int port) {
        run(Configuration.builder().bootClass(bootClass).serverPort(port).build());
    }

    /**
     * 启动
     */
    public static void run(Configuration configuration) {
        new Doodle().start(configuration);
    }

    /**
     * 初始化
     */
    private void start(Configuration configuration) {
        try {
            Doodle.configuration = configuration;
            String basePackage = configuration.getBootClass().getPackage().getName();
            BeanContainer.getInstance().loadBeans(basePackage);
		   //注意Aop必须在Ioc之前执行
            new Aop().doAop();
            new Ioc().doIoc();

            server = new TomcatServer(configuration);
            server.startServer();
        } catch (Exception e) {
            log.error("Doodle 启动失败", e);
        }
    }
}
复制代码

这个类中有三个启动方法都会调用 Doodle@start() 方法,在这个方法里做了三件事:

configuration

这里的执行是有顺序要求的,特别是Aop必须要在Ioc之前执行,不然注入到类中的属性都是没被代理的。

修改硬编码

在之前写mvc的时候有一处有个硬编码,现在有了启动器和全局配置,可以把之前的硬编码修改了

对在com.zbw.mvc包下的 ResultRender 类里的 resultResolver() 方法,当判断为跳转到jsp文件的时候跳转路径那一行代码修改:

try {
    Doodle.getConfiguration().getResourcePath();
    // req.getRequestDispatcher("/templates/" + path).forward(req, resp);
    req.getRequestDispatcher(Doodle.getConfiguration().getResourcePath() + path).forward(req, resp);
} catch (Exception e) {
    log.error("转发请求失败", e);
    // TODO: 异常统一处理,400等...
}
复制代码

启动和测试项目

现在 doodle 框架已经完成其功能了,我们可以简单的创建一个Controller来感受一下这个框架。

在com包下创建sample包,然后在com.sample包下创建启动类 APP

package com.sample;

import com.zbw.Doodle;
public class App {
    public static void main(String[] args) {
        Doodle.run(App.class);
    }
}
复制代码

然后再创建一个Controller DoodleController :

package com.sample;
import com.zbw.core.annotation.Controller;
import com.zbw.mvc.annotation.RequestMapping;
import com.zbw.mvc.annotation.ResponseBody;

@Controller
@RequestMapping
public class DoodleController {
    @RequestMapping
    @ResponseBody
    public String hello() {
        return "hello doodle";
    }
}
复制代码

接着再运行App的main方法,就能启动服务了。

从零开始实现一个简易的Java MVC框架(八)--制作Starter
从零开始实现一个简易的Java MVC框架(八)--制作Starter

源码地址: doodle

原文地址: 从零开始实现一个简易的Java MVC框架(八)--制作Starter


以上所述就是小编给大家介绍的《从零开始实现一个简易的Java MVC框架(八)--制作Starter》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Security, Privacy and Commerce, 2nd Edition

Web Security, Privacy and Commerce, 2nd Edition

Simson Garfinkel / O'Reilly Media / 2002-01-15 / USD 44.95

Since the first edition of this classic reference was published, World Wide Web use has exploded and e-commerce has become a daily part of business and personal life. As Web use has grown, so have ......一起来看看 《Web Security, Privacy and Commerce, 2nd Edition》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

html转js在线工具
html转js在线工具

html转js在线工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具