J2EE 基础(Servlet篇)
一、前置基础:
1.谁是谁的基础:
java 基础 是 servlet 的基础 servlet 是 jsp 的基础,Java本身不适合web 开发,但是 servlet 可以做到,jsp 适合做页面
java –> servlet –> jsp
2. 常见的java服务器
JBOSS
WebLogic
Tomcat
3.Tomcat 的目录结构
1.bin:
主要负责 tomcat 的启动和停止(二进制核心文件)
2.conf
tomcat 的配置文件目录,奇珍有三个 xml 文件非常重要,分别是 server.xml 、 tomcat-users.xml 、 web.xml
server.xml:
主要用于配置和server 相关的信息,比如 tomcat 的启动端口以及 Host
web.xml
主要配置和 web 应用(web站点)相关的,比如我们规定hello.jsp 为网站的默认首页,我们就可以在这个页面里面加
<welcome-file-list>
<welcome-file>hello.jsp</welcome-file>
</welcome-file-list>
Tomcat-users.xml
配置用户名密码和用户的权限,包括默认页面的manager
3.lib
该目录放置 tomcat 运行所需的 jar 包
4.logs
日志目录
5.webapps
该目录下放置我们的web应用
6.work 目录
用于存放java 文件被访问后生成的 .class 和 servlet 文件
4.web应用目录规范
如下图所示:
classes 目录存放的是 class文件,lib 目录存放的是该项目由需要的jar 文件
5.Tomcat 虚拟目录
如果我们的 webapps 所在的磁盘已经空间不足,那么我们能不能实现将web 应用放在别的地方但是还让tomcat 来管理呢?当然是可以的,我们这个时候就要配置虚拟目录
我们只要在 server.xml 里面的<HOST>
中间添加一个 <Context>
节点,格式如下:
<Context docBase="E:\jspstudy\WWW" path=""></Context>
path 是待会要在url 中显示的那个目录,可以随意指定(视觉效果)
docBase 是绝对路径
当然除了这两个参数以外,还有两个参数需要了解一下
1.reloadable 这个参数设为true 以后 Tomcat 就会及时的发现文件的变化,然后更新,开销较大,建议只在开发时开启
2.upackWAR 这个参数设置为 true 以后我们通过tomcat 上传的 war 就能自动的解压,并放在webapps 目录下
注意:
HOST 标签可以有很多个,有一个host就有一个虚拟主机
6.配置本地主机名(非必须)
如果我们不想看到loclhost ,根据网页的请求原理,我们可以在本地的hosts 文件中配置主机名,配置好了以后可以在 server.xml 中 找到 HOST 标签修改 name 参数
补充:tomcat 解析资源的流程
tomcat 拿到请求以后会先解析主机(一个tomcat 能管理多个主机),然后会解析web 应用,因为一个主机可能会有多个web 应用,就如我们之前说的可以设置,如下
<Context docBase="E:\jspstudy\WWW" path=""></Context>
最后才是解析资源并获取资源
7.tomcat 的体系结构
如图:
在引擎(engine)中可以配置默认主机
8.tomcat 和 servlet 在网络中的位置
如图所示:
二、servlet 快速入门
1.开发 servlet 有三种方法
(1)实现 servlet 接口
(2)继承 GeneraicServlet
(3)继承 HttpSerlvet
1.通过实现 servlet 接口的方式实现
(1)步骤一
下面的代码实际上是实现了一个接口,就是把所有的方法都实现了
示例代码:
package com.test;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
class myServlet implements Servlet{
//用于初始化 servlet,就是把 servlet 装载到内存中
//该函数只会被调用一次
public void init(ServletConfig config)
throws ServletException{
}
//得到 ServletConfig 对象
public ServletConfig getServletConfig(){
return null;
}
//该函数是服务函数,我们的业务逻辑代码写在这里
//浏览器每请求一次就会被调用一次
public void service(ServletRequest req,
ServletResponse res)
throws ServletException,
java.io.IOException{
System.out.println("hello,world");
}
// 该函数得到 servlet 的配置信息
public java.lang.String getServletInfo(){
return null;
}
// 销毁 servlet 从内存中清除该servlet
public void destroy(){
}
}
(2)步骤二
根据 servlet 的规范我们还要在 WEB-INF/web.xml 中部署servlet
示例代码:
<web-app 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"
version="3.0"
metadata-complete="true">
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>com.test.myServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>/ABC</url-pattern>
</servlet-mapping>
</web-app>
解释:
<servlet>
标签下的子标签
1.servlet-name 自定义该servlet 的名字,默认使用该servlet 的文件名
2.servlet-class 指明该 servlet 是放在哪个包下面的,形式:com.test.myServlet
<servlet-mapping>
标签(servlet 的映射)下的子标签
1.servlet-name 和上面的保持一致
2.url-pattern 是将来访问这个 servlet的URL 的一部分,默认是这个servlet 的名字 的资源名(当做出一个请求以后,首先找这个对应的,然后根据名字找到servlet文件)
(3)步骤三
因为我的web 目录是在 testweb ,于是我们访问 localhost:8080/testweb/ABC 就能在控制台输出 hello,world
(4)步骤四
现在我们想在网页上看到返回信息,而不是在控制台,那我们只要在 service 这个函数中使用 res.getWriter().println()方法
示例代码:
//该函数是服务函数,我们的业务逻辑代码写在这里
//浏览器每请求一次就会被调用一次
public void service(ServletRequest req,
ServletResponse res)
throws ServletException,
java.io.IOException{
System.out.println("hello ,world");
res.getWriter().println("hello ,world");
}
补充:
如果用 javac 去编译一个 java 文件,需要带上.(点号)
javac -d . 文件路径
2.servlet 的生命周期
1.当 servlet 第一次被调用的时候,会触发 init 函数,然后将servlet实例加载到内存,这个函数只会被调用一次
2.当第一次以后(包括第一次)就会去调用 service 函数,服务器已经将我们的请求变成一个对象传递给这个函数,并将返回结果以一个对象打包(当然还会对其进行解析,将必要的结果返回给浏览器)
3.当站点 reload 或者 服务器 restart 的时候 调用 destory 函数,销毁
2.通过继承 GeneraicServlet 来实现
这个是因为觉得 servlet 实现五个方法比较烦,于是把其他四个不是很重要的方法隐藏了,就留了一个service
3.通过继承 HttpServlet 来实现
myHttpServlet.java
package com.test;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class myHttpServlet extends HttpServlet{
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException,
java.io.IOException{//响应 get 方式的提交
resp.getWriter().println("调用 doGet 方法");
}
protected void doPost(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException,
java.io.IOException{//响应 post 方式的提交
resp.getWriter().println("调用 doPost 方法");
}
}
web.xml
<servlet>
<servlet-name>myHttpServlet</servlet-name>
<servlet-class>com.test.myHttpServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myHttpServlet</servlet-name>
<url-pattern>/myHttpServlet</url-pattern>
</servlet-mapping>
3.servlet 的一些细节
- servlet-mapping 和 servlet-name 并不是一一映射的关系,可以多个url 映射到一个 servlet 上
- url-parttern 映射可以有多层,并且可以是文件名,比如说 hello.html ,但是不要真的以为访问的是一个html
servlet 映射到 URL 也可以使用通配符,通配符只能有两种格式,一种就是:.扩展名 另一种是 `/xxx/`,这种通配可以起到屏蔽信息,或者统一报错回复上
注意:在匹配的时候参考的标准
(1)如果同时匹配到多个,谁长得最像就是访问谁
(2)*.do 这种形式的优先级最低servlet 是一个供 servlet 引擎(web服务器)调用的java 类,他不能独立运行,他的运行完全是由servlet 引擎控制和调度的
servlet 在被第一次调用后就加载到内存,然后内存中的 srvlert 就会对各个请求进行服务,不会重新创建,这也就是说 servlet 是单例,可能会出现线程安全问题(类变量),于是应该加同步机制 synchromized(this){}
<load-on-stratup>
配合线程 解决网站启动时初始化或者定时完成任务的问题
我们在 web.xml 的servlet 标签中使用
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>com.test2.myServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
这里的数字代表该servlet 的启动顺序
我们在 servlet 文件中 写上
@Override
public void init() throws ServletException {
super.init();
System.out.println("init 函数被调用 ");
}
在启动服务的时候就会被调用,而不用特地的去访问这个servlet ,这就能完成一些类似数据库之类的东西的初始化操作
3.servletConfig 对象
在 servlet 的配置文件中可以使用 <init-param>
标签为 servlet 配置一些初始化的参数
我们在 web.xml 这样写
<servlet>
<servlet-name>ServletConfigTest</servlet-name>
<servlet-class>com.test2.ServletConfigTest</servlet-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</servlet>
servlet 文件中就能用下面的语句去加载这个配置,这样就不是写死的了
response.getCharacterEncoding(this.getServletConfig().getInitParameter("encoding"));
当然上面的配置只是针对某一个 servlet 使用,如果想对多个 servlet 都应用的话我们可以
<context-param>
<param-name></param-name>
<param-value></param-value>
</context-param>
三、HttpServletResponse 详解
web 服务器收到客户端的 http 请求以后,会针对每一次请求创建一个代表请求的request 对象 和 代表相应的 response 对象,因此我们要获取客户机提交上来的数据只要找request 对象就可以了,如果我们想要向客户机输出数据只要找 response 对象就可以了
1.getWriter() 和 getOutputStream() 的区别
1.getWriter() 得到的是 PrintWriter 对象,用于向客户机会送字符数据
2.getOutputStream() 得到的是 OutputStream对象,用于向客户机会送字符数据或者二进制数据
注意:
这两个流不能同时使用,因为一旦你在一个流中做出了返回,服务器就会去检测和这个 response 相关的流有没有关闭,如果没有关,则强制关闭,于是下一次再去接收相同的信息是接收不到的
2.SendRedirect() 重定向
实际上这个重定向也是一个 get 请求,也就是说我们能通过? 利用 get方式传参
LoginCLServlet.java
if("admin".equals(name) && "123456".equals(pass)){
response.sendRedirect("/UserManager/MainFrameServlet?uname="+name);
}else{
response.sendRedirect("/UserLogin/LoginServlet");
}
MainFrameServlet.java
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
String name = request.getParameter("uname");
out.println("欢迎"+name+"进入管理界面</br>");
out.println("<a href='/UserLogin/LoginServlet'>返回重新登陆</a>");
}
当然这种方法只能传递字符串,不能传递对象,如果想传递对象可以用 session 的方式
3.session 传参
LoginCLServlet.java
if("admin".equals(name) && "123456".equals(pass)){
request.getSession().setAttribute("loginpass",pass);
response.sendRedirect("/UserManager/MainFrameServlet?uname="+name);
}else{
response.sendRedirect("/UserLogin/LoginServlet");
}
MainFrameServlet.java
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
String name = request.getParameter("uname");
String pass = (String) request.getSession().getAttribute("loginpass");
out.println("欢迎"+name+"进入管理界面,你的密码是:"+ pass + "</br>");
out.println("<a href='/UserLogin/LoginServlet'>返回重新登陆</a>");
}
四、j2ee 中文乱码详解
1.乱码在何处发生
只要是在有数据传递的地方就会出现中文乱码
(1)form 表单(get/post)
(2)URL传参
2.为什么会有乱码
我们的UTF-8 的请求首先是服务器接收,服务器是外国人编写的,默认编码一般为 ISO-8859-1,两者编码不一致就会出现乱码
3.对于 POST 请求
可以使用下面的方式修改请求体中的编码
request.setCharacterEncoding("utf-8");
但是这个设置对通过 get 方式在请行中传递的参数是不起作用的
4.对于GET请求
String u = new String(request.getParameter("username").getBytes("iso-8859-1"),"UTF-8");
5.对于超链接
由于也是通过 get 方式传参,因此和get 的处理方式一样
6.对于sendRedirect()
相当于返回到浏览器重新发起了 get 请求也是通过 get 请求的方式处理
五、HttpServletRequest 详解
1.常见函数
(1)getRequestURL()
(2)getRequestURI()
(3)getQueryString()
(4)getRemoteAddr()
(5)getRemoteHost()
(6)getRemotePort()
(7)getLocalPort()
(8)getLocalAddr()
(9)getLocalName()
(10)getHeader()
(11)getHeaderNames()
(12)getParameter()
(13)getParameterValues()
(14)getParameterNames()
2.常见应用
1.实现请求转发
请求转发: 一个web 资源受到服务器的请求以后,通知服务器去调用另一个web 资源进行处理的过程
我们可以通过他实现我们之前实现过得跳转并传递参数的功能
request.getRequestDispatcher("转向的地址").forward(request,response);
上面的代码实际上是使用了转向的方法将 request 和 response 对象转发到了下一个地址
上面的代码实际上是使用了转向的方法将 request 和 response 对象转发到了下一个地址
这实际上就是我们后面实现 MVC 的基础,Model 通过转向给Controller传递数据,同样Controller 也是通过转向将我们的model 传递过来的数据给view
其中 MVC 中的每一个部分都是用 servlet 实现的
这句话和 sendRedirect最本质的区别就是,他的转向不是打回给浏览器重新发起请求,而是打回给服务器