手写SpringBoot (简化版本)

对于SpringBoot的总结

其实对于SpringBoot来说,它做的事情主要有几个点

  1. 整合了SpringMVC
  2. 内置了Tomcat
  3. 去除了XML这类型的配置文件
  • web.xml
  • application.xml
  • springmvc.xml
  1. ContextLoaderListener (即web.xml中配置的listener,目的是为了借助xml中的一些配置信息进行spring容器的初始化)
  2. DispatcherServlet (即向Spring容器中注册了一个Servlet,目的是为了完成Controller扫描配置视图解析器)

点击查看官网资料
这里主要查看的是如果不是用web.xml而是使用代码的方式的话,要如何实现DispatcherServlet的配置。

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

结合SpringMVC简单实现SpringBoot

1. 修改pom,加入 spring-context 和 spring-mvc 的依赖
  • spring-context 和 spring-mvc
    
      org.springframework
      spring-context
      5.1.3.RELEASE
    

    
    
      org.springframework
      spring-webmvc
      5.1.3.RELEASE
    
2. 新建MyWebApplicationInitalizer(查看上方 DispatcherServlet 的代码)即可
package cn.lazyfennec.springboot;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;

/**
 * @Author: Neco
 * @Description: Copy from https://docs.spring.io/spring-framework/docs/5.2.7.RELEASE/spring-framework-reference/web.html#mvc-servlet
 * @Date: create in 2022/7/25 12:45
 */
public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        // 这里有个坑,看到后边会进行讲解
        ac.register(AppConfig.class);
        ac.refresh();

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}
3. 修改pom,添加 tomcat 的依赖
  • embed-tomcat

    
      org.apache.tomcat.embed
      tomcat-embed-core
      8.5.30
    
  • 这里也顺带加入一下jasper的依赖,其实不加入也不会有什么大的问题,就是等下启动的时候会提示缺少Jasper类
    
    
    
      org.apache.tomcat
      tomcat-jasper
      8.5.30
    
4. 添加 NecoApplication类
package cn.lazyfennec.springboot;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;

import javax.servlet.ServletException;

/**
 * @Author: Neco
 * @Description:
 * @Date: create in 2022/7/25 11:47
 */
public class NecoApplication {

    public static void run() {
        // 这就是为什么SpringBoot可以直接运行jar包无需丢到tomcat的原因,因为它内置了tomcat
        try {
            Tomcat tomcat = new Tomcat();
            tomcat.addWebapp("/boot", "G:\\workspaces\\neco-springboot");// 这里替换成你们的当前项目的路径,加上这个程序才会认为这个是一个web项目
            tomcat.start();
            tomcat.getServer().await();
        } catch (LifecycleException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        }
    }
}
5. 添加 AppConfig配置类
package cn.lazyfennec.springboot;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @Author: Neco
 * @Description:
 * @Date: create in 2022/7/25 11:52
 */

@Configuration 
@ComponentScan("cn.lazyfennec") // 去除了一部分xml的定义
public class AppConfig {

}
6. 创建一个启动类
/**
 * @Author: Neco
 * @Description: 启动类
 * @Date: create in 2022/7/25 12:52
 */
public class App {
    public static void main(String[] args) {
        NecoApplication.run();
    }
}
7. 创建controller
package cn.lazyfennec.springboot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author: Neco
 * @Description:
 * @Date: create in 2022/7/25 12:59
 */
@Controller
public class NecoController {

    @RequestMapping("/index1")
    @ResponseBody
    public String test() {
        System.out.println("=========我接收到请求了=============");
        return "Hello Neco";
    }

    @RequestMapping("/index2")
    @ResponseBody
    public Map test2() {
        System.out.println("==========这里返回的应该是Map的数据===============");
        HashMap map = new HashMap<>();
        map.put("neco", "27");
        return map;
    }
}
8. 测试

测试发现,访问http://localhost8080/boot/index1 访问正常

