4.嵌入式 Tomcat容器-SpringMVC

1,458 阅读5分钟

image.png

一、接口设计

我们将会在系统中实现两个接口:

GET http://localhost:8888/hello

GET http://localhost:8888/hello/way

其中,第一个接口“/hello”将会返回“Hello World!” 的字符串;而第二个接口“/hello/way”则会返回一个包含用户信息的JSON字符串。

二、项目配置

我们需要在应用中添加如下依赖:

<dependency>
    <groupId>org.springframework</groupId>
	<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
	<artifactId>tomcat-embed-core</artifactId>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
	<artifactId>javax.servlet-api</artifactId>
	<scope>provided</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>

其中,

spring-webmvc是为了使用 Spring MVC 的功能

tomcat-embed-core是为了提供内嵌的 Servlet 容器,这样我们就无需依赖外部的容器,可以直接运行我们的应用。

jackson-corejackson-databind为我们的应用提供 JSON 序列化的功能。

三、后台编码实现

领域模型

创建一个 User 类,代表用户信息。

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class User {

	private String username;
	
    private Integer age;
}

控制器

创建 HelloController 用于处理用户的请求。

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello() {
        return "Hello World!";
    }

    @RequestMapping("/hello/way")
    public User helloWay() {
        return new User("luopeng", 30);
    }
}

其中,映射到“/hello”的方法将会返回“Hello World!” 的字符串;而映射到“/hello/way”则会返回一个包含用户信息的JSON字符串

应用配置

在本应用中,我们采用基于 Java 注解的配置。

AppConfiguration 是我们的主应用配置:

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

@Configuration
@ComponentScan(basePackages = { "com.epom" })  
@Import({ MvcConfiguration.class })
public class AppConfiguration {

}

AppConfiguration 会扫描“com.epom”包下的文件,并自动将相关的 bean 进行注册。

AppConfiguration 同时又引入了 MVC 的配置类 MvcConfiguration:

import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@EnableWebMvc
@Configuration
public class MvcConfiguration implements WebMvcConfigurer {

    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new MappingJackson2HttpMessageConverter());
    }
}

@EnableWebMvc 在springboot mvc或者java config配置中,如果我们项配置mvc相关的一些配置,那么就会涉及到这三个注解中的某个,那么他们之间有什么关系呢。

@EnableWebMvc = WebMvcConfigurationSupport,使用@EnableWebMvc注解就等于扩展了WebMvcConfigurationSupport,但是没有扩展任何方法。具体用法有如下三种:

@EnableWebMvc+extends WebMvcConfigurationAdapter,在扩展的类中重写父类的方法即可,这种方式会屏蔽springboot的@EnableAutoConfiguration中的设置。

extends WebMvcConfigurationSupport,在扩展的类中重写父类的方法即可,这种方式会屏蔽springboot的@EnableAutoConfiguration中的设置。

extends WebMvcConfigurationAdapter,在扩展的类中重写父类的方法即可,这种方式依旧使用springboot的@EnableAutoConfiguration中的设置。

MvcConfiguration 配置类一方面启用了 MVC 的功能,另一方面添加了 Jackson JSON 的转换器。

最后,我们需要引入 tomcat 服务器 tomcatServer:

import java.io.File;

import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Service;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.startup.ContextConfig;
import org.apache.catalina.startup.Tomcat;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import com.epom.web.AppConfiguration;

public class TomcatServer {

	public static void main(String[] args) throws LifecycleException {

		String workspace = System.getProperty("user.dir");
		
		String webapp = new File(workspace,"src/main/webapp").getPath();
		
		Tomcat tomcat = new Tomcat();

		// 看源码发现,他只能设置一个service,直接拿默认的
		Service service = tomcat.getService();

		// 创建连接器,并且添加对应的连接器,同时连接器指定端口
		Connector connector = new Connector();
		connector.setPort(8888);
		service.addConnector(connector);

		// 创建一个引擎,放入service中
		Engine engine = new StandardEngine();
		service.setContainer(engine);
		engine.setDefaultHost("localhost");
		engine.setName("myTomcat");

		// 添加host
		Host host = new StandardHost();
		engine.addChild(host);
		host.setName("localhost");

		// 在对应的host下面创建一个 context 并制定他的工作路径,会加载该目录下的所有class文件,或者静态文件
		tomcat.getHost().setAppBase("webapp");
		StandardContext context = (StandardContext) tomcat.addContext(host, "/", webapp);

		// 初始化ContextConfig配置
		context.addLifecycleListener(new ContextConfig());
		
		
		WebApplicationContext webApplicationContext = webApplicationContext();
		
		// 创建一个servlet
		Wrapper myservlet = new StandardWrapper();
		myservlet.setServlet(new DispatcherServlet(webApplicationContext));
		myservlet.setName("srpingmvc");
		// 把servlet加入到contxt中
		context.addChild(myservlet);
		// 这里注意,要先添加到contxt中在映射路径,不然会空指针
		myservlet.addMapping("/");
		
		context.addApplicationEventListener(new ContextLoaderListener(webApplicationContext));

		
		//tomcat启动
		tomcat.start();
		//保持主线程不退出
		tomcat.getServer().await();
	}
	
