自己動(dòng)手實(shí)現(xiàn)精簡(jiǎn)版SpringBoot原來(lái)如此簡(jiǎn)單
環(huán)境:Spring5.3.23
1. 概述
Spring Boot是一個(gè)開(kāi)源的非常流行的Java框架,由Pivotal團(tuán)隊(duì)開(kāi)發(fā),用于簡(jiǎn)化Spring應(yīng)用程序的創(chuàng)建和部署。它遵循約定優(yōu)于配置的原則,讓開(kāi)發(fā)者能夠快速搭建和運(yùn)行應(yīng)用程序。Spring Boot通過(guò)自動(dòng)配置和內(nèi)置的依賴管理,簡(jiǎn)化了Spring應(yīng)用程序的初始搭建以及開(kāi)發(fā)過(guò)程。
Spring Boot為開(kāi)發(fā)者提供了許多有用的功能,包括:
- 自動(dòng)配置:Spring Boot會(huì)自動(dòng)配置應(yīng)用程序所需的各種組件,減少了手動(dòng)配置的工作量。
- 嵌入式Web服務(wù)器:Spring Boot內(nèi)置了Tomcat和Jetty等Web服務(wù)器,使得應(yīng)用程序能夠快速啟動(dòng)和運(yùn)行。
- 約定優(yōu)于配置的原則:Spring Boot遵循約定優(yōu)于配置的原則,讓開(kāi)發(fā)者能夠使用默認(rèn)的配置,而無(wú)需過(guò)多地關(guān)注底層技術(shù)實(shí)現(xiàn)。
- 強(qiáng)大的依賴管理:Spring Boot提供了強(qiáng)大的依賴管理功能,能夠自動(dòng)管理應(yīng)用程序所需的依賴項(xiàng)。
- 安全性:Spring Boot提供了安全性功能,包括身份驗(yàn)證和授權(quán)等,確保應(yīng)用程序的安全性。
總之,Spring Boot是一個(gè)強(qiáng)大而簡(jiǎn)單的框架,它讓開(kāi)發(fā)者能夠快速創(chuàng)建和運(yùn)行應(yīng)用程序。無(wú)論你是初學(xué)者還是有一定經(jīng)驗(yàn)的開(kāi)發(fā)者,Spring Boot都能夠?yàn)槟闾峁┍憷透咝У拈_(kāi)發(fā)體驗(yàn)。在接下來(lái)的文章中,我們將通過(guò)自己動(dòng)手寫(xiě)一個(gè)簡(jiǎn)單的Spring Boot應(yīng)用程序來(lái)揭示這個(gè)過(guò)程原來(lái)如此簡(jiǎn)單。讓我們一起開(kāi)始這個(gè)項(xiàng)目吧!
提示:本篇文章需要你對(duì)Spring啟動(dòng)流程有一點(diǎn)的了解。
2. 動(dòng)手實(shí)現(xiàn)
2.1 自定義Web類型的ApplicationContext
我們需要在自定義的ApplicationContext合適的方法中去啟動(dòng)內(nèi)嵌的Tomcat。
public class PackAnnotationApplicationContext extends GenericWebApplicationContext {
public PackAnnotationApplicationContext() {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this);
}
@Override
protected void onRefresh() {
super.onRefresh();
try {
// 創(chuàng)建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節(jié)點(diǎn) ---》 server.xml 【Server節(jié)點(diǎn)】
Server server = tomcat.getServer() ;
// 獲取Service節(jié)點(diǎn) ---》 server.xml 【Server---> Service節(jié)點(diǎn)】
Service service = server.findService("Tomcat") ;
// 配置Connector ---》 server.xml 【Server---> Service--->Connector節(jié)點(diǎn)】
Http11NioProtocol protocol = new Http11NioProtocol() ;
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()) ;
// 配置Connector ---》 server.xml 【Server---> Service--->Executor節(jié)點(diǎn)】
protocol.setExecutor(executor) ;
protocol.setConnectionTimeout(20000);
Connector connector = new Connector(protocol) ;
// 設(shè)置訪問(wèn)端口
connector.setPort(PORT) ;
connector.setURIEncoding("UTF-8") ;
service.addConnector(connector) ;
// 配置Engine ---》 server.xml 【Server--->Service--->Engine】
StandardEngine engine = new StandardEngine() ;
// 這里的設(shè)置的默認(rèn)host要和下面StandardHost設(shè)置的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() ;
// 如果不配置這個(gè),則會(huì)有這個(gè)錯(cuò)誤:One or more components marked the context as not correctly configured
context.addLifecycleListener(new FixContextListener()) ;
// 訪問(wèn)路徑
context.setPath("");
// Context節(jié)點(diǎn)添加到Host節(jié)點(diǎn)
host.addChild(context) ;
context.addServletContainerInitializer(new ServletContainerInitializer() {
@Override
public void onStartup(Set<Class<?>> c, ServletContext servletContext) throws ServletException {
// 這個(gè)的主要作用是,當(dāng)一個(gè)Bean實(shí)現(xiàn)了ServletContextAware接口時(shí),用來(lái)注入該ServletContext對(duì)象
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. 重寫(xiě)onRefresh方法。2. 創(chuàng)建內(nèi)嵌的Tomcat服務(wù)。
2.2 自定義啟動(dòng)類程序PackSpringApplication
該類就是用來(lái)模擬SpringBoot中的SpringApplication對(duì)象。
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 ;
}
// 刷新上下文,核心就是調(diào)用AbstractApplicationContext#refresh方法
private void refreshContex(ConfigurableApplicationContext context) {
context.refresh() ;
}
// 注備上下文環(huán)境,這里簡(jiǎn)單處理了命令行參數(shù)
private void prepareEnvironment(ConfigurableApplicationContext context, String[] args) {
ConfigurableEnvironment environment = new StandardEnvironment() ;
PropertySource<?> propertySource = new SimpleCommandLinePropertySource(args) ;
environment.getPropertySources().addFirst(propertySource) ;
context.setEnvironment(environment) ;
}
// 準(zhǔn)備上下文
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啟動(dòng)執(zhí)行流程的步驟。
2.3 定義Web請(qǐng)求相關(guān)的配置
@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 ;
}
}
這里為了讓你更加清晰的認(rèn)識(shí)到SpringMVC相關(guān)的內(nèi)容,就不使用@EnableWebMvc該注解。
有了上面的配置,接下來(lái)就可以寫(xiě)個(gè)測(cè)試程序進(jìn)行測(cè)試
2.4 測(cè)試
測(cè)試Controller接口
@RestController
@RequestMapping("/demo")
public class DemoController {
@GetMapping("/index")
public Object index() {
return "index......" ;
}
@GetMapping("/body")
public User body() {
return new User(1, "測(cè)試") ;
}
}
測(cè)試啟動(dòng)類
@Configuration
@ComponentScan({"com.pack.main.programmatic_tomcat_04"})
public class DemoApplication {
public static void main(String[] args) {
PackSpringApplication.run(DemoApplication.class, args) ;
}
}
測(cè)試第一個(gè)接口:/demo/index
圖片
測(cè)試第二個(gè)接口:/demo/body
圖片
返回406狀態(tài)碼,程序出錯(cuò)了,這是因?yàn)樵谀J(rèn)情況下,SpringMVC會(huì)使用系統(tǒng)默認(rèn)提供的RequestMappingHandlerAdapter對(duì)象進(jìn)行目標(biāo)Controller的調(diào)用。但是默認(rèn)的HandlerAdapter對(duì)象并不能處理返回值是這種對(duì)象類型。而默認(rèn)支持支如下4中類型:
- ByteArrayHttpMessageConverter
- StringHttpMessageConverter
- SourceHttpMessageConverter
- AllEncompassingFormHttpMessageConverter
所以,這里我們需要自定義RequestMappingHandlerAdapter。在WebConfig中定義該Bean對(duì)象,設(shè)置
@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 ;
}
再次測(cè)試
圖片