servlet
-
1.两种软件架构方式 c/s和b/s的区别
-
Tomcat服务器软件
-
Servlet
-
Servlet应用
-
状态管理Cookie,Session
-
Filter过滤器,实现自定义过滤规则
1.服务器
1.1 web(world wide web)称为万维网,即网站
作用:表示internert主机上供外界访问的资源
1.2 internet上供外界访问的资源分为两类:
静态资源:web页面中浏览的数据不变(html,css)
动态资源:浏览的数据有程序产生,
不同时间点,不同设备看到的内容不同(jsp,servlet)
1.3在java中,动态web资源开发技术称为javaweb
1.4 web服务器(运行及发布web的容器)
只有将开发的web项目放置到该容器中。才能使网络中
的所有用户通过浏览器进行访问
1.5 Tomcat服务器
特点:免费开源,支持servlet和jsp规范
2.Servlet(重点)
1.servlet: servlet Applet的简称,是服务器端的程序(代码,功能实现),
作用:
- 接受客户端请求,完成操作
- 动态生成网页(页面数据可变)
- 将包含操作结果的动态网页响应给客户端(浏览器)
2.部署web项目:
热部署:更改内容,想获取最新内容,重启tomcat即可
deployment部署
Artifacts项目部署形式
- 在tomcat的webapp下创建与项目同名的文件夹
- 将idea里编写好的WEB-INF放到该文件夹下
- 在WEB-INF下创建classess文件夹
- 将idea里build后out下的项目的.class和路径复制到classes中
- 打开bat文件,浏览器输入http://localhost:80/项目名称/实体类名
注意:再次编写新的servlet(或重新编译),
都需要手工将新的.class文件部署到tomcat中较为麻烦
👿 :如何实现自动部署?
3.HTTP协议
1.超文本?
页面,图片,音频等
2.什么是HTTP?
超文本传输协议:HyperText Transfer Protocol
是互联网上应用最广泛的一种协议
它基于请求和响应模式,无状态,应用层的协议
运用于TCP协议(三次握手,四次挥手)之上
3.HTTP特点:
-
支持客户端(浏览器)/服务器模式
-
简单快速:客户端只向服务器发送请求方法和路径,服务器即可响应数据,
因而通信速度很快,请求方法常用的有GET/POST
- 灵活:Http允许传输任意类型的数据,传输的数据类型由content-type标识
- 无连接: 每次TCP连接只处理一个或多个请求,服务器处理完客户的请求后,断开连接
- 无状态:对于事务处理没有记忆能力(http不知道客户端和服务器端发生了什么)
4.Servlet详解(重点)
🖍实现Servlet接口与继承GenericServlet类的实现类均与Http协议无关
⏰ 而继承HttpServlet的实现类与协议有关
1.编写Servlet类需要:
-
直接或间接的实现Servlet接口
-
继承该接口的类 通过继承GenericServlet或 HttpServlet即可
-
其中继承GenericServlet抽象类时,只需重写service方法,其他已在该方法里被重写
-
HttpServlet是继承自GenericServlet的基础上的扩展
不再由service提供单一的服务
而是根据不同请求方式的不同拆,调用不同do的方法处理请求,这些do方法与http协议有关
⛹doPost方法里调用doGet()方法是使得保证了两种请求方式处理的是同一种结果
Servlet接口中包含5个方法:
init(ServletConfig config);
destroy();
ServletConfig getServletConfig();
String getServletInfo();
service(ServletRequest req,ServletResponse res);
public abstract class GenericServlet implements Servlet,ServletCofig,Serializable{}
public abstract class HttpServlet extends GenericServlet {}
⏰ (abstract)抽象方法
- 必需被子类重写
- 不能被单独new
⏰ protected修饰的方法:
-
同一个类中可访问
-
同一个包中子类无关类可访问
-
不同包中的子类可访问
servlet配置方式:
💥注解和xml同时配置不冲突,建议使用注解
servlet2.5之前使用web.xml配置
servlet3.0后支持注解配置
web.xml文件元素
- url-patten 通配符匹配并不会影响精确匹配
- /*通配符会拦截到除精确匹配的通配符
- load-on-startup 启动的优先级
-
正整数或0:容器在应用启动时加载并初始化servlet
值越小,servlet优先级越高,值相同,容器自己选择顺序来加载
-
负数或没有设置:被请求时再加载
5.Servlet应用(重点)
5.1 Request对象
服务端与客户端打交道时,客户端发送到服务端的请求数据和请求内容都包含在request对象里
⏰ 用户通过页面的提交访问到servlet
客户端发送get请求到servelet后,调用doGet方法,
doGet方法通过request对象的getParameter方法
获得请求发过来的数据
Tomcat7之前get中文乱码,
客户端以UTF-8的编码传输到服务器,而服务器的request对象
使用的是ISO8859-1这个字符编码来接收数据
而tomcat8中的get不会乱码,因为服务器对url的编码可以进行自动转换
tomcat 没有解决post中文乱码
此时需要对request请求对象设置统一的编码:
借助ServletRequest接口继承而来的
setCharacterEncoding(“UTF-8”);
6.实验(Servlet+Jdbc)
概要:
- database.properties
- utils
- entity
- dao数据访问层
- service业务逻辑层
- controller和jsp
-
创建admin数据库并添加数据
-
需要jar包:存放在web-INF下创建的lib目录下
servlet-api.jar(2.5版本之后)
commons-dbutils-1.7-jar
druid-1.1.5 jar
mysql-connector-java-5.1.25-bin.jar
3.在src下选择Resource Bundle创建以.properties结尾的database文件
在它下面写数据库连接对象配置,以及druid连接池配置
⭕️记住对这个项目做配置fileEnncoding设置为utf8
4.在src下的项目包下创建一个utils包用于存放DbUtils
DbUtils的功能:
- 创建连接池
- 获取连接
- 事务的控制
- 释放资源
5.在src下的项目包下创建一个entity包存放实体类
该类用于完成数据库对应表的映射
6.在src下的项目包下创建dao包,再在dao包下创建impl包
dao数据访问对象层
⏰创建一个接口XxxDao,用于规范实现类的方法(增删查改)
在impl包里实现接口中方法(重写)
7.在src下的项目包下创建service包 sevice给用户的业务功能,
再在service下创建impl包
⏰创建一个接口XxxService
在impl包里实现接口中方法(重写)
8.在src下的项目包下创建serlet包用于存放具体功能的servlet类,配置用注解,后期学习把servlet的包分为了controller控制业务逻辑和jsp页面显示
9.根据业务逻辑 决定在web下是否创建html文件
7-1.转发(服务器端行为)
转发时地址栏没发生变化
重定向时地址栏发生了变化
即重定向客户端做了两次请求
1.现有问题:
在之前案例中,调用业务逻辑和显示结果页面都在同一个servlet里,应该将业务逻辑和显示结果分离
XxxServlet extends HttpServlet{
//调用业务逻辑
//显示结果页面
}
//将业务逻辑与显示结果分离
XxxController extends HttpServlet{
//调用业务逻辑
}
XxxJsp extends HttpServlet{
//显示结果页面
}
2.业务,显示分离后,面临的问题:
1.Controller如何跳转到Jsp
解决:转发
转发的作用在服务器端
将请求发送给服务器上的其他资源,以共同完成一次请求的处理
代码:request.getRequestDispatcher().forward(request,response)
使用forward跳转时,是在服务器内部跳转,
地址栏不发生变化,属于同一次请求
2.如何将业务逻辑得到的数据传递给显示结果的jsp
❎ forward表示一次请求,是在服务器内部跳转,可以共享
同一次request作用域中的数据
request作用域:拥有存储数据的空间,作用范围是一次请求有效(一次请求可以经过多次转发)
存数据时
以键值对形式存储在request作用域中
key为String类型,
value为Object类型
取数据时
通过String类型的key访问Object类型的value
7-2.重定向(客户端行为)
1.sendRedirect跳转时,地址栏改变,代表客户端重新发送的请求,属于两次请求
客户端发送请求1到服务器1,服务器1响应给客户端
客户端发送请求2到服务器2,服务器2响应给客户端
服务器
response.sendRedirect(“目标uri”)
URI统一资源标识符:uniform resource identifier
用来表示服务器中定位一个资源,
资源在web项目中的路径:project/source
resp.sendRedirect(“项目/b?username=tom”)
- response没有作用域,两次request请求中的数据无法共享
- 传递数据(字符串类型):通过url的拼接进行数据传递 ?后面为拼接的数据
- 获取数据:request.getParameter(“username”)
总结:
当两个Servlet需要传递数据时,选择forward转发,
不建议使用sendRedirect进行传递
8.Servlet生命周期
实例化-初始化-服务-销毁
初始化:当用户第一次访问servlet时,由容器调用servlet的构造器创建具体的servlet对象
也可以在容器启动之前l立刻创建实例
注意:可以设置servlet是否在服务器启动时就创建
正数, 启动tomcat时,创建具体的servlet对象
负数或不写,被请求访问时才去创建
9.Servlet特性线程安全问题
servlet在访问后,tomcat容器会调用servlet构造器,执行实例化对象,创建一个servlet对象
而tomcat允许同时多个线程并发访问同一个servlet(临界资源)
如果在方法中对成员变量做修改操作,会导致数据不一致,就会有线程安全问题
如何保证线程安全?
尽可能使用局部变量
synchronized:将存在线程安全的代码放在同步代码块中
把原子操作(接受参数,调用业务逻辑,客户端响应)上锁
缺点==:锁的等待释放需要时间,导致多线程并发访问时服务器的效率低==,同一时间只能有一个线程执行操作
实现SingleThreadModel接口:
这样,每个线程都会创建servlet实例,这样客户端请求就不会存在共享资源的问题,
但是servlet响应客户端请求的效率太低,且浪费资源所以被淘汰
10.状态管理
1.概念:将浏览器与web服务器之间多次交互当做一个整体来处理
并且将多次交互涉及的数据(状态)保存下来
2.现有问题:
- http协议是无状态的不能保存每次提交的信息
3.状态管理分类:
-
客户端状态管理技术:将状态保存在客户端,代表性是Cookie技术
-
服务器状态管理技术:将状态保存在服务器端,代表性是session技术(服务器传递sessionID时需要使用cookie的方式)和application
服务端可以响应给客户端多个cookie
只要保证cookie的名和路径一致即可修改cookie
10-1.Cookie的使用
客户端通过服务端拿到了一个cookie,
二次请求的时候会携带着cookie做访问
1.服务器响应给浏览器的数据,在浏览器访问web服务器的某个资源时得到的(一来一回,即一次请求,一次响应)
- 一旦web浏览器保存了某个Cookie,那么它每次访问该web浏览器时,将cookie回传给Web服务器
一个cookie主要有标识该信息的名称(name)和值(value)组成
1.//创建cookie
Cookie ck=new Cookie(name,vlaue);
//设置cookie的路径(哪些资源可以共享该cookie)
ck.setPath("/webs")
//设置cookie生命周期(>0有效期为秒,=0浏览器关闭,<0内存存储,默认-1)
ck.setMaxAge(-1)
//添加到response对象中,响应时发送给客户端
res.addCookie(ck)
--------------------------------------------------------
//获取cookie 服务端可以响应给客户端多个cookie
Cookie[] cookies = req.getCookies();
//通过循环遍历Cookie
//如果客户端没有cookie,获取cookie的话就会有空指针异常,需要加一条判断语句
if (cookies != null) {
for (Cookie cookie : cookies) {
System.out.println(cookie.getName() + ":" + cookie.getValue());
}
}
3.//修改cookie
如果改变cookie的name和有效路径会新建cookie,有效期会覆盖
原有的cookie
//浏览器端报500,服务器端出现异常或错误
4.//cookie的编码与解码
Cookie cookie=new Cookie(
URLEncoder.encode("姓名","UTF-8"),
URLEncoder.encode("郝飞宇","UTF-8")
);
System.out.println(
URLDecoder.decode(
cookie.getName(),"UTF-8")
+ ":" +
URLDecoder.decode( cookie.getValue(),"utf-8") );
10-2.Session
1.概述:
session用于记录用户的状态
session指在一段时间内,单个客户端与web服务器的一连串相关的交互(多次的请求与访问)过程
在一个session中,客户可能会多次请求访问同一个资源
或不同的服务器资源
2.原理:
- 服务器为每一次会话分配一个Session对象
- 同一个浏览器发起的多次请求,同属于一次会话(session)
- 首次使用到session时,服务器会自动创建session,并创建cookie存储sessionid发送回客户端
3.session使用:
session作用域:拥有存储数据的空间,作用范围是一次会话有效
而request作用域:也拥有存储数据的空间,但作用范围是一次请求有效
特点:
- 一次会话是使用同一浏览器发送的多次请求,一旦浏览器关闭则结束会话
- 可以将数据存入session中,在一次会话的任意位置进行获取
- 可传递任意数据(基本数据类型,对象,集合,数组)
//获取session对象
//首次使用session时,第一次浏览器的客户端发送了请求过来,服务器就会自动创建session
HttpSession session=request.getSession();
//session保存数据
session.setAttribute("name","Spring");
//session获取数据
//同一个会话中可以共用一个session
HttpSession session = req.getSession();
String name = (String) session.getAttribute("name");
System.out.println("从session中获得了:"+name);
//session移除数据
HttpSession session = request.getSession();
session.removeAttribute("name");
第一次访问没有session,第一次触发了创建,请求头给了一个以cookie形式存储的sessionid 第二次访问时,由于已经创建了且属于同一次会话,就不再新建 然后把这个sessionid回传给了客户端,告诉他与上一次的请求属于同一次会话
4.session的生命周期:
开始:
第一次使用到session的请求产生,则创建session
结束:
浏览器关闭,session超时,手工销毁
5.浏览器禁用cookie的解决方案
url重写:(重写url,追加sessionid)
String newURL=response.encodeRedirectURL(String url)
//生成重写的url
response.sendRedirect(newurl)
1.实验servlet完成登录功能
实验流程:
1.login.html里
<form action="/Ser_war_exploded/login" method="post"> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="登录"> </form> 1.由于客户端发送给servlet的请求有两种:get/post get请求: 将提交的数据放在url之后, 以?连接url和传输的数据,参数之间以&相连, 它以明文传递,数据量小,不安全 post请求: 把提交的数据放在Http包的Body中, 密文传递数据,数据量大,安全 所以提交数据的方法选择post
2.值为login的servlet里:
//1.收参之前对字符集做设置, 此处由于请求为post请求 而tomcat没有解决post乱码的方法, 需要借助ServletRequest接口继承而来的 setCharacterEncoding()方法来处理编码方式 /*客户端与服务器发送请求时,请求数据和请求内容都包含在request对象里 所以对象为req */ //注意:如果没有请求,就不需要对请求编码 req.setCharacterEncoding("utf-8"); //同时设置服务端的编码格式 //和客户端响应的文件类型及响应时的编码格式 //此处客户端响应的文件类型为html response.setContentType("text/html;charset=uft-8) //1.收参 客户端发送get请求到servelet后,调用doGet方法, doGet方法通过request对象的getParameter方法 获得请求发过来的数据(请求携带过来的数据) //getParameter()的返回值为String类型 String username=req.getParameter("username") ; String password=req.getParameter("password"); //2.调用业务逻辑 将用户输入的username和password传给login方法 AdminService adminservice=new AdminServiceImpl() Admin admin=adminService.login(username,password) //3.处理结果 用户存在,或用户名或密码错误 存在,则响应给客户端一个页面,显示成功 "怎样响应给客户端一个页面?" 通过response的getWriter方法获得一个流 PrintWriter printWriter=resp.getWriter(); if(admin!=null){ printWriter.println("<html>"); printWriter.println("<head>"); printWriter.println("<meta charset='utf-8'>"); printWriter.println("</head>") printWriter.println("<body>"); printWriter.println("<h1>登录成功!</h1>"); printWriter.println("</body>"); printWriter.println("</html>"); }else{ .... }
2.展示所有用户功能:
实验流程:
1.AdminDapImpl里:
//创建Apache的queryRunner private QueryRunner queryRunner=new QueryRunner() //想要实现事务控制,就要在queryRunner执行方法的时候再去传连接 public List<Admin> selectAll(){ String sql="select * from admin"; try{ BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。 List<admin> admins =queryRunner.query(DbUtils.getConnection,sql, new BeanListHandler<Admin>(Admin.class)) return admins; }catch(SQLException e){ e.printStackTrace(); } return null; }
2.AdminServiceImpl完成业务层逻辑:
//1.创建AdminDao接口的实现类对象,方便后期调用selectall方法 private AdminDao admindao=new AdminDaoImpl(); public List<Admin> showAllAdmin(){ //空指针异常的原因:1、没有对new出来的对象进行实例化,或者去数据库里查询一个空的对象;2、对象为null的情况下去调用该对象所拥有的方法或者成员变量造成的。 List<admin> admin=null;//防止空指针异常 try{ //事务前要开启事务 //因为事务出现错误时无法继续执行下去,使用try..catch能够保证程序继续执行下去 DbUtils.begin()//自己定义的工具类 List<Admin> admins=admindao.selectAll(); //查到相关内容提交事务 DbUtils.commit() }catch(Exception e){ DbUtils.rollback(); e.printStackTrace(); } return admins; }
3.showAllAdminServlet
resp.setContentType("text/html;charset=utf-8") //调用业务逻辑 AdminServerice ads=new AdminserviceImpl(); List<Admin> adminlist=ads.showAll();//showall方法里调用了dao里的selectall "怎样响应给客户端一个页面?" 通过response的getWriter方法获得一个流 PrintWriter pt=resp.getWriter(); //如果结果不为空 if(adminlist!=null){ pt.println() ..... pt.println("<table>") pt.println("<tr>") pt.println("<td>username</td>") .... for(Admin admin:adminlist ){ pt.println("<tr>") pt.println("<td>"+admin.getusernmae()+"</td>") .... } pt.println("</html>") }else{ pt.println("<html>") .. pt.println("<meta> charset=utf-8") .. pt.println("<h1>用户数据为空</h1>") ... } }
4.showAllAdmin分解1:showAllAdminController
功能:只负责调用业务逻辑功能
//调用业务逻辑: AdminService adminservice=new AdminserviceImpl(); list<Admin> adminlist=adminservice.showall(); //request存储数据: request.getAttribute("admins",adminlist); request作用域:拥有存储数据的空间, 作用范围是一次请求有效(一次请求可以经过多次转发) 1. 若将数据存入request作用域中,可以在一次请求的任何位置获取 2.可传递任何数据(基本数据类型,对象,数组,集合等) 3.存数据:以键值对形式存储在request作用域中 key为String类型,value为Object类型 request.setAttribute(key,value) 4.取数据:通过string类型的key,访问Object类型的value //通过转发,跳转到显示结果的servlet "表面访问的是controller但实际上访问的是jsp" "客户端发送请求到c,c又转发给j,j响应给客户端" req.getRequestDispacher("/jsp").forward(req.resp) //通过forward将请求和响应都转发过去 转发的作用在服务器端,将请求发送给服务器上的其他资源,以共同完成一次请求 forward表示一次请求,它跳转时,是在服务器内部跳转,地址栏不发生变化属于同一次请求,所以可以共享同一次request作用域中的数据
5.showAllAdmin分解2:showAllAdminJsp
//同时设置服务端的编码格式 //和客户端响应的文件类型及响应时的编码格式 resp.setContentType("text/html;charset=utf-8") //接受request作用域中的数据: //注意:接受结果的时候需要类型转换 List<Admin> adminlist= (Admin)req.getAttribute("admins") // "怎样响应给客户端一个页面?" 通过response的getWriter方法获得一个流 PrintWriter pt= res.getWriter() .....
3.实验 session实战权限验证:
session在本次实验中的作用:
- 权限验证
- 在一次会话的任意位置获取session里的管理员信息
实验流程:
4.Session实战保存验证码
因为在登录页面的验证码显示时发送了一次请求,得到了一次响应
当我们登录的时候,我们发送了一次新的请求用来收参和做验证码的匹配
所以将验证码保存在session里(多次请求)
实验流程:
1.创建验证码:
导入ValidateCode.jar
在controller下创建生成验证码的servlet
为什么要用servlet生成呢?
客户端在访问login.html时来获得验证码
11.ServletContext对象(重点)
1.ServletContext(共享的存储区域)概述:
ServletContext对象是全局对象,也拥有作用域,
对应一个tomcat中的Web应用
2.获取ServletContext对象:
我们现在写的servlet都是继承了httpServlet
httpServlet又继承了GenericServlet
GenericServlet提供了getServletContext()方法,
通过this.getServletContext()获取
HttpServletRequest提供了getServletContext()
通过request对象获取
HttpSession提供了getServletContext()方法
通过session对象获取
绝对路径,从根目录为起点到某一个目录的路径;
相对路径,从一个目录为起点到另外一个的目录的路径。
3.ServletContext的作用:
servletcontext的getRealPath()
获取当前项目在服务器发布的真实路径
servletContext的getContextPath()
或request的getContextPath()
获取当前项目上下文路径(应用程序名称)
全局容器:
servletContext拥有作用域,可以存储数据到全局容器中
1.存储数据:sc.setAttribute(“name”,value);
2.获取数据:
3.移除数据
setvlet特点:
唯一性:一个应用对应一个servletcontext
生命周期;
web服务器启动时创建,服务器关闭时销毁
servlet应用场景:
统计当前项目访问次数
12.作用域总结:
HttpServletRequest:一次请求,请求响应之前有效
HttpSession:一次会话开始,浏览器不关闭或不超时之前有效
ServletContext:服务器启动开始,服务器停止之前有效
13.过滤器:
1.概念:
客户端与服务器目标资源之间的一道过滤技术
2.过滤器作用:
执行地位在servlet之前,
客户端发送请求时,会先经过filter,再到达目标Servlet中
响应时,反向执行filter
3.编写过滤器:
- 编写java类实现filter接口
- 在doFilter方法中编写拦截逻辑
- 在webservlet注解里设置拦截资源路径
4.过滤器配置:
注解配置
xml配置:
⏰:在filter-mapping标签下的url-pattern里
设置的是拦截的资源路径,而不是访问的资源路径
5.拦截路径:
- 精准拦截匹配
- 后缀拦截匹配
- 通配符拦截匹配:/* 表示拦截所有
6.过滤器优先级:
在一个web应用中,编写的多个Filter组合起来称为一个Filter链,
它的优先级为:
- 如果全为注解的话,按照类全名称的字符串顺序决定作用顺序
- 如果是web.xml按照filter-mapping注册顺序。从上到下
- web.xml配置高于注解方式
- 如果注解和web.xml同时配置,会创建多个过滤器对象,造成过滤多次
13-1:过滤器典型应用之权限验证:
HttpServletRequest和ServletRequest都是接口 HttpServletRequest继承自ServletRequest