	 private static WebApplicationContext webApplicationContext() {
	        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
	        context.register(AppConfiguration.class);
	        return context;
	 }
}

四、运行

十二月 18, 2019 11:11:00 上午 org.apache.catalina.core.StandardContext setPath
警告: A context path must either be an empty string or start with a '/' and do not end with a '/'. The path [/] does not meet these criteria and has been changed to []
十二月 18, 2019 11:11:00 上午 org.apache.coyote.AbstractProtocol init
信息: Initializing ProtocolHandler ["http-nio-8888"]
十二月 18, 2019 11:11:00 上午 org.apache.catalina.core.StandardService startInternal
信息: Starting service [Tomcat]
十二月 18, 2019 11:11:00 上午 org.apache.catalina.core.StandardEngine startInternal
信息: Starting Servlet engine: [Apache Tomcat/9.0.27]
十二月 18, 2019 11:11:00 上午 org.apache.catalina.startup.ContextConfig getDefaultWebXmlFragment
信息: No global web.xml found
十二月 18, 2019 11:11:01 上午 org.apache.catalina.core.ApplicationContext log
信息: No Spring WebApplicationInitializer types detected on classpath
十二月 18, 2019 11:11:01 上午 org.apache.coyote.AbstractProtocol start
信息: Starting ProtocolHandler ["http-nio-8888"]十二月 18, 2019 11:11:00 上午 org.apache.catalina.core.StandardContext setPath
警告: A context path must either be an empty string or start with a '/' and do not end with a '/'. The path [/] does not meet these criteria and has been changed to []
十二月 18, 2019 11:11:00 上午 org.apache.coyote.AbstractProtocol init
信息: Initializing ProtocolHandler ["http-nio-8888"]
十二月 18, 2019 11:11:00 上午 org.apache.catalina.core.StandardService startInternal
信息: Starting service [Tomcat]
十二月 18, 2019 11:11:00 上午 org.apache.catalina.core.StandardEngine startInternal
信息: Starting Servlet engine: [Apache Tomcat/9.0.27]
十二月 18, 2019 11:11:00 上午 org.apache.catalina.startup.ContextConfig getDefaultWebXmlFragment
信息: No global web.xml found
十二月 18, 2019 11:11:01 上午 org.apache.catalina.core.ApplicationContext log
信息: No Spring WebApplicationInitializer types detected on classpath
十二月 18, 2019 11:11:01 上午 org.apache.coyote.AbstractProtocol start
信息: Starting ProtocolHandler ["http-nio-8888"]十二月 18, 2019 11:11:00 上午 org.apache.catalina.core.StandardContext setPath
警告: A context path must either be an empty string or start with a '/' and do not end with a '/'. The path [/] does not meet these criteria and has been changed to []
十二月 18, 2019 11:11:00 上午 org.apache.coyote.AbstractProtocol init
信息: Initializing ProtocolHandler ["http-nio-8888"]
十二月 18, 2019 11:11:00 上午 org.apache.catalina.core.StandardService startInternal
信息: Starting service [Tomcat]
十二月 18, 2019 11:11:00 上午 org.apache.catalina.core.StandardEngine startInternal
信息: Starting Servlet engine: [Apache Tomcat/9.0.27]
十二月 18, 2019 11:11:00 上午 org.apache.catalina.startup.ContextConfig getDefaultWebXmlFragment
信息: No global web.xml found
十二月 18, 2019 11:11:01 上午 org.apache.catalina.core.ApplicationContext log
信息: No Spring WebApplicationInitializer types detected on classpath
十二月 18, 2019 11:11:01 上午 org.apache.coyote.AbstractProtocol start
信息: Starting ProtocolHandler ["http-nio-8888"]

分别在浏览器中访问 http://localhost:8888/hellohttp://localhost:8888/hello/way 地址进行测试, 能看到图1和图2的响应效果。

image.png