javaEE的三层架构:web层、Service层、Dao层。
-
web层
JSP:获取用户信息,展示数据(利用重定向、请求转发),使用EL表达式展示页面 -
Service层
具体的业务层(用Servlet)
调用Dao层 -
Dao层(Data Access Object:数据访问对象)
查询数据库
Session:一个闭环 Session于Session之间没有关系
会话管理
程序中的会话:打开浏览器—》访问特定的网站(访问服务器)–》关闭浏览器
将浏览器和服务器之间产生的数据就称之为会话。
URL重写
URL重写是一种会话跟踪技术,它将一个或者多个token添加到URL的查询字符串汇总,每个token通常为 key=value 形式,如:url?key-1=value-1&key-2=value-2&key-3=value-3...&key-n=value-n
URL在某些浏览器上的最大长度为2000字符,因此URL重写仅适合在信息较少的页面间传递。
package com.sweet.github.servlet.urlrepeat;
/**
* Author:sweet
* Created:2019/5/25
*
* 省市区三级联动,利用URL重写查询
*/
import javax. servlet. ServletException;
import javax. servlet. http. HttpServlet;
import javax. servlet. http. HttpServletRequest;
import javax. servlet. http. HttpServletResponse;
import java. io. IOException;
import java. io. PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java. util . List;
import java. util . Map;
public class TokenServlet extends HttpServlet {
private Map<String, List<String>> cityMap = new HashMap<>();
private Map<String, List<String>> countryMap = new HashMap<>();
@Override
public void init() throws ServletException {
List<String> shannxi = new ArrayList<>();
shannxi . add("西安市");
shannxi . add("宝鸡市");
shannxi . add("铜川市");
shannxi . add("咸阳市");
cityMap. put("陕西省", shannxi );
List<String> xian = new ArrayList<>();
xian. add("临潼区");
xian. add("灞桥区");
xian. add("长安区");
countryMap. put("西安市", xian);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
resp. setContentType("text/html; charset=UTF-8");
PrintWriter writer = resp. getWriter();
String pro = req. getParameter("pro");
String city = req. getParameter("city");
if (pro == null && city == null ) {
writer. write("没有数据");
} else {
if (pro == null ) {
writer. write("pro 参数不能为空");
} else {
if (city == null ) {
List<String> cityList = cityMap. get(pro);
StringBuilder sb = new StringBuilder();
for (String c : cityList) {
sb. append("<a href='/token")
. append("?")
. append("pro="). append(pro)
. append("&")
. append("city="). append(c)
. append(" '>")
. append(pro)
. append(",")
. append(c)
. append("</a>")
. append("</br>");
}
writer. write(sb. toString());
} else {
List<String> cityList = cityMap. get(pro);
if (cityList. contains(city)) {
StringBuilder sb = new StringBuilder();
for (String country : countryMap. get(city)) {
sb. append("<a href='/token")
. append("?")
. append("pro="). append(pro)
. append("&")
. append("city="). append(city)
. append(" '>")
. append(pro)
. append(",")
. append(city)
. append(",")
. append(country)
. append("</a>")
. append("</br>");
}
writer. write(sb. toString());
} else {
writer. write(city + " 不属于 " + pro);
}
}
}
}
}
}
隐藏域
利用隐藏域保持状态类似于URL重写,但是不把值附加到URL上,因此没有长度限制而是反映到HTML表单的隐藏域中,当表单提交时,隐藏域的值也提交到服务器端。
隐藏域,需要通过表单提交时的hidden属性:
<!-- 表单隐藏域使用hidden属性 -->
<input type="text" hidden="hidden">
浏览器端会话技术:Cookie
cookie登录应用场景:
1.打开浏览器—》访问网站—》填写用户登录数据(会话数据)—》校验成功—》首页显示当前用户信息-----》关闭浏览器;当再次打开浏览器,访问同一个网站就会直接显示用户信息,这就是用到了cookie会话技术。
cookie浏览应用场景:
2.访问商品列表—》点击某一个商品—》浏览器关闭—》下一次进来----》商品列表—》记录之前访问过的商品数据(图片)
cookie的使用:
- 服务器端创建Cookie,将Cookie数据携带给浏览器
public Cookie(String name,String value)
- 通过浏览器端将数据存储在缓冲区(请求头中cookie:key=value)
public void addCookie(Cookie cookie):
将cookie写回浏览器,等待下次访问时,将指定cookie添加到响应。多次调用此方法设置一个以上的cookie public Cookie[] getCookies();
浏览器再次访问的时候,服务端就可以获取到cookie数组
public class HelloCookieServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.设置编码
resp.setContentType("text/html;charset=utf-8");
//2.cookie由服务器创建,并携带给浏览器
Cookie cookie=new Cookie("akey","avalue");
//3.由服务器写回到浏览器
resp.addCookie(cookie);
resp.getWriter().write("cookie已写回");
//4.获取浏览器中存储的cookie数据
Cookie[] cookies= req.getCookies();
if(cookies!=null){
for(Cookie cookie1:cookies){
System.out.println(cookie1);
}
}
}
}
删除Cookies:
cookie存活时间为0,就是删除cookie:rmCookie. setMaxAge(0);
注: cookie不适合存储私有数据!!
- cookie内容只能存储String类的数据
- 浏览器端存储cookie的个数有限制:300个,每一个站点可以存储20多个cookie,每一个cookie大小不超过4kb
- 有效期问题:浏览器关闭,cookie就会消失(可以设置最大生存时间(秒为单位,参数为正整数,超过了当前的值,就表示cookie过期了,参数为0,表示直接结束cookie),延长cookie的时间)
服务端会话技术:HttpSession
HttpSession对象是在用户第一次访问网站时自动创建,一个用户有且仅有一个HttpSession,并且不会被其他用户访问到,不同的浏览器对不同的会话有不同的定义:有的浏览器定义新打开一个标签页就是一个新的会话,在谷歌浏览器中打开新标签页也会返回原来的会话。
HttpSessionAPI:
创建HttpSession:HttpSession httpSession=req.getSession()
返回当前HttpSession,如果没有就返回null:HttpSession httpSession=req.getSession(false);
返回当前HttpSession,如果没有就新创建一个:HttpSession httpSession=req.getSession(true);
为Session赋值(session是以key-value的形式存放的,key为String类型,value为Object类型):httpSession.setAttribute("city","xian");
HttpSession 的特点:
不同于于URL重写、 隐藏域或cookie,HttpSession 的值是会存储到内存中的,因此,尽量不要往HttpSession中放入太多对象或大对象。Servlet容器在内存不够用时,会将HttpSession对象保存到磁盘上(二级存储)。
因为session对象是任何类型的对象,因此也可以采用序列化技术将session对象放入文件或数据库中。
HttpSession内部实现会话跟踪的原理:
HttpSession的对象会创建一个唯一的编号,就是我们在浏览器源码页面中看到的jsessionid。当创建一个session时,其内部相当于创建一个cookie,cookie的name就是jsessionid,value就是jsessionid的编号,浏览器每一次发起请求时,servlet容器会将cookie提交给服务器(两种方式:1.通过请求头2.带入到url中),这样服务器就能识别到由哪个用户发起的。
获取JESSIONID:String jsessionId= httpSession.getId();
HttpSession的有效性:
session是存储在服务端的内存中的,因此当有大量用户访问,并且一直要保存会话的话,会对服务器造成很大的负担。
1.在程序中设置过期时间:
如果设置为0,表示永不过期:httpSession.setMaxInactiveInterval(60 * 30);
`2.在web.xml中设置
<session-config>
<session-timeout>20</session-timeout>
</session-config>
不设置时间时默认时间是30min(在tomcat容器中)
过滤器Filter
Filter可以拦截Request请求对象,可以对客户端浏览器的请求进行一些预处理。
Filter的配置可以通过注解或者部署描述来完成。当一个资源或者某些资源需要被多个Filter所使用到,且它的触发 顺序很重要时,只能通过部署描述来配置。
Filter类API
Filter的实现类必须继承javax.servlet.Filter 接口。
-
Filter的初始化方法
public void init(FilterConfig filterConfig) throws ServletException
-
Filter的过滤方法:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
一个资源可能需要被多个Filter关联,也成为Filter链。这时候FilterChain#doFilter()将会触发Filter链中下一个 Filter。只有在Filter链条中后一个Filter里调用的FilterChain#doFilter(),才会触发处理资源的方法。
如果在Filter#doFilter()的实现中,没有在结尾处调用FilterChain#doFilter()的方法,那么该Request请求终止,后面的处理就会中断。 -
Filter的销毁方法
public void destroy();
请求计数器
实现一个过滤器,单独进行请求访问次数的统计
@WebFilter(
urlPatterns = {"/*"},
initParams = {
@WebInitParam(name = "logging_prefix", value = "Request"),
@WebInitParam(name =
"logging_filename", value = "filter.logging")
}
)
public class LoggingFilter implements Filter {
//1. 指定初始化参数 记录的前缀,记录的文件名
//2. 每个请求的URL记录达到文件中
//3. web.xml配置(Filter , 拦截的地址, 初始化参数)
private String prefix = "";
private String loggingFile;
private PrintWriter writer;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.prefix = filterConfig.getInitParameter("logging_prefix");
this.loggingFile = filterConfig.getInitParameter("logging_filename");
try {
//获取Web程序的根目录
String webAppRoot = filterConfig.getServletContext().getRealPath("/");
this.writer = new PrintWriter(new File(webAppRoot, this.loggingFile));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("Logging Filter init called.");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Logging Filter doFilter called.");
String url = ((HttpServletRequest) request).getRequestURI();
this.writer.println(this.prefix + " " + LocalDateTime.now() + " " + url);
this.writer.flush();
//request -> filter (chain.doFilter) -> Servlet
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("Logging filter destroy called.");
}
}
Filter顺序
如果多个Filter应用同一个资源,Filter的触发顺序有限制,那么就需要通过web.xml中进行部署配置,写在前面的Filter就会先执行。
监听器Listeners
JavaWeb程序构成:
监听器接口可以分为三类: ServletContext, HttpSession, ServletRequest
实现监听器接口后通过覆写sessionCreated
和sessionDestroyed
方法,来对相应的监听级别进行监听逻辑处理。
1.应用级监听
- ServletContextListener接口能对ServletContext的创建和销毁做出响应。
- ServletContextAttributeListener接口会响应ServletContext范围的属性被添加,删除或者替换。
应用场景:全局数据共享、程序的启动,销毁时的准备或清理工作、监听属性的变化
2.会话级监听
- HttpSessionListenern能够监听HttpSession的创建和销毁。
- HttpSessionAttributeListener和ServletContextAttributeListener类似,它会响应HttpSession范围的属性的 添加,删除,替换。
应用场景:浏览器会话(客户端)用户相关
3.请求级监听
- ServletRequestListener监听器会对ServletRequest的创建和销毁事件进行响应。容器会通过一个池子来存放 并复用多个ServletRequest,ServletRequest的创建时从容器池里被分配出来的时刻开始,而它的销毁时刻 是放回容器池里的时间。
- ServletRequestAttributeListener会响应ServletRequest范围的属性被添加,删除,或者替换。
应用场景:计算请求耗时、监听请求属性的变化、监听请求公共属性
@WebListener
public class SessionCountListener implements ServletContextListener, HttpSessionListener, HttpSessionAttributeListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
ServletContext context = se.getSession().getServletContext();
//当应用程序建立一个会话(session),session_count +1
Integer sessionCount = Integer.parseInt(String.valueOf(context.getAttribute("session_count")));
sessionCount++;
context.setAttribute("session_count", sessionCount);
System.out.println("HttpSession 创建啦");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
ServletContext context = se.getSession().getServletContext();
//当应用程序关闭一个会话(session),session_count -1
Integer sessionCount = Integer.parseInt(String.valueOf(context.getAttribute("session_count")));
sessionCount--;
context.setAttribute("session_count", sessionCount);
System.out.println("HttpSession 销毁啦");
}
@Override
public void contextInitialized(ServletContextEvent sce) {
//应用程序启动的时候,默认的session数量是0
ServletContext context = sce.getServletContext();
context.setAttribute("session_count", 0);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
//应用程序停止的时候,移除session_count属性
ServletContext context = sce.getServletContext();
context.removeAttribute("session_count");
}
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
System.out.println("应用程序会话的属性新增:" + event.getName() + " = " + event.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent event) {
System.out.println("应用程序会话的属性移除:" + event.getName() + " = " + event.getValue());
}
@Override
public void attributeReplaced(HttpSessionBindingEvent event) {
System.out.println("应用程序会话的属性替换:" + event.getName() + " = " + event.getValue());
}
}
注解描述
要求ServletAPI标准为3.1才支持注解!!
- WebServlet:标识Servlet类
- WebFilter:标识Filter类
- WebListener:标识Listener类
- WebInitParam:标识初始化参数
- MulitpartConfig:标识上传附件的配置