资讯专栏INFORMATION COLUMN

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

AprilJ / 1537人阅读

摘要:服务器相关配置启动类资源目录目录静态文件目录端口号目录目录实现内嵌服务器在上一章文章从零开始实现一个简易的框架七实现已经在文件中引入了依赖,所以这里就不用引用了。

spring-boot的Starter

一个项目总是要有一个启动的地方,当项目部署在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 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的启动器了。

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

加载一些参数变量

加载Bean(IOC、AOP)等工作

启动服务器

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


    default
    org.apache.catalina.servlets.DefaultServlet
    
        debug
        0
    
    
        listings
        false
    
    1


    jsp
    org.apache.jasper.servlet.JspServlet
    
        fork
        false
    
    
        xpoweredBy
        false
    
    3

而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中的配置

BeanContainer扫描包并加载Bean

执行Aop

执行Ioc

启动Tomcat服务器

这里的执行是有顺序要求的,特别是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);
    }
}

然后再创建一个ControllerDoodleController:

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框架(一)--前言

从零开始实现一个简易的Java MVC框架(二)--实现Bean容器

从零开始实现一个简易的Java MVC框架(三)--实现IOC

从零开始实现一个简易的Java MVC框架(四)--实现AOP

从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点

从零开始实现一个简易的Java MVC框架(六)--加强AOP功能

从零开始实现一个简易的Java MVC框架(七)--实现MVC

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

从零开始实现一个简易的Java MVC框架(九)--优化MVC代码

源码地址:doodle

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76627.html

相关文章

  • 从零开始实现一个简易Java MVC框架

    摘要:不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的框架。原文地址从零开始实现一个简易的框架 前言 最近在看spring-boot框架的源码,看了源码之后更是让我感受到了spring-boot功能的强大。而且使用了很多的设计模式,让人在看的时候觉得有点难以下手。 不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的...

    neuSnail 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(五)--引入aspectj实现AOP切点

    摘要:接下来就可以把这个切点类加入到我们之前实现的功能中了。实现的切点功能首先改装注解,把之前改成来存储表达式。测试用例在上一篇文章从零开始实现一个简易的框架四实现中的测试用例的基础上修改测试用例。 前言 在上一节从零开始实现一个简易的Java MVC框架(四)--实现AOP中我们实现了AOP的功能,已经可以生成对应的代理类了,但是对于代理对象的选择只能通过指定的类,这样确实不方便也不合理。...

    wupengyu 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(六)--加强AOP功能

    摘要:在前面的文章中实现的功能时,目标类都只能被一个切面代理,如果想要生成第二个代理类,就会把之前的代理类覆盖。改装原有功能现在要改装原来的的实现代码,让的功能加入到框架中为了让切面能够排序,先添加一个注解,用于标记排序。 前言 在前面从零开始实现一个简易的Java MVC框架(四)--实现AOP和从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点这两节文章...

    Loong_T 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(九)--优化MVC代码

    摘要:前言在从零开始实现一个简易的框架七实现中实现了框架的的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。 前言 在从零开始实现一个简易的Java MVC框架(七)--实现MVC中实现了doodle框架的MVC的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。 优化的目标是1.去除DispatcherServlet请求分发器中的http逻...

    ruicbAndroid 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(二)--实现Bean容器

    摘要:容器实际上就是存放所有的地方,即以及相关信息对应其实体的容器,为什么称之为呢,因为在中,定义信息和实例的东西叫。了解到这个以后接下来就可以开始编写容器了,在包下创建一个类叫。获取容器实例至此,这个容器就完成了。 项目准备 首先确保你拥有以下环境或者工具 idea java 8 maven 3.3.X lombok插件 然后我们创建一个maven工程,编写pom.xml引入一些需要的...

    paulquei 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<