自己動手實現精簡版SpringBoot原來如此簡單
環境:Spring5.3.23
1. 概述
Spring Boot是一個開源的非常流行的Java框架,由Pivotal團隊開發,用于簡化Spring應用程序的創建和部署。它遵循約定優于配置的原則,讓開發者能夠快速搭建和運行應用程序。Spring Boot通過自動配置和內置的依賴管理,簡化了Spring應用程序的初始搭建以及開發過程。
Spring Boot為開發者提供了許多有用的功能,包括:
- 自動配置:Spring Boot會自動配置應用程序所需的各種組件,減少了手動配置的工作量。
- 嵌入式Web服務器:Spring Boot內置了Tomcat和Jetty等Web服務器,使得應用程序能夠快速啟動和運行。
- 約定優于配置的原則:Spring Boot遵循約定優于配置的原則,讓開發者能夠使用默認的配置,而無需過多地關注底層技術實現。
- 強大的依賴管理:Spring Boot提供了強大的依賴管理功能,能夠自動管理應用程序所需的依賴項。
- 安全性:Spring Boot提供了安全性功能,包括身份驗證和授權等,確保應用程序的安全性。
總之,Spring Boot是一個強大而簡單的框架,它讓開發者能夠快速創建和運行應用程序。無論你是初學者還是有一定經驗的開發者,Spring Boot都能夠為你提供便利和高效的開發體驗。在接下來的文章中,我們將通過自己動手寫一個簡單的Spring Boot應用程序來揭示這個過程原來如此簡單。讓我們一起開始這個項目吧!
提示:本篇文章需要你對Spring啟動流程有一點的了解。
2. 動手實現
2.1 自定義Web類型的ApplicationContext
我們需要在自定義的ApplicationContext合適的方法中去啟動內嵌的Tomcat。
public class PackAnnotationApplicationContext extends GenericWebApplicationContext {
public PackAnnotationApplicationContext() {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this);
}
@Override
protected void onRefresh() {
super.onRefresh();
try {
// 創建WebServer
createWebServer() ;
} catch (Exception e) {
throw new ApplicationContextException("Unable to start web server", e) ;
}
}
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.addBeanPostProcessor(new PackServletContextAwareProcessor(this)) ;
super.postProcessBeanFactory(beanFactory);
}
private void createWebServer() throws Exception {
final int PORT = 8088 ;
Tomcat tomcat = new Tomcat() ;
// 獲取Server節點 ---》 server.xml 【Server節點】
Server server = tomcat.getServer() ;
// 獲取Service節點 ---》 server.xml 【Server---> Service節點】
Service service = server.findService("Tomcat") ;
// 配置Connector ---》 server.xml 【Server---> Service--->Connector節點】
Http11NioProtocol protocol = new Http11NioProtocol() ;
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()) ;
// 配置Connector ---》 server.xml 【Server---> Service--->Executor節點】
protocol.setExecutor(executor) ;
protocol.setConnectionTimeout(20000);
Connector connector = new Connector(protocol) ;
// 設置訪問端口
connector.setPort(PORT) ;
connector.setURIEncoding("UTF-8") ;
service.addConnector(connector) ;
// 配置Engine ---》 server.xml 【Server--->Service--->Engine】
StandardEngine engine = new StandardEngine() ;
// 這里的設置的默認host要和下面StandardHost設置的name一致
engine.setDefaultHost("localhost");
// 配置Engine ---》 server.xml 【Server--->Service--->Engine--->Host】
StandardHost host = new StandardHost() ;
host.setName("localhost") ;
host.setAppBase(System.getProperties().getProperty("user.home")) ;
engine.addChild(host) ;
service.setContainer(engine) ;
// 配置Context ---》 server.xml 【Server--->Service--->Engine--->Host--->Context】
StandardContext context = new StandardContext() ;
// 如果不配置這個,則會有這個錯誤:One or more components marked the context as not correctly configured
context.addLifecycleListener(new FixContextListener()) ;
// 訪問路徑
context.setPath("");
// Context節點添加到Host節點
host.addChild(context) ;
context.addServletContainerInitializer(new ServletContainerInitializer() {
@Override
public void onStartup(Set<Class<?>> c, ServletContext servletContext) throws ServletException {
// 這個的主要作用是,當一個Bean實現了ServletContextAware接口時,用來注入該ServletContext對象
PackAnnotationApplicationContext.this.setServletContext(servletContext) ;
DispatcherServlet dispatcherServlet = PackAnnotationApplicationContext.this.getBean(DispatcherServlet.class) ;
Dynamic dynamic = servletContext.addServlet("dispatcherServlet", dispatcherServlet) ;
dynamic.setLoadOnStartup(0) ;
dynamic.addMapping("/*") ;
}
}, null);
tomcat.start();
}
}
如上,我們做了2件非常重要的事:1. 重寫onRefresh方法。2. 創建內嵌的Tomcat服務。
2.2 自定義啟動類程序PackSpringApplication
該類就是用來模擬SpringBoot中的SpringApplication對象。
public class PackSpringApplication {
private Class<?>[] primarySources ;
public PackSpringApplication(Class<?>[] primarySources) {
this.primarySources = primarySources ;
}
public ConfigurableApplicationContext run(String[] args) {
ConfigurableApplicationContext context = new PackAnnotationApplicationContext() ;
prepareEnvironment(context, args) ;
prepareContext(context) ;
refreshContex(context) ;
return context ;
}
// 刷新上下文,核心就是調用AbstractApplicationContext#refresh方法
private void refreshContex(ConfigurableApplicationContext context) {
context.refresh() ;
}
// 注備上下文環境,這里簡單處理了命令行參數
private void prepareEnvironment(ConfigurableApplicationContext context, String[] args) {
ConfigurableEnvironment environment = new StandardEnvironment() ;
PropertySource<?> propertySource = new SimpleCommandLinePropertySource(args) ;
environment.getPropertySources().addFirst(propertySource) ;
context.setEnvironment(environment) ;
}
// 準備上下文
private void prepareContext(ConfigurableApplicationContext context) {
BeanDefinitionRegistry registry = getBeanDefinitionRegistry(context) ;
BeanNameGenerator generator = new DefaultBeanNameGenerator() ;
for (Class<?> clazz : primarySources) {
AbstractBeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(clazz).getBeanDefinition() ;
registry.registerBeanDefinition(generator.generateBeanName(definition, registry), definition) ;
}
}
private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
if (context instanceof BeanDefinitionRegistry) {
return (BeanDefinitionRegistry) context;
}
if (context instanceof AbstractApplicationContext) {
return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
}
throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
}
public static ConfigurableApplicationContext run(Class<?> primarySource, String[] args) {
return new PackSpringApplication(new Class<?>[] {primarySource}).run(args) ;
}
}
以上大致模擬的就是Spring Boot啟動執行流程的步驟。
2.3 定義Web請求相關的配置
@Configuration
public class WebConfig {
@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(true) ;
dispatcherServlet.setDispatchTraceRequest(true) ;
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true) ;
dispatcherServlet.setPublishEvents(true) ;
dispatcherServlet.setEnableLoggingRequestDetails(true) ;
return dispatcherServlet ;
}
}
這里為了讓你更加清晰的認識到SpringMVC相關的內容,就不使用@EnableWebMvc該注解。
有了上面的配置,接下來就可以寫個測試程序進行測試
2.4 測試
測試Controller接口
@RestController
@RequestMapping("/demo")
public class DemoController {
@GetMapping("/index")
public Object index() {
return "index......" ;
}
@GetMapping("/body")
public User body() {
return new User(1, "測試") ;
}
}
測試啟動類
@Configuration
@ComponentScan({"com.pack.main.programmatic_tomcat_04"})
public class DemoApplication {
public static void main(String[] args) {
PackSpringApplication.run(DemoApplication.class, args) ;
}
}
測試第一個接口:/demo/index
圖片
測試第二個接口:/demo/body
圖片
返回406狀態碼,程序出錯了,這是因為在默認情況下,SpringMVC會使用系統默認提供的RequestMappingHandlerAdapter對象進行目標Controller的調用。但是默認的HandlerAdapter對象并不能處理返回值是這種對象類型。而默認支持支如下4中類型:
- ByteArrayHttpMessageConverter
- StringHttpMessageConverter
- SourceHttpMessageConverter
- AllEncompassingFormHttpMessageConverter
所以,這里我們需要自定義RequestMappingHandlerAdapter。在WebConfig中定義該Bean對象,設置
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter() ;
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>() ;
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new MappingJackson2HttpMessageConverter()) ;
handlerAdapter.setMessageConverters(messageConverters) ;
return handlerAdapter ;
}
再次測試
圖片