Spring嵌入式Web服务器

Spring嵌入式Web服务器

以SpringBoot中嵌入式web服务器的应用为切入点,解析在Spring中使用嵌入式web服务器方法。

SpringBoot什么情况下开启web功能?

广义来说,如果需要SpringBoot支持web功能,只需要引入web-starter就可以了。现在从代码分析怎么确定开启web功能的。

SpringBoot应用业务代码的入口一般是程序主类。例如:

public class BootstrapApplication {

	public static void main(String[] args) {
		SpringApplication.run(BootstrapApplication.class, args);
	}
}

执行到SpringBoot的jar内代码就是下面这样。

public class SpringApplication {
	private boolean webEnvironment;

	private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

	public SpringApplication(Object... sources) {
		initialize(sources);
	}

	private void initialize(Object[] sources) {
		if (sources != null && sources.length > 0) {
			this.sources.addAll(Arrays.asList(sources));
		}
		this.webEnvironment = deduceWebEnvironment();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

	private boolean deduceWebEnvironment() {
		for (String className : WEB_ENVIRONMENT_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return false;
			}
		}
		return true;
	}
}

SpringApplication构造方法中会调用initialize方法,initialize方法中的deduceWebEnvironment方法根据程序是否引入javax.servlet.Servletorg.springframework.web.context.ConfigurableWebApplicationContext类来确定是否为web环境。在引入spring-boot-web-starter包时,会引入这两个类。
此时,就确定了SpringBoot要启动web功能。

SpringBoot如何创建嵌入式web服务器?

当执行到org.springframework.boot.SpringApplication#run(java.lang.String...)方法时,run方法中会调用org.springframework.boot.SpringApplication#createApplicationContext方法。

public class SpringApplication {
	public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
			+ "annotation.AnnotationConfigApplicationContext";
			+ 
	public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
			+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

	private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };
			
	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				contextClass = Class.forName(this.webEnvironment
						? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, "
								+ "please specify an ApplicationContextClass",
						ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
	}
}

createApplicationContext方法根据应用是否要启动web功能来确定Spring容器类,如果要启动web功能就使用org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext作为容器类,否则使用org.springframework.context.annotation.AnnotationConfigApplicationContext作为容器类。
容器启动时,容器类的refresh方法中调用到org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#onRefresh方法。

public class AnnotationConfigEmbeddedWebApplicationContext
		extends EmbeddedWebApplicationContext {
		// ...
	public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext, DisposableBean {
		
	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}
			// ...
		}
	}
}
public class EmbeddedWebApplicationContext extends GenericWebApplicationContext {
	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createEmbeddedServletContainer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start embedded container",
					ex);
		}
	}
	private void createEmbeddedServletContainer() {
		EmbeddedServletContainer localContainer = this.embeddedServletContainer;
		ServletContext localServletContext = getServletContext();
		if (localContainer == null && localServletContext == null) {
			EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
			this.embeddedServletContainer = containerFactory
					.getEmbeddedServletContainer(getSelfInitializer());
		}
		else if (localServletContext != null) {
			try {
				getSelfInitializer().onStartup(localServletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context",
						ex);
			}
		}
		initPropertySources();
	}
	protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
		// Use bean names so that we don't consider the hierarchy
		String[] beanNames = getBeanFactory()
				.getBeanNamesForType(EmbeddedServletContainerFactory.class);
		if (beanNames.length == 0) {
			throw new ApplicationContextException(
					"Unable to start EmbeddedWebApplicationContext due to missing "
							+ "EmbeddedServletContainerFactory bean.");
		}
		if (beanNames.length > 1) {
			throw new ApplicationContextException(
					"Unable to start EmbeddedWebApplicationContext due to multiple "
							+ "EmbeddedServletContainerFactory beans : "
							+ StringUtils.arrayToCommaDelimitedString(beanNames));
		}
		return getBeanFactory().getBean(beanNames[0],
				EmbeddedServletContainerFactory.class);
	}
}

org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#onRefresh方法的功能就是调用createEmbeddedServletContainer创建嵌入式的servlet容器。
createEmbeddedServletContainer方法调用getEmbeddedServletContainerFactory方法从容器获取EmbeddedServletContainerFactory类型的bean。并调用其getEmbeddedServletContainer方法创建servlet容器。

SpringBoot如何启动嵌入式web服务器

在容器刷新过程中,org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#finishRefresh方法负责启动web服务器。

