01-Servlet
一、理解Http
1、定义
HTTP是基于TCP协议的。TCP负责数据传输,而HTTP只是规范了TCP传输的数据的格式。
2、socket编程实现HTTP服务的底层。
class SocketHandler implements Runnable {
final static String CRLF = "\r\n"; // 1
private Socket clientSocket;
public SocketHandler(Socket clientSocket) {
this.clientSocket = clientSocket;
}
public void handleSocket(Socket clientSocket) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream())),true);
String requestHeader = "";
String s;
while ((s = in.readLine()) != null) {
s += CRLF; // 2 很重要,默认情况下in.readLine的结果中`\r\n`被去掉了
requestHeader = requestHeader + s;
if (s.equals(CRLF)){ // 3 此处HTTP请求头我们都得到了;如果从请求头中判断有请求正文,则还需要继续获取数据
break;
}
}
System.out.println("客户端请求头:");
System.out.println(requestHeader);
String responseBody = "客户端的请求头是:\n"+requestHeader;
String responseHeader = "HTTP/1.0 200 OK\r\n" +
"Content-Type: text/plain; charset=UTF-8\r\n" +
"Content-Length: "+responseBody.getBytes().length+"\r\n" +
"\r\n";
// 4 问题来了:1、浏览器如何探测编码 2、浏览器受到content-length后会按照什么方式判断?汉字的个数?字节数?
System.out.println("响应头:");
System.out.println(responseHeader);
out.write(responseHeader);
out.write(responseBody);
out.flush();
out.close();
in.close();
clientSocket.close();
}
@Override
public void run() {
try {
handleSocket(clientSocket);
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
public class MyHTTPServer {
public static void main(String[] args) throws Exception {
int port = 8000;
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("启动服务,绑定端口: " + port);
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(30); // 5
while (true) { // 6
Socket clientSocket = serverSocket.accept();
System.out.println("新的连接"
+ clientSocket.getInetAddress() + ":" + clientSocket.getPort());
try {
fixedThreadPool.execute(new SocketHandler(clientSocket));
} catch (Exception e) {
System.out.println(e);
}
}
}
}
二、Servlet
2.1、Servlet简介
Servlet是sun公司提供的一门用于开发动态web资源的技术。
Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:
1、编写一个Java类,实现servlet接口。
2、把开发好的Java类部署到web服务器中。
按照一种约定俗成的称呼习惯,通常我们也把实现了servlet接口的java程序,称之为Servlet
2.2、Servlet的运行过程
Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
①Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
②装载并创建该Servlet的一个实例对象。
③调用Servlet实例对象的init()方法。
④创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
⑤WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
2.3、Servlet与普通Java类的区别
Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。
针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁。
在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
如果在
<servlet>
<servlet-name>invoker</servlet-name>
<servlet-class>
org.apache.catalina.servlets.InvokerServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
用途:为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据。
2.4、Servlet接口实现类
Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServlet、HttpServlet。
HttpServlet指能够处理HTTP请求的servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。
HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。
public class ServletDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
out.println("<HTML>");
out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");
out.println(" <BODY>");
out.print(" This is ");
out.print(this.getClass());
out.println(", using the POST method");
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();
}
}
2.5、Servlet访问URL映射配置
由于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用
同一个Servlet可以被映射到多个URL上,即多个
示例:
<servlet>
<servlet-name>ServletDemo1</servlet-name>
<servlet-class>gacl.servlet.study.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletDemo1</servlet-name>
<url-pattern>/servlet/1</url-pattern>
<url-pattern>/servlet/2</url-pattern>
</servlet-mapping>
2.6、Servlet的线程安全问题
当多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。
示例:
public class ServletDemo3 extends HttpServlet {
int i=1;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
i++;
try {
Thread.sleep(1000*4);
} catch (InterruptedException e) {
e.printStackTrace();
}
response.getWriter().write(i+"");
}
}
/*
把i定义成全局变量,当多个线程并发访问变量i时,就会存在线程安全问题了,如下图所示:同时开启两个浏览器模拟并发访问同一个Servlet,本来正常来说,第一个浏览器应该看到2,而第二个浏览器应该看到3的,结果两个浏览器都看到了3,这就不正常。
*/
三、ServletConfig
3.1、配置Servlet初始化参数
在Servlet的配置文件web.xml中,可以使用一个或多个
<servlet>
<servlet-name>ServletConfigDemo1</servlet-name>
<servlet-class>gacl.servlet.study.ServletConfigDemo1</servlet-class>
<!--配置ServletConfigDemo1的初始化参数 -->
<init-param>
<param-name>name</param-name>
<param-value>gacl</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123</param-value>
</init-param>
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</servlet>
3.2、Servlet获取初始化参数
当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。进而,我们通过ServletConfig对象就可以得到当前servlet的初始化参数信息。
public class ServletConfigDemo1 extends HttpServlet {
private ServletConfig config;
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取在web.xml中配置的初始化参数
String paramVal = this.config.getInitParameter("name");//获取指定的初始化参数
response.getWriter().print(paramVal);
//获取所有的初始化参数
Enumeration<String> e = config.getInitParameterNames();
while(e.hasMoreElements()){
String name = e.nextElement();
String value = config.getInitParameter(name);
response.getWriter().print(name + "=" + value + "<br/>");
}
}
}
四、ServletContext
4.1、定义
WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用。ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象。由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象。
4.2、Servlet间数据共享
public class ServletContextDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String data = "xdp_gacl";
/**
* ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,
* 可以通过ServletConfig.getServletContext方法获得ServletContext对象。
*/
ServletContext context = this.getServletConfig().getServletContext();//获得ServletContext对象
context.setAttribute("data", data); //将data存储到ServletContext对象中
}
}
public class ServletContextDemo2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletContext context = this.getServletContext();
String data = (String) context.getAttribute("data");//从ServletContext对象中取出数据
response.getWriter().print("data="+data);
}
}
4.3、获取WEB应用的初始化参数
(1)、使用
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name></display-name>
<!-- 配置WEB应用的初始化参数 -->
<context-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/test</param-value>
</context-param>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
(2)、获取Web应用的初始化参数
public class ServletContextDemo3 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletContext context = this.getServletContext();
//获取整个web站点的初始化参数
String contextInitParam = context.getInitParameter("url");
response.getWriter().print(contextInitParam);
}
}
五、Servlet3.0
5.1、新注解
@WebServlet
@WebServlet用于将一个类声明为Servlet,该标注将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为Servlet。
@WebServlet(name = "servlet3annotation", urlPatterns = {"/servlet3"}, asyncSupported = true, loadOnStartup = -1)
public class Servlet3Annotation extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取ServletConfig的实例
ServletConfig config = this.getServletConfig();
//获取指定参数名称的值
String name = config.getInitParameter("username");
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.println("<html>");
out.println("<head><title>Servlet3应用实例</title></head>");
out.println("<body>");
out.print("获取InitParamServlet的初始化参数\"username\"的字符串值:" + name);
out.println("</body>");
out.println("</html>");
}
}
@WebFilter
@WebFilter用于将一个类声明为过滤器,该标注将会在部署时被容器处理。以下属性均为可选属性,但是value、urlPatterns、servletNames三者必需至少包含一个,且value和urlPattern不能共存,如果同时指定,通常忽略value的取值。
| 属性名 | 类型 | 描述 |
|---|---|---|
| filterName | String | 指定过滤器的name属性,等价于 |
| value | String[] | 该属性等价于urlPatterns属性,两个属性不能同时使用 |
| urlPatterns | String[] | 指定一组Servlet的URL匹配模式,等价于 |
| servletNames | String[] | @WebServlet中的name属性的取值,或者是web.xml中 |
| initParams | WebInitParam[] | 指定一组Servlet初始化参数,等价于 |
| dispatcherTypes | DispatcherType | 指定过滤器的转发模式。具体取值包括:ASYNC、ERROR、FORWARD、INCLUDE、REQUEST |
| asyncSupported | boolean | 声明过滤器是否支持异步操作模式,等价于 |
| description | String | 该Servlet的描述信息,等价于 |
| displayName | String | 该Servlet的显示名,通常配合工具使用,等价于 |
@WebFilter(servletNames = {"servlet3filterannotation"}, filterName = "characterFilter",
initParams = {@WebInitParam(name = "encoding", value = "UTF-8")})
public class Servlet3FilterAnnotation implements Filter {
private FilterConfig filterConfig = null;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//获取此Filter的初始参数的值
String encoding = filterConfig.getInitParameter("encoding");
System.out.println(encoding);
//设置请求数据的编码方式
servletRequest.setCharacterEncoding(encoding);
//把请求和响应对象传给过滤链中的下一个要调用的过滤器或Servlet
filterChain.doFilter(servletRequest,servletResponse);
}
}
@WebListener
该标注用于将类声明为监听器。
| 属性名 | 类型 | 是否可选 | 描述 |
|---|---|---|---|
| value | String | 是 | 该监听器的描述信息 |
@WebListener("This is the Listener")
public class Servlet3Listener implements ServletContextListener {
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
//应用上下文初始化会回调的方法
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//初始化一个application对象
this.application = servletContextEvent.getServletContext();
//设置一个列表属性,用于保存在线用户名
this.application.setAttribute("online",new LinkedList<String>());
}
}
@WebInitParam
@WebInitParam标注通常不单独使用,而是配合@WebServlet或者@WebFilter使用。它的作业是为Servlet或者过滤器指定初始化参数,这等价于web.xml中
| 属性名 | 类型 | 是否可选 | 描述 |
|---|---|---|---|
| name | String | 否 | 指定参数的名字,等价于 |
| value | String | 否 | 指定参数的值,等价于 |
| description | String | 是 | 指定参数的描述,等价于 |
@MultipartConfig
该标注主要是为了辅助Servlet3.0中HttpServletRequest提供的对上传文件的支持。该标注标注在Servlet上,表示该Servlet希望处理的请求的MIME类型是multipart/form-data。
| 属性名 | 类型 | 是否可选 | 描述 |
|---|---|---|---|
| fileSizeThreshold | int | 是 | 当数据量大于该值时,内容将被写入文件 |
| location | String | 是 | 存放生成的文件地址 |
| maxFileSize | long | 是 | 允许上传的文件最大值。默认值为-1,表示没有限制 |
| maxRequestSize | long | 是 | 针对该multipart/form-data请求的最大数量,默认值为-1,表示没有限制 |
5.2、异步支持
Servlet3.0支持异步处理支持,Servlet接收到请求之后,可能首先需要对请求携带的数据进行一些预处理;接着,Servlet线程将请求转交给一个异步线程来执行业务处理,线程本身返回至容器,此时Servlet还没有生成响应数据,异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有ServletRequest和ServletResponse对象的引用),或者将请求继续转发给其他Servlet。
@WebServlet(urlPatterns = {"/asyncdemo"}, asyncSupported = true)
public class AsyncDemoServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.println("进入Servlet的时间:" + new Date() + ".");
out.flush();
//在子线程中执行业务调用,并由其负责输出响应,主线程退出
AsyncContext ctx = req.startAsync();
new Thread(new Executor(ctx)).start();
out.println("结束Servlet的时间:" + new Date() + ".");
out.flush();
}
public class Executor implements Runnable {
private AsyncContext ctx = null;
public Executor(AsyncContext ctx) {
this.ctx = ctx;
}
public void run() {
try {
//等待10秒钟,以模拟业务方法的执行
Thread.sleep(10000);
PrintWriter out = ctx.getResponse().getWriter();
out.println("业务处理完毕的时间:" + new Date() + ".");
out.flush();
ctx.complete();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
5.3、ServletContainerInitializer
5.3.1、介绍
在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等,servlet规范中通过ServletContainerInitializer实现此功能。
要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体ServletContainerInitializer实现类,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作。
Tomcat容器的ServletContainerInitializer机制的实现,主要交由Context容器和ContextConfig监听器共同实现。ContextConfig监听器负责在容器启动时读取每个web应用的WEB-INF/lib目录下包含的jar包的META-INF/services/javax.servlet.ServletContainerInitializer,以及web根目录下的META-INF/services/javax.servlet.ServletContainerInitializer,通过反射完成这些ServletContainerInitializer的实例化,然后再设置到Context容器中。
5.3.2、实现机制
Context容器启动时就会分别调用每个ServletContainerInitializer的onStartup方法,并将感兴趣的类作为参数传入。
步骤:
- 首先通过ContextConfig监听器遍历每个jar包或web根目录的META-INF/services/javax.servlet.ServletContainerInitializer文件,根据读到的类路径实例化每个ServletContainerInitializer。
- 然后再分别将实例化好的ServletContainerInitializer设置进Context容器中。
- 最后Context容器启动时分别调用所有ServletContainerInitializer对象的onStartup方法。
5.3.3、@HandlesTypes
容器启动的时候通过@HandlesTypes可以感兴趣类型的所有子类型到ServletContainerInitializerde的onStartup方法作为参数传入。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@SuppressWarnings("rawtypes") // Spec API does not use generics
public @interface HandlesTypes {
/**
* @return array of classes
*/
Class[] value();
}
5.3.4、使用案例
(1)、实现接口重写方法
@HandlesTypes(value={HelloService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
/**
* Set<Class<?>> arg0:感兴趣的类型的所有子类型;
* ServletContext arg1:代表当前Web应用的ServletContext;一个Web应用一个ServletContext;
*/
@Override
public void onStartup(Set<Class<?>> arg0, ServletContext sc) throws ServletException {
for (Class<?> claz : arg0) {
System.out.println(claz);
}
//注册组件 ServletRegistration
ServletRegistration.Dynamic servlet = sc.addServlet("userServlet", new UserServlet());
//配置servlet的映射信息
servlet.addMapping("/user");
//注册Listener
sc.addListener(UserListener.class);
//注册Filter FilterRegistration
FilterRegistration.Dynamic filter = sc.addFilter("userFilter", UserFilter.class);
//配置Filter的映射信息
filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
}
}
(2)、编写文件绑定实现类
在META-INF/services目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类。

浙公网安备 33010602011771号