1.servlet规范
遵循Servlet规范的webapp可以放在不同的web服务器中运行;
2.创建servlet项目
-
创建项目目录,目录名称自定义不能是中文,例如crm;
-
在项目目录下创建WEB-INF文件夹,必须是这个名字;
-
WEB-INF文件下新建classes文件夹,必须是这个名字,这个目录下存放的一定是java程序编译之后的class文件(字节码文件);
-
在WEB-INF目录下新建目录lib,这个目录不是必须的,但是如果一个webapp需要第三方jar包的话,这个jar包要放到lib目录下,名字必须是全部小写的lib,例如java语言连接数据库的驱动jar包,那么这个Jar包就一定要放到lib目录;
-
在WEB-INF目录下新建web.xml文件,这个文件是必须的,名字必须是web,xml,一个合法的webapp,web.xml文件是必须的,这个web.xml文件是一个配置文件,这个文件中描述了请求路径与Servlet类之间的对应关系,这个文件最好从其他webapp中拷贝,没必要手写,可以从tomcat内置的项目中拷贝,删除内容,只保留标签内容;
-
编写java程序HelloWorld.java,实现Servlet接口;
这个Servlet接口不在jdk中,因为Servlet不属于JAVASE,属于JAVAEE,是另外的一套类库,是Oracle提供的,最早是sun公司提供的,Servlet是JAVAEE规范中的一员,tomcat实现了Servlet规范,所以tomcat也需要使用Servlet接口,在CATALINA_HOME/lib/servlet-api.jar中,从JAVAEE9开始Servlet的包名是jakarta.servlet.Servlet;
-
编译java程序HelloWorld.java,需要将servlet-api.jar配置到环境变量的classpath中才能编译;
-
将HelloWorld.class放入到项目文件夹下的WEB-INF的classes文件夹下;
-
在web.xml文件中编写配置信息,让请求路径和Servlet类名关联,这一步用专业术语描述叫做在web.xml文件中注册Servlet类;
<?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" metadata-complete="true"> <!-- Servlet描述信息 --> <!-- 任何一个servlet都对应一个Servlet-Mapping --> <servlet> <servlet-name>hellourl</servlet-name> <!-- 带有包名的全类名 --> <servlet-class>com.ServletTest1.Servlet.ServletTest1</servlet-class> </servlet> <!-- Servlet映射信息 --> <servlet-mapping> <!-- 路由命名,与上面的servlet-name相同 --> <servlet-name>hellourl</servlet-name> <!-- 这里写路径,必须以/开头--> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app>
10.重启tomcat,在浏览器上输入:localhost:8080/项目名/url-pattern;
此时可以将catalina_home下面的conf/logging.properties 修改为
java.util.logging.ConsoleHandler.encoding = GBK,即可显示中文提示
注意点:html文件只能放在WEB-INF文件夹外面;
整个url请求过程:浏览器通过/hello地址发出请求,tomcat根据/hello找到com.ServletTest1.Servlet.ServletTest1类,再通过反射机制调用类的service方法;
3.JAVAEE版本
javaee版本目前最高是javaee8,javaee被Oracle捐献给力Apache,Apache把JAVAEE更名为jakartaEE,以后都叫做jakataEE,javaee8升级之后的版本叫做jakartaee9,jakartaee10…
javaee8的Servlet的全类名是javax.servlet.Servlet
javaee9的Servlet的全类名是jakarta.servlet.Servlet
如果之前的项目使用的是javax.servlet.Servlet,那么该项目无法之间部署到tomcat10+的版本上,只能部署到tomcat9-的版本上,在tomcat9以及tomcat9以下的版本能够识别javax.servlet这个包;
4.向浏览器输出一段html代码
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.setContentType("text/html;charset=UTF-8");
PrintWriter pri = response.getWriter();
pri.print("<h1>hello servlet,你好啊</h1>");
}
5.IDEA创建普通java项目
1.创建空项目,再在空项目下创建普通java模块,将普通java模块转换成webapp框架支持的项目,右键模块目录添加框架支持,勾选JAVAEE下面的web应用程序,点击完成,删除jsp文件;
2.点击文件-项目结果-导入jar包或者库;
3.编写类实现Servlet接口,实现Servlet接口的方法;
4.在service中编写业务代码;
5.idea关联tomcat,
①IDEA右上角绿色小锤子,选择tomcat本地,然后添加应用服务器配置(本地tomcat文件路径)、jre配置(本地JDK路径),
②部署,加号,工件,应用程序上下文填写项目名称
6.点击右上角绿色虫子,debug模式启动程序,开发中建议用debug模式启动;
6.Servlet对象的生命周期
Servlet对象的创建、对象方法的调用、对象的销毁javaweb程序员是无权干预的,Servlet对象的生命周期是由Tomcat服务器(web server)全权负责的;Tomcat服务器通常外面称之为WEB容器(WEB Container),WEB容器在管理Servlet的生命周期;
我们自己创建的Servlet对象是不受WEB容器管理的,WEB容器创建的Servlet对象会被放到一个集合当中(HashMap),只要在HashMap中的Servlet才能被WEB容器管理;
WEB容器底层有一个HashMap这样的集合,在这个集合中存储Servlet对象和请求路径之间的关系;
默认情况下。服务器在启动的时候不会实例化Servlet对象;这个设计是合理的,在用户没有发送请求之前,如果提前创建所有Servlet对象,必然是非常耗费内存的,如果这个Servlet对象一直,没有用户访问的话,也是浪费资源的;
如何让服务器在启动的时候创建Servlet对象?
在Servlet子标签中添加3,该子标签中的数字越小,优先级越高;
<servlet>
<servlet-name>aservlet</servlet-name>
<servlet-class>com.ywxk.ServletTest.AServlet</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
用户在发送第一次请求的时候Servlet对象被实例化,Servlet对象被创建出来后,容器马上调用Servlet对象的init方法,后续继续发送请求的时候,容器只会调用service方法,说明Servlet对象是单例的,但是Servlet类并不符合单例模式,我们称之为假单例,真单例模式构造方法是私有化的,而Servlet的构造方法我们是可以自由编写的,在服务器关闭的时候,服务器在销毁Servlet对象的内存之前会自动调用Servlet对象的destroy方法,destroy方法在执行的时候Servlet对象还没有被销毁,destroy方法执行完后Servlet对象的内存才会被服务器释放;
通常在init方法中做初始化操作,例如初始化数据库连接池,初始化线程池…
通常在destroy方法中做一些资源的关闭
Servlet类必须有无参构造,否则会导致Servlet对象无法实例化,所以官方不建议程序员去编写Servlet的构造方法,如果有必需在构造方法中处理的业务,可以写在init方法中;
7.适配器设计模式
通常不能将手机直接放到220v电压上充电,需要一个适配器,手机通过适配器充电;
同理并不是所有的Servlet对象都会用到Servlet接口的所有方法,如果每个Servlet类都去实现Servlet接口的5个方法,那么代码会臃肿、不够优雅,此时我们可以创建一个通用类去实现Servlet接口,然后其他的Servlet类都去继承这个通用类,这样就可以避免写一些不必要的方法,简化代码,我们称这个设计模式为适配器设计模式;
/**
* 编写一个标准通用的Servlet类
* 以后所有的Servlet类都不要直接实现Serlet接口
* 都继承Servlet通用类GenericServlet
* GenericServlet就是一个适配器
*/
public abstract class GenericServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
/**
* Servlet子类必须实现service方法
* @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() {
}
}
8.GenericServlet
jakarta.servlet.GenericServlet;
实现了Servlet接口,并对init方法做了重写,扩展了一些其他方法;我们只需要继承该方法即可通过getServletConfig()方法获得servletConfig实例;
9.ServletConfig
jakarta.servlet.ServletConfig;
ServletConfig是一个接口,tomcat的org.apache.catalina.core.StandardWrapperFacade类实现了ServletConfig接口,jetty服务器的实现类可能不一样,但是他们实现的都是ServletConfig接口;
不同的Servlet类通过getServletConfig()方法获取的ServletConfig对象是不一样的,每一个Servlet对象都有一个对应的ServletConfig对象;
ServletConfig对象是tomcat服务器(web服务器)创建的,服务器在创建完servlet对象之后立马又创建了ServletConfig对象,然后通过servlet的init方法传递了ServletConfig对象;
源码大概是这样的:
//通过反射获取Servlet类
Class cls=Class.forName("com.xxx.xxxServlet");
//创建Servlet对象
Object obj=cls.newInstance();
//向上转型成Servlet对象
Servlet servlet=(Servlet)obj;
//创建ServletConfig对象
ServletConfig servletConfig=new org.apache.catalina.core.StandardWrapperFacade();
//将ServletConfig对象传递给我们的servlet对象
servlet.init(servletConfig);
从ServletConfig名字也可以很明显的看出ServletConfig是一个配置信息类,每个Servlet对象应该要对应一个自己的配置信息,所以每个Servlet对象都会对应一个ServletConfig对象是合理的;
ServletConfig对象中配置的信息就是:
web.xml文件中标签的信息;
获取初始化参数:
<servlet>
<servlet-name>loginServlet</servlet-name>
<servlet-class>com.ywxk.ServletTest.LoginServlet</servlet-class>
<init-param>
<param-name>driver</param-name>
<param-value>org.mysql.jdbc.Driver</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:8080/Servlet02/register</param-value>
</init-param>
<init-param>
<param-name>username</param-name>
<param-value>root</param-value>
</init-param>
</servlet>
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
servletResponse.setContentType("text/html;charset=UTF8");
ServletConfig servletConfig = getServletConfig();
PrintWriter pw= servletResponse.getWriter();
Enumeration<String> paramNames=getInitParameterNames();
while (paramNames.hasMoreElements()){
String paramName= paramNames.nextElement();
pw.println(paramName+"---"+getInitParameter(paramName));
}
}
10.ServletContext
ServletContext是接口
是Servlet的环境接口(Servlet对象的上下文对象)
由web服务器实现
ServletContext对象是由web服务器在启动的时候创建的
对于一个webapp来说,ServletContext对象只有一个,ServletContext对象在服务器关闭的时候销毁,
ServletContext对象其实对应的就是整个web.xml文件
放在ServletContext对象当中的数据是所有Servlet共享的;
tomcat的实现类:org.apache.catalina.core.ApplicationContextFacade;
获取ServletContext的初始化参数
<!-- 上下文的初始化参数 -->
<context-param>
<param-name>projectName</param-name>
<param-value>webapp-ywxk</param-value>
</context-param>
<context-param>
<param-name>projectDate</param-name>
<param-value>2022-05-04</param-value>
</context-param>
public void test2(ServletResponse servletResponse) throws IOException {
PrintWriter pw=servletResponse.getWriter();
ServletContext sc= getServletContext();
Enumeration<String> names=sc.getInitParameterNames();
while (names.hasMoreElements()){
String name=names.nextElement();
pw.println(name+"<hr/>");
pw.println(sc.getInitParameter(name));
}
}
ServletContext相关方法:
ServletContext sc= getServletContext();
//获取项目文件名
pw.println(sc.getContextPath());
//获取项目文件的绝对地址
pw.println(sc.getRealPath(sc.getContextPath()));
//动态获取文件的绝对路径
pw.println(sc.getRealPath("/index.html"));
//记录日志,日志的目录CATALINA_HOME/logs,IDEA的tomcat记录在IDEA的CATALINA_BASE里面
sc.log("记录了日志");
sc.log("记录异常",new RuntimeException("异常日志"));
ServletContext又叫做应用域,如果所有的用户共享一份数据,并且这个数据很少别被修改,并且这个数据量很少,可以将这些数据放到ServletContext这个应用域当中,当作一个长时间的缓存,可以提高工作效率;
PrintWriter pw=servletResponse.getWriter();
ServletContext sc= getServletContext();
//添加、修改
sc.setAttribute("name","James");
//获取,在其他的servlet中也能获取
pw.println(sc.getAttribute("name"));
//删除
sc.removeAttribute("name");
实际开发中,我们编写Servlet类的时候,不会去直接继承GenericServlet类,因为我们开发的是B/S架构的web应用,在Servlet规范当中,提供了一个HttpServlet类,它是专门为HTTP协议准备的一个Servlet类,HttpServlet继承了GenericServlet类,我们编写的Servlet类只要去继承HttpServlet类就可以了,使用HttpServlet类处理Http协议更方便;
Servlet类关系:
jakarta.servlet.Servlet(接口)
jakarta.servlet.GenericServlet(抽象类) implements Servlet (GenericServlet实现了Servlet )
jakarta.servlet.HttpServlet(抽象类) extends GenericServlet(HttpServlet继承了GenericServlet )
我们编写的类继承HttpServlet;
11.模板方法设计模式
在模板类的模板方法中定义核心算法骨架,具体的实现步骤可以延迟到子类当中完成;
模板类通常是一个抽象类,模板类当中的模板方法定义核心算法,这个方法通常是final的,但也可以不是;
模板类当中的抽象方法是不确定实现的方法,这个不确定怎么实现的方法交给子类完成;
/**
* MakeMedia就是模板设计方法设计模式的模板
* make就是模板设计方法设计模式当中的模板方法
* 模板类通常是抽象类
*/
public abstract class MakeMedia {
//模板方法
//添加了final之后,这个方法无法被覆盖,核心算法也可以得到保护,算法也得到了重复使用
//代码也得到了复用,因为算法中某些步骤是固定的,这些固定的代码不会随着子类的变化而变化,这一部分代码可以写到模板类当中
//模板方法定义核心的算法骨架,具体的实现步骤可以延迟到子类当中去实现
public final void make(){
xiejuben();
qingyanyuan();
paishechangdi();
shangyin();
}
public void xiejuben(){
System.out.println("写剧本");
}
public void qingyanyuan(){
System.out.println("请演员");
}
public void paishechangdi(){
System.out.println("挑选拍摄场地");
}
//某些方法可以放到子类去具体实现
public abstract void shangyin();
}
12.HttpServlet
jakarta.servlet.http.HttpServlet
HttpServlet类是专门为HTTP协议准备的,比GenericServlet更适合HTTP协议下的开发
http包下的类和接口
- jakarta.servlet.http.HttpServlet(Http协议专用的Servlet类,抽象类)
- jakarta.servlet.http.HttpServletRequest(Http协议专用的请求对象)
- jakarta.servlet.http.HttpServletResponse(Http协议专用的响应对象)
HttpServletRequest,简称request对象,其中封装了请求协议的全部内容,tomcat服务器(web服务器)将请求协议中的数据全部解析出来,然后将这些数据全部封装到request对象当中了,通过 HttpServletRequest对象我们可以获取请求协议的全部信息
HttpServletResponse对象同理
子类继承HttPServle抽象类后,能享受405错误,如果没有重写doGet方法,而前端用了get请求,那么会调用HttpServlet的doGet方法,页面报405方法不允许错误,后端要求用get请求,就必须重写doGet方法,其他方法同理
Servlet对象不能由程序员自己创建,自己new的Servlet对象生命周期不受tomcat服务器的管理,
13.设置欢迎页
在web文件夹下新建xxx.html,然后在web.xml新建标签
<!--配置欢迎页面-->
<welcome-file-list>
<welcome-file>welcome.html</welcome-file>
<welcome-file>page1/page2/test.html</welcome-file>
</welcome-file-list>
设置欢迎页的时候不需要添加/,并且这个路径默认是从webapp的根(web文件夹)下开始查找
一个webapp可以设置多个欢迎页面,越靠上的优先级越高,找不到再往下面找
实际上欢迎页有两个地方可以配置
-
一个是在webapp的web.xml文件中,在这个地方配置属于局部配置
-
一个是在CATALINA_HOME/conf/web.xml文件中进行配置,在这个地方配置属于全局配置
<welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list>
局部配置优先
欢迎页也可以是一个Servlet类
<welcome-file-list>
<welcome-file>welcome/page/hello</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>welcomePage</servlet-name>
<servlet-class>com.ywxk.ServletTest.welcome.WelcomePage</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>welcomePage</servlet-name>
<url-pattern>/welcome/page/hello</url-pattern>
</servlet-mapping>
public class WelcomePage extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf8");
PrintWriter pw= resp.getWriter();
pw.println("<h2>动态欢迎页</h2>");
}
}
14.关于WEB-INFO目录
放在WEB-INFO目录下的资源是受保护的,在浏览器上是不能通过路径直接访问,蓑衣像html、js、css等资源文件一定要放在WEB-INFO目录外面;
15.HttpServletRequest接口详解
jakarta.servlet.http.HttpServletRequest,是一个接口
用户在发送请求的时候,遵循了HTTP协议,tomcat服务器将http协议中的信息以及数据全部解析出来,然后tomcat服务器把这些信息封装到HttpServletRequest对象当中,]我们调用HttpServletRequest对象就可以获取请求信息了
request对象和response对象只在当前请求中有效,一个request请求对应一个request对象
Map<String,String[]> getParameterMap()获取map
Enumeration<String> getParameterNames() 获取map集合的所有key
String[] getParameterValues(String name) 根据key获取map集合的value
String getParameter(String name) 根据key获取map集合当中的value,再获取value数组的第一个元素,最常用
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf8");
PrintWriter pw= resp.getWriter();
pw.println("POST方法");
Map<String,String[]> maps= req.getParameterMap();
Set<String> keys= maps.keySet();
Iterator<String> it= keys.iterator();
while (it.hasNext()){
String key= it.next();
pw.println(key);
String[] values=maps.get(key);
for (String val:values) {
pw.println(val);
}
pw.println();
}
pw.println("<hr />");
Enumeration<String> parameterNames =req.getParameterNames();
while (parameterNames.hasMoreElements()){
String key2=parameterNames.nextElement();
pw.println(key2+"=");
String[] values2= req.getParameterValues(key2);
for (String v: values2) {
pw.println(v);
}
}
pw.println("<hr />");
//获取一维数组中的第一个元素
pw.println(req.getParameter("username"));
pw.println(req.getParameter("password"));
String[] values3=req.getParameterValues("hobby");
for (String val: values3) {
pw.println(val);
}
}
request对象实际上又称为请求域对象
一个请求对象request对应一个请求域对象,一次请求结束之后,这个请求域就销毁了
两个servlet共享数据
- 将数据放到servletContext应用域当中当然是可以的,但是应用域范围太大,占用资源较多,不建议使用
- 可以将数据放到request域当中,然后Aservlet转发到Bservlet当中,保证Aservlet与Bservlet在同一次请求当中,这样就可以做到两个Servlet或者多个Servlet共享一份数据
请求转发器
//请求转发机制,执行了AServlet之后跳转到BServlet
//第一步,获取请求转发器对象,相当于把/burl这个路径包装到请求转发器当中,实际上是吧下一个跳转的路径告诉给tomcat服务器
RequestDispatcher reqDis=req.getRequestDispatcher("/burl");
//第二部,调用请求转发器,将req与resp传给下一个资源
reqDis.forward(req,resp);
//合并代码
//req.getRequestDispatcher("/burl").forward(req,resp);
转发的下一个资源可以不是Servlet,只要是Tomcat服务器当中的合法资源都是可以转发的,例如:html…,转发的路径以/开头,不加项目名
req.getRequestDispatcher("/welcome.html").forward(req,resp);
request的其他方法
//获取客户端的ip地址
String addrIp= req.getRemoteAddr();
//设置请求体的字符集(处理的是post请求的乱码问题,不能解决get请求的乱码问题)
//tomcat10之后,request请求体当中的字符默认是UTF8,不需要设置字符集,不会出现乱码问题
//tomcat9之前包括9的版本,如果请求体提交的是中文,后端获取之后会出现乱码,需要设置字符集
req.setCharacterEncoding("UTF-8");
//获取应用的根路径
req.getContextPath()
//获取请求方式
req.getMethod();
//获取请求的URI
String uri=req.getRequestURI();
//获取servlet路径
String servletPath=req.getServletPath();
16.转发与重定向
转发:
req.getRequestDispatcher("/burl").forward(req,resp);
由于调用forward方法的时候,都会将request对象与response对象传递给下一个Servlet,所以不管转发了多少次,都是在同一个request对象中进行的,所以转发是一次请求;
转发之后浏览器地址上的请求地址没变
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kN1N94eE-1651927172325)(C:\Users\Administrator\Desktop\临时\1651903152(1)].png)
重定向:
//重定向的路径要以项目名开始
//response对象将路径响应给了浏览器,浏览器自发的向服务器发送了一次请求
//浏览器一共发生了两次请求,浏览器上最终显示的是最后一次发送请求的地址,所以重定向会导致地址栏上的地址发生改变
resp.sendRedirect(req.getContextPath()+"/burl");
重定向一次,浏览器发送了2次请求;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mhQgCX0E-1651927172326)(C:\Users\Administrator\Desktop\临时\1651903215(1)].png)
转发和重定向的本质区别:
转发是由web服务器来控制的,A资源跳转到B资源,这个跳转动作是tomcat服务器内部完成的;
重定向是由浏览器完成的,具体跳转哪个地址由浏览器决定;
实际开发中如果在上一个Servlet当中向request域当中绑定了数据,希望在下一个Servlet中取出数据,使用转发机制,其余情况均使用重定向,转发会存在浏览器刷新提交问题;
17.javabean
javabean,咖啡豆,咖啡由咖啡豆研磨而成,寓意是java程序是由一个又一个的javabean组成;
一个javabean一般是有规范的:
- 无参构造方法
- 属性私有化
- 对外提供setter和getter方法
- 重写toString、hashCode、equals
- 实现java.io.Serializable接口
javabean其实就是java的实体类,负责数据的封装,通常我们把这样的类叫做javabean
18.Servlet注解式开发
servlet3.0版本之后,推出了各种Servlet基于注解式开发
其优点是:直接在java类上使用注解进行标注,不需要编写大量的配置信息,开发效率高,缩小了web.xml的体积
@WebServlet,在Servlet类上使用
属性:
name:相当于<servlet-name>
urlPatterns:指定该Servlet的多个映射路径,一个Servlet可以有多个映射路径,等同于<url-pattern>
value:作用与urlPatterns一样,都是指定多个映射路径
loadOnStartup:服务器启动时是否创建servlet对象,等同于<load-on-startup>
initParams:等同于<init-param>
@WebServlet(name="helloServlet的注解",loadOnStartup = 1,urlPatterns = {"/helloUrl1","/helloUrl2","/helloUrl3"},initParams = {@WebInitParam(name="user",value = "root"),@WebInitParam(name = "password",value = "root")})
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doGet(req, resp);
resp.setContentType("text/html;charset=utf8");
PrintWriter pw=resp.getWriter();
String name=getServletName();
pw.println(name);
pw.println(req.getServletPath());
pw.println("<hr />");
Enumeration<String> parameterNames=getInitParameterNames();
while (parameterNames.hasMoreElements()){
String pName=parameterNames.nextElement();
pw.println(pName);
pw.println(getInitParameter(pName));
}
}
}
@WebServlet("/worldUrl")
public class WorldServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doGet(req, resp);
PrintWriter pw=resp.getWriter();
pw.println("worldUrlworldUrlworldUrl");
}
}
19.使用模板设计方法解决类爆炸问题
之前的开发都是一个请求对应一个Servlet类,1000个请求对应1000个Servlet类,会导致类爆炸
解决方案1:
- 一个请求对应一个方法,一个业务对应一个Servlet类
@WebServlet({"/movieList","/movieDetail","/movieDel","/movieAdd","/movieUpdate"})
public class MovieAction extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.service(req, resp);
PrintWriter pw=resp.getWriter();
resp.setContentType("text/html;charset=utf8");
String path= req.getServletPath();
if("/movieList".equals(path)){
getMovieList(req,resp);
}
if("/movieAdd".equals(path)){
addMovie(req, resp);
}
}
protected void getMovieList(HttpServletRequest req,HttpServletResponse resp) throws IOException {
resp.setContentType("text/html;charset=utf8");
PrintWriter pw=resp.getWriter();
pw.println("getMovieList<hr />");
ResultSet rs=null;
PreparedStatement ps=null;
Connection conn=null;
try {
conn= JDBCUtile.getConnection();
String sql="select * from y_movie";
ps=conn.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()){
pw.println("m_name="+rs.getString("m_name"));
pw.println("m_director="+rs.getString("m_director"));
pw.println("m_up_year="+rs.getString("m_up_year"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtile.close(rs,conn,ps);
}
}
protected void addMovie(HttpServletRequest request,HttpServletResponse response) throws IOException {
request.setCharacterEncoding("UTF-8");
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
PrintWriter pw=response.getWriter();
pw.println("addMovie方法<hr />");
response.setContentType("text/html;charset=utf8");
try {
conn= JDBCUtile.getConnection();
String m_name=request.getParameter("m_name");
String m_director=request.getParameter("m_director");
String m_up_year=request.getParameter("m_up_year");
String sql="insert into y_movie (m_name,m_director,m_up_year) values(?,?,?)";
ps=conn.prepareStatement(sql);
ps.setString(1,m_name);
ps.setString(2,m_director);
ps.setString(3,m_up_year);
int count= ps.executeUpdate();
pw.println(count>0 ? "新增成功" : "新增失败");
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtile.close(rs,conn,ps);
}
}
}
20.Session
jakarta.servlet.http.HttpSession
用户打开浏览器,进行一系列操作,最终关闭浏览器,这整个过程称为一次会话,一次会话包含多次请求,这个会话在服务器端也有一个对象,这个java对象叫做session,session也称为会话域;
因为http协议是一种无状态协议,请求结束之后链接就断开了,这样可以减轻服务器压力;
session最重要的机制是保存会话状态;
servletRequest保存的数据只在一次请求中有效,生命周期太短;servletContext在服务器启动的时候创建,在服务器停止的时候销毁,生命周期太长;
//从服务器获取session对象,如果获取不到则新建
HttpSession session =req.getSession();
//从服务器获取session对象,如果获取不到则不新建,返回null
HttpSession session2 =req.getSession(false);
//向会话域当中存数据
session.setAttribute("user","root");
//向会话域中取数据
String user= (String) session.getAttribute("user");
//销毁session
session.invalidate();```
session对象是存储在服务器端的;
session对象的销毁方式:超时销毁、手动销毁
![在这里插入图片描述](https://img-blog.csdnimg.cn/a0f33bb0c46347a88df43c25dd9920cd.png#pic_center)
Session的实现原理:
session列表是一个map,map的key是sessionId,map的value是session对象,用户第一次请求,服务器生成session对象,同时生成sessionId,将sessionId发送给浏览器,浏览器将它保存在cookie当中,例如Cookie:JSESSIONID=407AABC32F163AF4FB43C05E8BFAFE7F用户再次请求的时候,浏览器将内存中的sessionId发送给服务器,服务器根据sessionId查找session对象,关闭浏览器,内存消失,cookie消失,会话结束;
如果cookie被禁用了,可以用url重写机制获取session
http://localhost:8080/Servlet04/test01;JSESSIONID=F8B261F0CEA6E1BA69F7B5B39282C694
url重写机制会提供开发成本,开发中在任何请求的后面都有带上sessionId,给开发带来了很大的难度;
### 21.Cookie
jakarta.servlet.http.Cookie
cookie最终是保存在浏览器的客户端上的;
cookie和session机制都是为了保存会话状态,cookie是将会话的状态保存在浏览器客户端,session是将会话保存在服务器端;
实际上cookie机制和session机制都是http协议的一部分,其他后端编程语言也有;
HTT协议规定:任何一个cookie都是由name和value组成的,name和value都是字符串,当浏览器发送请求的时候会自动携带该path下的cookie数据给服务器;
```java
Cookie cookie=new Cookie("movie","haiwang2");
//设置cookie的有效时间,在2小时后失效
cookie.setMaxAge(60*60*2);
response.addCookie(cookie);
如果cookie没有设置有效时间,则默认保存在浏览器内存当中,浏览器关闭则cookie消失,只要设置cookie的有效时间大于0,则cookie就会保存在硬盘当中;
设置cookie的有效期为0,表示该cookie被删除,主要用这种方式删除浏览器上的同名cookie;
设置cookie的有效期小于0,则表示cookie被存储在内存当中,不会被存储到硬盘当中,和不设置有效时间是一个效果;
如果没有设置setPath,那么cookie会与保存cookie的请求路径的父路径相关联;只要请求路径包含该父路径,则浏览器就会提交cookie给服务端;
//表示/Servlet04路径下的请求都会提交该cookie给服务器
cookie.setPath("/Servlet04");
//获取cookie
//如果没有cookie,getCookies返回的是null
Cookie[] cookies=request.getCookies();
if(cookies!=null){
for (Cookie c: cookies) {
pw.println(c.getName());
pw.println(c.getValue());
}
}
22.JSP9大内置对象
ServletContext(application)
request
response
pageContext(当前页面作用域)
page(this,当前的Servlet对象)
session
out(负责输出)
exception
config
23.过滤器Filter
jakarta.servlet.Filter
filter是过滤器,可以在Servlet目标程序之前添加代码,也可以在Servlet目标程序之后添加代码,之前之后都可以添加过滤规则,一般情况下,都是在过滤器中编写公共代码;
Filter接口的3个方法
- init():在filter对象第一次被创建之后调用,并且只调用一次
- doFilter():该方法用户发送一次请求执行一次,在该方法中编写过滤规则
- destroy():在filter对象被释放/销毁之前调用,并且只调用一次
实现过滤器的步骤:
- 编写一个类实现jakarta.servlet.Filter,并实现接口的所有方法
- 在web.xml编写Filter配置(或者过滤器加注解@WebFilter)
默认情况下,服务器会在启动时创建过滤器对象
Filter对象与Servlet对象都是单例(单实例)的
//web.xml
<filter>
<filter-name>myFilter</filter-name>
<filter-class>com.ywxk.servlet04.Filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<!--模糊匹配中的扩展匹配,不能以/开头-->
<url-pattern>*.do</url-pattern>
<!--前缀匹配-->
<url-pattern>/filter/*</url-pattern>
<!--匹配所有路径-->
<url-pattern>/*</url-pattern>
<!--精确匹配-->
<url-pattern>/myFilter2</url-pattern>
</filter-mapping>
//过滤器类
//@WebFilter("/*")
public class MyFilter implements Filter {
public MyFilter() {
System.out.println("Myfilter构造方法");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init方法执行");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//目标程序之前执行过滤程序
System.out.println("doFilter方法执行之前");
//执行下一个过滤器,如果下一个不是过滤器,则执行目标程序
chain.doFilter(request,response);
//目标程序之后执行过滤程序
System.out.println("目标程序执行之后");
}
@Override
public void destroy() {
System.out.println("destroy方法执行");
}
}
//目标路径
@WebServlet("/filter/a")
public class FilterTestA extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
System.out.println("FilterTestA的方法");
}
}
目标Servlet是否执行需要满足两个条件:
- 在过滤器当中是否编写了:chain.doFilter(request,response)代码;
- 用户发送的请求路径与Servlet一致;
chain.doFilter(request,response)的作用是:执行下一个过滤器,如果下面没有过滤器了,则执行最终的Servlet;
Filter的优先级比Servlet的优先级高
如果在web.xml配置多个过滤器,越靠上,优先级越高
使用注解@WebFilter的时候,多个filter执行顺序是按照类名的ASCII码的顺序执行的;
过滤器的执行顺序遵循栈数据结构;
过滤器里面有一个设计模式:责任链设计模式;
过滤器最大的优点是:在程序编译阶段不会确定调用顺序,因为Filter的调用顺序是配置到web.xml里面的,只要修改web.xml配置文件中的的顺序就可以跳转filter的执行顺序,显然filter的执行顺序是在程序运行阶段动态组合的,那么这种设计模式被称为责任链设计模式;
责任链设计模式的核心思想是:
在程序运行阶段,动态的组合程序的调用顺序;
所以为了实现责任链设计模式,建议将filter配置写在web.xml文件中,而不要使用注解方式
24.监听器Listener
在Servlet中,所有的监听器都是以Listener结尾的;
监听器其实是Servlet规范留给javaweb程序员的特殊时机;
实现一个监听器的步骤,以ServletContextListener为例:
- 编写一个类实现ServletContextListener接口,并实现接口的方法
@Override
//这个方法在ServletContext对象被创建的时候调用
public void contextInitialized(ServletContextEvent sce) {
//ServletContextListener.super.contextInitialized(sce);
}
@Override
//这个方法在ServletContext对象被销毁的时候调用
public void contextDestroyed(ServletContextEvent sce) {
//ServletContextListener.super.contextDestroyed(sce);
}
- 在web.xml进行配置,或者对实现类添加注解@WebListener
<listener>
<listener-class>com.ywxk.servlet04.listener.MyListener</listener-class>
</listener>
所有监听器中的方法都不需要javaweb程序员调用,当某个特殊事件发生后,由服务器来负责调用;
@WebListener
public class MyServletRequestListener implements ServletRequestListener{
@Override
public void requestDestroyed(ServletRequestEvent sre) {
// ServletRequestListener.super.requestDestroyed(sre);
System.out.println("request对象销毁时执行");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
//ServletRequestListener.super.requestInitialized(sre);
System.out.println("requst对象创建时执行");
}
}
@WebListener
public class MyHttpSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
//HttpSessionListener.super.sessionCreated(se);
System.out.println("session创建时执行");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
//HttpSessionListener.super.sessionDestroyed(se);
System.out.println("session销毁时执行");
}
}
@WebListener
//session域中的数据发生变化的时候触发该监听
public class MyHttpAttributeListener implements HttpSessionAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
//HttpSessionAttributeListener.super.attributeAdded(se);
System.out.println("向session内存数据的时候调用");
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
//HttpSessionAttributeListener.super.attributeRemoved(se);
System.out.println("删除session属性的时候调用");
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
//HttpSessionAttributeListener.super.attributeReplaced(se);
System.out.println("修改session属性的时候调用");
}
}
//实现该接口的类被绑定到session的时候触发该监听,不需要添加@WebListener
public class Movie1 implements HttpSessionBindingListener {
private String mName;
public Movie1() {
}
public Movie1(String mName) {
this.mName = mName;
}
public String getmName() {
return mName;
}
public void setmName(String mName) {
this.mName = mName;
}
@Override
public void valueBound(HttpSessionBindingEvent event) {
//HttpSessionBindingListener.super.valueBound(event);
System.out.println("movie1绑定数据");
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
//HttpSessionBindingListener.super.valueUnbound(event);
System.out.println("movie1解绑数据");
}
}
可以用HttpSessionBindingListener来实现统计登录用户数量,User类实现HttpSessionBindingListener接口,并实现接口的方法,当用户登录的时候,往session中添加user对象的时候可以触发valueBound方法,用户数据++,当用户退出的时候,session中移除user对象,触发valueUnbound方法,用户数据–;