title: JAVAWeb
date: 2020-04-24 13:49:24
tags: “JAVAWeb”
categories: “java”
copyright: true
Tomcat
- web应用程序需要由一种软件来统一进行管理和运行,这样的软件就称为引擎或容器(注意这里的容器指web容器,和spring的对象容器是不同的),主流的容器有tomcat,jetty,apache,iis
- 安装:直接解压缩包到目标文件夹
- 目录介绍:
- bin:存放tomcat的可执行程序(启动服务器所在位置)
- conf:存放tomcat的配置文件
- lib:存放tomcat依赖的jar包
- logs:存放服务器运行时的日志信息
- temp:存放服务器运行时产生的临时数据
- webapps:存放部署的web工程(war包),每个目录就是一个项目
- work:是tomcat的工作目录,存放jsp被翻译为servlet的源码和class文件,和session的钝化目录(就是序列化写入磁盘文件)。
- 启动服务器:bin目录->双击statrtup.bat就可以启动成功
- 使用命令行:就和java程序的启动一样,找到可执行文件的目录看,输入启动命令,就是进入bin目录,输入catalina run即可。命令行启动的好处就是在
命令行中会有错误提示。
- 常见的失败原因:双击startup.bat之后黑窗口一闪而过,基本上是因为没有配置好JAVA_HOME,重新配置好就可以
- 停止tomcat:
- 点击tomcat服务器窗口的x即可
- 在服务器窗口按ctrl+c
- 找到bin目录下的shutdown.bat文件
- 修改默认端口号:conf文件夹->server.xml->修改port属性
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="utf-8" />
</Service>
- 部署web工程:
- 将工程文件夹直接赋值到webapps文件夹下,在浏览器访问时输入localhost:8080是进入webapps目录,后面斜杠就是每个工程的目录
localhost:8080/book/index.html 就是进入了webapps目录下的book目录下的index页面
- 第二种部署方法是在conf->catalina->localhost目录下新建xml配置文件(创建虚拟目录),或者直接在上面的server.xml文件的之前加入下段内容
<Context path="/web03" docBase="D:\test" />
<!--Context是上下文,path是访问路径(就是8080/${path}),docBase就是工程的路径-->
-
root根目录的访问:输入localhost:8080后面不接工程名就是默认访问root目录工程,输入工程名默认登录index页面(我输入localhost:8080之后出现404,就是因为我之前将ROOT目录下的所有文件都删除了,因此输入会找不到index,并且文件名必须叫index,其他的会报错)
-
idea整合tomcat,网上有教程,不赘述
-
tomcat配置虚拟目录,看这里,猴版
:conf->server.xml,host配置工程的映射路径,conf->web.xml,配置init-param,listings设为true。 -
配置临时域名和虚拟主机,看这里
-
虚拟主机就是同一台主机有多个网站,这些网站相互独立,互补干扰,但是共用一个主机,能够节省资源,通过域名相互区分。
-
浏览器访问资源的流程图
-
好家伙,刚刚搞了半小时想要自己实现原始servlet,但是发现自己太菜了,一开始以为是工程的虚拟目录出现了问题,但是后来在项目中添加了index.html,发现能够访问,但是访问servlet却无法访问,仔细一看原来是没有编译,那玩个ballball,于是直接启动idea配置的tomcat进行编译,嗯,在IDEA的tomcat中是能访问了,但是将项目部署到tomcat中,在本地打开tomcat访问时却无法访问,仔细排查一番后,明白了我放的是整个原始项目,随让经过编译,但是任然有src目录,并不是标准的web工程目录,也就是说要打war包,放到tomcat中才能运行,否则是不能运行的。即在tomcat中的文件是class字节码文件,而不是.java源码文件
-
idea中运行不是在tomcat中运行,运行的相当于副本,目录
C:\Users\徐泽泓\.IntelliJIdea2019.1\system\tomcat
,记得及时清理 -
tomcat会在coyoteConnector创建request底层对象并且用service方法通知有的监听器,订阅发布者模式
-
response和req都使用了facade模式,简单来说就是提供统一的对外接口,方便调用吗,详细资料百度
JSP
JSP的运行原理
- 虽然JSP现在挺拉的,但是了解有关的原理(特别是servlet),还是很有帮助的,只不过不需要这么了解jsp语法
- 运行原理,第一次请求页面时,先将jsp转为class源码文件,之后每当请求一次页面时,就开辟一个新线程,直接执行字节码文件
JSP的语法
- JSP的语法暂时先不写,等到后面学到的时候再写
JSP语法
- <%! %>内定义成员变量,所有用户共享一个成员变量
- JSP引擎会为每一个请求的用户创建一个新线程
- <% %>内定义程序片,程序篇内定义的变量是局部变量,局部变量在页面中后续的程序篇中都能够使用。这是为了在程序片之前插入html代码。
JSP的内置对象
- JSP的内置对象还是挺有用处的,在spring中也是可以拿到原生的servlet类对象,来进行一些底层的操作。
- 所谓内置对象就是在jsp中不需要声明就可以直接使用的对象
- 分类
- request
- response
- session
- application
取值(map)
capital: ${capitals.奥天不坏},
error和iserrorPage
- 两者的区别在于error指定当前页面错误时用来显示错误的页面,iserror用来表示当前页面是否是错误页面,如果是错误页面则可以用exception内置对象来获取异常信息
静态引入和动态引入的区别(include指令)
-
详细情况看这里
-
)include指令在转换时插入“Header.jsp”的源代码,而jsp:include动作在运行时插入“Header.jsp"的响应。
<%@include为静态包含,<%@include不论包含的是txt文本还是jsp文件,被包含的页面都不会从新编译。 <%@include为静态包含,包含了几个JSP转译成servlet时就会有 几 个 class文件,如果在jsp1定义了变量i同时在jsp2也定义了变量i那么你编译都会通不过的, jsp容器会告诉你i重复定义了. <jsp:include 为动态包含,<jsp:include 如包含jsp文件,这每次加载主页面的时候,被包含的页面都要重新编译。 就是说不管你包含了几个jsp页面转译成servlet时中有一个class文件 所以说对于<%@include要慎用! 静态导入是将被导入页面的代码完全融入,两个页面融合成一个整体Servlet;而动态导入则在Servlet中使用include方法来引入被导入页面的内容。
2、静态导入时被导入页面的编译指令会起作用;而动态导入时被导入页面的编译指令则失去作用,只是插入被导入页面的body内容。
3、动态导入还可以增加额外的参数。即用param标签
jsp中response.getwriter()插队先输出
- 看这里
.两者的类不同,out的类是JspWriter,response.getWriter()的类是PrintWriter。
*2.out是JSP的内置对象,直接就可以调用里面的方法,而PrintWriter调用之前需要response.getWriter()获取对象。 - JspWriter的print()方法会抛出IOException,而PrintWriter则不会 。
- JspWriter和PrintWriter都继承自Java.io.Writer,但前者是抽象类**,而后者却不是。
**5.最主要的区别:
在同一个jsp或servlet文件中同时使用到了JspWriter对象和PrintWriter对象,在调用print或println方法后的执行情况不同
只是JspWriter具有缓存,只有它的缓存刷新了后才会创建PrintWriter类,
并将JspWriter对象中的内容写入到PrintWriter类对象中。
自己的理解是这相当于JspWriter只是个代购,在收到钱之后,他不是马上就把货给你,而是满足他的条件后,再去PrintWriter的店取货然后把货给你。
只有满足以下任一条件时,out对象才去调用getWriter()方法,通过PrintWriter对象将out对象缓冲区中的内容写到Response响应对象中: - 设置page指令将“buffer”属性为“none” 。
- 写入到out对象中的内容充满了out对象的缓冲区。
- 整个JSP页面执行完毕。
如果想让out的输出直接输出出来,刷新out的缓冲区即可,即修改jsp代码为:
requset
- requset封装用户提交的数据供后端使用
- 解决中文乱码
- get
new String(name.getBytes("iso-8859-1"),"utf-8");
- post
request.sestCharacterEncoding("utf-8");
两者是否有区别
2. request的常用方法
3. post 可代替用户发送get,post请求
//拿到用户提交的数据,参数是数据的名称,当是get时,就是地址栏后面接的?username=xu,post方式就是表单提交的域的name属性对应的值
//但是注意可能用户提交的是空数据,注意要判空
req.getParamter(String paramName);
//设置用户传输数据的编码,解决中文乱码问题,这个只能对消息体有效,也就是对post方法有用,get方式用url传递参数对get方式的参数需要单独拿出来getParamter,再用newString(getBytes(),"charset")再次编码,暂时不确定这个对不对,也不知道是不是他在放屁。先这样了。
req.setCharacterEncoding(String env)
//获取请求的JSP目录,比如请求地址是http://127.0.0.1:8081/test/index.jsp,那么这个方法的返回结果是/index.jsp
req.getServletPath();
//还是上面哪个例子,方法的返回结果是/test,是工程目录
req.getContextPath();
//获取请求的方法
req.getMethod();
getMethod : GET<br />
getRequestURI : /j2019051058_war_exploded/RequestLineServlet<br />
getQueryString:null<br />
getContextPath:/j2019051058_war_exploded<br />
getServletPath:/RequestLineServlet<br />
getRemoteAddr : 0:0:0:0:0:0:0:1<br />
getRemoteHost : 0:0:0:0:0:0:0:1<br />
getRemotePort : 50714<br />
getLocalAddr : 0:0:0:0:0:0:0:1<br />
getLocalName : 0:0:0:0:0:0:0:1<br />
getLocalPort : 8080<br />
getServerName : localhost<br />
getServerPort : 8080<br />
getRequestURL : http://localhost:8080/j2019051058_war_exploded/RequestLineServlet<br />
getContextPath : 项目路径,注意不带主机名和端口,/j209051058_war_exploded
- 处理汉字信息,浏览器默认的编码是ISO-8859-1,需要对获得数据进行重新的编码
String message = req.getParameter("message");
byte[] bytes = message.getBytes("ISO-8859-1");
message = new String(bytes);
//另一种方式是在读取参数之前先设置编码
req.setCharacterEncoding("UTF-8");
- 转发,
req.getRequestDispatcher("a.jsp").forward(req,res);
redirect 后,确认了要跳转的页面的 url,继续执行 redirect 下面的代码;执行完后,断开当前的与
用户所发出的请求连接,即断开 request 的引用指向,因此 request 里存放的 username 信息也会丢失
;然后再与用户建立新的请求连接,即创建新的 request 对象,这样 false 页面的通过
request.getAttribute("user") 就会得到一个 null 值;最终显示的是页面是 false 页面,所以地址栏
的 url 内容会发生变化。
forward 后,确认了要跳转的页面的 url,现在停止继续执行后面的代码,而是先执行 success
(servlet) 里的代码,执行完后再回来继续执行 check 后面的代码;在这期间 check 和 success 共享
一个 request 和 response 对象,所以 success 页面通过 request.getAttribute("user") 便可以取到
值;这个过程最后执行的还是 check 页面(即使 forward 后面没有任何代码),所以地址栏的 url 内容
不会发生变化。
-
forward和include的区别,看这里.简单来说就是forward执行完之后虽然会回来执行功能servlet01的代码,但是输出流在写也不会输出了,而include任然会输出out流
-
//这些只是存入缓冲区中,并没有flush强制输出,这样
/*
* if (response.isCommitted()) {//就是没有使用flush方法
throw new IllegalStateException(sm.getString(“applicationDispatcher.forward.ise”));
} else {
try {
response.resetBuffer();//这里会将之前缓冲区中的内容清除
} catch (IllegalStateException var10) {
throw var10;
}
* 这样response.isCommitted()就是false,会执行resetBuffer方法,清空缓冲区,这样没有缓冲区中的
* 两条语句就会清空,只返回forward之后的响应
* */
out.write(“转发过滤器:Hello FilterTest”);
out.write(“
看到这个信息,说明已经到达不了转发控制器了,因为过滤器已经处理了请求了,给了响应”);
// out.flush();//todo forward should be called before the response has been committed to the //client (before response body output has been flushed). If the response //already has been committed, this method throws an //IllegalStateException. Uncommitted output in the response buffer is //automatically cleared before the forward. //todo 如果在forward方法之前适用response进行了输出(调用了flush方法,如果没有调用则forward方法的目标servelt的流会覆盖掉out缓冲区中的数据),就会触发异常,就会只返回已经写入response流中的数据 //不会执行将forward的响应输出。
-
第一次打开页面时,显示是两个人在线
检查后发现是idea配置tomcat时选择了项目部署完成之后自动打开浏览器,这样就会显示两个人,取消掉这
选项,手动打开页面请求,变为一个人.
response
//设置返回内容的类型和编码
res.setContentType("application/text;charset=UTF-8");
/*访问servletFilterChain出现乱码,经过检查之后发现是charsetencoding被设置为iso-8859-1,并且在 setcontenttype("text/html;charset=utf-8")只有前面的生效了,字符集无法修改,是因为在设置字符集前,会判断iscommitted,如果在setcontentType之前打开了流,那么就视为committed,就不会再次设置字符集合,因此在setcontenttype之前不要打开流,因此不要再filte中打开流,也不要再filte中进行响应,如果第一个打开了流且美哟设置contenttype就会使用默认的编码也就是8859
*/
//res对象的主要使用功能是重定向(redirect)和转发(forward)
res.sendRedirect("doc.jsp");//注意这里前面不要加/,不加/是表示相对目录,表示任然在当前目录下,而加了/就表示是在根目录下的
-
responser.getwriter的write和println由细微的区别,print方法可以将各种类型的数据转换成字符串的形式输出。重载的write方法只能输出字符、字符数组、字符串等与字符相关的数据。 另一个是write()方法,本身不会写入换行符,如果用write()写入了信息,在另一端如果用readLine()方法。由于读不到换行符,意味中读不到结束标记,然后由于IO流是阻塞式的,所以程序就是一直卡在那里不动了。原因即为缺少回车标识。如果在写入的时候加上“\r\n”,就可以解决这个问题了。而println()就自动加上了换行符了。
至于和jsp页面中的区别可以看这里。 -
resp.sendRedirect()通过设置响应头中的location字段,让浏览器再次向location对应的url发请求.
//todo http1.0 兼容字段 禁止页面缓存
response.setHeader(“Pragma”,“No-cache”);
response.setHeader(“Cache-Control”,“no-cache”);
response.setDateHeader(“Expires”, 0);
String action=request.getParameter("action");
if ((action != null) && action.equals("del")) {
//todo 重定向原理,设置refresh头,每隔五秒刷新页面
response.setHeader("refresh", "5; url=list.jsp?action=del");
详情看这里
session
- HTTP是一种无状态的协议,即不保留上一次连接的有关信息,每次连接都视为一次新的连接
- session就是为了让一个用户在同一个web服务的多个页面之间跳转的时候还能让服务器认出这是同一个用户的技术。session对象是有tomcat服务器产生的,session对象存储了用户在每个页面所提交的信息。
- 当session对象被创建时,会被分配一个id,同时会将这个id存入浏览器的cookie中,这样session就和用户建立一对一的关系,每个用户在一次会话间有一个唯一的session对象,不同的用户的session对象不同,同一个用户在不同的web服务中的session是不同的。直到session达到了最大生存时间或者服务器关闭,session对象就会被释放
- session对象的唯一性依赖浏览器允许存储cookie,如果不允许存储cookie那么,就无法保证session是唯一的,但是可以用res.encodeURL()或者res.encodeRedirectURL()进行url重定向,将请求携带session的id传到服务端,参考这里,当使用servlet进行页面输出时,这样通过servlet输出的页面进行请求时,只会有url,不会象浏览器那样发送cookie给服务器,这样服务器就无法识别用户,页面之间的跳转和请求就会失效拿不到数据,因此需要这两个方法来自动加上sessionid,两个方法的区别就是
第一种,在servlet生成的 web页面中含有嵌入的URL,应该将这URL传递给 response的 encodeURL()。这个方法确定当前是否在使用URL重写,仅在必需时附加会话信息;否则不做更改直接返回传入的URL:
String aURL = someURL;
String encodeUrl = response.encodeURL(aURL);
out.print(“< A href=” + encodeUrl ….);
第二种情况是在sendRedirect()调用中(即放入Location响应报头)。这种情况下,由于要根据不同规则确定是否需要附加会话信息,因而不能使用encodeURL,需要使用HttpServletResponse的 encodeRedirectURL()。
String originalURL = someURL;
String encodeUrl = response.encodeRedirectURL(originalURL);
response.sendRedirect(encodeUrl);
- 常用方法
//从session中取出数据的话要进行转型
String message = (String)session.getAttribute("message");
session.removeAttribute();
session.setAttribute();
- session的生命周期
6.1 session的destroy取决于是否调用了Invalidate()方法,是否达到最大发呆时间,服务器是否关闭
6.2 发呆时间指的是用户对某个web目录发出两次请求之间的间隔。
6.3 修改tomcat的默认发呆时间可以修改web.xml。注意是conf文件夹下的web.xml不是每个项目下的web.xml,conf文件夹下的配置是全局的.而单个项目的超时时间可以使用setMaxInactiveInterval()进行设置
<session-config>
<session-timeout>30</session-timeout>
</session-config>
- session的创建判定,当调用request.getSession(true),表明要创建一个新的session,当一个请求没有携带sessionid时就是要创建一个新的session
application
- application对象由服务器进行创建,于session的区别是application由所用用户共享,但是不同web服务的application不同
- 由于application对象是所有用户共享的,因此需要进行同步处理.有些服务器不支持直接使用application对象,必须ServletContext类声明这个对象.再使用getServletContext()对application进行初始化
context
//这个地方是输出的目录,就是当前应用程序的out/artifacts/项目名,而不是直接源码,因为部署到服务器
//上的都是web目录下的,java源码编译后就不会在web目录下
//这个方法是获取应用的绝对路径,参数是资源的位置
//比如这个方法返回的就是D:\\j2019051058\out\artifacts\war_exploded\
//加上参数就是D:\\j2019051058\out\artifacts\war_exploded\WEB-INF\count.txt
path = sc.getRealPath("WEB-INF/count.txt");
//这是从类路径中的,注意类路径是web-inf下的classpath和lib目录,因此要在class目录下建立和servlet路径一样的
//目录,否则会找不到路径,但是不用担心,count默认是0
InputStream is = LifeServlet.class.getResourceAsStream("count.txt");
//这两者之间的区别就是sc.getRealPath得到的是项目的绝对路径,而getResourceAsStream得到的是和当前类的类加载器所在目录的,必须要把资源文件放在所在类的目录下,比如LifeServlet的类路径是cn.edu.xzh.servlet,其绝对路径是
//D:\\j2019051058\out\artifacts\war_exploded\WEB-INF\cn.edu.xzh.servlet,
//文件就应该放在对应的servlet目录下,D:\\j2019051058\out\artifacts\war_exploded\WEB-INF\cn.edu.xzh.servlet
JAVA与Bean
- 等后面学到的时候再详细说
IDEA的动态web工程
- 创建:
- 通过勾选maven的archtype(原型构建)来创建
- 通过javaEE,记得勾选webapplication选项,点击createweb.xml不然还得手动建立xml文件。
- web工程的标准目录结构
是sun公司开发的一款用于开发动态web资源的技术,web资源分为动态和静态的两种。在服务器上运行的小程序
-
动态和静态的区别
- 动态web资源是客户端请求的动态资源,先将资源交于web容器,在由web容器连接数据库,数据库处理数据后交由web容器返回给客户端解析渲染处理
- 静态资源是web容器从内存中直接调取返回给客户端
- 静态资源一般是设计好的html页面,而动态资源是根据设计好的程序按照需求来动态响应
- 静态资源的交互性差,动态资源的交互性好
- 静态资源不需要数据库参与程序处理,而动态资源需要
servlet的实现方法
sun在java中提供了servlet的接口,只需要完成两个步骤
- 编写Java类,实现servlet接口
- 在WEB-INF目录下的web.xml中配置Servlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--配置Servlet-->
<servlet>
<!--配置Servlet的别名-->
<servlet-name>demo1</servlet-name>
<!--配置Servlet的路径,也就是全限定类名-->
<servlet-class>cn.itcast.web.servlet.ServletDemo1</servlet-class>
</servlet>
<!--配置Servlet的访问路径-->
<servlet-mapping>
<!--Servlet的别名-->
<servlet-name>demo1</servlet-name>
<!--配置访问路径-->
<url-pattern>/demo1</url-pattern>
</servlet-mapping>
</web-app>
Servlet的原理
tomcat通过解析web.xml文件,获取到标签下的Servlet对应的全限定类名,将类的字节码文件加载进内存,调用Class.forName().newInstance(),创建一个新的对象,掉用对象中的service方法,完成所需的功能。
Servlet的生命周期方法
Init:只执行一次,可以通过配置指定在服务器启动时执行或者在第一次访问Servelt时启动
配置在Servelt中用标签控制,为负数是指定第一次被访问时执行,为0或正数是在服务器启动时执行。为负数或者没有配置该节点,就是第一次访问时初始化。由于Init只执行一次,即Servelt是单例对象,那么就会存在多线程安全问题,解决方法就是在方法中定义局部变量,不在Servlet中定义成员变量
Service每次访问Servlet时执行,会为每个访问的用户开一个线程来执行service方法,destory在服务器正常关闭时才会执行
Servelt的体系结构
Servlet是接口,其下有GenericServlet的子抽象类和HttpServlet的孙子抽象类。其中GenericServlet只有Service的抽象,其他方法默认为空,HttpServlet封装了Http协议的相关操作,使用者不需要再根据请求的类型做相应的处理,直接写在doGet和doPost方法里。Servlet可以提供更加精确的控制,包括servleteConfig等
package com.xzh;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* 1.直接实现Servlet接口,编写用户需要的Serlvet【类,组件,控制器】<br>
* <li> Servlet接口
* <li> GenericServlet抽象类
* <li> HttpServlet抽象类<br>
* 2.采用了注解的配置方式,最简单的注解,只给出url-pattern即可,建议统一用小写<br>
* 3.所有的命名建议和要求:见名知义,英文
* 4.使用了三种方法实现servlet
*
*/
public class ServletDemo02 implements Servlet{
public static void main(String[] args) throws ServletException, IOException {
// 用户自己调用这些方法,但没有意义,服务器帮我们进行对象的init,service,destroy等方法
ServletDemo02 myServlet = new ServletDemo02();
myServlet.init(null);
myServlet.getServletConfig(); // 其实没有意义
myServlet.getServletInfo(); // 其实没有意义
myServlet.service(null, null);
myServlet.destroy();
}
@Override
public void destroy() {
System.out.println("call destroy");
}
/*servletConfig是在解析web.xml文件之后将<servlet>标签之间的内容拿到封装到servletConfig中*/
@Override
public ServletConfig getServletConfig() {
ServletConfig servletConfig = new ServletConfig() {
@Override
public String getServletName() {
return "servletName";
}
@Override
public ServletContext getServletContext() {
ServletContext servletContext = null;
return servletContext;
}
@Override
public Enumeration<String> getInitParameterNames() {
Enumeration<String> initParams = new Enumeration<String>() {
@Override
public String nextElement() {
return "only one string for initParams";
}
@Override
public boolean hasMoreElements() {
return false;
}
};
return initParams;
}
@Override
public String getInitParameter(String arg0) {
return "get init params";
}
};
return null;
}
@Override
public String getServletInfo() {
return "servlet infomation";
}
/**
* 初始化方法
*/
@Override
public void init(ServletConfig arg0) throws ServletException {
System.out.println("call init");
}
/**
* 服务方法,这是用户【程序员的工作位置】
*/
@Override
public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException {
System.out.println("call service");
}
}
- Servelt的体系结构
- Servlet是接口,其下有GenericServlet的子抽象类和HttpServlet的孙子抽象类。Servlet可以提供更加精确的控制,包括servleteConfig等。
//init,service,destroy方法在上面解释过了
void init(ServletConfig var1) throws ServletException;
//获取servlet的配置信息对象
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
//获取servlet的信息
String getServletInfo();
void destroy();
- GenericServlet扩展了Servlet接口的方法
//这里只写了扩展的方法
//获取指定的初始参数值,继承自servletConfig
public String getInitParameter(String name) {
return this.getServletConfig().getInitParameter(name);
}
//获取所有参数的名称(枚举),继承自servletConfig
public Enumeration<String> getInitParameterNames() {
return this.getServletConfig().getInitParameterNames();
}
//获取当前应用的上下文,继承自servletConfig
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
//日志,输出当前servlet的有关信息
public void log(String message) {
this.getServletContext().log(this.getServletName() + ": " + message);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
//定义的一个抽象方法由子类继承,通常使用HttpServlet就够了,也可以自己定义业务逻辑
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
//获取当前servlet的名称,继承自serveltConfig
public String getServletName() {
return this.config.getServletName();
}
- HttpServlet封装了Http协议的相关操作,使用者不需要再根据请求的类型做相应的处理,直接写在doGet和doPost方法里。
//方法太多,这里不全部显示,仅显示几个重要方法
//这个方法将传过来的请求转换为httpservletreq和httpservletresp
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
//这个方法会对req的类型进行判断,从而转换到相应的doget和dopost等方法,我们又重写了自己servelt中的doget方法,就会执行我们的业务逻辑
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
路径配置
- 一个Servlet可以匹配多个路径,
@WebServlet({"/d4","/ddd4","/d4"})
- 其他路径配置方式
/xxx/xx // 多层路径方式
/xxx/* //写什么样的路径都会访问
filter和listener
- filter和listener也是servlet,只不过是比较特殊的servlet
- filter在servlet2.3之后支持
- 访问特定资源时的身份认证
- 访问资源的记录跟踪
- 访问资源的转换,比如格式转换
- 监听器
- 监听容器事件
- 某个web应用上下文的创建和销毁
- 会话对象的创建销毁
- 会话属性信息增减
- 通过监听在事件前后进行一些必要的准备和善后工作
- 这些都是以后框架高级功能的基础
- 拦截器/过滤器
- 前端处理器
filter
- fiter可以组成filterChain,注意顺序,顺序就是在webxml文件中mapping的顺序
- filter来回都会拦截请求和响应
- 过滤器的应用场合
- 认证:统一拦截用户的请求进行登录验证
- 日志:对请求进行审核和日志记录
- 数据清洗:过滤修改替换,格式化
- 图像转换
- 加密解密
- 统计哪些资源高频访问
- 统计高峰时段
- 哪些用户最活跃
- 大小图过滤
- 资源下载次数
- 因为filter也是一种是servlet,因此生命周期也是一样的
- 过滤器返回执行是和forward一样的,执行dofilter和forward方法一样,都会停止当前servlet的执行,先执行完委托的servlet在回来继续执行
- filter中的req和resp是servletresquset类型需要下转型为httpServletRequset类型的使用
listener
- 常见的监听类型有上下文,会话,请求
- servletContextListener,对上下文创建和销毁做监听
- servletContextAttributeListener,响应属性里的属性值变化
- httpSessionListener,会话创建和销毁,统计session数量
- httpsessionActivationListener
- httpSessionAttributeListener
- httpSessionBingdingListener
- servletRequestListern,请求创建和销毁
- servletrequestAttributeLIstener
- AsyncListener
源码挖掘
- 体系结构详细见我的思维导图(在做了0%);
路径问题
- 相对路径
“./”:代表目前所在的目录。和不写是一样的
“../”:代表上一层目录。
“/”:代表根目录,注意页面中根目录的含义和服务器中的根目录的区别
- 一些方法中的路径
web.xml中,<url-pattern>是必须要加/否则编译错误(除扩展名匹配外)
getresourceAsStream中是以当前类所在的目录为前缀,拼接方法中的参数,注意参数前不要加/否则找不到
servletContext.getRealPath是以当前应用为根目录,拼接方法参数,参数前面也不加/
页面中发送到servlet路径注意层级目录关系,有../和不写和./注意区分
req.dispatcher构造应用目录+传入的servletPath,要加/
response.sendRedirect("test.html");重定向访问同级目录不用加/
转发和重定向不同的原因就是服务器和客户端对于根目录的理解不同,客户端认为根目录是网站,即主机名+端口,因为其他资源都是在网站目录下的,因此访问其他资源就要从根目录出发,而服务器上一般由多个项目,因此服务器内部获取资源就不能使用网站作为根目录。而需要使用项目路径
-
客户端的根目录和服务器端的根目录是不一样的,在进行客户端跳转和服务器端跳转时,都需要指定目标资源的URL,如果这个路径以“”开始。在客户端跳转当中“”代表的是应用服务器根目录,即“http://localhost:8080/”,而在服务器端跳转当中代表的是应用程序根目录,即“http://localhost:8080/项目名/”。参考这里
-
url小结,主要分为两大类,一类是j2ee中的url-pattern,另一类是springmvc中的dispatcherServlet。
- url-pattern,重点在org.apache.catalina.mapper.Mapper类中,这个类将url-pattern分为4类,通过调用addWrapper方法,分为
1. 以 /* 结尾的。 path.endsWith("/*")
2. 以 *. 开头的。 path.startsWith("*.")
3. 是否是 /。 path.equals("/")
4. 以上3种之外的
这4中会存入4总wrapper中,Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。
1. 我们看到 /* 对应的Servlet会被丢到wildcardWrappers中 (前缀匹配)
2. *. 会被丢到extensionWrappers中(扩展名/后缀匹配)
3. / 会被丢到defaultWrapper中(默认匹配)
4. 其他的映射都被丢到exactWrappers中(精确匹配)
用户请求这里进行url匹配的时候是有优先级的。 我们从上到下以优先级的高低进行说明:
规则1:精确匹配,使用contextVersion的exactWrappers
规则2:前缀匹配,使用contextVersion的wildcardWrappers
规则3:扩展名匹配,使用contextVersion的extensionWrappers
规则4:使用资源文件来处理servlet,使用contextVersion的welcomeResources属性,这个属性是个字符串数组
规则5:使用默认的servlet,使用contextVersion的defaultWrapper
但是注意defaultWrapper不包括.jsp和.jspx文件,这两个文件是tomcat帮我们扩展的,属于extensionWrapper
总结优先级:精确匹配 > /* > 扩展名匹配(*.do) > /
因为我们一般不会使用资源文件来处理请求,所以不写,但是这个到了mvc中就会造成静态资源文件无法访问的问题
<url-pattern> / </url-pattern> ———— 会匹配到路径型的url,不会匹配到以 .jsp 或 .html等为后缀的url;
而<url-pattern> /* </url-pattern> ———— 既会匹配到路径型的url,也会匹配到以 .jsp 或 .html等为后缀的url
每个路径匹配到的结果并不一定是固定,需要根据优先级的高低判定,如果没有优先级别高的就可以访问对应的资源。
比如上面两个例子,如果有.jsp全部是精确url,那么/*就不会访问到,同理,当url中只有/时,是可以匹配到.jsp和/html的
- springMVC
SpringMVC是使用DispatcherServlet做为主分发器的(也就是说所有的请求会)。 这个Servlet对应的url-pattern一般都会用“/”,当然用"/*"也是可以的,但是这样会造成静态资源无法访问,原因如下
如果不配置静态资源,它会把匹配所有请求都当做是对控制器的访问,去掉后缀进行匹配。例如你访问/main.css会变成/main,这也是造成你引入的.css,.js等静态资源无法访问的原因,因为他们都被当做控制器的路径了。
至于为什么不匹配.jsp和.jspx,是因为在tomcat的web.xml里将jsp和jspx作为扩展名匹配,优先级别比/高:这里由于*.jsp和*.jspx的精度比"/"更高,所以被JspServlet匹配到了,而不是DispatchServlet(jsp的本质也是servlet,它只是让返回数据动态加载的html更加方便)"/*"表示匹配所有请求(包含所有后缀)
这里*.jsp比"/*"的匹配度低,所以配置后会使.jsp也进入DispatcherServlet,而.jsp既不是控制器路径也不是静态资源,所以就算配了静态资源还是访问不到,所以这个/*的配置不适用于DispatcherServlet,一般用于过滤器来拦截所有资源
*.do表示匹配所有以.do结尾的请求
这也是比较推荐的一种配置,它只匹配.do结尾的请求,不会匹配到静态资源,静态资源可以被直接访问,省去了配置静态资源的麻烦
/的意义在于符合restful风格,restful风格的请求不能有后缀,需要像 /user/name这种,需要配置静态资源:mvc:resources或mvc:default-servlet-handler/或Tomcat的web.xml中配置DefaultServlet
/*的意义在于过滤器拦截所有资源使用,并不适用于DispatcherServlet
*.do适用于DispatcherServlet,免去了配置静态资源的麻烦,但不适用于restful风格
如果使用/*,本文已经分析过这个url-pattern除了精确地址,其他地址都由这个Servlet执行。
比如这个http://localhost:8888/SpringMVCDemo/index.jsp那么就会进入SpringMVC的DispatcherServlet中进行处理,最终没有没有匹配到 /index.jsp 这个 RequestMapping, 解决方法呢 就是配置一个:
最终没有跳到/webapp下的index.jsp页面,而是进入了SpringMVC配置的相应文件("/*"的优先级比.jsp高):
当然,这样有点别扭,毕竟SpringMVC支持RESTFUL风格的URL。
我们把url-pattern配置回 “/” 访问相同的地址, 结果返回的是相应的jsp页面("/"的优先级比.jsp低)。
- 配置出错统一页面可以使用errorpage标签,不要用servlet配置/来默认处理,因为这样会重定向次数过多,导致浏览器出错。因为被/路经的servelt拦截之后,无论是转发还是重定向,都会发送/error.html,这样就会导致再次被拦截到,再次转发或者冲定向,形成循环。
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
- 另外如果配置/会拦截静态资源,因为/的路径是由tomcat的defaultServlet进行处理的,如果我们自定义了/路径的servetl就会覆盖原来的defaultServlet,因此我们就使用tomcat的defaultServlet进行扩展名配置,这样使得静态资源的优先级比/高,就可以访问到静态资源
MVC框架设计
- MVC是一种用MVC(module-view-controller)设计创建web应用程序的模式
- M(module)模型表示程序的核心,例如表的各种操作
- V(view)视图显示数据,例如在html上显示表的各种数据
- C(controller)控制器控制输入和view与module之间的交互,例如将页面数据写入数据库
Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。
通常模型对象负责在数据库中存取数据。
View(视图)是应用程序中处理数据显示的部分。
通常视图是依据模型数据创建的。
Controller(控制器)是应用程序中处理用户交互的部分。
通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
模型的声明周期和视图更新
- 总共也是三种生命周期
1.1 request:servlet创建Bean之后放到req的属性中,再用转发,目标jsp就能接受到传输的数据,并且因为是requset范围的,请求完成之后这个bean就会消失
req.setAttribute("requestBean",new Bean());
req.getRequestDispatcher("a.jsp").forward(req,resp);
同理session是用session对象发送,application是用getServletContext()进行转发或者重定向
2. 注意页面中引用的bean的字节码的属性的名称必须是type,这样jsp才不会自己去创建bean,而是使用传输的bean,如果是class类型,就会使用jsp自己创建的Bean
文件操作
- servlet中的文件操作也是一项重点.值得研究,servlet也是通过java的字节流来传输文件的
- 进行文件的相关操作就要对路径和文件类有一定的了解,我们先来康康.
new File(String fileName);//如果是直接使用文件名的话,会默认和当前应用程序再同一目录,而服务器的目录是安装目录/bin
//另外几个拿到servlet和web目录的方法
//获取请求的JSP目录,比如请求地址是http://127.0.0.1:8081/test/index.jsp,那么这个方法的返回结果是/index.jsp
req.getServletPath();
//还是上面哪个例子,方法的返回结果是/test,是工程目录
req.getContextPath();
//下面是一套完整的求路径的方法
String servletPath = req.getServletPath();
String contextPath = req.getContextPath();
servletPath = servletPath.substring(1);//将路径前面的/去掉
contextPath = contextPath.substring(1);
File file = new File("");
String serverPath = file.getAbsolutePath();//获取文件的绝对路径,因为是空的,相对路劲,也就是当前应用程序的路经,即tomcat/bin
int index = serverPath.indexOf("bin");
String tomcatDir = serverPath.substring(0, index);//获取tomcat的安装目录
new File(tomcatDir+"/webapps/"+contextPath,serverPath);
- 文件的操作也每什么好说的,套就完事了,都是从req中获取输入流,读取到变量中,再从res中获取输出流,再写回浏览器,但是注意要将这些操作放在线程中,每来一个请求就开一个线程,满足高并发
至于常用的方法可以去看API,常用的类就是FileIn(Out)PutStream,和FileReader(Writer),RadomAccessFile,BufferedWriter(Reader).
//这是最简单的输入输出
ServletInputStream inputStream = req.getInputStream();
byte bytes[] = new byte[1000];
int n;
ServletOutputStream outputStream = resp.getOutputStream();
while((n=inputStream.read(bytes))!=-1){
outputStream.write(bytes);
}
//这种是用bufferedString来读取一行的
BufferedReader in = req.getReader();
String s = null;
StringBuffer stringBuffer = new StringBuffer();
while ((s=in.readLine())!=null){
stringBuffer.append("\n"+s);
}
//这是用RandomAccessFile类,random..类既可以读也可以写,也可以获取指针的位置.
//文件上传的原理是通过读取输入流,读取表单第4-6行的数据,这是用户提交的数据,其他部分是表单的自身数据.
//首先利用不同的用户有唯一的id,将所有的提交信息保存到临时文件中,再获取第二行的文件名,和4-6行中的数据.最后再将临时文件删除
req.setCharacterEncoding("UTF-8");
String fileName = null;
HttpSession session = req.getSession(true);//参数表示是否创建session
try {
String tempFileName = session.getId();//使用sessionid作为临时文件的名字,保证唯一性
String webDir = req.getContextPath();//获取web服务的路径
webDir = webDir.substring(1);
File file = new File("");
String tomcatPath = file.getAbsolutePath();
int index = tomcatPath.indexOf("bin");
String serverPath = tomcatPath.substring(0, index);
File temp = new File(serverPath + "/webapps/" + "/image");//构建临时文件的路经
temp.mkdir();//创建临时文件
File f1 = new File(temp, tempFileName);
FileOutputStream fileOutputStream = new FileOutputStream(f1);
ServletInputStream inputStream = req.getInputStream();
byte b[] = new byte[1000];
int n;
//将请求内容写入到临时文件中
while ((n=inputStream.read(b))!=-1){
fileOutputStream.write(b,0,n);
}
fileOutputStream.close();
inputStream.close();
//使用随机读写流,因为随机读写流可以定位当前流的位置方便我们截取4-6行的数据
RandomAccessFile randomAccessFile = new RandomAccessFile(temp,"r");
int second = 1;
String secondLine = null;
while (second<2){
secondLine = randomAccessFile.readLine();
second++;
}
int position = secondLine.lastIndexOf("=");
//拿到文件名字
String finalFileName = secondLine.substring(position + 2, secondLine.length() - 1);
//将指针重置到文件的起点
randomAccessFile.seek(0);
long forthEndPostion=0;
int forth=1;
//找到第四行结尾
while ((n=randomAccessFile.readByte())!=-1&&(forth<4)){
if (n=='\n'){
forthEndPostion = randomAccessFile.getFilePointer();
forth++;
}
}
//创建真正要保存的文件
File f2 = new File(dir, fileName);
RandomAccessFile rw = new RandomAccessFile(f2, "rw");
rw.seek(rw.length());
long endPosition = randomAccessFile.getFilePointer();
long mark = endPosition;
int j=1;
//找到第6行
while ((mark>=0)&&(j<=6)){
mark--;
rw.seek(mark);
n = rw.readByte();
if (n=='\n'){
endPosition = randomAccessFile.getFilePointer();
j++;
}
}
rw.seek(forthEndPostion);
long startPoint = randomAccessFile.getFilePointer();
while (startPoint<endPosition-1){
n = randomAccessFile.readByte();
rw.write(n);
startPoint = randomAccessFile.getFilePointer();
}
randomAccessFile.close();
rw.close();
f1.delete();
}catch (Exception e){
e.printStackTrace();
}
req.getRequestDispatcher("a.jsp").forward(req,resp);
}
//下面是文件下载,注意点就是要设置返回头,确定文件类型和文件名字,至于输出流和其他的方式是一样的
resp.setHeader("Content-disposition","attachment;filename='下载文件'");
byte b[] = new byte[1000];
.....
网站案例结构
- Web层:
a.servlet:前端控制器,用于控制前端页面和用户的交互。
b.html:视图层,用来展示相关内容,面向普通用户,如果面向内部人员,常常使用jsp展示
c.Jackson:json,因为html不能直接显示后端的数据,需要将数据序列化之后才能展示
d.BeanUtils:数据封装
e.Filter:用于向服务器发送请求之前进行的统一操作,例如统一字符集 - Service层:
a.javamail:Java发送邮件的工具
b.redis:nosql数据库,提升查询效率
c.jedis:java的redis客户端 - Dao层:
a.Mysql:数据库
b.Druid:数据池,提升效率
c.jdbc Template:jdbc的工具类 - DAO负责一些底层操作,例如表的增删改查,并且一张表对应一个DAO,service相当于业务逻辑,就像行动大纲,列出流程,调用DAO的方法完成某一个功能,Servlet相当于控制器,用户页面的交互,例如将表中的数据读取出来写到页面上,或者将表单中的数据写回到表中。
功能分析
注册功能
知识点:JQuery,
前台效果(V层)
- 表单校验:验证用户输入的数据是否符合要求,用Jquery中的正则表达式来约束,在写表单校验的时候,最后调用blur的时候,写函数名不需要加括号,
否则输入框会一直变红,无法消去。
$(function(){
//当提交表单时校验所有输入
$("#registerForm").submit(function(){
return ;
})
//当鼠标离开某个输入框框时,就进行校验
$("#username").blur(function({
checkusername();
}))
});
- 用户名:单词字符,8到20位
function checkUsername() {
//单词字符,8到20位
//1. 获取用户名
var username= $("#username").val();
//定义正则表达式
var red_username = /^\w{8,20}$/;
//判断是否符合
var flag= red_username.test(username);
//如果正确就将原来的红边框消去
if (flag){
$("#username").css("border","");
}else {//如果不符合,就弹出红边框
$("#username").css("border","1px solid red");
}
return flag;
}
- 密码:单词字符,8到20位
var red_username = /^\w{8,20}$/;
- email:邮件格式
var reg_email = /^\w+@\w+\.\w+$/;
- 姓名:非空
var reg_name= /^\s*$/g;
- 手机号:11位
/^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$/
- 要将jquery放在js文件夹下,浏览器才能接受到jquery资源
- 正则表达式,常常用来规定数据的输入格式,用于表单校验
字符 | 描述 |
---|---|
\ | 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“n”匹配字符“n”。“\n”匹配一个换行符。串行“\”匹配“\”而“(”则匹配“(”。 |
^ | 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。 |
$ | 匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。 |
* | 匹配前面的子表达式零次或多次。例如,zo*能匹配“z”以及“zoo”。*等价于{0,}。 |
+ | 匹配前面的子表达式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。 |
? | 匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“does”或“does”中的“do”。?等价于{0,1}。 |
{n} | n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。 |
{n,} | n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。 |
{n,m} | m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。 |
? | 当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o+”将匹配所有“o”。 |
. | 匹配除“\n”之外的任何单个字符。要匹配包括“\n”在内的任何字符,请使用像“(. |
(pattern) | 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“(”或“)” |
(?:pattern) | 匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“( |
(?=pattern) | 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95 |
(?!pattern) | 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“Windows(?!95 |
(?<=pattern) | 反向肯定预查,与正向肯定预查类拟,只是方向相反。例如,“(?<=95 |
(?<!pattern) | 反向否定预查,与正向否定预查类拟,只是方向相反。例如“(?<!95 |
x|y | 匹配x或y。例如,“z |
[xyz] | 字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a” |
[^xyz] | 负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“p”。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。 |
[^a-z] | 负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。 |
\B | 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。 |
\cx | 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。 |
\d | 匹配一个数字字符。等价于[0-9]。 |
\D | 匹配一个非数字字符。等价于[^0-9]。 |
\f | 匹配一个换页符。等价于\x0c和\cL。 |
\n | 匹配一个换行符。等价于\x0a和\cJ。 |
\r | 匹配一个回车符。等价于\x0d和\cM。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等价于[^ \f\n\r\t\v]。 |
\t | 匹配一个制表符。等价于\x09和\cI。 |
\v | 匹配一个垂直制表符。等价于\x0b和\cK。 |
\w | 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。 |
\W | 匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。 |
\xn | 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。正则表达式中可以使用ASCII编码。. |
\num | 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。 |
\n | 标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。 |
\nm | 标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm。 |
\nml | 如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。 |
\un | 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。 |
常用正则表达式 | |
---|---|
用户名 | /1{3,16}$/ |
密码 | /2{6,18}$/ |
十六进制值 | /^#?([a-f0-9]{6} |
电子邮箱 | /^([a-z0-9_.-]+)@([\da-z.-]+).([a-z.]{2,6})$/ |
/3+(.[a-z\d]+)*@(\da-z?)+(.{1,2}[a-z]+)+$/ | |
URL | /^(https?😕/)?([\da-z.-]+).([a-z.]{2,6})([/\w .-])/?$/ |
IP 地址 | /((2[0-4]\d |
/^(?😦?:25[0-5] | 2[0-4][0-9] |
HTML 标签 | /<([a-z]+)([<]+)(?:>(.)</\1> |
删除代码\注释 | (?<!http: |
Unicode编码中的汉字范围 | /4+$/ |
-
js:JavaScript是一种属于网络的脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。通常JavaScript脚本是通过嵌入在HTML中来实现自身的功能的。
是一种解释性脚本语言(代码不进行预编译)。
主要用来向HTML(标准通用标记语言下的一个应用)页面添加交互行为。]
可以直接嵌入HTML页面,但写成单独的js文件有利于结构和行为的分离。
跨平台特性,在绝大多数浏览器的支持下,可以在多种平台下运行(如Windows、Linux、Mac、Android、iOS等)。
Javascript脚本语言同其他语言一样,有它自身的基本数据类型,表达式和算术运算符及程序的基本程序框架。Javascript提供了四种基本的数据类型和两种特殊数据类型用来处理数据和文字。而变量提供存放信息的地方,表达式则可以完成较复杂的信息处理。
可以实现web页面的人机交互。
日常用途编辑
嵌入动态文本于HTML页面。
对浏览器事件做出响应。
读写HTML元素。
在数据被提交到服务器之前验证数据。
检测访客的浏览器信息。
控制cookies,包括创建和修改等。
基于Node.js技术进行服务器端编程。 -
Ajax 即“Asynchronous JavascriptAndXML”(异步JavaScript和XML),是指一种创建交互式、快速动态网页应用的网页开发技术,无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
$.post("registUserServlet",$(this).serialize(),function (data) {
serialize是序列化函数,能将表单的数据转为键值对形式的数据
在这里使用Ajax是因为前端使用的是html作为视图层,不能直接获取到服务器的数据,要用Ajax的方式获取数据
- 注册页面的编写,可以大致将页面分为三个部分,头部,注册表单,尾部,其中头部和尾部可以分别编写html文件,再在regist中引用。登录,注册等按钮的功能可以使用href超链接,转到响应的功能页面,进行相关的操作。
- session:Session 是 用于保持状态的基于Web服务器的方法。Session允许通过将对象存储在Web服务器的内存中在整个用户会话过程中保持任何对象。
Session通常用于执行以下操作
存储需要在整个用户会话过程中保持其状态的信息,例如登录信息或用户浏览Web应用程序时需要的其它信息。
存储只需要在页面重新加载过程中或按功能分组的一组页之间保持其状态的对象。
Session的作用就是它在Web服务器上保持用户的状态信息供在任何时间从任何设备上的页面进行访问。因为浏览器不需要存储任何这种信息,所以可以使用任何浏览器,即使是像Pad或手机这样的浏览器设备。这里用session来验证验证码
后台处理
- Servlet:
用到的技术
JQuery
定义
jQuery是一个快速、简洁的JavaScript框架,是继Prototype之后又一个优秀的JavaScript代码库(或JavaScript框架)。jQuery设计的宗旨是“write Less,Do More”,即倡导写更少的代码,做更多的事情。它封装JavaScript常用的功能代码,提供一种简便的JavaScript设计模式,优化HTML的DOM操作、事件处理、动画设计和Ajax交互。本质上是将一些JavaScript的重复操作进行封装,供用户调用。
使用前要将jquery-3.3.1.min.js导入到项目中。
JQuery对象和js对象的互相转换
<script>
//js
var divs = document.getElemByTagName("div");
//jquery
var $divs = $("div");
//将两者的内容替换为“aaa”
for(var i=0;i<divs.lenght;i++){
divs[i].innerHtml("aaa");
}
//jquery
$("div").html("aaa");
//由此可见,jquery的方法简便许多,而js的方法重复性非常高,但是jquery方法只能由jquery对象使用,js无法使用,所以就需要两者之间的转换
//js-->jquery
$(js对象)
//jquery-->js
jq对象.get(索引)或者jq对象[索引]
//上面两行代码可以改写为
//js-->jquery
$(divs).html("aaa");
//jquery-->js
$divs.get[0].innerHtml("aaa");
$divs[0].innerHtml("aaa");
</script>
选择器
- 事件绑定:在方法中内嵌一个function对象。
$("#id").click(function(){
alert("adfa");
})
- 入口函数:等页面加载完成后再进入入口函数的内容,=window.onload()
$(function(){
})
两者的区别是onload只能绑定一次,而$可以绑定多次
- 样式控制:就是通过.css方法修改元素的css样式
$(function(){
$("#id"),css("background","red");
})
-
分类:(具体语法在上面的超链接中)
- 基本选择器:
标签选择器:$(“html标签名”),获得所有匹配标签名的元素
$("div")获取当前页面中的所有div元素
id选择器:$(“#id名”),获得所有匹配id的元素
$("#btn")获取所有id值为btn的元素
类选择器:$(“.类的属性值”),获得所有匹配类名的元素
$(".hanahn")获得所有类的属性值与hanahn匹配的元素
中间加个逗号就可以选择多个条件。
$("#two,span")//选择id=two和span内的所有元素
- 层级选择器:
后代选择器:$(“A B”),获得A中的所有B元素,相当获取儿子辈和孙子辈的所有元素
$("body span")获得body里的所有span元素
子选择器:$(“A > B”),获得A中的所有B子元素,相当只获取儿子辈的
$("body > span"),获得body里的儿子级别的span元素
- 属性选择器:
属性名称选择器:选择指定属性名且含有名叫XX属性的元素,属性值不管
$("div[color]").css选择div元素,且div元素中有属性叫color的元素
属性选择器:选择指定属性=指定值的元素
$("div[color='red']")获得div元素,且元素的color属性=red的元素
属性复合选择器:包含多个属性条件的元素
$("div[color='red'][display]"),获得div元素,且满足color=red&&有display属性的元素
此外还有类似正则的写法
$("div[color!='red']"),获得div元素,且满足color不等于red的元素
$("div[color^='r']"),获得div元素,且满足color的开头=r的元素
$("div[color$='d']"),获得div元素,且满足color的结尾=d的元素
$("div[color*='d']"),获得div元素,且满足color的内容含有d的元素
- 过滤选择器:
首元素选择器: A:first 获得选择元素的第一个元素
尾元素选择器: A:last 获得选择元素的最后一个元素
非元素选择器: A:not(条件) 不包括指定内容的元素,条件也是用选择器语法写的
$("div:not(.one)")//选择class!=one的元素
偶数选择器: A:even 从0计数
奇数选择器: A:odd 从0计数
等于索引选择器:A:eq(index) 等于指定索引的元素
大于索引选择器: A:gt(index) 大于指定索引的元素
等于索引选择器: A:lt(index) 小于指定索引的元素
标题选择器: header () 获得标题为(h1~h6)的元素
- 表单过滤选择器
可用元素选择器: :enabled 获得可用元素
不可用元素选择器: :disabled 获得不可用元素
选中选择器: :checked 获得单选/复选框中的元素
选中选择器: selected 获得下拉框中的元素
注意下拉框选中的个数要获取select的子元素,option才能获取选中个数
$("#b4").click(function () {
alert($("select[name='job'] > option:selected").length);
})
- DOM操作
- 内容操作:val获取值,text获取纯文本,html获取标签之间的内容
$(function () {
var value = $("#myInput").val();//val获取value值
alert(value);
var html = $("#myDiv").html();//获取div间的所有元素,不止包括纯文本和标签
alert(html);
// var html = $("#myDiv").html("aaaa");//获取div间的所有元素,还可以更改标签内的内容,甚至可以写新标签
var text = $("#myDiv").text();
document.write(text);//获取div间的纯文本,不包括标签
})
- 属性操作:attr获取/设置值,removeAttr删除属性,prop获取/设置值,removeProp删除属性,attr和prop区别,如果是操作的标签的固有属性,用prop,用的是自定义属性,用attr
$(function () {
var name = $("#bj").attr("name");
alert(name);
$("#bj").attr("name","北京");
var name1 = $("#bj").attr("name");
alert(name1);
$("#bj").attr("discription","帝都");
$("#bj").removeAttr("discription");
var check= $("#hobby").prop("checked");
alert(check);
})
- class属性操作:addclass,增加属性,removeclass,删除属性,toggleclass如果有这个属性存在就删除,没有就创建。
$(function () {
//<input type="button" value="采用属性增加样式(改变id=one的样式)" id="b1"/>
$("#b1").click(function () {
$("#one").prop("class","second");
})
//<input type="button" value="采用属性增加样式(改变id=one的样式)" id="b1"/>
$("#b2").click(function () {
$("#one").addClass("second");
})
//<input type="button" value="removeClass" id="b3"/>
$("#b3").click(function () {
$("#one").removeClass("second");
})
//<input type="button" value=" 切换样式" id="b4"/>
$("#b4").click(function () {
$("#one").toggleClass("second");
})
//<input type="button" value=" 通过css()获得id为one背景颜色" id="b5"/>
$("#b5").click(function () {
$("#one").css("backgroundColor");//一个参数是获取对应的值,两个是设置参数的值
})
// <input type="button" value=" 通过css()设置id为one背景颜色为绿色" id="b6"/>
$("#b6").click(function () {
$("#one").css("background","green");
})
});
- CRUD:append和prepend元素是父子关系,before和after元素是兄弟关系。remove删除元素,empty清空元素
$(function () {
// <input type="button" value="将反恐放置到city的后面" id="b1"/>
$("#b1").click(function () {//append和prepend元素是父子关系
//append,将对象2放在对象1的后面
// $("#city").append($("#fk"));
//appendTo,将对象1放在对象2后面
// $("#fk").appendTo($("#city"))
})
// <input type="button" value="将反恐放置到city的最前面" id="b2"/>
//prepend
// $("#b2").click(function () {
// $("#city").prepend($("#fk"));
// })
//prependTo
// $("#b2").click(function () {
// $("#fk").prependTo($("#city"));
// })
// <input type="button" value="将反恐插入到天津后面" id="b3"/>
//insert和after元素是兄弟关系
//after
// $("#b3").click(function () {
// $("#tj").after($("#fk"));
// })
//insertAfter
// $("#b3").click(function () {
// $("#fk").insertAfter($("#tj"));
// })
// <input type="button" value="将反恐插入到天津前面" id="b4"/>
//before和insertBefore即可
##动画和遍历
- 动画的三种隐藏方式
- 默认显示和隐藏元素
speed有三种默认值,fast,slow,normal,或者自定义毫秒数,控制动画的速度
easing swing linear swing是先慢再快在慢 linear就是线性匀速的
fn是动画完成时执行的函数,每个元素执行一次
show(speed,easing,fn)
hide(speed,easing,fn)
toggle(speed,easing,fn)
- 滑动显示和隐藏
slideDown(speed,easing,fn)
slideUp(speed,easing,fn)
slideToggle(speed,easing,fn)
- 淡入淡出显示和隐藏
fadeIn(speed,easing,fn)
fadeOut(speed,easing,fn)
fadeToggle(speed,easing,fn)
- 遍历
- js的遍历
for(初始值,条件;自增长度)
//js的遍历方式
//首先拿到ul下的所有值,用选择器,并存放到数组中去
var cities = $("#city li");//返回的对象相当于数组,可以使用下标
// for (var i=0;i<cities.length;i++){
// alert(cities[i].innerHTML);
// }
//1. jq对象.each(callback)callback是回调函数
// $("#city li").each(function (index,element) {//可以在回调函数里定义相关操作
// // alert(this.innerHTML);//this表示当前对象
// //如果想要获取索引值,可以在函数参数列表中加入参数
// //2.还可以定义为jq对象可以调用更多的方法
// alert($(element).html()+index);
// })
//$.each
// $.each(cities,function () {//首先要获取要遍历的对象
// alert($(this).html());
// })
//for方式
for (li of cities){//同上,也要先定义遍历对象
alert($(li).html());
}
##事件绑定
//标准绑定
jq对象.事件方法(回调函数)
//链式编程,简化一个标签绑定多个操作的书写
// $("#name").mouseover(function () {
// alert("来了");
// }).mouseout(function () {
// alert("zoule");
// })
//onoff绑定
jq对象.on("事件名称",回调函数)
jq对象.off("事件名称",回调函数)
$("#btn3").toggle(function () {//1.9之后的版本是步支持toggle方法的,可以添加插件来解决jquery-migrate-1.0.0.js
$("#div").css("backgroundColor","pink");
},function () {
$("#div").css("backgroundColor","green");
});
//事件切换
jq对象.toggle(fn1,fn2)
Ajax
- 原生js实现ajax
function fun() {
var xmlHttp;
if (window.XMLHttpRequest){
//code for IE7+,chrome, firefox,Safari
xmlHttp = new XMLHttpRequest();
}
else{
//code for IE6 IE5
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
//建立连接
/*
* 第一个参数是请求方法
* get方法参数在URL的后面,send为空
* post方法参数在send方法里面
* 第二个是请求URL
* 第三个是同步或异步请求(true是异步,false是同步)
* */
xmlHttp.open("GET","AJAXServlet?username=delisha",true);
//发送请求
xmlHttp.send();
//接受并处理来自服务器的响应
//获取方式,xmlHTTP.responseText
//当服务器响应之后获取
xmlHttp.onreadystatechange=function () {
if (xmlHttp.readyState==4 && xmlHttp.status==200){
//获取服务器的响应结果
var responseText = xmlHttp.responseText;
alert(responseText);
}
}
}
- Jquery实现,jQuery有三种,分别是$.ajax $.GET $POST
$.ajax({//只要记住几个常用的参数即可,每个参数之间要用逗号分割,最后一个不需要加逗号
url:"ajaxServlet", //请求路径
type:"POST", //不写默认是GET
data:"username=jaxck&password=a", //请求参数
//还有一种写法,推荐使用第二种,类似json的键值对
data:{"username":"jaxck","password":"a"}
success:function(data){//data是变量,用来接受服务器返回的数据,即json、
alert(data.flag);
},
error:function(){//响应出错时执行的操作
alert("error!")
},
dataType:"json" //指定服务器返回的数据类型
})
$.GET(url,{data},function(){},type)
//url,请求路径
//data,请求参数,格式时{username:"jack"}
//function,回调函数
//type,返回数据类型
POST只要把GET改以下即可,其他参数一样
#Json
概念:JavaScript object notation(JavaScript对象表示法),作用类似于Java中的实体类,用来封装数据的。现在多用于存储和交换信息,类似xml,但比xml更小,更快,更易解析
语法
- 基本规则,数据在名称/值对中(名称和值都用引号括起来),数据由逗号分割,花括号保存对象,方括号保存数组
//取值类型由以下几种
数字:整数或浮点数
字符串:在双引号中
逻辑值:true或者false
数组:用方括号 {"provience":[{},{}]}
对象:用花括号 {"China":{"provience":"jiangsu","asfd":"dsaf"}}
null
##json数据的获取
- json对象.键名
- json对象[“键名”]
- 数组对象[]
##json和Java对象的互相转换
- 在服务器和浏览器之间需要一个数据交换的载体,就是json,但是服务器如果用Java语言编写,就不能解析json数据,因为json是js的,得通过Jackson转为实体类,在通过Dao与数据库之间相互交换数据
- Java转JSON
//1.建立实体类
Person p = new Person();
//2.赋值
p.setName("hanahn");
//3.转换方法
//3.1创建核心类
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.write(参数1, obj)
/*
参数1:
File:将obj转为字符串,并写入到文件中
Writer:将obj转为字符串,并写入到字符输出流中
OutPutStream:将obj转为字符串,并写入到字节输出流中
*/
objectMapper.writeValueAsString(obj)
//将对象转为json字符串
- 注解(要在实体类中的属性上加注解)
- @JsonIgnore:在json字符串中忽略该属性
public class Person{
private String name;
privat eint age;
private String gender;
//@JsonIgnore//忽略该字符串
@JsonFormat(pattern = "yyyy-MM-dd")
private Date brithday;
}
- @JsonFormat:给该属性指定格式
- json转java:和Java转json差不多,只是最后的方法是readvalue(json,class);
String json = "";//json格式书写
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.readValue(json,Person.class);
navicat常用快捷键
ctrl+q 打开查询窗口
ctrl+/ 注释
ctrl +shift+/ 解除注释
ctrl+r 运行选中的SQL语句
ctrl+l 删除选中行内容
ctrl+d 复制当前行内容并粘贴到下一行
ctrl+w 关闭当前查询窗口
注意事项
- 在使用导入的项目时,要重新写配置文件,更改数据库名称
- 在启动服务器前,要将JDBCUtils中的配置文件路径前面的/去掉,不然服务器会报错找不到配置文件
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
后台代码
- 编写RegisetUser,UserService,Userservicelmpl,UserDao,UserDaolmpl
- 使用@webservelt替代xml注解时,会报错,因为没有导包,需要的包是javax.servlet.annotation.WebServlet,并且3.0-alpha-1servlet包必须是3.0以上版本才支持,最后重要的是还必须得导入tomcat得依赖包方法如下:
-
打开项目结构
-
点击模块右边的+号
-
选择第二个库,然后选择tomcat即可
- 编写过滤器,统一字符编码
- json的定义
- 项目启动失败的原因是没有导入tomcat的插件,导致一直启动失败。
- 直接导入的项目里的tomcat插件不能下载,从网上又找了一个插件
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>/travel</path>
<port>80</port>
<uriEncoding>UTF-8</uriEncoding>
</configuration>
</plugin>
##JDBC
- JDBCUtils工具类的编写简化数据库相关操作
- JDBC Template:JDBC的封装
- druid时区错误,加上一段代码即可
url=jdbc:mysql://127.0.0.1:3306/valkyries?serverTimezone=GMT%2B8
- validconnection 报错,是因为druid版本太低,在pom.xml中将其版本改为1.1.10
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
验证码
- 首先在sevlet中创建验证码
用画笔对象,通过随机生成字符串函数创建验证码,写入session中,服务器从session中读取并验证 - 使用验证码时,要将表单提交的return值改为false,而不再是依赖checkusername&&checkname&&checkemail的返回值,因为这样的话只要表单符合
约束,就可以提交,这时是同步提交,而验证码是异步提交,所以得由post提交后由registerServlet判断是否正确,根据返回得值判断验证码是否正确
##邮件激活
- 邮箱激活:确保用户填写的邮箱是正确的,便于日后推广。
- 发送激活邮件,用户点击后就能验证,修改user里的status就能知道是否已经激活
#登录和退出功能
-
逻辑图
-
登录成功之后用location.href无法跳转,网上查阅资料后得知可能和表单的提交和href的执行先后有关,如果是用表单的提交的话,就像regist那样,在函数里return false 就能阻止表单提交,而login是用的button点击事件,不能用return false,用window.parent.Location.href=“xxxx”,就能提交,或者将按钮放在表单外,就可以不受表单提交的影响
-
登录成功后显示欢迎回来,+name,但是发现按照视频的写法失败,浏览器提示data is not definded和cannot property name of null,就是data为null,找不到。经百度查询,将script放在header的最后,但是没有用,并不是html元素未加载完全的原因。查询csdn,得知在findUserServlet中打印user,发现是null,也就是说session中根本没有user,即loginServlet中没有将user存入session中,因此,在login成功后,加上request.getSession().setAttribute(“user”,u);即可
-
退出:登录的实质就是指session中有user对象,这也映证了上面一个问题,退出就是访问servlet销毁session,跳转至登录页面
#优化servlet
- 减少servelt的数量,现在是一个功能一个servlet,可以优化功能为用户的方法,即数据库一张表对应一个servlet,具体功能写成方法
- BaseServlet继承HttpServlet,并重写service方法,这样当访问UserServlet时,就会调用BaseServlet中service进行方法分发,在去寻找UserServlet里的对应方法
- 改了路径后,页面的路径也要改动,改为user/方法名,注意serviceImpl里的邮件激活的路径也要改
#分类数据展示功能
- 又出现了bug,这次是因为categroy数组返回时,将data[i]写成了data,就变成了数组对象,当然显示不出名字。
#旅游线路的分页展示
- 注意使用非页展示使要将redis打开,不然会报timeout错误
- 特别注意,从github上复制的代码,前端一定要修改拼接字符串中的单引号,因为复制粘贴后就会又斜杆分隔符产生。
SQL数据库
数据库基本操作
- 注释:–空格(单行注释),/**/多行注释,#mysql独有
- CRUD:增删改查,create,retrieve(查询),update,delete
- 创建数据库(create)
--直接创建数据库
create database 数据库名;
--如果不存在数据库1就创建数据库2
create database if not esists db2;
--创建数据库并制定字符集
create database db3 default character set gbk;
- 查看所有数据库
show databases;
--查看某个数据库的定义信息
show create database db3;
- 修改数据库(alter)
--修改数据库默认的字符集
alter database 数据库名 default character 字符集;
- 删除数据库(drop)
--删除数据库3
drop database db3;
- 查看正在使用的数据库(select)
--select是MySQL中的一个全局函数
select database()
- 使用切换数据库(use),要想使用一个数据库,首先得确定当前使用的就是目标数据库,如果不是,就需要切换数据库
use 数据库名
表的操作
- 对表操作前首先得选中一个数据库
- 创建表,注意是先写字段名再写字段类型,和编程的顺序不同,另外字段类型也不同,每个字段类型中间用逗号分隔,最后一个不需要逗号,表名后面是括号不是花括号
create table 表名(
字段名1 字段类型,
字段名2 字段类型,
);
/*常用数据类型
int
double
varchar 字符串类型
date 日期类型格式为 yyyy-mm-dd
*/
Text类型 | 数据类型 | 描述 |
---|---|---|
1 | CHAR(size) | 保存固定长度的字符串(可包含字母、数字以及特殊字符)。在括号中指定字符串的长度。最多 255 个字符。 |
2 | VARCHAR(size) | 保存可变长度的字符串(可包含字母、数字以及特殊字符)。在括号中指定字符串的最大长度。最多 255 个字符。注释:如果值的长度大于 255,则被转换为 TEXT 类型。 |
3 | TINYTEXT | 存放最大长度为 255 个字符的字符串。 |
4 | TEXT | 存放最大长度为 65,535 个字符的字符串 |
5 | BLOB | 用于 BLOBs(Binary Large OBjects)。存放最多 65,535 字节的数据。 |
6 | MEDIUMTEXT | 存放最大长度为 16,777,215 个字符的字符串。 |
7 | MEDIUMBLOB | 用于 BLOBs(Binary Large OBjects)。存放最多 16,777,215 字节的数据。 |
8 | LONGTEXT | 存放最大长度为 4,294,967,295 个字符的字符串。 |
9 | LONGBLOB | 用于 BLOBs (Binary Large OBjects)。存放最多 4,294,967,295 字节的数据。 |
10 | ENUM(x,y,z,etc.) | 允许您输入可能值的列表。可以在ENUM列表中列出最大65535个值。如果列表中不存在插入的值,则插入空值。注释:这些值是按照您输入的顺序排序的。可以按照此格式输入可能的值: ENUM(‘X’,‘Y’,‘Z’) |
11 | SET | 与 ENUM 类似,不同的是,SET 最多只能包含 64 个列表项且 SET 可存储一个以上的选择。 |
Number类型 | 数据类型 | 描述 |
---|---|---|
1 | TINYINT(size) | 带符号-128到127 ,无符号0到255。 |
2 | SMALLINT(size) | 带符号范围-32768到32767,无符号0到65535, size 默认为 6。 |
3 | MEDIUMINT(size) | 带符号范围-8388608到8388607,无符号的范围是0到16777215。 size 默认为9 |
4 | INT(size) | 带符号范围-2147483648到2147483647,无符号的范围是0到4294967295。 size 默认为 11 |
5 | BIGINT(size) | 带符号的范围是-9223372036854775808到9223372036854775807,无符号的范围是0到18446744073709551615。size默认为20 |
6 | FLOAT(size,d) | 带有浮动小数点的小数字。在 size 参数中规定显示最大位数。在 d 参数中规定小数点右侧的最大位数。 |
7 | DOUBLE(size,d) | 带有浮动小数点的大数字。在 size 参数中规显示定最大位数。在 d 参数中规定小数点右侧的最大位数。 |
8 | DECIMAL(size,d) | 作为字符串存储的DOUBLE类型,允许固定的小数点。在size参数中规定显示最大位数。在d参数中规定小数点右侧的最大位数。 |
注意:以上的 size 代表的并不是存储在数据库中的具体的长度,如int(4)并不是只能存储4个长度的数字。实际上intsize所占多少存储空间并无任何关系。int(3)、int(4)、int(8) 在磁盘上都是占用 4 btyes 的存储空间。就是在显示给用户的方式有点不同外,int(M) 跟 int 数据类型是相同的。 | ||
例如: | ||
1、int的值为10 (指定zerofill) | ||
int(9)显示结果为000000010 | ||
int(3)显示结果为010 | ||
就是显示的长度不一样而已 都是占用四个字节的空间 |
Date类型 | 数据类型 | 描述 |
---|---|---|
1 | DATE() | 日期。格式:YYYY-MM-DD注释:支持的范围是从 ‘1000-01-01’ 到 ‘9999-12-31’ |
2 | DATETIME() | *日期和时间的组合。格式:YYYY-MM-DD HH:MM:SS注释:支持的范围是从 ‘1000-01-01 00:00:00’ 到 ‘9999-12-31 23:59:59’ |
3 | TIMESTAMP() | *时间戳。TIMESTAMP 值使用 Unix 纪元(‘1970-01-01 00:00:00’ UTC)至今的秒数来存储。格式:YYYY-MM-DDHH:MM:SS注释:支持的范围是从 ‘1970-01-01 00:00:01’ UTC 到 ‘2038-01-09 03:14:07’ UTC |
4 | TIME() | 时间。格式:HH:MM:SS注释:支持的范围是从 ‘-838:59:59’ 到 ‘838:59:59’ |
5 | YEAR() | 2 位或 4 位格式的年。注释:4 位格式所允许的值:1901 到 2155。2 位格式所允许的值:70 到 69,表示从 1970 到 2069。 |
*即便DATETIME和TIMESTAMP返回相同的格式,它们的工作方式很不同。在INSERT或UPDATE查询中,TIMESTAMP自动把自身设置为当前的日期和时间。TIMESTAMP 也接受不同的格式,比如 YYYYMMDDHHMMSS、YYMMDDHHMMSS、YYYYMMDD 或 YYMMDD。 |
- 查看表
--查看某个数据库中的所有表
show tables;
--查看表结构
desc 表名;
--查看创建表的sql语句
show create table 表名;
- 创建一个结构相同的表
create table 新表名 like 旧表名;
- 删除表
drop table 表名;
--如果不存在不删除,存在删除
drop table if esists 'create';
- 修改表的结构(增加ADD)
alter table 表名 ADD 列名 类型;
- 修改列的类型(MODIFY)
alter table 表名 MODIFY 列名 类型;
- 插入数据(插入字符串数据要加上单引号)
insert into 表名 (字段名1,字段名2,字段名3) values (值1,值2,值3);
- 删除数据
delete from 表名 where 条件表达式;
- 设置主键
alter table 表名 add primary key(id);
-设置id自增
alter table 表名 change id id AUTO_INCREMENT;
--设置初始值
alter table 表名 AUTO_INCREMENT = 1000;
##表的约束
- 约束是为了保证数据的正确性,完整性,有效性,限定非法数据的插入
- 分类:
- 主键约束:primary key(自动增长)auto_increment
create table stu(
id int primary key,
name varchar(20)
)
--添加非空
alter table stu MODIFY id int primary key;
--删除非空,只需要修改列名的属性
alter table stu MODIFY id int;
- 非空约束:not null
create table stu(
id int,
name varchar(20) not null
)
--添加非空
alter table stu MODIFY name varchar(20) not null;
--删除非空,只需要修改列名的属性
alter table stu MODIFY name varchar(20);
- 唯一约束:unique
create table stu(
id int unique,
name varchar(20)
)
--添加非空
alter table stu MODIFY id int unique;
--删除非空,只需要修改列名的属性
alter table stu MODIFY id int;
- 主键约束:foreign key constraint 外键名称 foreign Key (当前表的外键) reference 表名(列名)
alter table stu drop foreign key 外键名称 --删除外键
alter table stu add constraint employee_foreign foreign key (id) reference employee(UID)--添加外键
--设置级联更新,这样一张表更新,另一张表就会更新
alter table stu add constraint employee_foreign foreign key (id) reference employee(UID) on update cascade
--级联删除
alter table stu add constraint employee_foreign foreign key (id) reference employee(UID) on delete cascade
##数据库的多表操作
- 表之间的关系有四种,一对一,一对多,多对一,多对多
- 一对多实现,在多的表中建立外键,指向一的表的主键
- 多对多的实现,需要第三张中间表,中间表至少包含两个字段,这两个字段作为第三张表的外键,分别指向两张表的主键。并且为了保证两个主键不会出现一摸一样的情况,需要联合主键 primary key(rid,uid)
- 一对一实现,在任意一个表中添加外键指向另一表的主键,并且令外键唯一
##数据库涉及范式
- 就是数据库涉及时的规范
- 分类:有六类,但是一般项目只要前面三式
- 第一范式:每一列都是不可分割的原子项,就是每一列都是单独的列,里面不包含其他子列。
这里面的系就可以拆分为系名和系主任两个独立选项
- 第二范式:在第一范式的基础上,非码属性必须完全依赖于候选码(在第一范式的基础上消除非主属性对主码的部分函数依赖),消除部分依赖
上面的表中存在这许多问题,这些问题的解决就需要用到二三范式。 - 存在的问题:首先,数据有冗余。姓名,系名和系主任有大量重复,其次数据添加存在问题,添加新开设的系和系主任时数据不合法,最后数据删除存在问题,一名同学毕业后回将其所在的系和系主任都删除
- 函数依赖:A–>B,如果通过A属性的值,可以确定唯一b属性的值,就称b依赖于a。例如表中学号–>姓名(姓名依赖于学号,通过学号可以找到唯一姓名),(学号,课程)–>分数(通过学号和课程可以确定唯一的分数,依赖于属性组)
- 完全函数依赖:A–>B,如果A是一个属性组,则B属性值需要依赖A属性组中的所有属性值,例如(学号,课程名称)–>分数
- 部分函数依赖:A–>B,如果A是一个属性组,B属性的确定只需要A属性组的一些属性值,例如(学号,课程名称)–>姓名
- 传递函数依赖:A–>B,B–>C,如果通过A属性的值,可以确定唯一B属性的值,在通过B属性的值可以确定唯一C属性的值,例如学号–>系名,系名–>系主任。
- 码:在一张表中,一个属性或属性组,被其他所有属性所完全依赖,则称这个属性(属性值)为该表的码,例如(学号,课程名称)
主属性:码属性组的所有属性
非主属性:除码属性的所有属性
第二范式是为了消除非主属性对主属性的部分依赖,就是要把表拆分为完全依赖的表,解决第一个数据冗余问题,第一张表中,分数完全依赖于学号和课程名称,第二张表中,姓名系主任,系名都完全依赖学号,因此可以删除一大部分冗余数据。但是第二三个问题没有解决,还需要第三范式。
- 第三范式:在第一二范式的基础上,任何非主属性不依赖于其他非主属性(消除传递依赖)
系主任完成依赖于系名,系名依赖于学号,因此系主任传递依赖于学号,就可以拆分,同理系名也传递依赖学号,就可以把这两个分出去。就解决了插入和删除对其他数据的影响。因此判断表设计是否合理,就是看其有没有数据冗余,插入删除数据会不会对其他数据产生影响。
##数据库的多表查询
- 多表查询会查询除许多不需要的数据,这时就需要去除这些无用的数据,需要用到内连接,外连接和子查询。
- 内连接:分为隐式和显式,隐式用where来删除无用的信息。显式是select 字段 from 表1 inner join 表2 on 条件
- 外连接:也分为左外和右外,select 字段 from 表1 left (outer) join 表2 on 条件,括号中可写可不写,左连接是查询左表所有数据和右表的并集部分,就是左表全部查询,右表如果没有对应信息就会默认空,右表相反,语句吧left改为right即可。
- 子查询:
- 子查询的结果是单行单列的,子查询可以作为条件,使用运算符(> < =)去判断。
select * from 表1 where 字段 = (select MAX(字段) from 表2)
- 查询结果是多行单列的,查询的结果作为集合在其中查询
select * from emploment e where e.id in (select * form depaetment where name = '市场部' or name = '技术部')
- 查询结果是多行多列的,子查询可以作为虚拟表进行查询
select * from depr t1, (select * from emp where emp.date > 12) t2 where t1.id = t2.id
表单注册
-
可定义文档中的分区或节(division/section)。标签可以把文档分割为独立的、不同的部分。它可以用作严格的组织工具,并且不使用任何格式与其关联。如果用 id 或 class 来标记,那么该标签的作用会变得更加有效。并且不推荐使用align属性来确定div的对齐方式,通过样式来确定对齐方式
- 表单中的action属性是指定向哪个地方发送表单数据,是本站就写相对URL,外部网站就写绝对URL
- colspan 属性规定单元格可横跨的列数,即一个单元格可以横跨几列,colspan=“0” 指示浏览器横跨到列组的最后一列。
- 设置背景图片
body{
background: url("") no-repeat center;<!--no-repeat是为了让图片不出现重复--!>
}
- 类选择器,设置某一类的共同样式,前面要加上点
.rg_layout{
width: 900px;
height: 500px;
border: 8px solid #EEEEEE;
background: white;
margin: auto;
margin-top: 15px;
}
- 让div变成水平放置,用浮动float
.rg_center{
border: 1px solid red;
float: right;
}
- 子类选择器,首先通过>找到子类,在通过first-child找到第一个子元素
.rg_left > p:first-child{
}
-
内边框是padding,设置内部元素与外部的间距
-
并集选择器,将相同的样式设置成多个id
#username,#birthday,#name,#email,#password,#checkcode,#tel{
border: 1px solid #A6A6A6;
width: 251px;
height: 32px;
border-radius: 5px;
padding-left: 10px;
}
#注意点
baseservlet优化之后记得修改,激活成功,请登录,改为login的绝对路径,因为原来activeservlet和login是在同一travel路经下的,而用的userservlet后,路径就回多一个/user,就不可能找到login的路径,同时,找login的路径,一定要把服务器打开找路径,不然找到的只是login静态资源的路径,服务器打开时也会找不到
public void active(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.获取激活码
String code = request.getParameter("code");
if(code != null){
//2.调用service完成激活
boolean flag = service.active(code);
//3.判断标记
String msg = null;
if(flag){
//激活成功
msg = "激活成功,请<a href='http://localhost/travel/login.html'>登录</a>";
}else{
//激活失败
msg = "激活失败,请联系管理员!";
}
//将信息返回,注意这里不是用json返回,而是用response以html形式返回,用json只能显示键值对,不能显示点击超连接的效果
response.setContentType("text/html;charset=utf-8");
response.getWriter().write(msg);
}
}
Http(HyperText Transfer protocol)超文本传输协议
- 请求消息格式:
- 请求行
请求方式 请求url 请求协议/版本
GET /login.html Http/1.1 - 请求头
请求头名称: 值
常见的请求头:
user-Agent:告诉服务器浏览器的版本,从而解决浏览器的兼容问题
Accept:告诉服务器浏览器可以接受的数据格式
Accept-Language:浏览器的语言环境
refer:https://cn.bing.com/ 告诉服务器当前请求从哪里来,防止盗链和统计工作。 - 请求空行
就是空行,分割请求头和请求体 - 请求体
GET是没有请求体的,因为请求体中装的是请求的一些参数,而GET方式的话,请求参数是装在请求行(url)中的
username=zhangsan
- 响应消息格式:
一些杂谈
-
得到表单提交的数据用String name = request.getParameter(“name”);
这里request.getParameter(“name”)的作用是获得提交的表单中name值为“name”的value,这是通过表单标签的action属性完成,这个属性是规定了表单获得的数据发送到哪个页面 -
Servlet的框架是由两个Java包组成:javax.servlet和javax.servlet.http. 在javax.servlet包中定义了所有的Servlet类都必须实现或扩展的的通用接口和类.在javax.servlet.http包中定义了采用HTTP通信协议的HttpServlet类.
-
Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必须实现这一接口.在Servlet接口中定义了5个方法,其中有3个方法代表了Servlet的声明周期:
-
init方法,负责初始化Servlet对象
service方法,负责相应客户的请求
destory方法,当Servlet对象退出声明周期时,负责释放占有的资源 -
ServletConfig:代表当前Servlet在web.xml中的配置信息(用的不多)
String getServletName() – 获取当前Servlet在web.xml中配置的名字
String getInitParameter(String name) – 获取当前Servlet指定名称的初始化参数的值
Enumeration getInitParameterNames() – 获取当前Servlet所有初始化参数的名字组成的枚举
ServletContext getServletContext() – 获取代表当前web应用的ServletContext对象
在Servlet的配置文件中,可以使用一个或多个标签为servlet配置一些初始化参数。
当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。进而,程序员通过ServletConfig对象就可以得到当前servlet的初始化参数信息。
这样做的好处是:如果将数据库信息、编码方式等配置信息放在web.xml中,如果以后数据库的用户名、密码改变了,则直接很方便地修改web.xml就行了,避免了直接修改源代码的麻烦
-
不管是客户端还是服务端,要想跳转,就得把跳转页面放在web文件夹下,而不是放在WEB-INF文件夹下
-
servlet用web动态项目做的project,打开时要加127.0.0.1:8080/j2ee/listHero,不直接加listHero,并且点击增删查改操作时,点击跳转后会无法跳转,这也是由于上述原因,只要按上面格式输入就好(其实就是相对路径和绝对路径),通过web动态项目run as就是相对路径,要加上项目名称
tomcat匹配的jdk
- 如果是用的how2j提供的tomcat,那么Java的jdk版本必须是1.8(我之前用的是13,打开tomcat就闪退),具体安装教程how2j上也有。
- 如果想用url访问html文件,必须在tomcat/bin/webapps下手动新建一个ROOT(大写)文件夹,才能在网站上看见。
- tomcat的乱码显示问题
- 部署项目时要加上端口号,并且在server配置文件里写文件路径时,要新建一个web项目文件
- 用eclipsse打开时,会发现报failed to find JDK…jvm,这是因为jdk版本与eclipse版本不一致导致,全部用how2j提供的版本进行下载
部署servlet时的错误
- 将WEB-INF文件错打成了WEB_INF,
jdbc查询语句中String里面不加单引号,但是在sql中要加
爬虫时遇到的问题
- spring里的@table()配置出错,这是因为IDEA没有和数据库建立连接,博客,是因为驱动和sql版本不对,换成5.1即可
- application要放在itcast.cn.crawler.jd的下面,放在其他包下面可能不能运行
- 爬取京东网页内容时爬取失败,发现是要先登录,这是因为没有进行模拟登录,没有设置headers信息,不能被服务器识别为浏览器。设置headwes信息即可
- 获取spu和sku失败,经过调试后发现,有的spu是空值,没有对空值的情况进行处理,进行处理后发现spu为空值时,ware-type=“0”,就将其设为条件,当=0时,就令spu=null,sku找不到是因为select根本没有获取到元素,视频中的选择器写错了,应该是"img",它写为了"[data-sku]",改过之后就能正常下载了。
IDEA中resource下创建多级目录
- 在左边小窗口中的设置中选择取消紧凑中间包选项即可
- alt+7,显示类下的所有方法
项目的总结
- 令牌机制:属于会话技术的一种,是指当第一次访问页面时会生成一个令牌,以后每次发送请求时就带上这个令牌,当不按F5刷新页面时,发送请求携带的令牌也不会变,当servlet第一次拿到令牌后进行比对,比对完毕就会更换或者删除令牌,那么没有刷新的页面携带的任然是上一次的令牌,就会失效,可以用来防止表单重发提交,和验证码。博客项目中点赞就会出现这种情况,重复点击点赞数或者一直刷新就会一直增加,我是用的每次重定向的方法,但是这样无法解决点赞数一直增加的情况。
- token:用户认证,如果用户每次请求都要发送用户名和密码,然后由后端来验证的话是非常不现实的,会降低性能并且一点也不优雅,而携带token就可以不用从数据库中查询验证,而是在session中验证就可以。
- mybatis中如果想知道增删改是否成功,就可以将增删改方法的返回值改为int类型,返回的就是修改的数据库的数据条数,就可以通过返回值是否等于0来判断所修改的条数。从而知道是否操作成功
杂记
编码
- 参考文章看这里和这里.
- 首先要明白两个概念,一个是编码,另一个是编码格式,编码就是字符和二进制的映射关系,相当于一个字母表,比如a=10001,c=111001010之类.而编码格式,就是用一个或多个字节标识编码中的字符,因为编码中的字符的长度是不一样的,因此同一种编码有不同的编码格式,比如utf-8就是一种已知“严”的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此“严”的UTF-8编码需要三个字节,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,“严”的UTF-8编码是“11100100 10111000 10100101”,转换成十六进制就是E4B8A5。
- java中string是unicode编码,注意是编码,当string在内存中时,是以编码的形式存在的而不是以编码格式的形式存在,编码格式只有在存储和网络传输中。
- getBytes()是用的系统默认的编码格式,指定charsetName,就是用指定编码格式去解码字符串,在用另一种编码格式编码,详细的还是要看上面的文章。