JavaEE+Tomcat学习笔记
JavaEE
1、Servlet
工作流程
- 通过Http请求头,获取访问主机
- 通过Http请求行,获取访问时那一个web应用
- 再通过Http请求行,获取请求路径
- 服务器创建Servlet对象(第一次请求,会创建Servlet实例,并执行init方法)
- 调用Service(request,response)方法来处理请求和响应
- 调用完毕Service方法,由服务器将Response缓冲区的数据取出,以Http响应的格式发送给浏览器。
生命周期
生命周期是指servlet容器何时创建servlet实例,何时调用其方法进行请求处理,何时并销毁实例的整个过程。
- 实例和初始化时机:当请求到达容器时,容器查找该servlet对象是否存在,如果不存在,则会创建实例并进行初始化。
- 就绪/调用/服务阶段:有请求到达容器,容器调用servlet对象的service()方法,处理请求的方法在整个生命周期中可以被多次调用,HttpServlet的servlce()方法,会依据请求方式来调用doGet()或者doPost()方法。
- 销毁时机:当容器关闭时(应用程序停止时),会将程序中的servlet实例进行销毁。
servlet生命周期分为四步:servlet类加载->实例化->服务->销毁。
- 客户端向servlet容器(Tomcat)发送请求
- Tomcat接收请求
- Tomcat会创建一个HttpServletReqest对象,将客户端请求封装到对象中,创建一个响应对象HttpServletResponse。
- Tomcat调用HttpServlet对象的service方法,把HttpServletReqest和HttpServletResponse作为参数,传给HttpServlet。
- HttpServlet调用HttpServletRequest对象有关方法,获取Http请求信息
- HttpServlet调用HttpServletResponse对象有关方法,生成响应数据
- Tomcat把HttpServlet的响应结果传给客户端。
HttpServletRequest
HttpServletRequest是ServletRequest的子接口。用来封装客户端请求的详细信息。
请求乱码
乱码问题的原因是:ISO-8859-1编码不支持中文,需要设置其编码格式为UTF-8。
处理方法:在参数获取钱设置编码格式,HttpServletRequest#setCharacterEncoding(“UTF-8”);
请求转发:
在服务器中将请求资源转发到另一份资源中,并且共享HttpServletRequest和HttpServletResponse。
转发方法,HttpServletRequest#getRequestDispatcher("")#forward(HttpServletRequest,HttpServletResponse);
作用域:
HttpServletRequest有一个可以携带其他非请求资源的信息,attribute通过HttpServletRequest#setAttribute(key,val)方法来往作用域里面,添加数据,通过HttpServletRequest#getAttribute(key)来获取数据,通过HttpServletRequest#removeAttribute(key)来移除数据。
HttpServletRequestWrapper和ServletRequestWrapper分别是HttpServletRequest和ServletRequest接口的实现类。在实际应用中,可以通过继承这两个类来重写HttpServletRequest方法。
例:在HttpServletRequest中body的方法只能被读取一次,无法重复读取,可以通过继承HttpServletRequestWrapper类来重写这个类,来实现body可以多次被读取。
HttpServletResponse
用来向客户端吐出响应数据。
响应数据的方法:
getWriter():获取字符流(只能响应字符)
getOutputStream():获取字节流(能响应一切数据)
设置响应的数据类型
HttpServletResponse#setHeader(“content-type”:“xxxxxx”);
响应乱码:
产生原因:当客户端和服务端编码格式不同或者编码不支持中文时则会出现乱码问题。
处理方法:设置客户端和服务端编码格式一致。同时支持中文编码UTF-8。通过HttpServletResponse#setContentType(“xxxxxx;charset=utf-8”)来设置两端编码格式。
重定向:服务端指定客户端发出请求的方法,可以指定客户端进入全新的页面,并发送请求,客户端的请求地址会发生改变。
HttpServletResponse#sendRedirect(String)方法进行重定向。
HttpServletResponseWrapper和ServletResponseWrapper是HttpServletResponse和ServletResponse的实现类。在实际应用中,可以通过继承这两个类来重写HttpServletResponse方法。
Cookie对象
浏览器技术,将数据保存在本地计算机内,但是安全性能低,Cookie采用键值对的方式存储,多个键值对采用**;**分割,Cookie通过domain(域名)做区分。
创建和发送:
创建:Cookie cookie = new Cookie(key,val);
发送:HttpServletResponse#addCookie(cookie)
获取Cookie:
获取所有的Cookie对象,遍历查询对象名称的Cookie。
最大存活时间:
- -1:默认值,表示浏览器关闭失效
- 0:表示删除
- 正整数:表示存货指定秒数
注意:Cookie中不能存储中文。
Cookie的Path,Cookie可以设置指定path下拿到,当Path设置为**/时,代表当前域名下所有都能路径都能拿到。默认为/**。通过HttpServletResponse#setPath("/xxxx")方法设置Cookie路径。
注意:路径是包含关系,路径/s1/s2是包含/s1下面的Cookie的。
HttpSession
它是一个顶级父接口,是完全属于Http协议的特有属性。
创建和获取session对象:
HttpServletRequest#getSession()方法,当有session的时候就会直接获取session,当没有的时候就会创建session。
标识符JSESSIONID:
每次请求如果开启了session,服务器会查看请求是否携带了JSESSIONID的Cookie,如果没有就会认为是一个新的对话,会创建新的Session对象,如果有直接使用。对于这个JSESSIONID的Cookie有效时间为关闭浏览器。
SESSION的底层借助Cookie来实现的。
session的作用域:
session表示一次会话,再一次会话中数据是可以共享的,session可以当作一个域对象存储数据,当HttpSession#setAttribute(key,val)来存储数据,通过HttpSession#getAttribute(key)来获取数据,通过HttpSession#removeAttribute(key)来移除数据。当session对象不存在时,数据也就不存在了。
session对象的销毁:
session存在默认事件,在tomcat中默认session存活的时间为30分钟,当有会话再次被唤醒时间会被刷新。
<session-config>
<session-timeout>30</session-timeout>
</session-config>
也可通过HttpSession#setMaxInactiveInterval(16)设置存活时间单位为秒。通过HttpSession#getMaxInactiveInterval()获取最大存活时间。
ServletContext
每个应用有且仅有一个ServletContext对象,在web创建时则会创建一个对应的ServletContext对象。通过ServletContext对象可以获取tomcat配置的服务信息,可以设置和获取session的过期时间,可以设置和获取初始化参数,可以设置和获取域对象数据。
获取ServletContext:
通过HttpServletRequest#getServletContext()来获取ServiceContext对象。
同样在ServiceContext也存在域对象和session、request中的域对象一样的操作,不过这个域对象是全局存在的。
文件上传和文件下载
文件上传
需要设置http的格式为context-type为multipart/form-data
添加@MultipartConfig()注解,在注解中可以设置文件的最大大小。
文件下载
需要设置context-type为application/octet-stream
2、过滤器(Filter)
Filter为过滤器,用于servlet之外对请求和响应做处理,主要用于对用户请求的预处理和响应的后处理。
请求流程为:
客户端->Filter->servlet->Filter->客户端
继承Filter类重写doFilter方法,对请求和响应做预处理和 后处理。添加@WebFilter注解,添加需要进行过滤的路径。
3、监听器(Listener)
监听器是对ServletContext、HttpSession、ServletRequest的创建和销毁,变量的创建、销毁和修改,在这些动作的前后添加处理,实现监控。
1、监听生命周期:
ServletContextListener
HttpSessionListener
ServletRequestListener
2、监听值的变化
ServletContextAttributeListener
HttpSessionAttributeListener
ServletRequestAttributeListener
3、监听session中Java对象,Java对象直接实现监听器接口
使用时添加@WebListener注解表示这个类是监听器
Tomcat
1、整体架构
连接器(Coyote)
Coyote主要作具体协议的解析和IO的相关操作,并没有实现Servlet的相关规范,
IO模型和协议(9.0版本)支持nio、nio2和APR的IO模型,其中NIO是默认的IO模型,支持的协议包括HTTP、HTTP2和AJP,其中HTTP是默认模型。
组件介绍
EndPoint:Coyote的通信端点,进行通信端口的监听,具体对Socket接受和发送进行处理,是对传输层的处理。其中在Tomcat中有AbstractEndpoint类,其中有两个内部类分别为Acceptor和SocketProcessor。其中Acceptor用于Socket链接请求监听,SocketProcessor用于处理接收到的Socket请求。
Processor:主要是来进行HTTP协议的解析,将读取的字节流转换为Request和Response,交给Adapter来处理。
Adapter:是用来将Tomcat的Request对象封装为ServletRequest对象,在调用容器的Service方法。
Catalina
负责解析Tomcat的配置文件,以此来创建Server组件并管理。
Server:服务启动Servlet引擎和Connector连接器,并实现了Lifecycle接口。
Service:是Server的内部组件,一个Server下可以存在多个Service,主要是吧多个Connector连接到一个Container上。
Connector:连接器,接受请求,转交给响应容器处理,然后向客户端响应。
Container:容器,负责处理Servlet请求。
Container
在Container中包含四种容器,分别为Engine、Host、Context和Wrapper,他们之间是嵌套关系。
Engine:Servlet引擎,在一个Service中只包含一个引擎,但是在一个引擎下面可以包含多个Host。
Host:代表一个虚拟主机,Tomcat可以配置多个虚拟主机,在一个虚拟主机下面可包含多个Context。
Context:表示一个Web引用程序,在一个Web应用程序下,可以包含多个Wrapper。
Wrapper:表示Servlet的具体实现。
Tomcat启动流程
tomcat的各组件接口统一实现了LifeCycle接口,进行生命周期的管理(init() 、start()、stop()、destory()方法进行生命周期的管理),同样对应个各组件都有对应的接口的实现类。
其中在连接器中对IO模型处理Endpoint,则提供了NioEndpoint默认处理实现类。
其中tomcat对于不通的网络协议分别提供了对应io模型的处理实现类分别是:
AJP协议:
AjpNioProtocol、AjpNio2Protocol、AjpAprProtocol
Http协议:
Http11NioProtocol、Http11Nio2Protocol、Http11AprProtocol
其中Http11NioProtocol是默认的处理实现类。
Tomcat请求流程
CoyoteAdapter适配器通过映射,获取到了对应引擎下面的Host进行请求对应的引擎,通过Pipline(管道)中的valve(阀门)进行下一步处理,直至在Wrapper中获取对应的Servlet,然后通过ApplicationFilterChain,依次执行当前Servlet有关的Filter,最后执行Servlet#service()方法。
2、配置优化
在tomcat的conf下有server.xml配置
设置连接器的参数:
maxConnections:最大连接数,当到达数值后,服务器会接受但不会处理更多请求。
maxThreads:最大线程数
acceptCount:最大排队等待数,当请求达到maxConnections之后,tomcat会把后面的请求存放在任务队列中进行排序,acceptCount限定了最大的等待数。
3、Tomcat附加功能WebSocket
介绍
与Http协议的区别
- 以ws开头去请求
- 请求头中包含Connection:Upgrade、Upgrade:websocket、Sec-WebSocket-Key、Sec-WebSocket-Version
- 响应头中包含Upgrade:websocket、Sec-WebSocket-Accept
- Sec-WebSocket-Key是用Base64编码的请求的密文,响应同时以Sec-WebSocket-Accept做应答。
- Sec-WebSocket-Accept指定版本
- status 101标识服务端已经识别并切换为WebSocket协议。
请求流程:
- 首先发送一个Http请求,携带Connection:Upgrade、Upgrade:websocket
- 然后服务端确认请求,返回Upgrade:websocket和状态码101,进行协议转换,转换为长链接
- 最后进行数据请求接受
- 关闭协议
使用
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@ServerEndpoint(value = "/test/one") //WebSocket注解
@Component
public class SimpleWebSocketController {
/** 记录当前在线连接数 */
private static AtomicInteger onlineCount = new AtomicInteger(0);
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session) {
onlineCount.incrementAndGet(); // 在线数加1
log.info("有新连接加入:{},当前在线人数为:{}", session.getId(), onlineCount.get());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(Session session) {
onlineCount.decrementAndGet(); // 在线数减1
log.info("有一连接关闭:{},当前在线人数为:{}", session.getId(), onlineCount.get());
}
/**
* 收到客户端消息后调用的方法
*
* @param message
* 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("服务端收到客户端[{}]的消息:{}", session.getId(), message);
this.sendMessage("Hello, " + message, session);
}
/**
* 链接发生错误时调用
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 服务端发送消息给客户端
*/
private void sendMessage(String message, Session toSession) {
try {
log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
toSession.getBasicRemote().sendText(message);
} catch (Exception e) {
log.error("服务端发送消息给客户端失败:{}", e);
}
}
}