public class EmbeddedWebApplicationContext extends GenericWebApplicationContext {
	@Override
	protected void finishRefresh() {
		super.finishRefresh();
		EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
		if (localContainer != null) {
			publishEvent(
					new EmbeddedServletContainerInitializedEvent(this, localContainer));
		}
	}
	private EmbeddedServletContainer startEmbeddedServletContainer() {
		EmbeddedServletContainer localContainer = this.embeddedServletContainer;
		if (localContainer != null) {
			localContainer.start();
		}
		return localContainer;
	}
}

嵌入式servlet容器

由上可知,servlet容器的创建只要由EmbeddedServletContainerFactory工厂类负责。EmbeddedServletContainerFactory是只有一个getEmbeddedServletContainer方法的接口,实现根据具体servlet容器类型有所不同。
自动配置类EmbeddedServletContainerAutoConfiguration根据引入servlet容器jar包中的类向spring容器中导入不同工厂bean。

public class EmbeddedServletContainerAutoConfiguration {
	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {

		@Bean
		public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
			return new TomcatEmbeddedServletContainerFactory();
		}

	}

	@Configuration
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
			WebAppContext.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedJetty {

		@Bean
		public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
			return new JettyEmbeddedServletContainerFactory();
		}

	}

	@Configuration
	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedUndertow {

		@Bean
		public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
			return new UndertowEmbeddedServletContainerFactory();
		}

	}
	
	// ...
}

简单看下TomcatEmbeddedServletContainerFactory的实现:

public class TomcatEmbeddedServletContainerFactory
		extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
	@Override
	public EmbeddedServletContainer getEmbeddedServletContainer(
			ServletContextInitializer... initializers) {
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null ? this.baseDirectory
				: createTempDir("tomcat"));
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatEmbeddedServletContainer(tomcat);
	}

	protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
			Tomcat tomcat) {
		return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
	}
}
public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {
	public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;
		initialize();
	}

	private void initialize() throws EmbeddedServletContainerException {
		TomcatEmbeddedServletContainer.logger
				.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();
				try {
					final Context context = findContext();
					context.addLifecycleListener(new LifecycleListener() {

						@Override
						public void lifecycleEvent(LifecycleEvent event) {
							if (context.equals(event.getSource())
									&& Lifecycle.START_EVENT.equals(event.getType())) {
								// Remove service connectors so that protocol
								// binding doesn't happen when the service is
								// started.
								removeServiceConnectors();
							}
						}

					});

					// Start the server to trigger initialization listeners
					this.tomcat.start();

					// We can re-throw failure exception directly in the main thread
					rethrowDeferredStartupExceptions();

					try {
						ContextBindings.bindClassLoader(context, getNamingToken(context),
								getClass().getClassLoader());
					}
					catch (NamingException ex) {
						// Naming is not enabled. Continue
					}

					// Unlike Jetty, all Tomcat threads are daemon threads. We create a
					// blocking non-daemon to stop immediate shutdown
					startDaemonAwaitThread();
				}
				catch (Exception ex) {
					containerCounter.decrementAndGet();
					throw ex;
				}
			}
			catch (Exception ex) {
				stopSilently();
				throw new EmbeddedServletContainerException(
						"Unable to start embedded Tomcat", ex);
			}
		}
	}
	private void startDaemonAwaitThread() {
		Thread awaitThread = new Thread("container-" + (containerCounter.get())) {

			@Override
			public void run() {
				TomcatEmbeddedServletContainer.this.tomcat.getServer().await();
			}

		};
		awaitThread.setContextClassLoader(getClass().getClassLoader());
		awaitThread.setDaemon(false);
		awaitThread.start();
	}

	@Override
	public void start() throws EmbeddedServletContainerException {
		synchronized (this.monitor) {
			if (this.started) {
				return;
			}
			try {
				addPreviouslyRemovedConnectors();
				Connector connector = this.tomcat.getConnector();
				if (connector != null && this.autoStart) {
					performDeferredLoadOnStartup();
				}
				checkThatConnectorsHaveStarted();
				this.started = true;
				TomcatEmbeddedServletContainer.logger
						.info("Tomcat started on port(s): " + getPortsDescription(true));
			}
			catch (ConnectorStartFailedException ex) {
				stopSilently();
				throw ex;
			}
			catch (Exception ex) {
				throw new EmbeddedServletContainerException(
						"Unable to start embedded Tomcat servlet container", ex);
			}
			finally {
				Context context = findContext();
				ContextBindings.unbindClassLoader(context, getNamingToken(context),
						getClass().getClassLoader());
			}
		}
	}
}

现在编写代码测试一下:

