Build a Custom Spring Boot 3 Application with Embedded Tomcat – Step‑by‑Step Guide
This article walks through creating a Spring Boot 3 application from scratch, demonstrating how to customize the ApplicationContext, embed a Tomcat server, define a custom SpringApplication class, configure web components, and test REST endpoints, providing complete code snippets and explanations for each step.
Overview
Spring Boot is an open‑source, widely used Java framework developed by the Pivotal team to simplify the creation and deployment of Spring applications. It follows the convention‑over‑configuration principle, offering automatic configuration, embedded web servers, powerful dependency management, and built‑in security features, enabling developers to quickly build and run applications.
Hands‑on Implementation
2.1 Custom Web‑type ApplicationContext
<code>public class PackAnnotationApplicationContext extends GenericWebApplicationContext {
public PackAnnotationApplicationContext() {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this);
}
@Override
protected void onRefresh() {
super.onRefresh();
try {
// Create 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 node
Server server = tomcat.getServer();
// Service node
Service service = server.findService("Tomcat");
// Connector configuration
Http11NioProtocol protocol = new Http11NioProtocol();
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
protocol.setExecutor(executor);
protocol.setConnectionTimeout(20000);
Connector connector = new Connector(protocol);
connector.setPort(PORT);
connector.setURIEncoding("UTF-8");
service.addConnector(connector);
// Engine configuration
StandardEngine engine = new StandardEngine();
engine.setDefaultHost("localhost");
StandardHost host = new StandardHost();
host.setName("localhost");
host.setAppBase(System.getProperties().getProperty("user.home"));
engine.addChild(host);
service.setContainer(engine);
// Context configuration
StandardContext context = new StandardContext();
context.addLifecycleListener(new FixContextListener());
context.setPath("");
host.addChild(context);
context.addServletContainerInitializer(new ServletContainerInitializer() {
@Override
public void onStartup(Set<Class<?>> c, ServletContext servletContext) throws ServletException {
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();
}
}
</code>2.2 Custom PackSpringApplication
<code>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;
}
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);
}
}
</code>2.3 Web Configuration
<code>@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;
}
}
</code>2.4 Custom RequestMappingHandlerAdapter
<code>@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;
}
</code>2.5 Testing Controllers
<code>@RestController
@RequestMapping("/demo")
public class DemoController {
@GetMapping("/index")
public Object index() {
return "index......";
}
@GetMapping("/body")
public User body() {
return new User(1, "测试");
}
}
</code> <code>@Configuration
@ComponentScan({"com.pack.main.programmatic_tomcat_04"})
public class DemoApplication {
public static void main(String[] args) {
PackSpringApplication.run(DemoApplication.class, args);
}
}
</code>Running the application and accessing /demo/index and /demo/body initially returns a 406 error because the default RequestMappingHandlerAdapter cannot handle the custom return type. By defining a custom RequestMappingHandlerAdapter bean with appropriate HttpMessageConverter s, the endpoints respond correctly.
After adding the custom adapter and retesting, the endpoints return the expected JSON and plain‑text responses, completing a minimal yet functional Spring Boot application built programmatically.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.