1传统Servlet容器
1.1Eclipse Jetty:是一个嵌入式的容器,最新版本jetty9.0。支持的功能如下:
异步http server
标准的servlet容器
websocket
http/2 server
asynchronous Client(http/1.1, http/2, websocket) Java7开始才有AIO
OSGI,JNDI,JMX,JASPI,AJP support
1.2 Apache Tomcat:
1.2.1 标准实现:
Servlet
JSP
Expression Language
WebSocket
1.2.2 Apache Tomcat
1)核心组件Components
Engine
Host
管理主机
Context:是tomcat运维中重要的一块
和Application同等级别,类似于ServletContext
我们可以查看Tomcat的配置文件server.xml
注意:Engin中有Host,Host中有Context,Tomcat8.5中Host并没有配置Context,后面的版本建议在Host中配置Context(见Context.xml)
2)静态资源处理
查看web.xml配置文件servlet节点
default
org.apache.catalina.servlets.DefaultServlet
debug
0
listings
false
1
例子:在Idea中创建项目web application
启动项目,访问:http://localhost:8080/tomcat-test/
页面默认返回index.jsp页面
在webapp中创建index.html静态文件,重启访问:http://localhost:8080/tomcat-test/index.html
页面返回index.html内容,其实任何一个请求都会走ServletContext
3)欢迎页面
查看web.xml配置文件
index.html
index.htm
index.jsp
4)JSP处理
5)类加载
例子:
package com.segmentfault.lesson6;
public class Demo {
public static void main(String[] args) {
//查看加载本类的所有的ClassLoader
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
System.out.println(classLoader.getClass().getName());
while(true){
classLoader = classLoader.getParent();
if(classLoader!=null){
System.out.println(classLoader.getClass().getName());
}
else{
break;
}
}
//获取当前程序的SystemClassLoader
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader.getClass().getName());
}
}
打印结果:
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
例子:创建一个Listener的Servlet类
查看ServletContext的classloader过程
package com.segmentfault.lesson6;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ServletContextListenerImpl implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
ServletContext sc = servletContextEvent.getServletContext();
ClassLoader classLoader = sc.getClassLoader();
System.out.println(classLoader.getClass().getName());
while(true){
classLoader = classLoader.getParent();
if(classLoader!=null){
System.out.println(classLoader.getClass().getName());
}else{
break;
}
}
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
控制台打印结果:
org.apache.catalina.loader.ParallelWebappClassLoader
java.net.URLClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
6)连接器
在tomcat的server.xml配置中:
端口(port)
协议(Protocol)
连接池(Thread Pool)
超时时间(Timeout)
等等。。。
我们可以查看Connector类的源码(导入tomcat的源码)
可以看到里面的port,
/**
* Coyote Protocol handler class name.
* Defaults to the Coyote HTTP/1.1 protocolHandler.
*/
protected String protocolHandlerClassName =
"org.apache.coyote.http11.Http11NioProtocol";
/**
* @return the Coyote protocol handler in use.
*/
public String getProtocol() {
if (("org.apache.coyote.http11.Http11NioProtocol".equals(getProtocolHandlerClassName()) &&
(!AprLifecycleListener.isAprAvailable() || !AprLifecycleListener.getUseAprConnector())) ||
"org.apache.coyote.http11.Http11AprProtocol".equals(getProtocolHandlerClassName()) &&
AprLifecycleListener.getUseAprConnector()) {
return "HTTP/1.1";
} else if (("org.apache.coyote.ajp.AjpNioProtocol".equals(getProtocolHandlerClassName()) &&
(!AprLifecycleListener.isAprAvailable() || !AprLifecycleListener.getUseAprConnector())) ||
"org.apache.coyote.ajp.AjpAprProtocol".equals(getProtocolHandlerClassName()) &&
AprLifecycleListener.getUseAprConnector()) {
return "AJP/1.3";
}
return getProtocolHandlerClassName();
}
从这里也可以看出,我们的很多属性都是可以设置的,运行的时候显示的信息: Starting ProtocolHandler ["http-nio-8888"],其中的nio就是协议,所以任何的显示都是有根据的。
7)JDBC数据源
8)JNDI(Java Naming and Directory Interface
auth="Container" 认证方式:容器内部
type="org.apache.catalina.UserDatabase" 接口名称
maxTotal="100" 最大连接数
maxIdle="30" 所谓的Idle就是不活动连接数
maxWaitMillis="10000" 最大等待时间10000毫秒
username="javauser" password="javadude" 账号密码
driverClassName="com.mysql.jdbc.Driver" 驱动名称
url="jdbc:mysql://localhost:3306/javatest" 数据库连接
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
使用Eclipse工具
例子:配置tomcat中的Context.xml文件,配置数据库信息
7.1)首先创建数据库testdemo,并创建user表
create database testdemo;
mysql> create table user(id int primary key auto_increment,
-> name varchar(100));
7.2)修改Server中的Context.xml
WEB-INF/web.xml
${catalina.base}/conf/web.xml
username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/"
/>
7.3)配置web.xml
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
com.segmentfault.lesson6.ServletContextListenerImpl
jdbc/testdemo
javax.sql.DataSource
Container
Bean
java.lang.String
hello
7.4)编写java测试代码
package com.spring.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
public class JdbcServlet extends HttpServlet{
private DataSource dataSource;
@Override
public void init() throws ServletException {
try {
Context context = new InitialContext();
Context envContext = (Context) context.lookup("java:comp/env");
dataSource = (DataSource) envContext.lookup("jdbc/TestDB");
String bean = (String) envContext.lookup("Bean");
System.out.println(bean);
} catch (NamingException e) {
e.printStackTrace();
}
}
@Override
protected void service(HttpServletRequest arg0, HttpServletResponse resp) throws ServletException, IOException {
Writer writer = resp.getWriter();
try {
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("show databases;");
while(resultSet.next()) {
writer.write(resultSet.getString(1));
System.out.println(resultSet.getString(1));
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行后:
控制台打印:
hello
information_schema
mysql
performance_schema
test
testdemo
这个其实就是依赖反转的原理和鼻祖
运行:报错
Caused by: java.sql.SQLException: No suitable driver
显然,没有驱动,将驱动包复制到项目中,并引用进来
另外,如果出现权限安全问题
在url后加上?useSSL=false
2 Spring Boot嵌入式Web容器
1)使用IDEA软件
打开applicationContext.xml配置server.port,并查找源码的地方
org.springframework.boot.autoconfigure.web.ServerProperties
对应的配置
{
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties",
"name": "server.port",
"description": "Server HTTP port.",
"type": "java.lang.Integer"
},
然后我们可以找到getPort的地方:
这个customize方法中,设置了port,address,contextPath, SSL,timeout等等
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (getPort() != null) {
container.setPort(getPort());
}
if (getAddress() != null) {
container.setAddress(getAddress());
}
if (getContextPath() != null) {
container.setContextPath(getContextPath());
}
if (getDisplayName() != null) {
container.setDisplayName(getDisplayName());
}
if (getSession().getTimeout() != null) {
container.setSessionTimeout(getSession().getTimeout());
}
container.setPersistSession(getSession().isPersistent());
container.setSessionStoreDir(getSession().getStoreDir());
if (getSsl() != null) {
container.setSsl(getSsl());
}
if (getJspServlet() != null) {
container.setJspServlet(getJspServlet());
}
if (getCompression() != null) {
container.setCompression(getCompression());
}
container.setServerHeader(getServerHeader());
if (container instanceof TomcatEmbeddedServletContainerFactory) {
getTomcat().customizeTomcat(this,
(TomcatEmbeddedServletContainerFactory) container);
}
if (container instanceof JettyEmbeddedServletContainerFactory) {
getJetty().customizeJetty(this,
(JettyEmbeddedServletContainerFactory) container);
}
if (container instanceof UndertowEmbeddedServletContainerFactory) {
getUndertow().customizeUndertow(this,
(UndertowEmbeddedServletContainerFactory) container);
}
container.addInitializers(new SessionConfiguringInitializer(this.session));
container.addInitializers(new InitParameterConfiguringServletContextInitializer(
getContextParameters()));
}
其中,方法customize是接口EmbeddedServletContainerCustomizer的方法,这个是嵌入式servlet容器引擎
public interface EmbeddedServletContainerCustomizer {
/**
* Customize the specified {@link ConfigurableEmbeddedServletContainer}.
* @param container the container to customize
*/
void customize(ConfigurableEmbeddedServletContainer container);
}
我们来看一下嵌入式引擎的connector
查找到Connector,查看源码中的setProtocol代码
@Deprecated
public void setProtocol(String protocol) {
boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
AprLifecycleListener.getUseAprConnector();
if ("HTTP/1.1".equals(protocol) || protocol == null) {
if (aprConnector) {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
} else {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
}
} else if ("AJP/1.3".equals(protocol)) {
if (aprConnector) {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
} else {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
}
} else {
setProtocolHandlerClassName(protocol);
}
}
可以看到,这个setProtocol已经是过时的方法了,建议使用构造器的方法进行初始化
public Connector(String protocol) {
setProtocol(protocol);
// Instantiate protocol handler
ProtocolHandler p = null;
try {
Class> clazz = Class.forName(protocolHandlerClassName);
p = (ProtocolHandler) clazz.getConstructor().newInstance();
} catch (Exception e) {
log.error(sm.getString(
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
} finally {
this.protocolHandler = p;
}
if (Globals.STRICT_SERVLET_COMPLIANCE) {
uriCharset = StandardCharsets.ISO_8859_1;
} else {
uriCharset = StandardCharsets.UTF_8;
}
}
默认的Protocol是
/**
* The class name of default protocol used.
*/
public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
那么,我们可以在哪里修改这个protocol呢?
重写:EmbeddedServletContainerCustomizer的customize方法
注入:
private List tomcatConnectorCustomizers = new ArrayList();
并将connector设置对应的port和protocol,并加入到tomcat嵌入式servlet工厂中
代码实现:
package com.segmentfault.springbootlesson6;
import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.coyote.http11.Http11Nio2Protocol;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class SpringBootLesson6Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootLesson6Application.class, args);
}
@GetMapping("/hello")
public String index(){
return "hello,world";
}
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
return new EmbeddedServletContainerCustomizer(){
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if(container instanceof TomcatEmbeddedServletContainerFactory){
TomcatEmbeddedServletContainerFactory factory = TomcatEmbeddedServletContainerFactory.class.cast(container);
factory.addContextCustomizers(new TomcatContextCustomizer() {
@Override
public void customize(Context context) {
context.setPath("/spring-boot");
}
});
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
connector.setPort(8888);
//这个方法已经过时的
connector.setProtocol(Http11Nio2Protocol.class.getName());
}
});
}
}
};
}
}
其中测试:可以看到控制台中打印
2018-02-12 10:05:44.971 INFO 17768 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8888 (http)
然后打开jconsole,查看该java进程中的http协议
同样的,我们也可以手写修改context
factory.addContextCustomizers(new TomcatContextCustomizer() {
@Override
public void customize(Context context) {
context.setPath("/spring-boot");
}
});
并创建一个接口:
@GetMapping("/hello")
public String index(){
return "hello,world";
}
访问:http://localhost:8888/spring-boot/hello
返回hello,world
3 Q&A
1)jndi在实际开发中有什么用?
jndi告訴我們,不要直接使用new的方式生成對象,而是用get的方法从容器中取。
如果设置了密码,我们可以使用jndi的方式,到指定的路径取密码,而不用直接配置密码
jndi对应spring的上下文,和classloader不同。是以虚拟路径的方式如 jdbc/testDB等
资源以树形结构的方式存储资源
2)微服务和j2ee
微服务强调的是无状态
j2ee强调有状态
3)嵌入式的tomcat是怎么搭配集群的?
因为强调的是无状态,所以集群容易搭建,类似于克隆
无状态,每个机器上不存储用户信息