-
当编写一个Servlet类直接实现Servlet接口有什么缺点?
- service方法是最常用的,其他方法大部分情况下是不需要使用的。如此其他的方法就显得有些冗余,代码很丑陋。
解决思路:编写一个“中间商”,使中间商实现Servlet接口,我们编写的Servlet类再继承“中间商”
Servlet
- 版本一
- 创建一个抽象类GenericServlet实现Servlet接口,将子类需要的方法设置为抽象方法。
- 将子类继承 GenericServletTest抽象类,并实现对应的抽象方法。
- 需要注意的是:父类的init方法(非抽象)也会执行
- 版本一问题:子类继承GenericServletTest,并实现他的抽象方法,父类的init方法也会实现。会执行父类GenericServlet类中的init方法。
子类的对象是Tomcat创建的,init方法也是Tomcat调用的,当然,init方法中需要传入的ServletConfig类型的参数也是tomcat创建的。这里需要考虑到ServletConfig对象肯定在service方法中使用 解决方法为了确保ServletConfig可以在其他方法中使用,把ServletConfig设置为成员变量
- 版本二
- 设置成员变量servletConfig
- 考虑到该变量是Tomcat创建的,成员变量的servletConfig一开始是null,如何赋值呢?
- 下图是解决方法:
- 版本二问题:通过上述方法可以解决其他方法获得servletConfig问题,但是如果子类想重写init方法时,父类的init方法可能被破坏,导致servletConfig获取不到,成员变量依旧为空。
解决方法:使父类的init方法被final修饰,即父类的init方法不能被重写,杜绝了重写init方法导致成员变量为空的问题
- 解决后问题:万一子类实在是需要重写init方法怎么办?
解决方法:写一个无参的init类,使子类重写无参的init类,方法重载
- 版本三
- “中间商代码”
package adapter;
import jakarta.servlet.*;
import java.io.IOException;
/** 理解 GenericServlet 原理
* 设置“中间商”——GenericServletTest
* 在servlet包中的loginServlet类继承Servlet类之后需要实现Servlet的所有方法,但是在使用时我们发现,不是所有方法都需要
*实现,那么loginServlet类中的有些方法就显得冗余。
* 此时我们创建一个抽象类GenericServlet实现Servlet接口,将需要编写的方法设置为抽象方法
* 将loginServlet类继承 GenericServletTest抽象类,并实现对应的抽象方法。
*/
public abstract class GenericServletTest implements Servlet {
private ServletConfig servletConfig;
/**
* 子类loginServlet继承GenericServletTest,并实现他的抽象方法,父类的init方法也会实现。会执行父类GenericServlet类中的init方法。
* loginServlet的对象是Tomcat创建的,init方法也是Tomcat调用的,当然,init方法中需要传入的ServletConfig类型的参数也是tomcat
*创建的。
* 这里需要考虑到ServletConfig对象肯定在service方法中使用,为了确保ServletConfig可以在其他方法中使用,把ServletConfig设置为成员变量
* @param servletConfig
* @throws ServletException
*/
@Override
public final void init(ServletConfig servletConfig) throws ServletException {
//Tomcat创建的servletConfig参数,传入之后,通过等号赋给成员变量servletConfig。
this.servletConfig =servletConfig;
/**
*当我们确定了“中间商”之后,有的需求,子类还需要重写init方法
* 在子类重写init方法时,父类的init方法可能被破坏
* 因为父类中的init方法可以从Tomcat创建的参数中获得servletConfig参数,进一步赋值给成员变量
* 如果被破坏,可能成员变量servletConfig类型的变量为空,那么在其他的方法中就不能使用。
* 解决:父类的init方法不让子类重写,避免错误,在父类的init方法上添加final关键字
* 但,当子类确实需要重写init方法时,可以编写另外一个init方法——方法重载
*/
this.init();
}
/**
* 该方法供子类重写
*/
public void init(){
}
@Override
public ServletConfig getServletConfig() {
return servletConfig;
}
/**
* 抽象方法
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException ;
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
- 子类代码
package adapter;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.IOException;
public class loginServlet extends GenericServletTest{
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("");
//在loginServlet子类中使用servletConfig对象
ServletConfig config = this.getServletConfig();
}
}
- GenericServlet源代码:
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable
- 到此为止,明确了GenericServlet为什么实现Servlet
ServletConfig
- 上文提到,需要考虑ServletConfig对象肯定在service方法中使用,那ServletConfig是什么呢?
- jakarta.servlet.ServletConfig,ServletConfig是Servlet规范中的一员。
- ServletConfig是一个接口
- 实现ServletConfig接口的是Tomcat服务器,且一个servlet对象就有一个对应的ServletConfig对象不同的servlet有不同的ServletConfig对象
- 创建ServletConfig对象的是Tomcat服务器
- ServletConfig的用处
- ServletConfig对象是servlet对象的配置信息对象
- ServletConfig对象包含的信息:web.xml文件中标签的配置信息
- Tomcat将web.xml文件配置信息自动包装到ServletConfig对象中。
- 这就解释了:ServletConfig方法.getServletName();
- 这就解释了:ServletConfig方法.getServletName();
-
思考:既然web.xml文件中标签的配置信息都可以被包装到 ServletConfig对象里面,那么是不是除了servlet-name,我们还可以往里面塞点其他的东西,取的时候也可以通过ServletConfig获得。
-
答案是可以的,结合ServletConfig的四个方法,其中一个是getInitParameterNames(),就可以知道塞了什么——init-param初始化参数
-
java.util.Enumeration<java.lang.String> getInitParameterNames()
java.lang.String getInitParameter(java.lang.String name)
上面的两个方法可以获取到初始化参数
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<servlet>
<servlet-name>config</servlet-name>
<servlet-class>javaWeb.servlet.configTest.ConfigTestServlet</servlet-class>
<!--配置Servlet对象初始化信息-->
<init-param>
<param-name>driver</param-name>
<param-value>com.mysql.cj.jdbc.Driver</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/yiyu</param-value>
</init-param>
<init-param>
<param-name>user</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123123</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>config</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
public class ConfigTestServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
//获取ServletConfig对象
ServletConfig config = this.getServletConfig();
//输出对象
PrintWriter out=response.getWriter();
out.print("ServletConfig对象——"+config);
out.print("<br>"+"<servlet-name>"+config.getServletName()+"<servlet-name>");
// 通过ServletConfig对象方法,获取<init-param></init-param>初始化参数
Enumeration<String> initParameterNames= config.getInitParameterNames();
while(initParameterNames.hasMoreElements()){
String name = initParameterNames.nextElement();
//config.getInitParameter("") 通过名字获得vlaue
String diver = config.getInitParameter(name);
out.print("<br>"+name+"="+diver);
}
}
}
- GenericServlet源代码:
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable
- 到此为止,就可以知道ServletConfig是什么了,也知道GenericServlet继承了ServletConfig,因为我们写的Servlet类需要继承GenericServlet,也就是说ServletConfig的方法直接使用this调用就可以。
ServletConfig ——ServletContext
- 上文说到ServletConfig有四个方法,到目前为止我们知道三个是用于获得的初始化内容的。那么另外一个是什么呢?——ServletContext
- ServletContext是一个接口
- ServletContext的实现,创建都是Tmocat服务器。
- ServletContext在服务器启动时创建,在服务器关闭时销毁
- ServletContext是Servlet的环境对象,背景;应用域。
- 只要在同一个webapp当中,所有的servlet共享放在ServletContext对象当中的数据
- 50个学生在教室里面听老师讲课
- 50个servlet 一个ServletContext老师
- 只要在同一个webapp当中,所有的servlet共享放在ServletContext对象当中的数据
- Tomcat当中有webapps,这个webapps里面可以存放多个webapp,有多少个webapp就有多少个ServletContext。即:ServletContext是应用级对象
- 既然ServletContext是应用级的对象,那么我们对多个servlet对象进行同样的初始化时,有些数据就可以塞到ServletContext里面
<!--Context初始化参数 以下的配置信息可以使用ServletContext对象获得-->
<context-param>
<param-name>pageSize</param-name>
<param-value>10</param-value>
</context-param>
<context-param>
<param-name>startIndex</param-name>
<param-value>0</param-value>
</context-param>
- 以上,我们分辨出来了 ServletConfig ——ServletContext两者的区别。那么如何判断数据是否应该存放在ServletContext中?
- 如果所有用户共享同一份数据,且数据量小,且该数据很少被修改,就可以放入
- 数据量小:数据量大的话太占用堆内存,并且该context对象生命周期长,服务器关闭的时候,该对象才会被销毁,大量数据会影响服务器性能
- 很少修改:如果涉及修改操作,必然存在线程并发所带来的安全问题,所以ServletContext对象中的数据一般都是可读的
- 向ServletContext应用域中存储数据,也等于是将数据存放到缓存cache当中了。
- 如果所有用户共享同一份数据,且数据量小,且该数据很少被修改,就可以放入
- 判断出应该放入ServletContext的数据,如何放、如何取、如何删
- 存数据
public void setAttribute(String name,Object value) - 取数据
public void getAttribute(String name); - 删数据
public void removeAttribute(String name);
- 存数据
缓存机制
- 堆内存当中的字符串常量池。
- "abc" 先在字符串常量池中查找,如果有,直接拿来用。如果没有则新建,然后再放入字符串常量池。
- 堆内存当中的整数型常量池。
- [-128 ~ 127] 一共256个Integer类型的引用,放在整数型常量池中。没有超出这个范围的话,直接从常量池中取。
- 连接池(Connection Cache)
- 这里所说的连接池中的连接是java语言连接数据库的连接对象:java.sql.Connection对象。
- JVM是一个进程。MySQL数据库是一个进程。进程和进程之间建立连接,打开通道是很费劲的。是很耗费资源的。怎么办?可以提前先创建好N个Connection连接对象,将连接对象放到一个集合当中,我们把这个放有Connection对象的集合称为连接池。每一次用户连接的时候不需要再新建连接对象,省去了新建的环节,直接从连接池中获取连接对象,大大提升访问效率。
- 连接池
- 最小连接数
- 最大连接数
- 连接池可以提高用户的访问效率。当然也可以保证数据库的安全性。
- 线程池
- Tomcat服务器本身就是支持多线程的。
- Tomcat服务器是在用户发送一次请求,就新建一个Thread线程对象吗?
- 当然不是,实际上是在Tomcat服务器启动的时候,会先创建好N多个线程Thread对象,然后将线程对象放到集合当中,称为线程池。用户发送请求过来之后,需要有一个对应的线程来处理这个请求,这个时候线程对象就会直接从线程池中拿,效率比较高。
- 所有的WEB服务器,或者应用服务器,都是支持多线程的,都有线程池机制。
- redis
- NoSQL数据库。非关系型数据库。缓存数据库。
- 向ServletContext应用域中存储数据,也等于是将数据存放到缓存cache当中了。
********动力节点*************
-
ServletContext的方法
-
动态获取根路径 getContextPath()
;该处获得的根,是index比web.cml程序多写的那个应用的根。 -
获取文件的绝对路径 getRealPath();
- ServletContext对象的getRealPath(“index.html”);
- 输出:E:\IDEAProjects\javaweb\out\artifacts\servlet03_war_exploded\index.html
-
记录日志 log(String message) / log(String message,Throwable t)
- ServletContext对象.log(“哒哒哒”);
- 在控制台查看路径:Using CATALINA_BASE
- 日志类型
-