public class HelloWorldServlet extends HttpServlet {
    private static final long serialVersionUID = -5104370001038878212L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try (ServletOutputStream outputStream = resp.getOutputStream()) {
            outputStream.write("hello world!".getBytes());
        }
    }
}
public class EmbeddedServletContainerTest {
    public static void main(String[] args) {
        TomcatEmbeddedServletContainerFactory servletContainerFactory = new TomcatEmbeddedServletContainerFactory();
        EmbeddedServletContainer servletContainer = servletContainerFactory.getEmbeddedServletContainer(new ServletContextInitializer() {

            @Override
            public void onStartup(ServletContext servletContext) {
                final ServletRegistration.Dynamic servletRegistration = servletContext.addServlet("helloWorldServlet", HelloWorldServlet.class);
                servletRegistration.addMapping("/helloWorld");
            }
        });
        servletContainer.start();
    }
}

运行com.zhht.EmbeddedServletContainerTest#main方法后,在浏览器访问:
helloWorld

嵌入式web服务器boa框架的基础上, 使用C语言cgi, 或者Python脚本, 结合HTML + javascript + ajax 的嵌入式web系统的开发实例 html 中使用javascritp + ajax 从C语言生成的cgi文件的get, set 一些值. boa服务器的相关配置参数说明: http://www.cnblogs.com/liuweiqiang/p/3859130.html boa安装包文件名: boa-for-hi3516a.tar.gz boa.conf 文件的保存路径: cat /etc/boa/boa.conf boa可 执行文件的路径: /usr/local/bin/boa, 可以设置为: 系统启动的时候, 这个进程自动启动 boa.conf 文件的重要参数 保存html文件的目录 DocumentRoot /www 可以将这个目录, 设置为samb共享文件夹的目录, 方便修改调试 修改完成以后, 肯定要重启boa进程的 保存python脚本, 或者C语言cgi文件的目录 ScriptAlias /cgi-bin/ /var/www/cgi-bin/ 说明: cgi-bin/ 后面的斜杠, 一定要加上 可以将这个目录, 设置为samb共享文件夹的目录, 方便修改调试 修改完成以后, 肯定要重启boa进程的 html文件文件中, 调用python脚本的时候, 指定的路径, 需要有: /cgi-bin, 比如: var url = "/cgi-bin/getuser.py"; 这个是python 或者 var url = "/cgi-bin/output.cgi"; 这个是C语言 说明: 如果发现, html文件, 修改了, 可是在浏览器中, 查看html源代码的时候, 这个代码, 还是旧的, 那么可以通过清空"IE浏览器", "360浏览器"的浏览记录 以上, javascript 可以调用python 同样, 也可以调用C语言生成的cgi文件(其实, 就是可执行文件) C语言 + Html 例子 C语言 CGI实例 http://blog.csdn.net/ajrm0925/article/details/8810342 http://blog.csdn.net/liang890319/article/details/6277900 http://blog.csdn.net/gnefniu/article/details/42432657 上传文件: http://blog.csdn.net/yu_xiang/article/details/7996670 查找文件 find . -type f -name "boa.conf" -print -mount find . -type f -name "boa" -print -mount 四、嵌入式web服务器boa的配置和使用 嵌入式web服务器boa的配置文件为boa.conf, 在boa-0.94.13目录下面,复制该文件到文件 系统的/etc/boa目录下面,打开boa.conf,修改为如下内容: Port 80 User root Group root ErrorLog /dev/console AccessLog /dev/null ServerName SoftEmbed.com DocumentRoot /www DirectoryIndex index.html KeepAliveMax 1000 KeepAliveTimeout 10 MimeTypes /etc/mime.types DefaultType text/plain CGIPath /bin:/usr/bin:/usr/local/bin ScriptAlias /cgi-bin/ /www/cgi-bin/ 几个重要配置参数如下: DocumentRoot: 存放html文档的主目录; DirectoryIndex: 默认返回的html文档; ScriptAlias:cgi脚本虚拟路径对应的实际路径,/www/cgi-bin/为cgi脚本存放的实际路径; 其他配置选项的意义请参考相关资料。 复制boa可执行文件到/usr/sbin目录中, 启动boa进程 重新制作文件系统,系统启动后,在客户端浏览器上输入开发板的ip 地址,例如: http://192.168.0.218, 就可以看到显示的测试网页了,如下图所示 CGI getenv函数的参数详解: http://www.cnblogs.com/ser0632/p/5498228.html s = getenv("环境变量名"); 取得环境变量内容 putenv改变或增加环境变量 int putenv(const char * string); setenv(改变或增加环境变量) http://www.jb51.net/article/71940.htm
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值