index1

访问index2,报500错误


index2 500错误

这里的问题是,需要对Map数据进行转换处理,这里我们需要引入fast-json

9. 处理返回Map类型时,500错误的问题
  • 引入 fast-json
    
    
      com.alibaba
      fastjson
      1.2.72
    
  • 修改AppConfig配置类
  1. 使类实现WebMvcConfigurer接口,并Override configureMessageConverters 方法
   @Override
   public void configureMessageConverters(List> converters) {
       FastJsonHttpMessageConverter fastJsonHttpMessageConverter
               = new FastJsonHttpMessageConverter();
       converters.add(fastJsonHttpMessageConverter);
   }
  1. 添加@EnableWebMvc 注解

最后的修改结果如下

package cn.lazyfennec.springboot;

import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * @Author: Neco
 * @Description:
 * @Date: create in 2022/7/25 11:52
 */

@Configuration
@ComponentScan("cn.lazyfennec") // 去除了一部分xml的定义
@EnableWebMvc //
public class AppConfig  implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List> converters) {
        FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
        converters.add(fastJsonHttpMessageConverter);
    }

}
  • 然后尝试启动,发现报错,无法正常启动
严重: A child container failed during start
java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Tomcat].StandardHost[localhost].StandardContext[/boot]]
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:941)
    at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:872)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1421)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1411)
    at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
    at java.util.concurrent.FutureTask.run(FutureTask.java)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Tomcat].StandardHost[localhost].StandardContext[/boot]]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:167)
    ... 7 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'resourceHandlerMapping' defined in org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'resourceHandlerMapping' threw exception; nested exception is java.lang.IllegalStateException: No ServletContext set
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:627)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:456)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1288)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1127)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:846)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:863)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546)
    at cn.lazyfennec.springboot.MyWebApplicationInitializer.onStartup(MyWebApplicationInitializer.java:24)
    at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:171)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5229)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    ... 7 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'resourceHandlerMapping' threw exception; nested exception is java.lang.IllegalStateException: No ServletContext set
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622)
    ... 23 more
Caused by: java.lang.IllegalStateException: No ServletContext set
    at org.springframework.util.Assert.state(Assert.java:73)
    at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.resourceHandlerMapping(WebMvcConfigurationSupport.java:486)
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerBySpringCGLIB$$afba84d0.CGLIB$resourceHandlerMapping$35()
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerBySpringCGLIB$$afba84d0$$FastClassBySpringCGLIB$$c325d314.invoke()
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363)
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerBySpringCGLIB$$afba84d0.resourceHandlerMapping()
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
    ... 24 more

其中重点在于 No ServletContext set

10. 解决无法启动的问题

这里可以进行查看,发现AnnotationConfigWebApplicationContext 其实是DispatcherServlet的父容器,但是在执行ac.refresh的时候,其实是会initServlet的,但是这个时候,子容器其实还没有初始化完成,所以就报了No ServletContext set(更具体的内容建议查看源代码),这里要解决也很简单,设置一下相对应的ServletContext即可

即在MyWebApplicationInitializer中的合适位置(执行refresh之前)添加ac.setServletContext(servletCxt);即可。

最后完整的代码如下:

package cn.lazyfennec.springboot;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;

/**
 * @Author: Neco
 * @Description: Copy from https://docs.spring.io/spring-framework/docs/5.2.7.RELEASE/spring-framework-reference/web.html#mvc-servlet
 * @Date: create in 2022/7/25 12:45
 */
public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.setServletContext(servletCxt); // 当启用了@EnableWebMvc 的时候,需要设置这个,否则会报错 "No ServletContext set" 无法正常执行
        ac.register(AppConfig.class);
        ac.refresh();


        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

如果觉得有收获就点个赞吧,更多知识,请点击关注查看我的主页信息哦~

你可能感兴趣的:(手写SpringBoot (简化版本))